{"action":"created","comment":{"url":"https://api.github.com/repos/buildbot/buildbot/pulls/comments/37648375","id":37648375,"diff_hunk":"@@ -617,6 +617,139 @@ Several methods are added to each \"restangularized\" objects, aside from get(), p\n     Call the control data api.\n     This builds up a POST with jsonapi encoded parameters\n \n+DataService\n+.............\n+\n+DataService is the future replacement of BuildbotService for accessing the Buildbot data API.\n+It has a modern interface for accessing data. It uses IndexedDB for storing cached data as a single data store,\n+and LocalStorage for broadcasting events between browser tabs. DataService works in a master/slave architecture.\n+The master browser tab is responsible for keeping the requested data up to date in the IndexedDB and notify slaves when a data is ready to be used or it is updated.\n+It handles both the Rest API calls and the WebSocket subscriptions globally.\n+\n+It uses the following libraries:\n+\n+* Dexie.js (https://github.com/dfahlander/Dexie.js) - Minimalistic IndexedDB API with bulletproof transactions\n+* Tabex (https://github.com/nodeca/tabex) - Master election and in browser message bus\n+\n+The DataService is available as a standalone AngularJS module.\n+Installation via bower:\n+\n+  .. code-block:: none\n+\n+      bower install buildbot-data --save\n+\n+Inject the ``bbData`` module to your application:\n+\n+  .. code-block:: javascript\n+\n+      angular.module('myApp', ['bbData'])\n+\n+Methods:\n+\n+* ``.get(endpoint, [id], [query])``: returns a promise<Collection>, when the promise is resolved, the Collection contains all the requested data\n+\n+  * call .getArray() on the returned promise to get the updating Collection before it's filled with the initial data\n+\n+  .. code-block:: coffeescript\n+\n+      # assign builds to $scope.builds once the Collection is filled\n+      dataService.get('builds', builderid: 1).then (builds) ->\n+          $scope.builds = builds\n+          # load steps for every build\n+          builds.forEach (b) -> b.loadSteps()\n+\n+      # assign builds to $scope.builds before the Collection is filled using the .getArray() function\n+      $scope.builds = dataService.get('builds', builderid: 1).getArray()\n+\n+* ``.getXXX([id], [query])``: returns a promise<Collection>, when the promise is resolved, the Collection contains all the requested data\n+\n+  * it's higly advised to use these instead of the ``.get('string')`` function\n+  * XXX can be the following: Builds, Builders, Buildrequests, Buildsets, Buildslaves, Changes, Changesources, Forceschedulers, Masters, Schedulers, Sourcestamps\n+  * call .getArray() on the returned promise to get the updating Collection before it's filled with the initial data\n+\n+  .. code-block:: coffeescript\n+\n+      # assign builds to $scope.builds once the Collection is filled\n+      dataService.getBuilds(builderid: 1).then (builds) ->\n+          $scope.builds = builds\n+          # load steps for every build\n+          builds.forEach (b) -> b.loadSteps()\n+\n+      # assign builds to $scope.builds before the Collection is filled using the .getArray() function\n+      $scope.builds = dataService.getBuilds(builderid: 1).getArray()\n+\n+* ``.open(scope)``: returns a DataAccessor, handles bindings, open a new accessor every time you need updating data in a controller\n+\n+  .. code-block:: coffeescript\n+\n+      # open a new accessor every time you need updating data in a controller\n+      class DemoController extends Controller\n+          constructor: ($scope, dataService) ->\n+              # automatically closes all the bindings when the $scope is destroyed\n+              opened = dataService.open($scope)\n+              # alternative syntax:\n+              #   opened = dataService.open()\n+              #   opened.closeOnDestroy($scope)\n+              # closing it manually is also possible:\n+              #   opened.close()\n+\n+              # request new data, it updates automatically\n+              @builders = opened.getBuilders(limit: 10, order: '-started_at').getArray()\n+\n+* ``.control(url, method, [params])``: returns a promise, sends a JSON RPC2 POST request to the server\n+\n+  .. code-block:: coffeescript\n+\n+      # open a new accessor every time you need updating data in a controller\n+      dataService.control('Forceschedulers/force', 'force').then (response) ->\n+          $log.debug(response)\n+      , (reason) ->\n+          $log.error(reason)\n+\n+* ``.clearCache()``: clears the IndexedDB tables and reloads the current page\n+\n+  .. code-block:: coffeescript\n+\n+      class DemoController extends Controller\n+          constructor: (@dataService) ->\n+          onClick: -> @dataService.clearCache()\n+\n+Methods on the Wrapper classes:\n+\n+* ``.getXXX([id], [query])``: returns a promise<Collection>, when the promise is resolved, the Collection contains all the requested data\n+\n+  * same as dataService.getXXX, but with relative endpoint\n+\n+  .. code-block:: coffeescript\n+\n+      # assign builds to $scope.builds once the Collection is filled\n+      dataService.getBuilds(builderid: 1).then (builds) ->\n+          $scope.builds = builds\n+          # get steps for every build\n+          builds.forEach (b) ->\n+              b.getSteps().then (steps) ->\n+                  # assign completed test to every build\n+                  b.complete_steps = steps.map (s) -> s.complete\n+\n+* ``.loadXXX([id], [query])``: returns a promise<Collection>, the Collection contains all the requested data, it is also assigned to wrapperInstance.xxx\n+\n+  .. code-block:: coffeescript\n+\n+      $q (resolve) ->\n+          # get builder with id = 1\n+          dataService.getBuilders(1).then (builders) ->\n+              builders.forEach (builder) ->\n+                  # load all builds\n+                  builder.loadBuilds().then (builds) ->\n+                      builds.forEach (build) ->\n+                          # load all buildsteps\n+                          build.loadSteps().then -> resolve(builders[0])\n+      .then (builder) ->\n+          # builder has a builds field, and the builds have a steps field containing the corresponding data\n+          $log.debug(builder)\n+\n+* ``.control(method, params)``: returns a promise, sends a JSON RPC2 POST request to the server","path":"master/docs/developer/www.rst","position":135,"original_position":135,"commit_id":"9bea1c343b66c83124d8c0a109eaddcf543ecfec","original_commit_id":"9bea1c343b66c83124d8c0a109eaddcf543ecfec","user":{"login":"sa2ajj","id":447092,"avatar_url":"https://avatars.githubusercontent.com/u/447092?v=3","gravatar_id":"","url":"https://api.github.com/users/sa2ajj","html_url":"https://github.com/sa2ajj","followers_url":"https://api.github.com/users/sa2ajj/followers","following_url":"https://api.github.com/users/sa2ajj/following{/other_user}","gists_url":"https://api.github.com/users/sa2ajj/gists{/gist_id}","starred_url":"https://api.github.com/users/sa2ajj/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sa2ajj/subscriptions","organizations_url":"https://api.github.com/users/sa2ajj/orgs","repos_url":"https://api.github.com/users/sa2ajj/repos","events_url":"https://api.github.com/users/sa2ajj/events{/privacy}","received_events_url":"https://api.github.com/users/sa2ajj/received_events","type":"User","site_admin":false},"body":"ditto","created_at":"2015-08-21T15:55:24Z","updated_at":"2015-08-21T15:55:24Z","html_url":"https://github.com/buildbot/buildbot/pull/1823#discussion_r37648375","pull_request_url":"https://api.github.com/repos/buildbot/buildbot/pulls/1823","_links":{"self":{"href":"https://api.github.com/repos/buildbot/buildbot/pulls/comments/37648375"},"html":{"href":"https://github.com/buildbot/buildbot/pull/1823#discussion_r37648375"},"pull_request":{"href":"https://api.github.com/repos/buildbot/buildbot/pulls/1823"}}},"pull_request":{"url":"https://api.github.com/repos/buildbot/buildbot/pulls/1823","id":42915296,"html_url":"https://github.com/buildbot/buildbot/pull/1823","diff_url":"https://github.com/buildbot/buildbot/pull/1823.diff","patch_url":"https://github.com/buildbot/buildbot/pull/1823.patch","issue_url":"https://api.github.com/repos/buildbot/buildbot/issues/1823","number":1823,"state":"open","locked":false,"title":"dataService bug fixes and documentation","user":{"login":"tothandras","id":4157749,"avatar_url":"https://avatars.githubusercontent.com/u/4157749?v=3","gravatar_id":"","url":"https://api.github.com/users/tothandras","html_url":"https://github.com/tothandras","followers_url":"https://api.github.com/users/tothandras/followers","following_url":"https://api.github.com/users/tothandras/following{/other_user}","gists_url":"https://api.github.com/users/tothandras/gists{/gist_id}","starred_url":"https://api.github.com/users/tothandras/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tothandras/subscriptions","organizations_url":"https://api.github.com/users/tothandras/orgs","repos_url":"https://api.github.com/users/tothandras/repos","events_url":"https://api.github.com/users/tothandras/events{/privacy}","received_events_url":"https://api.github.com/users/tothandras/received_events","type":"User","site_admin":false},"body":"@shanzi has found a few bugs, this PR contains fixes for those.\r\nI also added a simple documentation for dataService.\r\n\r\nI have started to migrate the base module to the dataService, I will include that in my last PR before GSoC ends.\r\n\r\nIn this semester I can actively contribute to Buildbot, I feel like there is still a lot to do to in terms of optimisation. Sharing the WebSocket connection and using IndexedDB as a single data store introduced a lot of unexpected problems, it took more time to solve those than expected.","created_at":"2015-08-20T10:43:49Z","updated_at":"2015-08-21T15:55:24Z","closed_at":null,"merged_at":null,"merge_commit_sha":"533eaa48ba7e1e792fbaa19ed2984e97b95dfee4","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/buildbot/buildbot/pulls/1823/commits","review_comments_url":"https://api.github.com/repos/buildbot/buildbot/pulls/1823/comments","review_comment_url":"https://api.github.com/repos/buildbot/buildbot/pulls/comments{/number}","comments_url":"https://api.github.com/repos/buildbot/buildbot/issues/1823/comments","statuses_url":"https://api.github.com/repos/buildbot/buildbot/statuses/9bea1c343b66c83124d8c0a109eaddcf543ecfec","head":{"label":"tothandras:data_service","ref":"data_service","sha":"9bea1c343b66c83124d8c0a109eaddcf543ecfec","user":{"login":"tothandras","id":4157749,"avatar_url":"https://avatars.githubusercontent.com/u/4157749?v=3","gravatar_id":"","url":"https://api.github.com/users/tothandras","html_url":"https://github.com/tothandras","followers_url":"https://api.github.com/users/tothandras/followers","following_url":"https://api.github.com/users/tothandras/following{/other_user}","gists_url":"https://api.github.com/users/tothandras/gists{/gist_id}","starred_url":"https://api.github.com/users/tothandras/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tothandras/subscriptions","organizations_url":"https://api.github.com/users/tothandras/orgs","repos_url":"https://api.github.com/users/tothandras/repos","events_url":"https://api.github.com/users/tothandras/events{/privacy}","received_events_url":"https://api.github.com/users/tothandras/received_events","type":"User","site_admin":false},"repo":{"id":18769479,"name":"buildbot","full_name":"tothandras/buildbot","owner":{"login":"tothandras","id":4157749,"avatar_url":"https://avatars.githubusercontent.com/u/4157749?v=3","gravatar_id":"","url":"https://api.github.com/users/tothandras","html_url":"https://github.com/tothandras","followers_url":"https://api.github.com/users/tothandras/followers","following_url":"https://api.github.com/users/tothandras/following{/other_user}","gists_url":"https://api.github.com/users/tothandras/gists{/gist_id}","starred_url":"https://api.github.com/users/tothandras/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tothandras/subscriptions","organizations_url":"https://api.github.com/users/tothandras/orgs","repos_url":"https://api.github.com/users/tothandras/repos","events_url":"https://api.github.com/users/tothandras/events{/privacy}","received_events_url":"https://api.github.com/users/tothandras/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/tothandras/buildbot","description":"Python-based continuous integration testing framework.","fork":true,"url":"https://api.github.com/repos/tothandras/buildbot","forks_url":"https://api.github.com/repos/tothandras/buildbot/forks","keys_url":"https://api.github.com/repos/tothandras/buildbot/keys{/key_id}","collaborators_url":"https://api.github.com/repos/tothandras/buildbot/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/tothandras/buildbot/teams","hooks_url":"https://api.github.com/repos/tothandras/buildbot/hooks","issue_events_url":"https://api.github.com/repos/tothandras/buildbot/issues/events{/number}","events_url":"https://api.github.com/repos/tothandras/buildbot/events","assignees_url":"https://api.github.com/repos/tothandras/buildbot/assignees{/user}","branches_url":"https://api.github.com/repos/tothandras/buildbot/branches{/branch}","tags_url":"https://api.github.com/repos/tothandras/buildbot/tags","blobs_url":"https://api.github.com/repos/tothandras/buildbot/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/tothandras/buildbot/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/tothandras/buildbot/git/refs{/sha}","trees_url":"https://api.github.com/repos/tothandras/buildbot/git/trees{/sha}","statuses_url":"https://api.github.com/repos/tothandras/buildbot/statuses/{sha}","languages_url":"https://api.github.com/repos/tothandras/buildbot/languages","stargazers_url":"https://api.github.com/repos/tothandras/buildbot/stargazers","contributors_url":"https://api.github.com/repos/tothandras/buildbot/contributors","subscribers_url":"https://api.github.com/repos/tothandras/buildbot/subscribers","subscription_url":"https://api.github.com/repos/tothandras/buildbot/subscription","commits_url":"https://api.github.com/repos/tothandras/buildbot/commits{/sha}","git_commits_url":"https://api.github.com/repos/tothandras/buildbot/git/commits{/sha}","comments_url":"https://api.github.com/repos/tothandras/buildbot/comments{/number}","issue_comment_url":"https://api.github.com/repos/tothandras/buildbot/issues/comments{/number}","contents_url":"https://api.github.com/repos/tothandras/buildbot/contents/{+path}","compare_url":"https://api.github.com/repos/tothandras/buildbot/compare/{base}...{head}","merges_url":"https://api.github.com/repos/tothandras/buildbot/merges","archive_url":"https://api.github.com/repos/tothandras/buildbot/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/tothandras/buildbot/downloads","issues_url":"https://api.github.com/repos/tothandras/buildbot/issues{/number}","pulls_url":"https://api.github.com/repos/tothandras/buildbot/pulls{/number}","milestones_url":"https://api.github.com/repos/tothandras/buildbot/milestones{/number}","notifications_url":"https://api.github.com/repos/tothandras/buildbot/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/tothandras/buildbot/labels{/name}","releases_url":"https://api.github.com/repos/tothandras/buildbot/releases{/id}","created_at":"2014-04-14T17:22:12Z","updated_at":"2015-05-13T22:46:07Z","pushed_at":"2015-08-21T14:35:15Z","git_url":"git://github.com/tothandras/buildbot.git","ssh_url":"git@github.com:tothandras/buildbot.git","clone_url":"https://github.com/tothandras/buildbot.git","svn_url":"https://github.com/tothandras/buildbot","homepage":"","size":37364,"stargazers_count":1,"watchers_count":1,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":1,"default_branch":"master"}},"base":{"label":"buildbot:master","ref":"master","sha":"a57d74d558fcab58827806d7c21715f798ee78aa","user":{"login":"buildbot","id":324515,"avatar_url":"https://avatars.githubusercontent.com/u/324515?v=3","gravatar_id":"","url":"https://api.github.com/users/buildbot","html_url":"https://github.com/buildbot","followers_url":"https://api.github.com/users/buildbot/followers","following_url":"https://api.github.com/users/buildbot/following{/other_user}","gists_url":"https://api.github.com/users/buildbot/gists{/gist_id}","starred_url":"https://api.github.com/users/buildbot/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/buildbot/subscriptions","organizations_url":"https://api.github.com/users/buildbot/orgs","repos_url":"https://api.github.com/users/buildbot/repos","events_url":"https://api.github.com/users/buildbot/events{/privacy}","received_events_url":"https://api.github.com/users/buildbot/received_events","type":"Organization","site_admin":false},"repo":{"id":760165,"name":"buildbot","full_name":"buildbot/buildbot","owner":{"login":"buildbot","id":324515,"avatar_url":"https://avatars.githubusercontent.com/u/324515?v=3","gravatar_id":"","url":"https://api.github.com/users/buildbot","html_url":"https://github.com/buildbot","followers_url":"https://api.github.com/users/buildbot/followers","following_url":"https://api.github.com/users/buildbot/following{/other_user}","gists_url":"https://api.github.com/users/buildbot/gists{/gist_id}","starred_url":"https://api.github.com/users/buildbot/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/buildbot/subscriptions","organizations_url":"https://api.github.com/users/buildbot/orgs","repos_url":"https://api.github.com/users/buildbot/repos","events_url":"https://api.github.com/users/buildbot/events{/privacy}","received_events_url":"https://api.github.com/users/buildbot/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/buildbot/buildbot","description":"Python-based continuous integration testing framework; your pull requests are more than welcome!","fork":false,"url":"https://api.github.com/repos/buildbot/buildbot","forks_url":"https://api.github.com/repos/buildbot/buildbot/forks","keys_url":"https://api.github.com/repos/buildbot/buildbot/keys{/key_id}","collaborators_url":"https://api.github.com/repos/buildbot/buildbot/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/buildbot/buildbot/teams","hooks_url":"https://api.github.com/repos/buildbot/buildbot/hooks","issue_events_url":"https://api.github.com/repos/buildbot/buildbot/issues/events{/number}","events_url":"https://api.github.com/repos/buildbot/buildbot/events","assignees_url":"https://api.github.com/repos/buildbot/buildbot/assignees{/user}","branches_url":"https://api.github.com/repos/buildbot/buildbot/branches{/branch}","tags_url":"https://api.github.com/repos/buildbot/buildbot/tags","blobs_url":"https://api.github.com/repos/buildbot/buildbot/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/buildbot/buildbot/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/buildbot/buildbot/git/refs{/sha}","trees_url":"https://api.github.com/repos/buildbot/buildbot/git/trees{/sha}","statuses_url":"https://api.github.com/repos/buildbot/buildbot/statuses/{sha}","languages_url":"https://api.github.com/repos/buildbot/buildbot/languages","stargazers_url":"https://api.github.com/repos/buildbot/buildbot/stargazers","contributors_url":"https://api.github.com/repos/buildbot/buildbot/contributors","subscribers_url":"https://api.github.com/repos/buildbot/buildbot/subscribers","subscription_url":"https://api.github.com/repos/buildbot/buildbot/subscription","commits_url":"https://api.github.com/repos/buildbot/buildbot/commits{/sha}","git_commits_url":"https://api.github.com/repos/buildbot/buildbot/git/commits{/sha}","comments_url":"https://api.github.com/repos/buildbot/buildbot/comments{/number}","issue_comment_url":"https://api.github.com/repos/buildbot/buildbot/issues/comments{/number}","contents_url":"https://api.github.com/repos/buildbot/buildbot/contents/{+path}","compare_url":"https://api.github.com/repos/buildbot/buildbot/compare/{base}...{head}","merges_url":"https://api.github.com/repos/buildbot/buildbot/merges","archive_url":"https://api.github.com/repos/buildbot/buildbot/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/buildbot/buildbot/downloads","issues_url":"https://api.github.com/repos/buildbot/buildbot/issues{/number}","pulls_url":"https://api.github.com/repos/buildbot/buildbot/pulls{/number}","milestones_url":"https://api.github.com/repos/buildbot/buildbot/milestones{/number}","notifications_url":"https://api.github.com/repos/buildbot/buildbot/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/buildbot/buildbot/labels{/name}","releases_url":"https://api.github.com/repos/buildbot/buildbot/releases{/id}","created_at":"2010-07-06T17:56:53Z","updated_at":"2015-08-21T14:58:05Z","pushed_at":"2015-08-21T14:35:17Z","git_url":"git://github.com/buildbot/buildbot.git","ssh_url":"git@github.com:buildbot/buildbot.git","clone_url":"https://github.com/buildbot/buildbot.git","svn_url":"https://github.com/buildbot/buildbot","homepage":"http://buildbot.net","size":53212,"stargazers_count":1694,"watchers_count":1694,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":746,"mirror_url":null,"open_issues_count":22,"forks":746,"open_issues":22,"watchers":1694,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/buildbot/buildbot/pulls/1823"},"html":{"href":"https://github.com/buildbot/buildbot/pull/1823"},"issue":{"href":"https://api.github.com/repos/buildbot/buildbot/issues/1823"},"comments":{"href":"https://api.github.com/repos/buildbot/buildbot/issues/1823/comments"},"review_comments":{"href":"https://api.github.com/repos/buildbot/buildbot/pulls/1823/comments"},"review_comment":{"href":"https://api.github.com/repos/buildbot/buildbot/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/buildbot/buildbot/pulls/1823/commits"},"statuses":{"href":"https://api.github.com/repos/buildbot/buildbot/statuses/9bea1c343b66c83124d8c0a109eaddcf543ecfec"}}}}