{"action":"created","comment":{"url":"https://api.github.com/repos/propelml/propel/pulls/comments/180605409","pull_request_review_id":111055032,"id":180605409,"diff_hunk":"@@ -14,263 +14,347 @@\n  */\n \n import { Component, h } from \"preact\";\n+import { IS_WEB, isNumericalKey, randomString } from \"../src/util\";\n+import {\n+  BaseObjectDescriptor,\n+  PropertyDescriptor,\n+  ValueDescriptor\n+} from \"./serializer\";\n \n-export interface InspectorProps {\n-  object: any;\n-  name?: string;\n-  depth?: number;\n-  inline?: boolean;\n-}\n+type ElementLike = JSX.Element | string;\n+type ElementList = ElementLike[];\n+type Visibility = \"always\" | \"collapsed\" | \"expanded\" | \"parent-expanded\";\n \n-export interface InspectorState {\n-  isCollapsed: boolean;\n-  isCollapsible: boolean;\n-}\n+export interface InspectorProps { descriptors: ValueDescriptor[]; }\n \n-export class Inspector extends Component<InspectorProps, InspectorState> {\n-  inlineRendered: JSX.Element[];\n-  constructor(props) {\n-    super();\n-    const isCollapsible = this.isCollapsible(props);\n-    this.state = {\n-      isCollapsed: this.shouldExpandByDefault(props, isCollapsible),\n-      isCollapsible\n-    };\n+export class Inspector extends Component<InspectorProps, void>{\n+  private renderKey(d: ValueDescriptor,\n+                    arrayIndex: number | null = null): ElementList {\n+    switch (d.type) {\n+      case \"string\":\n+        const value = d.value;\n+        // Keys that look like javascript identifiers are rendered without\n+        // quote marks. Numerical keys can be omitted entirely.\n+        const isSimpleKey =  /^[a-z$_][\\w$_]*$/i.test(value);\n+        const isArrayKey = (typeof arrayIndex === \"number\")\n+                           && isNumericalKey(value);\n+        if (isArrayKey || isSimpleKey) {\n+          // 12: {...}\n+          // simple_key: {...}\n+          // If the array keys are in the right order without holes, hide them\n+          // from the collapsed view.\n+          const keyVisible = (isArrayKey && Number(value) === arrayIndex)\n+                              ? \"parent-expanded\" : \"always\";\n+          return showWhen(keyVisible,\n+                          <span class=\"cm-property\">{ value }</span>, \": \");\n+        } else {\n+          // \"my!weird*key\\n\": {...}\n+          const quoted = JSON.stringify(value);\n+          return [<span class=\"cm-property\">{ quoted }</span>, \": \"];\n+        }\n+      case \"symbol\":\n+        // [Symbol(some symbol)]: {...}\n+        return [\"[\", <span class=\"cm-property\">{ d.value }</span>, \"]: \"];\n+      case \"proto\":\n+        // [prototype]: {...}\n+        return [\"[\", <span class=\"cm-property\">prototype</span>, \"]: \"];\n+      default:\n+        // Keys are always either a string, symbol, or prototype key.\n+        throw new Error(\"Unexpected key type\");\n+    }\n   }\n \n-  shouldExpandByDefault({ depth = 0, object }: InspectorProps,\n-                        isCollapsible: boolean): boolean {\n-    if (!isCollapsible) return false;\n-    if (object.isCircular) return false;\n-    if (depth === 0) return true;\n-    return Object.getOwnPropertyNames(object.data).length <= 15;\n-  }\n+  private renderValue(d: ValueDescriptor): ElementList {\n+    // Render primitive values.\n+    switch (d.type) {\n+      case \"boolean\":\n+        return [<span class=\"cm-atom\">{ d.value }</span>];\n+      case \"circular\":\n+        return [<span class=\"cm-keyword\">[circular]</span>];\n+      case \"date\":\n+        return [<span class=\"cm-string-2\">{ d.value }</span>];\n+      case \"getter\":\n+        return [<span class=\"cm-keyword\">[getter]</span>];\n+      case \"gettersetter\":\n+        return [<span class=\"cm-keyword\">[getter/setter]</span>];\n+      case \"null\":\n+        return [<span class=\"cm-atom\">null</span>];\n+      case \"number\":\n+        return [<span class=\"cm-number\">{ d.value }</span>];\n+      case \"proto\":\n+        return [<span class=\"cm-keyword\">[prototype]</span>];\n+      case \"regexp\":\n+        return [<span class=\"cm-string-2\">{ d.value }</span>];\n+      case \"setter\":\n+        return [<span class=\"cm-keyword\">[Setter]</span>];\n+      case \"string\":\n+        return [<span class=\"cm-string\">{ JSON.stringify(d.value) }</span>];\n+      case \"symbol\":\n+        return [<span class=\"cm-string\">{ d.value }</span>];\n+      case \"undefined\":\n+        return [<span class=\"cm-atom\">undefined</span>];\n+    }\n \n-  toggle(): void {\n-    this.setState(s => ({\n-      isCollapsed: !s.isCollapsed\n-    }));\n-  }\n+    // Render the list of properties and other content that is expanded when the\n+    // expand button is clicked.\n+    const listItems: ElementList = [];\n+    // TODO: the tensor is currently formatted by Tensor.prototype.toString()\n+    // and displayed in a monospace font. Make it nicer by using a table.\n+    if (d.type === \"tensor\") {\n+      listItems.push(\n+        <li>\n+          <div class=\"inspector__content inspector__content--pre\">\n+            { d.formatted }\n+          </div>\n+        </li>\n+      );\n+    }\n+    // Add regular array/object properties.\n+    const isArray = d.type === \"array\";\n+    listItems.push(...d.props.map((prop, index) =>\n+      this.renderProperty(prop, isArray ? index : null)\n+    ));\n+    // Wrap the items in an <ul> element, but only if the list contains at\n+    // least one item.\n+    const list =\n+      (listItems.length > 0)\n+        ? <ul class=\"inspector__list inspector__prop-list\">\n+            { ...listItems }\n+          </ul>\n+        : undefined;\n \n-  renderChild(): JSX.Element {\n-    const { object: { data, type }, depth = 0 } = this.props;\n-    const properties = [];\n-    switch (type) {\n-      case \"object\":\n+    // Render the object header, containing the class name, sometimes\n+    // some metadata between parentheses, and boxed primitive content.\n+    const elements: ElementList = [];\n+    switch (d.type) {\n       case \"array\":\n-        for (const key in data) {\n-          if (data.hasOwnProperty(key)) {\n-            properties.push({\n-              name: key,\n-              object: data[key]\n-            });\n-          }\n+        // If the constructor is \"Array\", it is hidden when collapsed.\n+        // Square brackets are used and they are always visible.\n+        //   [1, 2, 3, 4, 5, foo: \"bar\"]\n+        //   Array(3) [1, 2, 3, ...properties...]\n+        //   Float32Array(30) [...]\n+        const ctorVisible = d.ctor === \"Array\" ? \"expanded\" : \"always\";\n+        elements.push(\n+          ...showWhen(ctorVisible,\n+                      <span class=\"cm-def\">{ d.ctor }</span>,\n+                      \"(\", <span class=\"cm-number\">{ d.length }</span>, \") \"),\n+          \"[\", list || \"\", \"]\"        );\n+        break;\n+      case \"box\":\n+        // The class name is omitted when it is \"Date\" or \"RegExp\".\n+        // Number, Boolean, String are shown to distinguish from primitives.\n+        // Properties are wrapped in curly braces; these are hidden when\n+        // there are no properties..\n+        //   Number:42 {...properties...}\n+        //   /abcd/i {...properties...}\n+        if (d.ctor !== \"Date\" && d.ctor !== \"RegExp\") {\n+          elements.push(<span class=\"cm-def\">{ d.ctor }</span>, \":\");\n         }\n+        elements.push(\n+          ...this.renderValue(d.primitive),\n+          ...this.renderPropList(d, list)\n+        );\n+        break;\n+      case \"function\":\n+        // Constructor name is only shown if it is nontrivial.\n+        //   function() {…} {...properties...}\n+        //   class Foo {…}\n+        //   async function* bar() {…}\n+        //   MyFunction:function baz() {…}\n+        if (!/^(Async)?(Generator)?Function$/.test(d.ctor)) {\n+          elements.push(<span class=\"cm-def\">{ d.ctor }</span>, \":\");\n+        }\n+        elements.push(\n+          <span class=\"cm-keyword\">\n+            { d.async ? \"async \" : \"\" }\n+            { d.class ? \"class\" : \"function\"}\n+            { d.generator ? \"*\" : \"\" }\n+          </span>,\n+          d.name ? \" \" : \"\",\n+          d.name ? <span class=\"cm-def\">{ d.name }</span> : \"\",\n+          d.class ? \" {…}\" : \"() {…}\",\n+          ...this.renderPropList(d, list)\n+        );\n         break;\n       case \"tensor\":\n-        properties.push({\n-          object: {\n-            data: data.data,\n-            type: \"tensorValue\"\n-          }\n-        });\n+        // Tensor(float32 2✕2) [[1, 2], [3, 4]]\n+        // Tensor(uint32 3✕4✕4) { [[[tensor]]]], ...properties... }\n+        elements.push(\n+          <span class=\"cm-def\">{ d.ctor }</span>,\n+          \"(\",\n+          <span class=\"cm-string-2\">{ d.dtype }</span>\n+        );\n+        for (const [dim, size] of d.shape.entries()) {\n+          elements.push(\n+            dim === 0 ? \" \" : \"✕\",\n+            <span class=\"cm-number\">{ size }</span>\n+          );\n+        }\n+        elements.push(\n+          \")\",\n+          ...showWhen(\"collapsed\", \" \"),\n+          ...this.renderPropList(d, list)\n+        );\n+        break;\n+      case \"object\":\n+        // Constructor name is omitted when it's \"Object\", or when the\n+        // object doesn't have a prototype at all (ctor === null).\n+        // Curly braces are always shown, even when there are no properties.\n+        //   {a: \"hello\", b: \"world\"}\n+        //   MyObject {\"#^%&\": \"something\"}\n+        if (d.ctor !== null && d.ctor !== \"Object\") {\n+          elements.push(<span class=\"cm-def\">{ d.ctor }</span>, \" \");\n+        }\n+        elements.push(\"{\", list || \"\", \"}\");\n         break;\n     }\n-    const nextDepth = depth + 1;\n+\n+    return elements;\n+  }\n+\n+  private renderPropList(d: BaseObjectDescriptor,\n+                         list?: ElementLike): ElementList {\n+    if (!list) return [];\n+    // If the list contains items that are visible in collapsed mode, always\n+    // show the curly braces. Otherwise show them only when expanded.\n+    const hasVisibleProps = d.props.some(prop => !prop.hidden);\n+    const bracesVisible = hasVisibleProps ? \"always\" : \"expanded\";\n+    return [\n+       ...showWhen(bracesVisible, \" {\"),\n+       list,\n+       ...showWhen(bracesVisible, \"}\"),\n+    ];\n+  }\n+\n+  private hasChildren(d: ValueDescriptor): boolean {\n+    // Returns true if a descriptor has properties that warrant it getting an\n+    // expand/collapse button.\n+    return d.type === \"tensor\" ||\n+           (d.type === \"array\" && d.props.length > 0) ||\n+           (d.type === \"box\" && d.props.length > 0) ||\n+           (d.type === \"function\" && d.props.length > 0) ||\n+           (d.type === \"object\" && d.props.length > 0);\n+  }\n+\n+  private renderExpandButton(): ElementLike {\n+    const id = randomString();\n+    return <a id={ id } class=\"inspector__toggle\"\n+              href={ `javascript:Inspector.toggle(\"${id}\")` } />;\n+  }\n+\n+  private renderProperty(d: PropertyDescriptor,\n+                         arrayIndex: number | null = null): ElementLike {\n+    const hasChildren = this.hasChildren(d.value);\n+    const attr = d.hidden ? { class: \"inspector__li--hidden\" } : {};\n     return (\n-      <div class=\"tree-group\">\n-        {properties.map(({ name, object }) => (\n-          <Inspector depth={ nextDepth } name={ name } object={ object } />\n-        ))}\n-      </div>\n+      <li { ...attr }>\n+       { hasChildren && this.renderExpandButton() }\n+       <div class=\"inspector__content\">\n+          { ...this.renderKey(d.key, arrayIndex) }\n+          { ...this.renderValue(d.value) }\n+        </div>\n+      </li>\n     );\n   }\n \n-  isCollapsible(props: InspectorProps): boolean {\n-    const { object: { data, type } } = props;\n-    switch (type) {\n-      case \"array\":\n-      case \"object\":\n-        let keys = Object.getOwnPropertyNames(data);\n-        if (type === \"array\") {\n-          keys = keys.filter(x => x !== \"length\");\n-        }\n-        this.inlineRendered = this.renderInlinePreview(data, type);\n-        return this.inlineRendered.length < keys.length;\n-      case \"tensor\":\n-        return true;\n+  private renderRoot(descriptors: ValueDescriptor[]): ElementLike {\n+    if (descriptors.length === 0) return \"\";\n+    // When inspecting a only non-expandable values, simply lay them out\n+    // next to one another in a single div.\n+    if (descriptors.every(d => !this.hasChildren(d))) {\n+      const elements = [].concat(...descriptors.map(\n+        (d, index) => [\n+          index > 0 ? \" \" : \"\", // Space between items.\n+          ...this.renderValue(d)\n+        ]));\n+      return <div class=\"inspector__content\">{ ...elements }</div>;\n     }\n-    return false;\n-  }\n-\n-  renderInlinePreview(data, type: string): JSX.Element[] {\n-    let keys = Object.getOwnPropertyNames(data);\n-    let elements = null;\n-    if (type === \"array\") {\n-      keys = keys.filter(x => x !== \"length\");\n+    // Since there is at least something expandable at the top level, make <li>\n+    // elements for all items.\n+    let rootItems = descriptors.map(d =>\n+      <li>\n+        { this.hasChildren(d) && this.renderExpandButton() }\n+        <div class=\"inspector__content\">\n+          { ...this.renderValue(d) }\n+        </div>\n+      </li>\n+    );\n+    // If there is more than one descriptor, wrap the list in another list\n+    // and add an expand button that allows the user to display all descriptors\n+    // on a separate line.\n+    if (descriptors.length > 1) {\n+      rootItems = [\n+        <li>\n+          { this.renderExpandButton() }\n+          <div class=\"inspector__content\">\n+            { ...showWhen(\"expanded\", \"(\") }\n+            <ul class=\"inspector__list inspector__arg-list\">\n+              { ...rootItems }\n+            </ul>\n+            { ...showWhen(\"expanded\", \")\") }\n+          </div>\n+        </li>\n+      ];\n     }\n-    const maxElements = type === \"array\" ? 20 : 10;\n-    const num = Math.min(Math.max(keys.length * 2 - 1, 0), maxElements);\n-    elements = new Array(num)\n-      .fill(null).map((x, i) => {\n-        if (i === maxElements - 1) return <span>,...</span>;\n-        if (i % 2) return <span>, </span>;\n-        return (\n-          <Inspector\n-            object={ data[keys[i / 2]] }\n-            inline={ true }\n-            name= { type === \"object\" && keys[i / 2] } />\n-        );\n-      });\n-    return elements;\n+    // Create the tree-root list of one element.\n+    return (\n+      <ul class=\"inspector__list inspector__tree-root\">\n+        { ...rootItems }\n+      </ul>\n+    );\n   }\n \n   render(): JSX.Element {\n-    const { object: { data, type, cons, isCircular }, name } = this.props;\n-    const { depth = 0, inline = false } = this.props;\n-    const { isCollapsed, isCollapsible } = this.state;\n-    let value = null;\n-    if (inline && isCircular) {\n-      value = (\n-        <span class=\"cm-keyword\">[Circular]</span>\n-      );\n+    const d = this.props.descriptors;\n+    return <div class=\"inspector cm-s-syntax\">{ this.renderRoot(d) }</div>;\n+  }\n+\n+  static toggle(id: string): void {\n+    const el = document.getElementById(id);\n+    const parent = el.parentNode as HTMLElement;\n+    parent.classList.toggle(\"inspector__li--expanded\");","path":"website/inspector.tsx","position":400,"original_position":400,"commit_id":"ee2a3114de1129127149e3608dc4f85c8dcb832d","original_commit_id":"ee2a3114de1129127149e3608dc4f85c8dcb832d","user":{"login":"ry","id":80,"avatar_url":"https://avatars1.githubusercontent.com/u/80?v=4","gravatar_id":"","url":"https://api.github.com/users/ry","html_url":"https://github.com/ry","followers_url":"https://api.github.com/users/ry/followers","following_url":"https://api.github.com/users/ry/following{/other_user}","gists_url":"https://api.github.com/users/ry/gists{/gist_id}","starred_url":"https://api.github.com/users/ry/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ry/subscriptions","organizations_url":"https://api.github.com/users/ry/orgs","repos_url":"https://api.github.com/users/ry/repos","events_url":"https://api.github.com/users/ry/events{/privacy}","received_events_url":"https://api.github.com/users/ry/received_events","type":"User","site_admin":false},"body":"Can you add a comment here about why react state isn't used - i.e. to simplify event handling after static rendering","created_at":"2018-04-11T00:28:36Z","updated_at":"2018-04-11T00:28:36Z","html_url":"https://github.com/propelml/propel/pull/497#discussion_r180605409","pull_request_url":"https://api.github.com/repos/propelml/propel/pulls/497","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/propelml/propel/pulls/comments/180605409"},"html":{"href":"https://github.com/propelml/propel/pull/497#discussion_r180605409"},"pull_request":{"href":"https://api.github.com/repos/propelml/propel/pulls/497"}}},"pull_request":{"url":"https://api.github.com/repos/propelml/propel/pulls/497","id":180073062,"html_url":"https://github.com/propelml/propel/pull/497","diff_url":"https://github.com/propelml/propel/pull/497.diff","patch_url":"https://github.com/propelml/propel/pull/497.patch","issue_url":"https://api.github.com/repos/propelml/propel/issues/497","number":497,"state":"open","locked":false,"title":"[wip] inspector improvements","user":{"login":"piscisaureus","id":218257,"avatar_url":"https://avatars2.githubusercontent.com/u/218257?v=4","gravatar_id":"","url":"https://api.github.com/users/piscisaureus","html_url":"https://github.com/piscisaureus","followers_url":"https://api.github.com/users/piscisaureus/followers","following_url":"https://api.github.com/users/piscisaureus/following{/other_user}","gists_url":"https://api.github.com/users/piscisaureus/gists{/gist_id}","starred_url":"https://api.github.com/users/piscisaureus/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/piscisaureus/subscriptions","organizations_url":"https://api.github.com/users/piscisaureus/orgs","repos_url":"https://api.github.com/users/piscisaureus/repos","events_url":"https://api.github.com/users/piscisaureus/events{/privacy}","received_events_url":"https://api.github.com/users/piscisaureus/received_events","type":"User","site_admin":false},"body":"fixes\r\n\r\nwip\r\n\r\nwip\r\n\r\nwip\r\n\r\nwip\r\n\r\nrebase","created_at":"2018-04-06T23:12:26Z","updated_at":"2018-04-11T00:28:36Z","closed_at":null,"merged_at":null,"merge_commit_sha":"693eadc497419e9ab742c27c9d4fa978e91b5f5b","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/propelml/propel/pulls/497/commits","review_comments_url":"https://api.github.com/repos/propelml/propel/pulls/497/comments","review_comment_url":"https://api.github.com/repos/propelml/propel/pulls/comments{/number}","comments_url":"https://api.github.com/repos/propelml/propel/issues/497/comments","statuses_url":"https://api.github.com/repos/propelml/propel/statuses/ee2a3114de1129127149e3608dc4f85c8dcb832d","head":{"label":"propelml:b/inspect","ref":"b/inspect","sha":"ee2a3114de1129127149e3608dc4f85c8dcb832d","user":{"login":"propelml","id":33702266,"avatar_url":"https://avatars1.githubusercontent.com/u/33702266?v=4","gravatar_id":"","url":"https://api.github.com/users/propelml","html_url":"https://github.com/propelml","followers_url":"https://api.github.com/users/propelml/followers","following_url":"https://api.github.com/users/propelml/following{/other_user}","gists_url":"https://api.github.com/users/propelml/gists{/gist_id}","starred_url":"https://api.github.com/users/propelml/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/propelml/subscriptions","organizations_url":"https://api.github.com/users/propelml/orgs","repos_url":"https://api.github.com/users/propelml/repos","events_url":"https://api.github.com/users/propelml/events{/privacy}","received_events_url":"https://api.github.com/users/propelml/received_events","type":"Organization","site_admin":false},"repo":{"id":110989998,"name":"propel","full_name":"propelml/propel","owner":{"login":"propelml","id":33702266,"avatar_url":"https://avatars1.githubusercontent.com/u/33702266?v=4","gravatar_id":"","url":"https://api.github.com/users/propelml","html_url":"https://github.com/propelml","followers_url":"https://api.github.com/users/propelml/followers","following_url":"https://api.github.com/users/propelml/following{/other_user}","gists_url":"https://api.github.com/users/propelml/gists{/gist_id}","starred_url":"https://api.github.com/users/propelml/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/propelml/subscriptions","organizations_url":"https://api.github.com/users/propelml/orgs","repos_url":"https://api.github.com/users/propelml/repos","events_url":"https://api.github.com/users/propelml/events{/privacy}","received_events_url":"https://api.github.com/users/propelml/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/propelml/propel","description":"Differential Programming in JavaScript.","fork":false,"url":"https://api.github.com/repos/propelml/propel","forks_url":"https://api.github.com/repos/propelml/propel/forks","keys_url":"https://api.github.com/repos/propelml/propel/keys{/key_id}","collaborators_url":"https://api.github.com/repos/propelml/propel/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/propelml/propel/teams","hooks_url":"https://api.github.com/repos/propelml/propel/hooks","issue_events_url":"https://api.github.com/repos/propelml/propel/issues/events{/number}","events_url":"https://api.github.com/repos/propelml/propel/events","assignees_url":"https://api.github.com/repos/propelml/propel/assignees{/user}","branches_url":"https://api.github.com/repos/propelml/propel/branches{/branch}","tags_url":"https://api.github.com/repos/propelml/propel/tags","blobs_url":"https://api.github.com/repos/propelml/propel/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/propelml/propel/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/propelml/propel/git/refs{/sha}","trees_url":"https://api.github.com/repos/propelml/propel/git/trees{/sha}","statuses_url":"https://api.github.com/repos/propelml/propel/statuses/{sha}","languages_url":"https://api.github.com/repos/propelml/propel/languages","stargazers_url":"https://api.github.com/repos/propelml/propel/stargazers","contributors_url":"https://api.github.com/repos/propelml/propel/contributors","subscribers_url":"https://api.github.com/repos/propelml/propel/subscribers","subscription_url":"https://api.github.com/repos/propelml/propel/subscription","commits_url":"https://api.github.com/repos/propelml/propel/commits{/sha}","git_commits_url":"https://api.github.com/repos/propelml/propel/git/commits{/sha}","comments_url":"https://api.github.com/repos/propelml/propel/comments{/number}","issue_comment_url":"https://api.github.com/repos/propelml/propel/issues/comments{/number}","contents_url":"https://api.github.com/repos/propelml/propel/contents/{+path}","compare_url":"https://api.github.com/repos/propelml/propel/compare/{base}...{head}","merges_url":"https://api.github.com/repos/propelml/propel/merges","archive_url":"https://api.github.com/repos/propelml/propel/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/propelml/propel/downloads","issues_url":"https://api.github.com/repos/propelml/propel/issues{/number}","pulls_url":"https://api.github.com/repos/propelml/propel/pulls{/number}","milestones_url":"https://api.github.com/repos/propelml/propel/milestones{/number}","notifications_url":"https://api.github.com/repos/propelml/propel/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/propelml/propel/labels{/name}","releases_url":"https://api.github.com/repos/propelml/propel/releases{/id}","deployments_url":"https://api.github.com/repos/propelml/propel/deployments","created_at":"2017-11-16T15:42:09Z","updated_at":"2018-04-10T21:10:50Z","pushed_at":"2018-04-10T21:10:15Z","git_url":"git://github.com/propelml/propel.git","ssh_url":"git@github.com:propelml/propel.git","clone_url":"https://github.com/propelml/propel.git","svn_url":"https://github.com/propelml/propel","homepage":"http://propelml.org/","size":6665,"stargazers_count":2772,"watchers_count":2772,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":80,"mirror_url":null,"archived":false,"open_issues_count":63,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0"},"forks":80,"open_issues":63,"watchers":2772,"default_branch":"master"}},"base":{"label":"propelml:master","ref":"master","sha":"adffe44b55aa14cfa577ce4aeabe8a4db2c10f6d","user":{"login":"propelml","id":33702266,"avatar_url":"https://avatars1.githubusercontent.com/u/33702266?v=4","gravatar_id":"","url":"https://api.github.com/users/propelml","html_url":"https://github.com/propelml","followers_url":"https://api.github.com/users/propelml/followers","following_url":"https://api.github.com/users/propelml/following{/other_user}","gists_url":"https://api.github.com/users/propelml/gists{/gist_id}","starred_url":"https://api.github.com/users/propelml/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/propelml/subscriptions","organizations_url":"https://api.github.com/users/propelml/orgs","repos_url":"https://api.github.com/users/propelml/repos","events_url":"https://api.github.com/users/propelml/events{/privacy}","received_events_url":"https://api.github.com/users/propelml/received_events","type":"Organization","site_admin":false},"repo":{"id":110989998,"name":"propel","full_name":"propelml/propel","owner":{"login":"propelml","id":33702266,"avatar_url":"https://avatars1.githubusercontent.com/u/33702266?v=4","gravatar_id":"","url":"https://api.github.com/users/propelml","html_url":"https://github.com/propelml","followers_url":"https://api.github.com/users/propelml/followers","following_url":"https://api.github.com/users/propelml/following{/other_user}","gists_url":"https://api.github.com/users/propelml/gists{/gist_id}","starred_url":"https://api.github.com/users/propelml/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/propelml/subscriptions","organizations_url":"https://api.github.com/users/propelml/orgs","repos_url":"https://api.github.com/users/propelml/repos","events_url":"https://api.github.com/users/propelml/events{/privacy}","received_events_url":"https://api.github.com/users/propelml/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/propelml/propel","description":"Differential Programming in JavaScript.","fork":false,"url":"https://api.github.com/repos/propelml/propel","forks_url":"https://api.github.com/repos/propelml/propel/forks","keys_url":"https://api.github.com/repos/propelml/propel/keys{/key_id}","collaborators_url":"https://api.github.com/repos/propelml/propel/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/propelml/propel/teams","hooks_url":"https://api.github.com/repos/propelml/propel/hooks","issue_events_url":"https://api.github.com/repos/propelml/propel/issues/events{/number}","events_url":"https://api.github.com/repos/propelml/propel/events","assignees_url":"https://api.github.com/repos/propelml/propel/assignees{/user}","branches_url":"https://api.github.com/repos/propelml/propel/branches{/branch}","tags_url":"https://api.github.com/repos/propelml/propel/tags","blobs_url":"https://api.github.com/repos/propelml/propel/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/propelml/propel/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/propelml/propel/git/refs{/sha}","trees_url":"https://api.github.com/repos/propelml/propel/git/trees{/sha}","statuses_url":"https://api.github.com/repos/propelml/propel/statuses/{sha}","languages_url":"https://api.github.com/repos/propelml/propel/languages","stargazers_url":"https://api.github.com/repos/propelml/propel/stargazers","contributors_url":"https://api.github.com/repos/propelml/propel/contributors","subscribers_url":"https://api.github.com/repos/propelml/propel/subscribers","subscription_url":"https://api.github.com/repos/propelml/propel/subscription","commits_url":"https://api.github.com/repos/propelml/propel/commits{/sha}","git_commits_url":"https://api.github.com/repos/propelml/propel/git/commits{/sha}","comments_url":"https://api.github.com/repos/propelml/propel/comments{/number}","issue_comment_url":"https://api.github.com/repos/propelml/propel/issues/comments{/number}","contents_url":"https://api.github.com/repos/propelml/propel/contents/{+path}","compare_url":"https://api.github.com/repos/propelml/propel/compare/{base}...{head}","merges_url":"https://api.github.com/repos/propelml/propel/merges","archive_url":"https://api.github.com/repos/propelml/propel/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/propelml/propel/downloads","issues_url":"https://api.github.com/repos/propelml/propel/issues{/number}","pulls_url":"https://api.github.com/repos/propelml/propel/pulls{/number}","milestones_url":"https://api.github.com/repos/propelml/propel/milestones{/number}","notifications_url":"https://api.github.com/repos/propelml/propel/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/propelml/propel/labels{/name}","releases_url":"https://api.github.com/repos/propelml/propel/releases{/id}","deployments_url":"https://api.github.com/repos/propelml/propel/deployments","created_at":"2017-11-16T15:42:09Z","updated_at":"2018-04-10T21:10:50Z","pushed_at":"2018-04-10T21:10:15Z","git_url":"git://github.com/propelml/propel.git","ssh_url":"git@github.com:propelml/propel.git","clone_url":"https://github.com/propelml/propel.git","svn_url":"https://github.com/propelml/propel","homepage":"http://propelml.org/","size":6665,"stargazers_count":2772,"watchers_count":2772,"language":"TypeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":80,"mirror_url":null,"archived":false,"open_issues_count":63,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0"},"forks":80,"open_issues":63,"watchers":2772,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/propelml/propel/pulls/497"},"html":{"href":"https://github.com/propelml/propel/pull/497"},"issue":{"href":"https://api.github.com/repos/propelml/propel/issues/497"},"comments":{"href":"https://api.github.com/repos/propelml/propel/issues/497/comments"},"review_comments":{"href":"https://api.github.com/repos/propelml/propel/pulls/497/comments"},"review_comment":{"href":"https://api.github.com/repos/propelml/propel/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/propelml/propel/pulls/497/commits"},"statuses":{"href":"https://api.github.com/repos/propelml/propel/statuses/ee2a3114de1129127149e3608dc4f85c8dcb832d"}},"author_association":"OWNER"}}
{"actor":{"display_login":"ry"}}