{"action":"created","comment":{"url":"https://api.github.com/repos/juju/juju-gui/pulls/comments/44314162","id":44314162,"diff_hunk":"@@ -152,11 +152,423 @@ var module = module;\n     this._makeRequest(url, 'POST', body, callback);\n   };\n \n+\n+  /**\n+   * Charmstore object for jujulib.\n+   *\n+   * Provides access to the charmstore API.\n+   */\n+\n+  /**\n+   * Initializer\n+   *\n+   * @function charmstore\n+   * @param url {String} The URL, including scheme and port, of the charmstore\n+   * @param version {String} The api version, e.g. v4\n+   * @param bakery {Object} A bakery object for communicating with the charmstore instance.\n+   * @param processEnity {function} A function to massage entity data into the\n+   *    desired form (e.g. turning it into juju gui model objects.\n+   * @returns {Object} A client object for making charmstore API calls.\n+   */\n+  function charmstore(url, apiVersion, bakery, processEntity) {\n+    this.url = url;\n+    this.version = apiVersion;\n+    this.bakery = bakery;\n+\n+    // XXX jcsackett 2015-11-09 Methods that return entity data should\n+    // accept an additional modifier function as a callback, but for now\n+    // we've made it an attribute of the charmstore.\n+    this.processEntity = processEntity;\n+  }\n+\n+  charmstore.prototype = {\n+    /**\n+      Takes the path supplied by the caller and makes a request to the\n+      requestHandler instance.\n+\n+      @method _makeRequest\n+      @param {String} The path to make the api request to.\n+      @param {Function} successCallback Called when the api request completes\n+        successfully.\n+      @param {Function} failureCallback Called when the api request fails\n+        with a response of >= 400.\n+      @return {Object} The asynchronous request instance.\n+    */\n+    _makeRequest: function(path, successCallback, failureCallback) {\n+      return this.bakery.sendGetRequest(\n+        path,\n+        successCallback,\n+        failureCallback\n+      );\n+    },\n+\n+    /**\n+      Generates a path to the charmstore apiv4 based on the query and endpoint\n+      params passed in.\n+\n+      @method _generatePath\n+      @param {String} endpoint The endpoint to call at the charmstore.\n+      @param {Object} query The query parameters that are required for the\n+        request.\n+      @param {Boolean} extension Any extension to add to the endpoint\n+        such as /meta/any or /archive.\n+      @return {String} A charmstore url based on the query and endpoint params\n+        passed in.\n+    */\n+    _generatePath: function(endpoint, query, extension) {\n+      query = query ? '?' + query : '';\n+      if (extension) {\n+        endpoint = endpoint + extension;\n+      }\n+      return this.url + this.version + '/' + endpoint + query;\n+    },\n+\n+    /**\n+      Transforms the results from a charmstore query into model objects.\n+\n+      @method _transformQueryResults\n+      @param {Function} successCallback Called when the api request completes\n+        successfully.\n+      @param {Object} response Thre XHR response object.\n+    */\n+    _transformQueryResults: function(successCallback, response) {\n+      var data = JSON.parse(response.target.responseText);\n+      // If there is a single charm or bundle being requested then we need\n+      // to wrap it in an array so we can use the same map code.\n+      data = data.Results ? data.Results : [data];\n+      var models = [];\n+      data.forEach(function(entity) {\n+        var entityData = this._processEntityQueryData(entity);\n+        if (this.processEntity !== undefined) {\n+          entityData = this.processEntity(entityData);\n+        }\n+        models.push(entityData);\n+      }, this);\n+      successCallback(models);\n+    },\n+\n+    /**\n+      Recursively converts all keys to lowercase when assigning them to the\n+      supplied host object.\n+\n+      @method lowerCaseKeys\n+      @param {Object} obj The source object with the uppercase keys.\n+      @param {Object} host The host object in which the keys will be assigned.\n+      @param {Integer} exclude Exclude a particular level from lowercasing when\n+        recursing; uses a 0-based index, so if 0 is specified, the keys at the\n+        first level of recursion will not be lowercased. If 3 is specified, the\n+        keys at the fourth level of recursion will not be lowercased.\n+      @return {Undefined} Does not return a value, modifies the supplied host\n+        object in place.\n+    */\n+    _lowerCaseKeys: function(obj, host, exclude) {\n+      if (!obj) {\n+        return;\n+      }\n+      Object.keys(obj).forEach(function(key) {\n+        // An exclude of 0 means \"don't lowercase this level\".\n+        var newKey = key;\n+        if (exclude !== 0) {\n+          newKey = key.toLowerCase();\n+        }\n+        // Create shallow copies of objects for the copy.\n+        var copy;\n+        // XXX jcsackett 2015-11-09: Previously we used Y.merge to make shallow","path":"jujugui/static/gui/src/app/assets/javascripts/jujulib/juju.js","position":143,"original_position":143,"commit_id":"c1dde9360e7c94adafe92c89b97cd36dc8aae89f","original_commit_id":"c1dde9360e7c94adafe92c89b97cd36dc8aae89f","user":{"login":"hatched","id":532033,"avatar_url":"https://avatars.githubusercontent.com/u/532033?v=3","gravatar_id":"","url":"https://api.github.com/users/hatched","html_url":"https://github.com/hatched","followers_url":"https://api.github.com/users/hatched/followers","following_url":"https://api.github.com/users/hatched/following{/other_user}","gists_url":"https://api.github.com/users/hatched/gists{/gist_id}","starred_url":"https://api.github.com/users/hatched/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hatched/subscriptions","organizations_url":"https://api.github.com/users/hatched/orgs","repos_url":"https://api.github.com/users/hatched/repos","events_url":"https://api.github.com/users/hatched/events{/privacy}","received_events_url":"https://api.github.com/users/hatched/received_events","type":"User","site_admin":false},"body":"card please","created_at":"2015-11-09T18:54:47Z","updated_at":"2015-11-09T18:54:47Z","html_url":"https://github.com/juju/juju-gui/pull/969#discussion_r44314162","pull_request_url":"https://api.github.com/repos/juju/juju-gui/pulls/969","_links":{"self":{"href":"https://api.github.com/repos/juju/juju-gui/pulls/comments/44314162"},"html":{"href":"https://github.com/juju/juju-gui/pull/969#discussion_r44314162"},"pull_request":{"href":"https://api.github.com/repos/juju/juju-gui/pulls/969"}}},"pull_request":{"url":"https://api.github.com/repos/juju/juju-gui/pulls/969","id":50159817,"html_url":"https://github.com/juju/juju-gui/pull/969","diff_url":"https://github.com/juju/juju-gui/pull/969.diff","patch_url":"https://github.com/juju/juju-gui/pull/969.patch","issue_url":"https://api.github.com/repos/juju/juju-gui/issues/969","number":969,"state":"open","locked":false,"title":"Move the charmstore code to jujulib.","user":{"login":"jaycee","id":26728,"avatar_url":"https://avatars.githubusercontent.com/u/26728?v=3","gravatar_id":"","url":"https://api.github.com/users/jaycee","html_url":"https://github.com/jaycee","followers_url":"https://api.github.com/users/jaycee/followers","following_url":"https://api.github.com/users/jaycee/following{/other_user}","gists_url":"https://api.github.com/users/jaycee/gists{/gist_id}","starred_url":"https://api.github.com/users/jaycee/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jaycee/subscriptions","organizations_url":"https://api.github.com/users/jaycee/orgs","repos_url":"https://api.github.com/users/jaycee/repos","events_url":"https://api.github.com/users/jaycee/events{/privacy}","received_events_url":"https://api.github.com/users/jaycee/received_events","type":"User","site_admin":false},"body":null,"created_at":"2015-11-09T18:38:04Z","updated_at":"2015-11-09T18:54:47Z","closed_at":null,"merged_at":null,"merge_commit_sha":"59a3517074e3c0381463bf161ff64f5ad8a11ad6","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/juju/juju-gui/pulls/969/commits","review_comments_url":"https://api.github.com/repos/juju/juju-gui/pulls/969/comments","review_comment_url":"https://api.github.com/repos/juju/juju-gui/pulls/comments{/number}","comments_url":"https://api.github.com/repos/juju/juju-gui/issues/969/comments","statuses_url":"https://api.github.com/repos/juju/juju-gui/statuses/c1dde9360e7c94adafe92c89b97cd36dc8aae89f","head":{"label":"jaycee:charmstore-in-jujulib","ref":"charmstore-in-jujulib","sha":"c1dde9360e7c94adafe92c89b97cd36dc8aae89f","user":{"login":"jaycee","id":26728,"avatar_url":"https://avatars.githubusercontent.com/u/26728?v=3","gravatar_id":"","url":"https://api.github.com/users/jaycee","html_url":"https://github.com/jaycee","followers_url":"https://api.github.com/users/jaycee/followers","following_url":"https://api.github.com/users/jaycee/following{/other_user}","gists_url":"https://api.github.com/users/jaycee/gists{/gist_id}","starred_url":"https://api.github.com/users/jaycee/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jaycee/subscriptions","organizations_url":"https://api.github.com/users/jaycee/orgs","repos_url":"https://api.github.com/users/jaycee/repos","events_url":"https://api.github.com/users/jaycee/events{/privacy}","received_events_url":"https://api.github.com/users/jaycee/received_events","type":"User","site_admin":false},"repo":{"id":20962584,"name":"juju-gui","full_name":"jaycee/juju-gui","owner":{"login":"jaycee","id":26728,"avatar_url":"https://avatars.githubusercontent.com/u/26728?v=3","gravatar_id":"","url":"https://api.github.com/users/jaycee","html_url":"https://github.com/jaycee","followers_url":"https://api.github.com/users/jaycee/followers","following_url":"https://api.github.com/users/jaycee/following{/other_user}","gists_url":"https://api.github.com/users/jaycee/gists{/gist_id}","starred_url":"https://api.github.com/users/jaycee/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jaycee/subscriptions","organizations_url":"https://api.github.com/users/jaycee/orgs","repos_url":"https://api.github.com/users/jaycee/repos","events_url":"https://api.github.com/users/jaycee/events{/privacy}","received_events_url":"https://api.github.com/users/jaycee/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/jaycee/juju-gui","description":"Juju-GUI is a web-based GUI for Juju <https://juju.ubuntu.com/>.","fork":true,"url":"https://api.github.com/repos/jaycee/juju-gui","forks_url":"https://api.github.com/repos/jaycee/juju-gui/forks","keys_url":"https://api.github.com/repos/jaycee/juju-gui/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jaycee/juju-gui/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jaycee/juju-gui/teams","hooks_url":"https://api.github.com/repos/jaycee/juju-gui/hooks","issue_events_url":"https://api.github.com/repos/jaycee/juju-gui/issues/events{/number}","events_url":"https://api.github.com/repos/jaycee/juju-gui/events","assignees_url":"https://api.github.com/repos/jaycee/juju-gui/assignees{/user}","branches_url":"https://api.github.com/repos/jaycee/juju-gui/branches{/branch}","tags_url":"https://api.github.com/repos/jaycee/juju-gui/tags","blobs_url":"https://api.github.com/repos/jaycee/juju-gui/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jaycee/juju-gui/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jaycee/juju-gui/git/refs{/sha}","trees_url":"https://api.github.com/repos/jaycee/juju-gui/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jaycee/juju-gui/statuses/{sha}","languages_url":"https://api.github.com/repos/jaycee/juju-gui/languages","stargazers_url":"https://api.github.com/repos/jaycee/juju-gui/stargazers","contributors_url":"https://api.github.com/repos/jaycee/juju-gui/contributors","subscribers_url":"https://api.github.com/repos/jaycee/juju-gui/subscribers","subscription_url":"https://api.github.com/repos/jaycee/juju-gui/subscription","commits_url":"https://api.github.com/repos/jaycee/juju-gui/commits{/sha}","git_commits_url":"https://api.github.com/repos/jaycee/juju-gui/git/commits{/sha}","comments_url":"https://api.github.com/repos/jaycee/juju-gui/comments{/number}","issue_comment_url":"https://api.github.com/repos/jaycee/juju-gui/issues/comments{/number}","contents_url":"https://api.github.com/repos/jaycee/juju-gui/contents/{+path}","compare_url":"https://api.github.com/repos/jaycee/juju-gui/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jaycee/juju-gui/merges","archive_url":"https://api.github.com/repos/jaycee/juju-gui/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jaycee/juju-gui/downloads","issues_url":"https://api.github.com/repos/jaycee/juju-gui/issues{/number}","pulls_url":"https://api.github.com/repos/jaycee/juju-gui/pulls{/number}","milestones_url":"https://api.github.com/repos/jaycee/juju-gui/milestones{/number}","notifications_url":"https://api.github.com/repos/jaycee/juju-gui/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jaycee/juju-gui/labels{/name}","releases_url":"https://api.github.com/repos/jaycee/juju-gui/releases{/id}","created_at":"2014-06-18T13:16:17Z","updated_at":"2014-06-25T15:54:41Z","pushed_at":"2015-11-09T18:44:51Z","git_url":"git://github.com/jaycee/juju-gui.git","ssh_url":"git@github.com:jaycee/juju-gui.git","clone_url":"https://github.com/jaycee/juju-gui.git","svn_url":"https://github.com/jaycee/juju-gui","homepage":null,"size":112075,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"develop"}},"base":{"label":"juju:pyramid-fork","ref":"pyramid-fork","sha":"0641e7ca8d058cd7c48fe283792d3b4af81c1091","user":{"login":"juju","id":4604548,"avatar_url":"https://avatars.githubusercontent.com/u/4604548?v=3","gravatar_id":"","url":"https://api.github.com/users/juju","html_url":"https://github.com/juju","followers_url":"https://api.github.com/users/juju/followers","following_url":"https://api.github.com/users/juju/following{/other_user}","gists_url":"https://api.github.com/users/juju/gists{/gist_id}","starred_url":"https://api.github.com/users/juju/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/juju/subscriptions","organizations_url":"https://api.github.com/users/juju/orgs","repos_url":"https://api.github.com/users/juju/repos","events_url":"https://api.github.com/users/juju/events{/privacy}","received_events_url":"https://api.github.com/users/juju/received_events","type":"Organization","site_admin":false},"repo":{"id":14960387,"name":"juju-gui","full_name":"juju/juju-gui","owner":{"login":"juju","id":4604548,"avatar_url":"https://avatars.githubusercontent.com/u/4604548?v=3","gravatar_id":"","url":"https://api.github.com/users/juju","html_url":"https://github.com/juju","followers_url":"https://api.github.com/users/juju/followers","following_url":"https://api.github.com/users/juju/following{/other_user}","gists_url":"https://api.github.com/users/juju/gists{/gist_id}","starred_url":"https://api.github.com/users/juju/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/juju/subscriptions","organizations_url":"https://api.github.com/users/juju/orgs","repos_url":"https://api.github.com/users/juju/repos","events_url":"https://api.github.com/users/juju/events{/privacy}","received_events_url":"https://api.github.com/users/juju/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/juju/juju-gui","description":"Juju-GUI is a web-based GUI for Juju <https://juju.ubuntu.com/>.","fork":false,"url":"https://api.github.com/repos/juju/juju-gui","forks_url":"https://api.github.com/repos/juju/juju-gui/forks","keys_url":"https://api.github.com/repos/juju/juju-gui/keys{/key_id}","collaborators_url":"https://api.github.com/repos/juju/juju-gui/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/juju/juju-gui/teams","hooks_url":"https://api.github.com/repos/juju/juju-gui/hooks","issue_events_url":"https://api.github.com/repos/juju/juju-gui/issues/events{/number}","events_url":"https://api.github.com/repos/juju/juju-gui/events","assignees_url":"https://api.github.com/repos/juju/juju-gui/assignees{/user}","branches_url":"https://api.github.com/repos/juju/juju-gui/branches{/branch}","tags_url":"https://api.github.com/repos/juju/juju-gui/tags","blobs_url":"https://api.github.com/repos/juju/juju-gui/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/juju/juju-gui/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/juju/juju-gui/git/refs{/sha}","trees_url":"https://api.github.com/repos/juju/juju-gui/git/trees{/sha}","statuses_url":"https://api.github.com/repos/juju/juju-gui/statuses/{sha}","languages_url":"https://api.github.com/repos/juju/juju-gui/languages","stargazers_url":"https://api.github.com/repos/juju/juju-gui/stargazers","contributors_url":"https://api.github.com/repos/juju/juju-gui/contributors","subscribers_url":"https://api.github.com/repos/juju/juju-gui/subscribers","subscription_url":"https://api.github.com/repos/juju/juju-gui/subscription","commits_url":"https://api.github.com/repos/juju/juju-gui/commits{/sha}","git_commits_url":"https://api.github.com/repos/juju/juju-gui/git/commits{/sha}","comments_url":"https://api.github.com/repos/juju/juju-gui/comments{/number}","issue_comment_url":"https://api.github.com/repos/juju/juju-gui/issues/comments{/number}","contents_url":"https://api.github.com/repos/juju/juju-gui/contents/{+path}","compare_url":"https://api.github.com/repos/juju/juju-gui/compare/{base}...{head}","merges_url":"https://api.github.com/repos/juju/juju-gui/merges","archive_url":"https://api.github.com/repos/juju/juju-gui/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/juju/juju-gui/downloads","issues_url":"https://api.github.com/repos/juju/juju-gui/issues{/number}","pulls_url":"https://api.github.com/repos/juju/juju-gui/pulls{/number}","milestones_url":"https://api.github.com/repos/juju/juju-gui/milestones{/number}","notifications_url":"https://api.github.com/repos/juju/juju-gui/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/juju/juju-gui/labels{/name}","releases_url":"https://api.github.com/repos/juju/juju-gui/releases{/id}","created_at":"2013-12-05T17:15:48Z","updated_at":"2015-11-08T15:28:15Z","pushed_at":"2015-11-09T18:44:52Z","git_url":"git://github.com/juju/juju-gui.git","ssh_url":"git@github.com:juju/juju-gui.git","clone_url":"https://github.com/juju/juju-gui.git","svn_url":"https://github.com/juju/juju-gui","homepage":null,"size":117722,"stargazers_count":57,"watchers_count":57,"language":"JavaScript","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":45,"mirror_url":null,"open_issues_count":2,"forks":45,"open_issues":2,"watchers":57,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/juju/juju-gui/pulls/969"},"html":{"href":"https://github.com/juju/juju-gui/pull/969"},"issue":{"href":"https://api.github.com/repos/juju/juju-gui/issues/969"},"comments":{"href":"https://api.github.com/repos/juju/juju-gui/issues/969/comments"},"review_comments":{"href":"https://api.github.com/repos/juju/juju-gui/pulls/969/comments"},"review_comment":{"href":"https://api.github.com/repos/juju/juju-gui/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/juju/juju-gui/pulls/969/commits"},"statuses":{"href":"https://api.github.com/repos/juju/juju-gui/statuses/c1dde9360e7c94adafe92c89b97cd36dc8aae89f"}}}}