{"action":"created","comment":{"url":"https://api.github.com/repos/metabase/metabase/pulls/comments/242769815","pull_request_review_id":186360871,"id":242769815,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDI0Mjc2OTgxNQ==","diff_hunk":"@@ -0,0 +1,294 @@\n+(ns metabase.serialization.dump\n+  \"Serialize a Matabase instance into a directory structure of YAMLs.\"\n+  (:require [clojure.java.io :as io]\n+            [clojure.string :as str]\n+            [clojure.walk :as walk]\n+            [metabase.automagic-dashboards.filters :refer [field-reference?]]\n+            [metabase.models\n+             [card :refer [Card]]\n+             [collection :refer [Collection]]\n+             [dashboard :refer [Dashboard]]\n+             [dashboard-card :refer [DashboardCard]]\n+             [dashboard-card-series :refer [DashboardCardSeries]]\n+             [database :refer [Database]]\n+             [dependency :refer [Dependency]]\n+             [dimension :refer [Dimension]]\n+             [field :refer [Field]]\n+             [field-values :refer [FieldValues]]\n+             [metric :refer [Metric]]\n+             [pulse :refer [Pulse]]\n+             [pulse-card :refer [PulseCard]]\n+             [pulse-channel :refer [PulseChannel]]\n+             [segment :refer [Segment]]\n+             [setting :as setting]\n+             [table :refer [Table]]]\n+            [metabase.query-processor.util :as qp.util]\n+            [metabase.util :as u]\n+            [metabase.util.schema :as su]\n+            [schema.core :as s]\n+            [toucan.db :as db]\n+            [yaml.core :as yaml]))\n+\n+(defmulti\n+  ^{:doc      \"Get the logical path for entity `entity`.\n+\n+               The idea is to replace all IDs with these human readable paths which are also\n+               instance-independent, making deserialization eaiser.\"\n+    :private  true\n+    :arglists '([entity])}\n+  fully-qualified-name type)\n+\n+(defmethod fully-qualified-name (type Database)\n+  [db]\n+  (str \"/databases/\" (:name db)))\n+\n+(defmethod fully-qualified-name (type Table)\n+  [table]\n+  (format \"%s/schemas/%s/tables/%s\"\n+          (->> table :db_id Database fully-qualified-name)\n+          (:schema table)\n+          (:name table)))\n+\n+(defmethod fully-qualified-name (type Field)\n+  [field]\n+  (str (->> field :table_id Table fully-qualified-name) \"/fields/\" (:name field)))\n+\n+(defmethod fully-qualified-name (type Metric)\n+  [metric]\n+  (str (->> metric :table_id Table fully-qualified-name) \"/metrics/\" (:name metric)))\n+\n+(defmethod fully-qualified-name (type Segment)\n+  [segment]\n+  (str (->> segment :table_id Table fully-qualified-name) \"/segments/\" (:name segment)))\n+\n+(defmethod fully-qualified-name (type Collection)\n+  [collection]\n+  (let [parents (->> (str/split (:location collection) #\"/\")\n+                     rest\n+                     (map #(-> % Integer/parseInt Collection :name (str \"/collections\")))\n+                     (str/join \"/\")\n+                     (format \"%s/\"))]\n+    (str \"/collections/collections/\" parents (:name collection))))\n+\n+(defmethod fully-qualified-name (type Dashboard)\n+  [dashboard]\n+  (format \"%s/dashboards/%s\"\n+          (or (some->> dashboard :collection_id Collection fully-qualified-name)\n+              \"/collections\")\n+          (:name dashboard)))\n+\n+(defmethod fully-qualified-name (type Pulse)\n+  [pulse]\n+  (format \"%s/pulses/%s\"\n+          (or (some->> pulse :collection_id Collection fully-qualified-name)\n+              \"/collections\")\n+          (:name pulse)))\n+\n+(defmethod fully-qualified-name (type Card)\n+  [card]\n+  (format \"%s/cards/%s\"\n+          (or (some->> card\n+                       :dataset_query\n+                       qp.util/query->source-card-id\n+                       Card\n+                       fully-qualified-name)\n+              (some->> card\n+                       :collection_id\n+                       Collection\n+                       fully-qualified-name)\n+              \"/collections\")\n+          (:name card)))\n+\n+(defmethod fully-qualified-name nil\n+  [_]\n+  nil)\n+\n+(def ^:private SegmentOrMetric\n+  [(s/one (s/constrained su/KeywordOrString\n+                         (comp #{:metric :segment} qp.util/normalize-token))\n+          \"head\")\n+   (s/cond-pre s/Int su/KeywordOrString)])\n+\n+(def ^{:arglists '([form])} entity-reference?\n+  \"Is given form an MBQL entity reference?\"\n+  (some-fn field-reference? (complement (s/checker SegmentOrMetric))))\n+\n+(defn- entity-reference->fully-qualified-name\n+  [[op & args :as entity-reference]]\n+  (case (qp.util/normalize-token op)\n+    :metric        [op (fully-qualified-name (Metric (first args)))]\n+    :segment       [op (fully-qualified-name (Segment (first args)))]\n+    :field-id      [op (if (string? (first args))\n+                         (first args)\n+                         (fully-qualified-name (Field (first args))))]\n+    :fk->          (into [op]\n+                         (for [arg args]\n+                           (if (number? arg)\n+                             (Field arg)\n+                             (entity-reference->fully-qualified-name arg))))\n+    :field-literal entity-reference))\n+\n+(defn- humanize-entity-references\n+  [entity]\n+  (walk/postwalk\n+   (fn [form]\n+     (cond\n+       (entity-reference? form)\n+       (entity-reference->fully-qualified-name form)\n+\n+       (map? form)\n+       (let [id->fully-qualified-name (fn [entity-id model]\n+                                        (if (string? entity-id)\n+                                          entity-id\n+                                          (fully-qualified-name (model entity-id))))]\n+         (-> form\n+             (u/update-when :database (fn [db]\n+                                        (if (= db -1337)\n+                                          \"database/virtual\"\n+                                          (id->fully-qualified-name db Database))))\n+             (u/update-when :card_id id->fully-qualified-name Card)\n+             (u/update-when :source-table (fn [source-table]\n+                                            (if (and (string? source-table)\n+                                                     (str/starts-with? source-table \"card__\"))\n+                                              (-> source-table\n+                                                  (str/split #\"__\")\n+                                                  second\n+                                                  Integer/parseInt\n+                                                  (id->fully-qualified-name Card))\n+                                              (id->fully-qualified-name source-table Table))))))\n+\n+       :else\n+       form))\n+   entity))\n+\n+(defmulti\n+  ^{:doc      \"Serialize entity `entity` to location `dir`.\n+\n+               Depending on the entity, it will be serialized either as a single YAML, or a\n+               directory structure.\n+\n+               Removes unneeded fields that can either be reconstructed from context or are\n+               meaningless (eg. :created_at).\"\n+    :arglists '([dir entity])}\n+  dump (fn [_ entity]\n+         (type entity)))\n+\n+(defn- strip-crud\n+  [entity]\n+  (cond-> (dissoc entity :id :creator_id :created_at :updated_at :db_id :database_id :location\n+                  :dashboard_id :fields_hash :personal_owner_id :made_public_by_id :collection_id\n+                  :pulse_id)\n+    (some #(instance? % entity) (map type [Metric Field Segment])) (dissoc :table_id)))\n+\n+(defn- spit-yaml\n+  [fname obj]\n+  (io/make-parents fname)\n+  (spit fname (yaml/generate-string obj :dumper-options {:flow-style :block})))","path":"src/metabase/serialization/dump.clj","position":186,"original_position":186,"commit_id":"61e0aee38bdd669dbeadd003ffa7665840edc702","original_commit_id":"61e0aee38bdd669dbeadd003ffa7665840edc702","user":{"login":"camsaul","id":1455846,"node_id":"MDQ6VXNlcjE0NTU4NDY=","avatar_url":"https://avatars3.githubusercontent.com/u/1455846?v=4","gravatar_id":"","url":"https://api.github.com/users/camsaul","html_url":"https://github.com/camsaul","followers_url":"https://api.github.com/users/camsaul/followers","following_url":"https://api.github.com/users/camsaul/following{/other_user}","gists_url":"https://api.github.com/users/camsaul/gists{/gist_id}","starred_url":"https://api.github.com/users/camsaul/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/camsaul/subscriptions","organizations_url":"https://api.github.com/users/camsaul/orgs","repos_url":"https://api.github.com/users/camsaul/repos","events_url":"https://api.github.com/users/camsaul/events{/privacy}","received_events_url":"https://api.github.com/users/camsaul/received_events","type":"User","site_admin":false},"body":"Personal option, but I think this namespace could benefit from narrowing its focus and moving the YAML related stuff into a different `dump.yaml` namespace or something like that. \r\n\r\nIt would be good to have one namespace that focuses on the underlying logic of generating dumps and creating a Clojure representation of them and have another namespace take that dump and worry about writing it to YAML. Or maybe some configuration even more focused than that. Division of labor / clear responsibilities /small, focused modules and all that. Would be easier to test that way. And if we wanted to do something wacky like support EDN as a dump format it that code wouldn't have to go in one uber-namespace as well. (Not saying we want to -- I don't -- but I think in this case organizing things in an extensible way also means they'd be organized in a clearer way.)","created_at":"2018-12-19T01:57:34Z","updated_at":"2018-12-19T01:57:34Z","html_url":"https://github.com/metabase/metabase/pull/8845#discussion_r242769815","pull_request_url":"https://api.github.com/repos/metabase/metabase/pulls/8845","author_association":"MEMBER","_links":{"self":{"href":"https://api.github.com/repos/metabase/metabase/pulls/comments/242769815"},"html":{"href":"https://github.com/metabase/metabase/pull/8845#discussion_r242769815"},"pull_request":{"href":"https://api.github.com/repos/metabase/metabase/pulls/8845"}}},"pull_request":{"url":"https://api.github.com/repos/metabase/metabase/pulls/8845","id":228006056,"node_id":"MDExOlB1bGxSZXF1ZXN0MjI4MDA2MDU2","html_url":"https://github.com/metabase/metabase/pull/8845","diff_url":"https://github.com/metabase/metabase/pull/8845.diff","patch_url":"https://github.com/metabase/metabase/pull/8845.patch","issue_url":"https://api.github.com/repos/metabase/metabase/issues/8845","number":8845,"state":"open","locked":false,"title":"Serialization [WiP]","user":{"login":"sbelak","id":178038,"node_id":"MDQ6VXNlcjE3ODAzOA==","avatar_url":"https://avatars2.githubusercontent.com/u/178038?v=4","gravatar_id":"","url":"https://api.github.com/users/sbelak","html_url":"https://github.com/sbelak","followers_url":"https://api.github.com/users/sbelak/followers","following_url":"https://api.github.com/users/sbelak/following{/other_user}","gists_url":"https://api.github.com/users/sbelak/gists{/gist_id}","starred_url":"https://api.github.com/users/sbelak/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sbelak/subscriptions","organizations_url":"https://api.github.com/users/sbelak/orgs","repos_url":"https://api.github.com/users/sbelak/repos","events_url":"https://api.github.com/users/sbelak/events{/privacy}","received_events_url":"https://api.github.com/users/sbelak/received_events","type":"User","site_admin":false},"body":"# Instance serialization\r\n\r\n## General idea\r\n\r\nDump all our entities into a directory structure of YAMLs, and load them back again.\r\n\r\nTo make diffs more human readable, IDs in MBQL (field references) are replaced with absolute paths. Eg. `[:sum [field-id 4]]` becomes `[:sum [field-id \"databases/Sample database/schemas/PUBLIC/tables/ORDERS/fields/TOTAL\"]]]`. The model types in the path are there to disambiguate cases such as when card source is another card. Technically we could omit them in most places, but I kind of like that the IDs are also file system paths.\r\n\r\nWhere possible dumps are structured as nested dirs. Eg. tables of a given DB are enclosed in the folder for that DB. Ditto for anything that belongs to a collection.\r\n\r\nWhere possible we use entity's `:name` attribute for the name of the YAML.\r\n\r\nI've consolidated some of the models to help with readability. So dashboards also include dashboard cards and dashboard card series.\r\n\r\n## Usage\r\n\r\n### Dump\r\n\r\n```\r\njava -jar metabase.jar dump [target-dir]\r\n```\r\n### Load\r\n\r\n```\r\njava -jar metabase.jar load [target-dir] [mode]\r\n```\r\nwhere `mode` is one of `skip`, or `update`.\r\n\r\n## TODO v1 (cold start)\r\n- [x] collections should probably be in hierarchical folders\r\n- [x] don't dump sensitive info\r\n- [x] proper entrypoints \r\n- [x] segments referencing other segments\r\n- [x] cards referencing other cards\r\n- [x] field values\r\n- [x] pulses\r\n- [ ] tests\r\n- [x] robustly handle dependencies (eg. one card referring to another) \r\n- [x] dashboard card series\r\n- [x] dependency\r\n- [x] dimensions\r\n- [x] settings\r\n- [x] get joins working\r\n- [x] update/ignore\r\n\r\n## TODO v2 (full backup)\r\n- [ ] users\r\n- [ ] permissions\r\n- [ ] alerts\r\n- [ ] revisions\r\n\r\n## TODO v3 (integration with the admin interface)\r\n- [ ] UI\r\n- [ ] selective dumping?","created_at":"2018-11-02T17:22:11Z","updated_at":"2018-12-19T01:57:34Z","closed_at":null,"merged_at":null,"merge_commit_sha":"36f39b48e11f36f1b7ef180c3b4541cabccd20ca","assignee":null,"assignees":[],"requested_reviewers":[{"login":"tlrobinson","id":18193,"node_id":"MDQ6VXNlcjE4MTkz","avatar_url":"https://avatars0.githubusercontent.com/u/18193?v=4","gravatar_id":"","url":"https://api.github.com/users/tlrobinson","html_url":"https://github.com/tlrobinson","followers_url":"https://api.github.com/users/tlrobinson/followers","following_url":"https://api.github.com/users/tlrobinson/following{/other_user}","gists_url":"https://api.github.com/users/tlrobinson/gists{/gist_id}","starred_url":"https://api.github.com/users/tlrobinson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tlrobinson/subscriptions","organizations_url":"https://api.github.com/users/tlrobinson/orgs","repos_url":"https://api.github.com/users/tlrobinson/repos","events_url":"https://api.github.com/users/tlrobinson/events{/privacy}","received_events_url":"https://api.github.com/users/tlrobinson/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/metabase/metabase/pulls/8845/commits","review_comments_url":"https://api.github.com/repos/metabase/metabase/pulls/8845/comments","review_comment_url":"https://api.github.com/repos/metabase/metabase/pulls/comments{/number}","comments_url":"https://api.github.com/repos/metabase/metabase/issues/8845/comments","statuses_url":"https://api.github.com/repos/metabase/metabase/statuses/61e0aee38bdd669dbeadd003ffa7665840edc702","head":{"label":"metabase:serialization-poc","ref":"serialization-poc","sha":"61e0aee38bdd669dbeadd003ffa7665840edc702","user":{"login":"metabase","id":10520629,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEwNTIwNjI5","avatar_url":"https://avatars1.githubusercontent.com/u/10520629?v=4","gravatar_id":"","url":"https://api.github.com/users/metabase","html_url":"https://github.com/metabase","followers_url":"https://api.github.com/users/metabase/followers","following_url":"https://api.github.com/users/metabase/following{/other_user}","gists_url":"https://api.github.com/users/metabase/gists{/gist_id}","starred_url":"https://api.github.com/users/metabase/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/metabase/subscriptions","organizations_url":"https://api.github.com/users/metabase/orgs","repos_url":"https://api.github.com/users/metabase/repos","events_url":"https://api.github.com/users/metabase/events{/privacy}","received_events_url":"https://api.github.com/users/metabase/received_events","type":"Organization","site_admin":false},"repo":{"id":30203935,"node_id":"MDEwOlJlcG9zaXRvcnkzMDIwMzkzNQ==","name":"metabase","full_name":"metabase/metabase","private":false,"owner":{"login":"metabase","id":10520629,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEwNTIwNjI5","avatar_url":"https://avatars1.githubusercontent.com/u/10520629?v=4","gravatar_id":"","url":"https://api.github.com/users/metabase","html_url":"https://github.com/metabase","followers_url":"https://api.github.com/users/metabase/followers","following_url":"https://api.github.com/users/metabase/following{/other_user}","gists_url":"https://api.github.com/users/metabase/gists{/gist_id}","starred_url":"https://api.github.com/users/metabase/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/metabase/subscriptions","organizations_url":"https://api.github.com/users/metabase/orgs","repos_url":"https://api.github.com/users/metabase/repos","events_url":"https://api.github.com/users/metabase/events{/privacy}","received_events_url":"https://api.github.com/users/metabase/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/metabase/metabase","description":"The simplest, fastest way to get business intelligence and analytics  to everyone in your company :yum:","fork":false,"url":"https://api.github.com/repos/metabase/metabase","forks_url":"https://api.github.com/repos/metabase/metabase/forks","keys_url":"https://api.github.com/repos/metabase/metabase/keys{/key_id}","collaborators_url":"https://api.github.com/repos/metabase/metabase/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/metabase/metabase/teams","hooks_url":"https://api.github.com/repos/metabase/metabase/hooks","issue_events_url":"https://api.github.com/repos/metabase/metabase/issues/events{/number}","events_url":"https://api.github.com/repos/metabase/metabase/events","assignees_url":"https://api.github.com/repos/metabase/metabase/assignees{/user}","branches_url":"https://api.github.com/repos/metabase/metabase/branches{/branch}","tags_url":"https://api.github.com/repos/metabase/metabase/tags","blobs_url":"https://api.github.com/repos/metabase/metabase/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/metabase/metabase/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/metabase/metabase/git/refs{/sha}","trees_url":"https://api.github.com/repos/metabase/metabase/git/trees{/sha}","statuses_url":"https://api.github.com/repos/metabase/metabase/statuses/{sha}","languages_url":"https://api.github.com/repos/metabase/metabase/languages","stargazers_url":"https://api.github.com/repos/metabase/metabase/stargazers","contributors_url":"https://api.github.com/repos/metabase/metabase/contributors","subscribers_url":"https://api.github.com/repos/metabase/metabase/subscribers","subscription_url":"https://api.github.com/repos/metabase/metabase/subscription","commits_url":"https://api.github.com/repos/metabase/metabase/commits{/sha}","git_commits_url":"https://api.github.com/repos/metabase/metabase/git/commits{/sha}","comments_url":"https://api.github.com/repos/metabase/metabase/comments{/number}","issue_comment_url":"https://api.github.com/repos/metabase/metabase/issues/comments{/number}","contents_url":"https://api.github.com/repos/metabase/metabase/contents/{+path}","compare_url":"https://api.github.com/repos/metabase/metabase/compare/{base}...{head}","merges_url":"https://api.github.com/repos/metabase/metabase/merges","archive_url":"https://api.github.com/repos/metabase/metabase/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/metabase/metabase/downloads","issues_url":"https://api.github.com/repos/metabase/metabase/issues{/number}","pulls_url":"https://api.github.com/repos/metabase/metabase/pulls{/number}","milestones_url":"https://api.github.com/repos/metabase/metabase/milestones{/number}","notifications_url":"https://api.github.com/repos/metabase/metabase/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/metabase/metabase/labels{/name}","releases_url":"https://api.github.com/repos/metabase/metabase/releases{/id}","deployments_url":"https://api.github.com/repos/metabase/metabase/deployments","created_at":"2015-02-02T19:25:47Z","updated_at":"2018-12-19T01:10:15Z","pushed_at":"2018-12-19T00:41:40Z","git_url":"git://github.com/metabase/metabase.git","ssh_url":"git@github.com:metabase/metabase.git","clone_url":"https://github.com/metabase/metabase.git","svn_url":"https://github.com/metabase/metabase","homepage":"https://metabase.com","size":92635,"stargazers_count":12577,"watchers_count":12577,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1674,"mirror_url":null,"archived":false,"open_issues_count":2038,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":1674,"open_issues":2038,"watchers":12577,"default_branch":"master"}},"base":{"label":"metabase:master","ref":"master","sha":"c31dc81e76bc71869af2aef900fe8f1fbb1a80ad","user":{"login":"metabase","id":10520629,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEwNTIwNjI5","avatar_url":"https://avatars1.githubusercontent.com/u/10520629?v=4","gravatar_id":"","url":"https://api.github.com/users/metabase","html_url":"https://github.com/metabase","followers_url":"https://api.github.com/users/metabase/followers","following_url":"https://api.github.com/users/metabase/following{/other_user}","gists_url":"https://api.github.com/users/metabase/gists{/gist_id}","starred_url":"https://api.github.com/users/metabase/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/metabase/subscriptions","organizations_url":"https://api.github.com/users/metabase/orgs","repos_url":"https://api.github.com/users/metabase/repos","events_url":"https://api.github.com/users/metabase/events{/privacy}","received_events_url":"https://api.github.com/users/metabase/received_events","type":"Organization","site_admin":false},"repo":{"id":30203935,"node_id":"MDEwOlJlcG9zaXRvcnkzMDIwMzkzNQ==","name":"metabase","full_name":"metabase/metabase","private":false,"owner":{"login":"metabase","id":10520629,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEwNTIwNjI5","avatar_url":"https://avatars1.githubusercontent.com/u/10520629?v=4","gravatar_id":"","url":"https://api.github.com/users/metabase","html_url":"https://github.com/metabase","followers_url":"https://api.github.com/users/metabase/followers","following_url":"https://api.github.com/users/metabase/following{/other_user}","gists_url":"https://api.github.com/users/metabase/gists{/gist_id}","starred_url":"https://api.github.com/users/metabase/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/metabase/subscriptions","organizations_url":"https://api.github.com/users/metabase/orgs","repos_url":"https://api.github.com/users/metabase/repos","events_url":"https://api.github.com/users/metabase/events{/privacy}","received_events_url":"https://api.github.com/users/metabase/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/metabase/metabase","description":"The simplest, fastest way to get business intelligence and analytics  to everyone in your company :yum:","fork":false,"url":"https://api.github.com/repos/metabase/metabase","forks_url":"https://api.github.com/repos/metabase/metabase/forks","keys_url":"https://api.github.com/repos/metabase/metabase/keys{/key_id}","collaborators_url":"https://api.github.com/repos/metabase/metabase/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/metabase/metabase/teams","hooks_url":"https://api.github.com/repos/metabase/metabase/hooks","issue_events_url":"https://api.github.com/repos/metabase/metabase/issues/events{/number}","events_url":"https://api.github.com/repos/metabase/metabase/events","assignees_url":"https://api.github.com/repos/metabase/metabase/assignees{/user}","branches_url":"https://api.github.com/repos/metabase/metabase/branches{/branch}","tags_url":"https://api.github.com/repos/metabase/metabase/tags","blobs_url":"https://api.github.com/repos/metabase/metabase/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/metabase/metabase/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/metabase/metabase/git/refs{/sha}","trees_url":"https://api.github.com/repos/metabase/metabase/git/trees{/sha}","statuses_url":"https://api.github.com/repos/metabase/metabase/statuses/{sha}","languages_url":"https://api.github.com/repos/metabase/metabase/languages","stargazers_url":"https://api.github.com/repos/metabase/metabase/stargazers","contributors_url":"https://api.github.com/repos/metabase/metabase/contributors","subscribers_url":"https://api.github.com/repos/metabase/metabase/subscribers","subscription_url":"https://api.github.com/repos/metabase/metabase/subscription","commits_url":"https://api.github.com/repos/metabase/metabase/commits{/sha}","git_commits_url":"https://api.github.com/repos/metabase/metabase/git/commits{/sha}","comments_url":"https://api.github.com/repos/metabase/metabase/comments{/number}","issue_comment_url":"https://api.github.com/repos/metabase/metabase/issues/comments{/number}","contents_url":"https://api.github.com/repos/metabase/metabase/contents/{+path}","compare_url":"https://api.github.com/repos/metabase/metabase/compare/{base}...{head}","merges_url":"https://api.github.com/repos/metabase/metabase/merges","archive_url":"https://api.github.com/repos/metabase/metabase/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/metabase/metabase/downloads","issues_url":"https://api.github.com/repos/metabase/metabase/issues{/number}","pulls_url":"https://api.github.com/repos/metabase/metabase/pulls{/number}","milestones_url":"https://api.github.com/repos/metabase/metabase/milestones{/number}","notifications_url":"https://api.github.com/repos/metabase/metabase/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/metabase/metabase/labels{/name}","releases_url":"https://api.github.com/repos/metabase/metabase/releases{/id}","deployments_url":"https://api.github.com/repos/metabase/metabase/deployments","created_at":"2015-02-02T19:25:47Z","updated_at":"2018-12-19T01:10:15Z","pushed_at":"2018-12-19T00:41:40Z","git_url":"git://github.com/metabase/metabase.git","ssh_url":"git@github.com:metabase/metabase.git","clone_url":"https://github.com/metabase/metabase.git","svn_url":"https://github.com/metabase/metabase","homepage":"https://metabase.com","size":92635,"stargazers_count":12577,"watchers_count":12577,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1674,"mirror_url":null,"archived":false,"open_issues_count":2038,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":1674,"open_issues":2038,"watchers":12577,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/metabase/metabase/pulls/8845"},"html":{"href":"https://github.com/metabase/metabase/pull/8845"},"issue":{"href":"https://api.github.com/repos/metabase/metabase/issues/8845"},"comments":{"href":"https://api.github.com/repos/metabase/metabase/issues/8845/comments"},"review_comments":{"href":"https://api.github.com/repos/metabase/metabase/pulls/8845/comments"},"review_comment":{"href":"https://api.github.com/repos/metabase/metabase/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/metabase/metabase/pulls/8845/commits"},"statuses":{"href":"https://api.github.com/repos/metabase/metabase/statuses/61e0aee38bdd669dbeadd003ffa7665840edc702"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"camsaul"}}