{"action":"created","comment":{"url":"https://api.github.com/repos/Microsoft/dotnet-apiport/pulls/comments/191012537","pull_request_review_id":123515994,"id":191012537,"diff_hunk":"@@ -3,100 +3,115 @@\n \n using Microsoft.Azure.WebJobs;\n using Microsoft.Azure.WebJobs.Extensions.Http;\n-using Microsoft.Azure.WebJobs.Host;\n+using Microsoft.Extensions.Logging;\n+using Microsoft.Fx.Portability;\n+using Microsoft.Fx.Portability.Azure;\n+using Microsoft.Fx.Portability.ObjectModel;\n+using Microsoft.Fx.Portability.Reporting;\n+using Microsoft.Fx.Portability.Reports;\n+using Microsoft.WindowsAzure.Storage;\n+using PortabilityService.Functions.DependencyInjection;\n using System;\n+using System.Collections.Generic;\n using System.IO;\n using System.Linq;\n using System.Net;\n using System.Net.Http;\n using System.Net.Http.Headers;\n+using System.Threading.Tasks;\n \n namespace PortabilityService.Functions\n {\n     public static class Report\n     {\n         [FunctionName(\"report\")]\n-        public static HttpResponseMessage Run(\n+        public static async Task<HttpResponseMessage> Run(\n             [HttpTrigger(AuthorizationLevel.Anonymous, \"get\", Route = \"report/{submissionId}\")] HttpRequestMessage req,\n             string submissionId,\n-            TraceWriter log)\n+            [Inject] IStorage storage,\n+            [Inject] IReportTokenValidator validator,\n+            ILogger log)\n         {\n-            if (!ValidAccessKey(req))\n+            if (!validator.RequestHasValidToken(req))\n             {\n                 return req.CreateResponse(HttpStatusCode.Unauthorized);\n             }\n \n-            // simulate report generation taking some time\n-            if (new Random().Next(10) < 4)\n+            var (analyzeRequest, analyzeResult) = await GetSubmissionDataAsync(submissionId, storage, log);\n+            if (analyzeRequest == null)\n             {\n-                // TODO this should return 202 only when the analyze request is known to have been received,\n-                // 404 otherwise (e.g. when the client tries to get the report before its analyze request\n-                // has propagated)\n-                var response = req.CreateResponse(HttpStatusCode.Accepted);\n-                response.Headers.RetryAfter = new RetryConditionHeaderValue(TimeSpan.FromSeconds(2d));\n+                // the request may have been received but not yet stored => the client should poll again\n+                var res = req.CreateResponse(HttpStatusCode.NotFound);\n+                res.Headers.RetryAfter = new RetryConditionHeaderValue(TimeSpan.FromSeconds(4d));\n+            }\n+            if (analyzeResult == null)\n+            {\n+                // AnalyzeRequest received but AnalyzeResult not available => client should continue polling\n+                var res = req.CreateResponse(HttpStatusCode.Accepted);\n+                res.Headers.RetryAfter = new RetryConditionHeaderValue(TimeSpan.FromSeconds(2d));\n \n-                return response;\n+                return res;\n             }\n \n-            switch (req.Headers.Accept.ToString())\n+            var response = req.CreateResponse();\n+            try\n+            {\n+                response.Content = GetReportContent(req.Headers.Accept.SingleOrDefault(), analyzeRequest, analyzeResult);\n+                response.StatusCode = HttpStatusCode.OK;\n+            }\n+            catch (UnsupportedMediaTypeException ex)\n             {\n-                case MediaType.Json:\n-                    return JsonReport(req);\n-                case MediaType.Excel:\n-                    return ExcelReport(req);\n-                default:\n-                    return req.CreateResponse(HttpStatusCode.UnsupportedMediaType);\n+                log.LogError(\"unknown format {MediaType} requested\", ex.MediaType);\n+                response.StatusCode = HttpStatusCode.UnsupportedMediaType;\n             }\n+\n+            return response;\n         }\n \n-        public static bool ValidAccessKey(HttpRequestMessage request)\n+        public static async Task<(AnalyzeRequest, AnalyzeResult)> GetSubmissionDataAsync(string submissionId, IStorage storage, ILogger log)\n         {\n-            // TODO generate and persist a new unique key in Analyze, validate it here\n-            var authHeader = request.Headers.Authorization;\n-            if (authHeader == null || !authHeader.Scheme.Equals(\"Bearer\", StringComparison.Ordinal))\n+            AnalyzeRequest analyzeRequest = null;\n+            AnalyzeResult analyzeResult = null;\n+            try\n             {\n-                return false;\n+                analyzeRequest = await storage.RetrieveRequestAsync(submissionId);","path":"src/backend/PortabilityService.Functions/Report.cs","position":99,"original_position":99,"commit_id":"e436a43d6169e05749d9c8dc7ede2d3579e80f79","original_commit_id":"e436a43d6169e05749d9c8dc7ede2d3579e80f79","user":{"login":"twsouthwick","id":583206,"avatar_url":"https://avatars1.githubusercontent.com/u/583206?v=4","gravatar_id":"","url":"https://api.github.com/users/twsouthwick","html_url":"https://github.com/twsouthwick","followers_url":"https://api.github.com/users/twsouthwick/followers","following_url":"https://api.github.com/users/twsouthwick/following{/other_user}","gists_url":"https://api.github.com/users/twsouthwick/gists{/gist_id}","starred_url":"https://api.github.com/users/twsouthwick/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/twsouthwick/subscriptions","organizations_url":"https://api.github.com/users/twsouthwick/orgs","repos_url":"https://api.github.com/users/twsouthwick/repos","events_url":"https://api.github.com/users/twsouthwick/events{/privacy}","received_events_url":"https://api.github.com/users/twsouthwick/received_events","type":"User","site_admin":false},"body":"Can you move the variable declarations and definitions together? You can include the `return (analyzeRequest, analyzeResult)` in the try statement","created_at":"2018-05-25T21:23:38Z","updated_at":"2018-05-25T21:28:17Z","html_url":"https://github.com/Microsoft/dotnet-apiport/pull/664#discussion_r191012537","pull_request_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/pulls/664","author_association":"MEMBER","_links":{"self":{"href":"https://api.github.com/repos/Microsoft/dotnet-apiport/pulls/comments/191012537"},"html":{"href":"https://github.com/Microsoft/dotnet-apiport/pull/664#discussion_r191012537"},"pull_request":{"href":"https://api.github.com/repos/Microsoft/dotnet-apiport/pulls/664"}}},"pull_request":{"url":"https://api.github.com/repos/Microsoft/dotnet-apiport/pulls/664","id":190435903,"html_url":"https://github.com/Microsoft/dotnet-apiport/pull/664","diff_url":"https://github.com/Microsoft/dotnet-apiport/pull/664.diff","patch_url":"https://github.com/Microsoft/dotnet-apiport/pull/664.patch","issue_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/issues/664","number":664,"state":"open","locked":false,"title":"Generate reports with an Azure Function","user":{"login":"chlowell","id":10964656,"avatar_url":"https://avatars0.githubusercontent.com/u/10964656?v=4","gravatar_id":"","url":"https://api.github.com/users/chlowell","html_url":"https://github.com/chlowell","followers_url":"https://api.github.com/users/chlowell/followers","following_url":"https://api.github.com/users/chlowell/following{/other_user}","gists_url":"https://api.github.com/users/chlowell/gists{/gist_id}","starred_url":"https://api.github.com/users/chlowell/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/chlowell/subscriptions","organizations_url":"https://api.github.com/users/chlowell/orgs","repos_url":"https://api.github.com/users/chlowell/repos","events_url":"https://api.github.com/users/chlowell/events{/privacy}","received_events_url":"https://api.github.com/users/chlowell/received_events","type":"User","site_admin":false},"body":"This replaces the Function I added earlier to return mock reports with one that generates and returns real reports, albeit for hard-coded data because the analysis engine doesn't yet analyze anything. This completes the end-to-end scenario for ApiPort. I also updated the `ReportFormat` Function to return the formats we have writers for.\r\n\r\nI learned along the way that generating a report requires an `AnalyzeRequest` and `AnalyzeResponse` both; that might be worth refactoring in the future. For now, we'll have to hang on to `AnalyzeRequest`s.","created_at":"2018-05-24T22:20:08Z","updated_at":"2018-05-25T21:28:17Z","closed_at":null,"merged_at":null,"merge_commit_sha":"6a2ffa8cf53716a8a360fbf6897c38ae85f34fcb","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/pulls/664/commits","review_comments_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/pulls/664/comments","review_comment_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/pulls/comments{/number}","comments_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/issues/664/comments","statuses_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/statuses/e436a43d6169e05749d9c8dc7ede2d3579e80f79","head":{"label":"Microsoft:report-function","ref":"report-function","sha":"e436a43d6169e05749d9c8dc7ede2d3579e80f79","user":{"login":"Microsoft","id":6154722,"avatar_url":"https://avatars2.githubusercontent.com/u/6154722?v=4","gravatar_id":"","url":"https://api.github.com/users/Microsoft","html_url":"https://github.com/Microsoft","followers_url":"https://api.github.com/users/Microsoft/followers","following_url":"https://api.github.com/users/Microsoft/following{/other_user}","gists_url":"https://api.github.com/users/Microsoft/gists{/gist_id}","starred_url":"https://api.github.com/users/Microsoft/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Microsoft/subscriptions","organizations_url":"https://api.github.com/users/Microsoft/orgs","repos_url":"https://api.github.com/users/Microsoft/repos","events_url":"https://api.github.com/users/Microsoft/events{/privacy}","received_events_url":"https://api.github.com/users/Microsoft/received_events","type":"Organization","site_admin":false},"repo":{"id":27931923,"name":"dotnet-apiport","full_name":"Microsoft/dotnet-apiport","owner":{"login":"Microsoft","id":6154722,"avatar_url":"https://avatars2.githubusercontent.com/u/6154722?v=4","gravatar_id":"","url":"https://api.github.com/users/Microsoft","html_url":"https://github.com/Microsoft","followers_url":"https://api.github.com/users/Microsoft/followers","following_url":"https://api.github.com/users/Microsoft/following{/other_user}","gists_url":"https://api.github.com/users/Microsoft/gists{/gist_id}","starred_url":"https://api.github.com/users/Microsoft/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Microsoft/subscriptions","organizations_url":"https://api.github.com/users/Microsoft/orgs","repos_url":"https://api.github.com/users/Microsoft/repos","events_url":"https://api.github.com/users/Microsoft/events{/privacy}","received_events_url":"https://api.github.com/users/Microsoft/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/Microsoft/dotnet-apiport","description":"This repo contains .NET Portability Analyzer (VSIX and Console) libraries and tools","fork":false,"url":"https://api.github.com/repos/Microsoft/dotnet-apiport","forks_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/forks","keys_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/teams","hooks_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/hooks","issue_events_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/issues/events{/number}","events_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/events","assignees_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/assignees{/user}","branches_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/branches{/branch}","tags_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/tags","blobs_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/git/refs{/sha}","trees_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/statuses/{sha}","languages_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/languages","stargazers_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/stargazers","contributors_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/contributors","subscribers_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/subscribers","subscription_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/subscription","commits_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/commits{/sha}","git_commits_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/git/commits{/sha}","comments_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/comments{/number}","issue_comment_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/issues/comments{/number}","contents_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/contents/{+path}","compare_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/merges","archive_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/downloads","issues_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/issues{/number}","pulls_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/pulls{/number}","milestones_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/milestones{/number}","notifications_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/labels{/name}","releases_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/releases{/id}","deployments_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/deployments","created_at":"2014-12-12T18:04:11Z","updated_at":"2018-05-24T00:22:37Z","pushed_at":"2018-05-25T21:18:28Z","git_url":"git://github.com/Microsoft/dotnet-apiport.git","ssh_url":"git@github.com:Microsoft/dotnet-apiport.git","clone_url":"https://github.com/Microsoft/dotnet-apiport.git","svn_url":"https://github.com/Microsoft/dotnet-apiport","homepage":"","size":3155,"stargazers_count":527,"watchers_count":527,"language":"C#","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":95,"mirror_url":null,"archived":false,"open_issues_count":38,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit"},"forks":95,"open_issues":38,"watchers":527,"default_branch":"dev"}},"base":{"label":"Microsoft:service","ref":"service","sha":"63bc84ad6124eab5c99247dfdac5882c52474b07","user":{"login":"Microsoft","id":6154722,"avatar_url":"https://avatars2.githubusercontent.com/u/6154722?v=4","gravatar_id":"","url":"https://api.github.com/users/Microsoft","html_url":"https://github.com/Microsoft","followers_url":"https://api.github.com/users/Microsoft/followers","following_url":"https://api.github.com/users/Microsoft/following{/other_user}","gists_url":"https://api.github.com/users/Microsoft/gists{/gist_id}","starred_url":"https://api.github.com/users/Microsoft/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Microsoft/subscriptions","organizations_url":"https://api.github.com/users/Microsoft/orgs","repos_url":"https://api.github.com/users/Microsoft/repos","events_url":"https://api.github.com/users/Microsoft/events{/privacy}","received_events_url":"https://api.github.com/users/Microsoft/received_events","type":"Organization","site_admin":false},"repo":{"id":27931923,"name":"dotnet-apiport","full_name":"Microsoft/dotnet-apiport","owner":{"login":"Microsoft","id":6154722,"avatar_url":"https://avatars2.githubusercontent.com/u/6154722?v=4","gravatar_id":"","url":"https://api.github.com/users/Microsoft","html_url":"https://github.com/Microsoft","followers_url":"https://api.github.com/users/Microsoft/followers","following_url":"https://api.github.com/users/Microsoft/following{/other_user}","gists_url":"https://api.github.com/users/Microsoft/gists{/gist_id}","starred_url":"https://api.github.com/users/Microsoft/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Microsoft/subscriptions","organizations_url":"https://api.github.com/users/Microsoft/orgs","repos_url":"https://api.github.com/users/Microsoft/repos","events_url":"https://api.github.com/users/Microsoft/events{/privacy}","received_events_url":"https://api.github.com/users/Microsoft/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/Microsoft/dotnet-apiport","description":"This repo contains .NET Portability Analyzer (VSIX and Console) libraries and tools","fork":false,"url":"https://api.github.com/repos/Microsoft/dotnet-apiport","forks_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/forks","keys_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/teams","hooks_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/hooks","issue_events_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/issues/events{/number}","events_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/events","assignees_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/assignees{/user}","branches_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/branches{/branch}","tags_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/tags","blobs_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/git/refs{/sha}","trees_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/statuses/{sha}","languages_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/languages","stargazers_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/stargazers","contributors_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/contributors","subscribers_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/subscribers","subscription_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/subscription","commits_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/commits{/sha}","git_commits_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/git/commits{/sha}","comments_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/comments{/number}","issue_comment_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/issues/comments{/number}","contents_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/contents/{+path}","compare_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/merges","archive_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/downloads","issues_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/issues{/number}","pulls_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/pulls{/number}","milestones_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/milestones{/number}","notifications_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/labels{/name}","releases_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/releases{/id}","deployments_url":"https://api.github.com/repos/Microsoft/dotnet-apiport/deployments","created_at":"2014-12-12T18:04:11Z","updated_at":"2018-05-24T00:22:37Z","pushed_at":"2018-05-25T21:18:28Z","git_url":"git://github.com/Microsoft/dotnet-apiport.git","ssh_url":"git@github.com:Microsoft/dotnet-apiport.git","clone_url":"https://github.com/Microsoft/dotnet-apiport.git","svn_url":"https://github.com/Microsoft/dotnet-apiport","homepage":"","size":3155,"stargazers_count":527,"watchers_count":527,"language":"C#","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":95,"mirror_url":null,"archived":false,"open_issues_count":38,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit"},"forks":95,"open_issues":38,"watchers":527,"default_branch":"dev"}},"_links":{"self":{"href":"https://api.github.com/repos/Microsoft/dotnet-apiport/pulls/664"},"html":{"href":"https://github.com/Microsoft/dotnet-apiport/pull/664"},"issue":{"href":"https://api.github.com/repos/Microsoft/dotnet-apiport/issues/664"},"comments":{"href":"https://api.github.com/repos/Microsoft/dotnet-apiport/issues/664/comments"},"review_comments":{"href":"https://api.github.com/repos/Microsoft/dotnet-apiport/pulls/664/comments"},"review_comment":{"href":"https://api.github.com/repos/Microsoft/dotnet-apiport/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/Microsoft/dotnet-apiport/pulls/664/commits"},"statuses":{"href":"https://api.github.com/repos/Microsoft/dotnet-apiport/statuses/e436a43d6169e05749d9c8dc7ede2d3579e80f79"}},"author_association":"MEMBER"}}
{"actor":{"display_login":"twsouthwick"}}