{"action":"created","comment":{"url":"https://api.github.com/repos/caskdata/cdap/pulls/comments/178216624","pull_request_review_id":108245698,"id":178216624,"diff_hunk":"@@ -0,0 +1,379 @@\n+/*\n+ * Copyright © 2018 Cask Data, Inc.\n+ *\n+ * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n+ * use this file except in compliance with the License. You may obtain a copy of\n+ * the License at\n+ *\n+ * http://www.apache.org/licenses/LICENSE-2.0\n+ *\n+ * Unless required by applicable law or agreed to in writing, software\n+ * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n+ * License for the specific language governing permissions and limitations under\n+ * the License.\n+ */\n+\n+package co.cask.cdap.report;\n+\n+import co.cask.cdap.api.Transactionals;\n+import co.cask.cdap.api.dataset.InstanceConflictException;\n+import co.cask.cdap.api.dataset.lib.FileSet;\n+import co.cask.cdap.api.dataset.lib.FileSetProperties;\n+import co.cask.cdap.api.service.http.HttpServiceRequest;\n+import co.cask.cdap.api.service.http.HttpServiceResponder;\n+import co.cask.cdap.api.spark.AbstractExtendedSpark;\n+import co.cask.cdap.api.spark.JavaSparkExecutionContext;\n+import co.cask.cdap.api.spark.JavaSparkMain;\n+import co.cask.cdap.api.spark.service.AbstractSparkHttpServiceHandler;\n+import co.cask.cdap.api.spark.service.SparkHttpServiceContext;\n+import co.cask.cdap.api.spark.service.SparkHttpServiceHandler;\n+import co.cask.cdap.report.proto.FilterDeserializer;\n+import co.cask.cdap.report.proto.ReportContent;\n+import co.cask.cdap.report.proto.ReportGenerationInfo;\n+import co.cask.cdap.report.proto.ReportGenerationRequest;\n+import co.cask.cdap.report.proto.ReportList;\n+import co.cask.cdap.report.proto.ReportStatus;\n+import co.cask.cdap.report.proto.ReportStatusInfo;\n+import co.cask.cdap.report.util.ProgramRunMetaFileUtil;\n+import co.cask.cdap.report.util.ReportField;\n+import co.cask.cdap.report.util.ReportIds;\n+import com.google.common.base.Charsets;\n+import com.google.common.collect.ImmutableMap;\n+import com.google.common.io.ByteStreams;\n+import com.google.gson.Gson;\n+import com.google.gson.GsonBuilder;\n+import com.google.gson.JsonSyntaxException;\n+import com.google.gson.reflect.TypeToken;\n+import org.apache.spark.api.java.JavaSparkContext;\n+import org.apache.spark.sql.SQLContext;\n+import org.apache.spark.sql.SparkSession;\n+import org.apache.twill.filesystem.Location;\n+import org.slf4j.Logger;\n+import org.slf4j.LoggerFactory;\n+\n+import java.io.BufferedReader;\n+import java.io.IOException;\n+import java.io.InputStreamReader;\n+import java.io.PrintWriter;\n+import java.lang.reflect.Type;\n+import java.util.ArrayList;\n+import java.util.List;\n+import java.util.concurrent.Executors;\n+import java.util.concurrent.TimeUnit;\n+import java.util.stream.Collectors;\n+import java.util.stream.Stream;\n+import javax.ws.rs.DefaultValue;\n+import javax.ws.rs.GET;\n+import javax.ws.rs.POST;\n+import javax.ws.rs.Path;\n+import javax.ws.rs.PathParam;\n+import javax.ws.rs.QueryParam;\n+\n+/**\n+ * A Spark program for generating reports, querying for report statuses, and reading reports.\n+ */\n+public class ReportGenerationSpark extends AbstractExtendedSpark implements JavaSparkMain {\n+  private static final Logger LOG = LoggerFactory.getLogger(ReportGenerationSpark.class);\n+  private static final Gson GSON = new GsonBuilder()\n+    .registerTypeAdapter(ReportGenerationRequest.Filter.class, new FilterDeserializer())\n+    .create();\n+\n+  @Override\n+  protected void configure() {\n+    setMainClass(ReportGenerationSpark.class);\n+    addHandlers(new ReportSparkHandler());\n+  }\n+\n+  @Override\n+  public void run(JavaSparkExecutionContext sec) throws Exception {\n+    JavaSparkContext jsc = new JavaSparkContext();\n+  }\n+\n+  /**\n+   * A {@link SparkHttpServiceHandler} generating reports, querying for report statuses, and reading reports.\n+   */\n+  public static final class ReportSparkHandler extends AbstractSparkHttpServiceHandler {\n+\n+    private static final Logger LOG = LoggerFactory.getLogger(ReportSparkHandler.class);\n+    private static final Type REPORT_GENERATION_REQUEST_TYPE = new TypeToken<ReportGenerationRequest>() {\n+    }.getType();\n+    private static final int MAX_LIMIT = 10000;\n+    private static final String DEFAULT_LIMIT = \"10000\";\n+    private SparkSession sparkSession;\n+\n+    public static final String START_FILE = \"_START\";\n+    public static final String REPORT_DIR = \"reports\";\n+    public static final String COUNT_FILE = \"COUNT\";\n+    public static final String SUCCESS_FILE = \"_SUCCESS\";\n+    public static final String FAILURE_FILE = \"_FAILURE\";\n+\n+    @Override\n+    public void initialize(SparkHttpServiceContext context) throws Exception {\n+      super.initialize(context);\n+      try {\n+        // TODO: [CDAP-13216] temporarily create the run meta fileset and generate mock program run meta files here.\n+        // Will remove once the TMS subscriber writing to the run meta fileset is implemented.\n+        context.getAdmin().createDataset(ReportGenerationApp.RUN_META_FILESET, FileSet.class.getName(),\n+                                         FileSetProperties.builder().build());\n+        ProgramRunMetaFileUtil.populateMetaFiles(getDatasetBaseLocation(ReportGenerationApp.RUN_META_FILESET));\n+      } catch (InstanceConflictException e) {\n+        // It's ok if the dataset already exists\n+      }\n+      sparkSession = new SQLContext(getContext().getSparkContext()).sparkSession();\n+    }\n+\n+    @GET\n+    @Path(\"/reports\")\n+    public void getReports(HttpServiceRequest request, HttpServiceResponder responder,\n+                           @QueryParam(\"offset\") @DefaultValue(\"0\") int offset,\n+                           @QueryParam(\"limit\")  @DefaultValue(DEFAULT_LIMIT) int limit)\n+      throws IOException {\n+      Location reportFilesetLocation = getDatasetBaseLocation(ReportGenerationApp.REPORT_FILESET);\n+      List<ReportStatusInfo> reportStatuses = new ArrayList<>();\n+      // The index of the report directory to start reading from, initialized to the given offset\n+      int idx = offset;\n+      List<Location> reportBaseDirs = reportFilesetLocation.list();\n+      // Keep add report status information to the list until the index is no longer smaller than\n+      // the number of report directories or the list is reaching the given limit\n+      while (idx < reportBaseDirs.size() && reportStatuses.size() < limit) {\n+        Location reportBaseDir = reportBaseDirs.get(idx++);\n+        String reportId = reportBaseDir.getName();\n+        // Report ID is time based UUID. Get the creation time from the report ID.\n+        long creationTime = ReportIds.getTime(reportId, TimeUnit.SECONDS);\n+        reportStatuses.add(new ReportStatusInfo(reportId, creationTime, getReportStatus(reportBaseDir)));\n+      }\n+      responder.sendJson(200, new ReportList(offset, limit, reportBaseDirs.size(), reportStatuses));\n+    }\n+\n+    @GET\n+    @Path(\"/reports/{report-id}\")\n+    public void getReportStatus(HttpServiceRequest request, HttpServiceResponder responder,\n+                                @PathParam(\"report-id\") String reportId,\n+                                @QueryParam(\"share-id\") String shareId)\n+      throws IOException {\n+      Location reportBaseDir = getDatasetBaseLocation(ReportGenerationApp.REPORT_FILESET).append(reportId);\n+      if (!reportBaseDir.exists()) {\n+        responder.sendError(404, String.format(\"Report with id %s does not exist.\", reportId));\n+        return;\n+      }\n+      long creationTime = ReportIds.getTime(reportId, TimeUnit.SECONDS);\n+      // Read the report request from _START file, which was written at the beginning of report generation\n+      String reportRequest =\n+        new String(ByteStreams.toByteArray(reportBaseDir.append(START_FILE).getInputStream()), Charsets.UTF_8);\n+      responder.sendJson(new ReportGenerationInfo(creationTime, getReportStatus(reportBaseDir), reportRequest));\n+    }\n+\n+    @GET\n+    @Path(\"reports/{report-id}/runs\")\n+    public void getReportRuns(HttpServiceRequest request, HttpServiceResponder responder,\n+                              @PathParam(\"report-id\") String reportId,\n+                              @QueryParam(\"offset\") @DefaultValue(\"0\") long offset,\n+                              @QueryParam(\"limit\") @DefaultValue(DEFAULT_LIMIT) int limit,\n+                              @QueryParam(\"share-id\") String shareId) throws IOException {\n+      if (offset < 0) {\n+        responder.sendError(400, \"offset cannot be negative\");\n+        return;\n+      }\n+      if (limit <= 0) {\n+        responder.sendError(400, \"limit must be a positive integer\");\n+        return;\n+      }\n+      if (limit > MAX_LIMIT) {\n+        responder.sendError(400, \"limit must cannot be larger than \" + MAX_LIMIT);\n+        return;\n+      }\n+      Location reportBaseDir = getDatasetBaseLocation(ReportGenerationApp.REPORT_FILESET).append(reportId);\n+      if (!reportBaseDir.exists()) {\n+        responder.sendError(404, String.format(\"Report with id %s does not exist.\", reportId));\n+        return;\n+      }\n+      // Get the status of the report and only COMPLETED report can be read\n+      ReportStatus status = getReportStatus(reportBaseDir);\n+      if (!ReportStatus.COMPLETED.equals(status)) {\n+        responder.sendError(400, String.format(\"Report with id %s with status %s cannot be read.\", reportId, status));\n+        return;\n+      }\n+      List<String> reportRecords = new ArrayList<>();\n+      long lineCount = 0;\n+      Location reportDir = reportBaseDir.append(REPORT_DIR);\n+      Location reportFile = null;\n+      // TODO: assume only one report file for now\n+      for (Location file : reportDir.list()) {\n+        if (file.getName().endsWith(\".json\")) {\n+          reportFile = file;\n+          break;\n+        }\n+      }\n+      if (reportFile == null) {\n+        responder.sendError(500, \"No files found for report \" + reportId);","path":"cdap-app-templates/cdap-program-report/src/main/java/co/cask/cdap/report/ReportGenerationSpark.java","position":209,"original_position":209,"commit_id":"e7ee77a94af3114b36652bff1e2dfb8350921ad3","original_commit_id":"e7ee77a94af3114b36652bff1e2dfb8350921ad3","user":{"login":"maochf","id":5809268,"avatar_url":"https://avatars3.githubusercontent.com/u/5809268?v=4","gravatar_id":"","url":"https://api.github.com/users/maochf","html_url":"https://github.com/maochf","followers_url":"https://api.github.com/users/maochf/followers","following_url":"https://api.github.com/users/maochf/following{/other_user}","gists_url":"https://api.github.com/users/maochf/gists{/gist_id}","starred_url":"https://api.github.com/users/maochf/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/maochf/subscriptions","organizations_url":"https://api.github.com/users/maochf/orgs","repos_url":"https://api.github.com/users/maochf/repos","events_url":"https://api.github.com/users/maochf/events{/privacy}","received_events_url":"https://api.github.com/users/maochf/received_events","type":"User","site_admin":false},"body":"This should never happen but just need to handle the null case","created_at":"2018-03-30T01:14:49Z","updated_at":"2018-03-30T01:14:49Z","html_url":"https://github.com/caskdata/cdap/pull/9966#discussion_r178216624","pull_request_url":"https://api.github.com/repos/caskdata/cdap/pulls/9966","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/caskdata/cdap/pulls/comments/178216624"},"html":{"href":"https://github.com/caskdata/cdap/pull/9966#discussion_r178216624"},"pull_request":{"href":"https://api.github.com/repos/caskdata/cdap/pulls/9966"}},"in_reply_to_id":178209189},"pull_request":{"url":"https://api.github.com/repos/caskdata/cdap/pulls/9966","id":176315252,"html_url":"https://github.com/caskdata/cdap/pull/9966","diff_url":"https://github.com/caskdata/cdap/pull/9966.diff","patch_url":"https://github.com/caskdata/cdap/pull/9966.patch","issue_url":"https://api.github.com/repos/caskdata/cdap/issues/9966","number":9966,"state":"open","locked":false,"title":"[CDAP-13211] Ops Dashboard Report Generation App","user":{"login":"maochf","id":5809268,"avatar_url":"https://avatars3.githubusercontent.com/u/5809268?v=4","gravatar_id":"","url":"https://api.github.com/users/maochf","html_url":"https://github.com/maochf","followers_url":"https://api.github.com/users/maochf/followers","following_url":"https://api.github.com/users/maochf/following{/other_user}","gists_url":"https://api.github.com/users/maochf/gists{/gist_id}","starred_url":"https://api.github.com/users/maochf/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/maochf/subscriptions","organizations_url":"https://api.github.com/users/maochf/orgs","repos_url":"https://api.github.com/users/maochf/repos","events_url":"https://api.github.com/users/maochf/events{/privacy}","received_events_url":"https://api.github.com/users/maochf/received_events","type":"User","site_admin":false},"body":"https://issues.cask.co/browse/CDAP-13211\r\n\r\nUnit test still having problem with running, ignore for now. Tested on Sandbox and cluster.\r\nReport records not including some of the fields, will add them while implementing the TMS subscriber that actually writes the real program run meta files.\r\n\r\nThe PR is organized in 4 commits as described in the commit messages:\r\n1. pom, package info, and the ReportGenerationApp definition with no actual implementation\r\n2. Utility classes for report generation app\r\n3. classes representing data in HTTP request/response and data used in Spark SQL calculation\r\n4. Classes containing the actual report generation logic and REST API's, such as ReportGenerationSpark and scala helper classes, and an ignored test to be fixed","created_at":"2018-03-20T20:37:19Z","updated_at":"2018-03-30T01:14:49Z","closed_at":null,"merged_at":null,"merge_commit_sha":"59a1566e7759331aeb9d50176e50c7156f0647d6","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[{"id":673594855,"url":"https://api.github.com/repos/caskdata/cdap/labels/5.0","name":"5.0","color":"ff6600","default":false}],"milestone":null,"commits_url":"https://api.github.com/repos/caskdata/cdap/pulls/9966/commits","review_comments_url":"https://api.github.com/repos/caskdata/cdap/pulls/9966/comments","review_comment_url":"https://api.github.com/repos/caskdata/cdap/pulls/comments{/number}","comments_url":"https://api.github.com/repos/caskdata/cdap/issues/9966/comments","statuses_url":"https://api.github.com/repos/caskdata/cdap/statuses/e7ee77a94af3114b36652bff1e2dfb8350921ad3","head":{"label":"caskdata:feature/report-spark-app","ref":"feature/report-spark-app","sha":"e7ee77a94af3114b36652bff1e2dfb8350921ad3","user":{"login":"caskdata","id":8123023,"avatar_url":"https://avatars2.githubusercontent.com/u/8123023?v=4","gravatar_id":"","url":"https://api.github.com/users/caskdata","html_url":"https://github.com/caskdata","followers_url":"https://api.github.com/users/caskdata/followers","following_url":"https://api.github.com/users/caskdata/following{/other_user}","gists_url":"https://api.github.com/users/caskdata/gists{/gist_id}","starred_url":"https://api.github.com/users/caskdata/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/caskdata/subscriptions","organizations_url":"https://api.github.com/users/caskdata/orgs","repos_url":"https://api.github.com/users/caskdata/repos","events_url":"https://api.github.com/users/caskdata/events{/privacy}","received_events_url":"https://api.github.com/users/caskdata/received_events","type":"Organization","site_admin":false},"repo":{"id":22542759,"name":"cdap","full_name":"caskdata/cdap","owner":{"login":"caskdata","id":8123023,"avatar_url":"https://avatars2.githubusercontent.com/u/8123023?v=4","gravatar_id":"","url":"https://api.github.com/users/caskdata","html_url":"https://github.com/caskdata","followers_url":"https://api.github.com/users/caskdata/followers","following_url":"https://api.github.com/users/caskdata/following{/other_user}","gists_url":"https://api.github.com/users/caskdata/gists{/gist_id}","starred_url":"https://api.github.com/users/caskdata/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/caskdata/subscriptions","organizations_url":"https://api.github.com/users/caskdata/orgs","repos_url":"https://api.github.com/users/caskdata/repos","events_url":"https://api.github.com/users/caskdata/events{/privacy}","received_events_url":"https://api.github.com/users/caskdata/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/caskdata/cdap","description":"A Unified Integration Platform - Cask Data Application Platform (CDAP)","fork":false,"url":"https://api.github.com/repos/caskdata/cdap","forks_url":"https://api.github.com/repos/caskdata/cdap/forks","keys_url":"https://api.github.com/repos/caskdata/cdap/keys{/key_id}","collaborators_url":"https://api.github.com/repos/caskdata/cdap/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/caskdata/cdap/teams","hooks_url":"https://api.github.com/repos/caskdata/cdap/hooks","issue_events_url":"https://api.github.com/repos/caskdata/cdap/issues/events{/number}","events_url":"https://api.github.com/repos/caskdata/cdap/events","assignees_url":"https://api.github.com/repos/caskdata/cdap/assignees{/user}","branches_url":"https://api.github.com/repos/caskdata/cdap/branches{/branch}","tags_url":"https://api.github.com/repos/caskdata/cdap/tags","blobs_url":"https://api.github.com/repos/caskdata/cdap/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/caskdata/cdap/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/caskdata/cdap/git/refs{/sha}","trees_url":"https://api.github.com/repos/caskdata/cdap/git/trees{/sha}","statuses_url":"https://api.github.com/repos/caskdata/cdap/statuses/{sha}","languages_url":"https://api.github.com/repos/caskdata/cdap/languages","stargazers_url":"https://api.github.com/repos/caskdata/cdap/stargazers","contributors_url":"https://api.github.com/repos/caskdata/cdap/contributors","subscribers_url":"https://api.github.com/repos/caskdata/cdap/subscribers","subscription_url":"https://api.github.com/repos/caskdata/cdap/subscription","commits_url":"https://api.github.com/repos/caskdata/cdap/commits{/sha}","git_commits_url":"https://api.github.com/repos/caskdata/cdap/git/commits{/sha}","comments_url":"https://api.github.com/repos/caskdata/cdap/comments{/number}","issue_comment_url":"https://api.github.com/repos/caskdata/cdap/issues/comments{/number}","contents_url":"https://api.github.com/repos/caskdata/cdap/contents/{+path}","compare_url":"https://api.github.com/repos/caskdata/cdap/compare/{base}...{head}","merges_url":"https://api.github.com/repos/caskdata/cdap/merges","archive_url":"https://api.github.com/repos/caskdata/cdap/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/caskdata/cdap/downloads","issues_url":"https://api.github.com/repos/caskdata/cdap/issues{/number}","pulls_url":"https://api.github.com/repos/caskdata/cdap/pulls{/number}","milestones_url":"https://api.github.com/repos/caskdata/cdap/milestones{/number}","notifications_url":"https://api.github.com/repos/caskdata/cdap/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/caskdata/cdap/labels{/name}","releases_url":"https://api.github.com/repos/caskdata/cdap/releases{/id}","deployments_url":"https://api.github.com/repos/caskdata/cdap/deployments","created_at":"2014-08-02T09:00:10Z","updated_at":"2018-03-29T21:51:41Z","pushed_at":"2018-03-30T01:13:03Z","git_url":"git://github.com/caskdata/cdap.git","ssh_url":"git@github.com:caskdata/cdap.git","clone_url":"https://github.com/caskdata/cdap.git","svn_url":"https://github.com/caskdata/cdap","homepage":"http://docs.cask.co/cdap","size":530093,"stargazers_count":217,"watchers_count":217,"language":"Java","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":114,"mirror_url":null,"archived":false,"open_issues_count":10,"license":{"key":"other","name":"Other","spdx_id":null,"url":null},"forks":114,"open_issues":10,"watchers":217,"default_branch":"develop"}},"base":{"label":"caskdata:develop","ref":"develop","sha":"fda66f631a576e34c30d806bcb3f7ec15f784d7c","user":{"login":"caskdata","id":8123023,"avatar_url":"https://avatars2.githubusercontent.com/u/8123023?v=4","gravatar_id":"","url":"https://api.github.com/users/caskdata","html_url":"https://github.com/caskdata","followers_url":"https://api.github.com/users/caskdata/followers","following_url":"https://api.github.com/users/caskdata/following{/other_user}","gists_url":"https://api.github.com/users/caskdata/gists{/gist_id}","starred_url":"https://api.github.com/users/caskdata/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/caskdata/subscriptions","organizations_url":"https://api.github.com/users/caskdata/orgs","repos_url":"https://api.github.com/users/caskdata/repos","events_url":"https://api.github.com/users/caskdata/events{/privacy}","received_events_url":"https://api.github.com/users/caskdata/received_events","type":"Organization","site_admin":false},"repo":{"id":22542759,"name":"cdap","full_name":"caskdata/cdap","owner":{"login":"caskdata","id":8123023,"avatar_url":"https://avatars2.githubusercontent.com/u/8123023?v=4","gravatar_id":"","url":"https://api.github.com/users/caskdata","html_url":"https://github.com/caskdata","followers_url":"https://api.github.com/users/caskdata/followers","following_url":"https://api.github.com/users/caskdata/following{/other_user}","gists_url":"https://api.github.com/users/caskdata/gists{/gist_id}","starred_url":"https://api.github.com/users/caskdata/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/caskdata/subscriptions","organizations_url":"https://api.github.com/users/caskdata/orgs","repos_url":"https://api.github.com/users/caskdata/repos","events_url":"https://api.github.com/users/caskdata/events{/privacy}","received_events_url":"https://api.github.com/users/caskdata/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/caskdata/cdap","description":"A Unified Integration Platform - Cask Data Application Platform (CDAP)","fork":false,"url":"https://api.github.com/repos/caskdata/cdap","forks_url":"https://api.github.com/repos/caskdata/cdap/forks","keys_url":"https://api.github.com/repos/caskdata/cdap/keys{/key_id}","collaborators_url":"https://api.github.com/repos/caskdata/cdap/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/caskdata/cdap/teams","hooks_url":"https://api.github.com/repos/caskdata/cdap/hooks","issue_events_url":"https://api.github.com/repos/caskdata/cdap/issues/events{/number}","events_url":"https://api.github.com/repos/caskdata/cdap/events","assignees_url":"https://api.github.com/repos/caskdata/cdap/assignees{/user}","branches_url":"https://api.github.com/repos/caskdata/cdap/branches{/branch}","tags_url":"https://api.github.com/repos/caskdata/cdap/tags","blobs_url":"https://api.github.com/repos/caskdata/cdap/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/caskdata/cdap/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/caskdata/cdap/git/refs{/sha}","trees_url":"https://api.github.com/repos/caskdata/cdap/git/trees{/sha}","statuses_url":"https://api.github.com/repos/caskdata/cdap/statuses/{sha}","languages_url":"https://api.github.com/repos/caskdata/cdap/languages","stargazers_url":"https://api.github.com/repos/caskdata/cdap/stargazers","contributors_url":"https://api.github.com/repos/caskdata/cdap/contributors","subscribers_url":"https://api.github.com/repos/caskdata/cdap/subscribers","subscription_url":"https://api.github.com/repos/caskdata/cdap/subscription","commits_url":"https://api.github.com/repos/caskdata/cdap/commits{/sha}","git_commits_url":"https://api.github.com/repos/caskdata/cdap/git/commits{/sha}","comments_url":"https://api.github.com/repos/caskdata/cdap/comments{/number}","issue_comment_url":"https://api.github.com/repos/caskdata/cdap/issues/comments{/number}","contents_url":"https://api.github.com/repos/caskdata/cdap/contents/{+path}","compare_url":"https://api.github.com/repos/caskdata/cdap/compare/{base}...{head}","merges_url":"https://api.github.com/repos/caskdata/cdap/merges","archive_url":"https://api.github.com/repos/caskdata/cdap/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/caskdata/cdap/downloads","issues_url":"https://api.github.com/repos/caskdata/cdap/issues{/number}","pulls_url":"https://api.github.com/repos/caskdata/cdap/pulls{/number}","milestones_url":"https://api.github.com/repos/caskdata/cdap/milestones{/number}","notifications_url":"https://api.github.com/repos/caskdata/cdap/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/caskdata/cdap/labels{/name}","releases_url":"https://api.github.com/repos/caskdata/cdap/releases{/id}","deployments_url":"https://api.github.com/repos/caskdata/cdap/deployments","created_at":"2014-08-02T09:00:10Z","updated_at":"2018-03-29T21:51:41Z","pushed_at":"2018-03-30T01:13:03Z","git_url":"git://github.com/caskdata/cdap.git","ssh_url":"git@github.com:caskdata/cdap.git","clone_url":"https://github.com/caskdata/cdap.git","svn_url":"https://github.com/caskdata/cdap","homepage":"http://docs.cask.co/cdap","size":530093,"stargazers_count":217,"watchers_count":217,"language":"Java","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":114,"mirror_url":null,"archived":false,"open_issues_count":10,"license":{"key":"other","name":"Other","spdx_id":null,"url":null},"forks":114,"open_issues":10,"watchers":217,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/caskdata/cdap/pulls/9966"},"html":{"href":"https://github.com/caskdata/cdap/pull/9966"},"issue":{"href":"https://api.github.com/repos/caskdata/cdap/issues/9966"},"comments":{"href":"https://api.github.com/repos/caskdata/cdap/issues/9966/comments"},"review_comments":{"href":"https://api.github.com/repos/caskdata/cdap/pulls/9966/comments"},"review_comment":{"href":"https://api.github.com/repos/caskdata/cdap/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/caskdata/cdap/pulls/9966/commits"},"statuses":{"href":"https://api.github.com/repos/caskdata/cdap/statuses/e7ee77a94af3114b36652bff1e2dfb8350921ad3"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"maochf"}}