From a4a75912b0ce38c0fffebce2b904cb5dc6b5d015 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Mon, 27 Apr 2015 12:31:48 +0200 Subject: [PATCH] Implement ajax polling for things that are visible on the screen We sometimes miss pusher updates, which started to be more common lately. Until we investigate what's going on, this should be a good workaround for keeping UI in sync with the DB. --- app/components/running-jobs-item.coffee | 8 +++ app/controllers/running-jobs.coffee | 3 +- app/mixins/polling.coffee | 63 +++++++++++++++++++ app/routes/abstract-builds.coffee | 1 + app/services/polling.coffee | 49 +++++++++++++++ .../components/running-jobs-item.hbs | 1 + app/templates/running-jobs.hbs | 24 +------ app/views/build.coffee | 5 +- app/views/builds.coffee | 18 ++++++ app/views/job.coffee | 5 +- app/views/repos-list.coffee | 5 +- app/views/running-jobs.coffee | 8 +++ .../components/running-jobs-item-test.coffee | 17 +++++ 13 files changed, 180 insertions(+), 27 deletions(-) create mode 100644 app/components/running-jobs-item.coffee create mode 100644 app/mixins/polling.coffee create mode 100644 app/services/polling.coffee create mode 100644 app/templates/components/running-jobs-item.hbs create mode 100644 app/views/builds.coffee create mode 100644 app/views/running-jobs.coffee create mode 100644 tests/unit/components/running-jobs-item-test.coffee diff --git a/app/components/running-jobs-item.coffee b/app/components/running-jobs-item.coffee new file mode 100644 index 00000000..fb5fe7c7 --- /dev/null +++ b/app/components/running-jobs-item.coffee @@ -0,0 +1,8 @@ +`import Ember from 'ember'` +`import Polling from 'travis/mixins/polling'` + +RunningJobsItemComponent = Ember.Component.extend(Polling, + pollModels: 'job' +) + +`export default RunningJobsItemComponent` diff --git a/app/controllers/running-jobs.coffee b/app/controllers/running-jobs.coffee index 10b09e45..1058b304 100644 --- a/app/controllers/running-jobs.coffee +++ b/app/controllers/running-jobs.coffee @@ -12,7 +12,8 @@ Controller = Ember.ArrayController.extend isLoaded: false content: (-> - result = @store.filter('job', { state: 'started' }, (job) -> + # TODO: this should also query for received jobs + result = @store.filter('job', {}, (job) -> ['started', 'received'].indexOf(job.get('state')) != -1 ) result.then => diff --git a/app/mixins/polling.coffee b/app/mixins/polling.coffee new file mode 100644 index 00000000..b55b7c1b --- /dev/null +++ b/app/mixins/polling.coffee @@ -0,0 +1,63 @@ +`import Ember from 'ember'` + +mixin = Ember.Mixin.create + polling: Ember.inject.service() + + didInsertElement: -> + @_super.apply(this, arguments) + + @startPolling() + + willDestroyElement: -> + @_super.apply(this, arguments) + + @stopPolling() + + willDestroy: -> + @_super.apply(this, arguments) + + @stopPolling() + + pollModelDidChange: (sender, key, value) -> + @pollModel(key) + + pollModelWillChange: (sender, key, value) -> + @stopPollingModel(key) + + pollModel: (property) -> + model = @get(property) + + @get('polling').startPolling(model) + + stopPollingModel: (property) -> + model = @get(property) + + @get('polling').stopPolling(model) + + startPolling: -> + pollModels = @get('pollModels') + + if pollModels + pollModels = [pollModels] unless pollModels.forEeach + + pollModels.forEach (property) => + @pollModel(property) + @addObserver(property, this, 'pollModelDidChange') + Ember.addBeforeObserver(this, property, this, 'pollModelWillChange') + + @get('polling').startPollingHook(this) if @pollHook + + stopPolling: -> + pollModels = @get('pollModels') + return unless pollModels + + pollModels = [pollModels] unless pollModels.forEeach + + pollModels.forEach (property) => + @stopPollingModel(property) + @removeObserver(property, this, 'pollModelDidChange') + Ember.removeBeforeObserver(this, property, this, 'pollModelWillChange') + + @get('polling').stopPollingHook(this) + +`export default mixin` diff --git a/app/routes/abstract-builds.coffee b/app/routes/abstract-builds.coffee index 1a79b7ec..d64f93e3 100644 --- a/app/routes/abstract-builds.coffee +++ b/app/routes/abstract-builds.coffee @@ -11,6 +11,7 @@ Route = TravisRoute.extend @controllerFor('repo').activate(@get('contentType')) @contentDidChange() @controllerFor('repo').addObserver(@get('path'), this, 'contentDidChange') + @controllerFor('build').set('contentType', @get('contentType')) deactivate: -> @controllerFor('repo').removeObserver(@get('path'), this, 'contentDidChange') diff --git a/app/services/polling.coffee b/app/services/polling.coffee new file mode 100644 index 00000000..a9b916b8 --- /dev/null +++ b/app/services/polling.coffee @@ -0,0 +1,49 @@ +`import Ember from 'ember'` + +service = Ember.Object.extend + init: -> + @_super.apply(this, arguments) + + @set('watchedModels', []) + @set('sources', []) + + interval = setInterval => + @poll() + , 30000 + + @set('interval', interval) + + willDestroy: -> + @_super.apply(this, arguments) + + clearInterval(@get('interval')) + + startPollingHook: (source) -> + sources = @get('sources') + unless sources.contains(source) + sources.pushObject(source) + + stopPollingHook: (source) -> + sources = @get('sources') + sources.removeObject(source) + + startPolling: (model) -> + watchedModels = @get('watchedModels') + unless watchedModels.contains(model) + watchedModels.pushObject(model) + + stopPolling: (model) -> + watchedModels = @get('watchedModels') + watchedModels.removeObject(model) + + poll: -> + @get('watchedModels').forEach (model) -> + model.reload() + + @get('sources').forEach (source) => + if source.get('destroyed') + @get('sources').removeObject(source) + else + source.pollHook() + +`export default service` diff --git a/app/templates/components/running-jobs-item.hbs b/app/templates/components/running-jobs-item.hbs new file mode 100644 index 00000000..889d9eea --- /dev/null +++ b/app/templates/components/running-jobs-item.hbs @@ -0,0 +1 @@ +{{yield}} diff --git a/app/templates/running-jobs.hbs b/app/templates/running-jobs.hbs index 0c12c853..c99fd7e3 100644 --- a/app/templates/running-jobs.hbs +++ b/app/templates/running-jobs.hbs @@ -1,29 +1,7 @@ {{#if isLoaded}} {{#if controller.length}} {{#each job in controller}} -
- {{#if job.repo.slug}} - - {{#link-to "job" job.repo job}}{{job.repo.slug}}{{/link-to}} - {{/if}} - -

- - {{#if job.repo.slug}} - {{#link-to "job" job.repo job}}{{job.number}}{{/link-to}} - {{/if}} -

- -

- - Duration: - - {{format-duration job.duration}} - -

- -
- + {{running-jobs-item job=job}} {{/each}} {{else}}
There are no jobs running
diff --git a/app/views/build.coffee b/app/views/build.coffee index 10792cdb..124b8055 100644 --- a/app/views/build.coffee +++ b/app/views/build.coffee @@ -1,10 +1,13 @@ `import { colorForState } from 'travis/utils/helpers'` `import BasicView from 'travis/views/basic'` +`import Polling from 'travis/mixins/polling'` -View = BasicView.extend +View = BasicView.extend Polling, classNameBindings: ['color'] buildBinding: 'controller.build' + pollModels: 'controller.build' + color: (-> colorForState(@get('build.state')) ).property('build.state') diff --git a/app/views/builds.coffee b/app/views/builds.coffee new file mode 100644 index 00000000..cdee99f0 --- /dev/null +++ b/app/views/builds.coffee @@ -0,0 +1,18 @@ +`import BasicView from 'travis/views/basic'` +`import Polling from 'travis/mixins/polling'` + +View = BasicView.extend Polling, + pollHook: (store) -> + contentType = @get('controller.contentType') + repositoryId = @get('controller.repo.id') + store = @get('controller.store') + + if contentType == 'builds' + store.find('build', { event_type: 'push', repository_id: repositoryId }) + else if contentType == 'pull_requests' + store.filter('build', { event_type: 'pull_request', repository_id: repositoryId }) + else + store.find 'build', repository_id: repositoryId, branches: true + + +`export default View` diff --git a/app/views/job.coffee b/app/views/job.coffee index 8eaeeafb..622b0bcd 100644 --- a/app/views/job.coffee +++ b/app/views/job.coffee @@ -1,8 +1,11 @@ `import Ember from 'ember'` `import { colorForState } from 'travis/utils/helpers'` `import { githubCommit, gravatarImage } from 'travis/utils/urls'` +`import Polling from 'travis/mixins/polling'` + +View = Ember.View.extend Polling, + pollModels: 'controller.job' -View = Ember.View.extend repoBinding: 'controller.repo' jobBinding: 'controller.job' commitBinding: 'job.commit' diff --git a/app/views/repos-list.coffee b/app/views/repos-list.coffee index 65239f90..5d4fba03 100644 --- a/app/views/repos-list.coffee +++ b/app/views/repos-list.coffee @@ -1,5 +1,6 @@ `import Ember from 'ember'` `import { colorForState } from 'travis/utils/helpers'` +`import Polling from 'travis/mixins/polling'` View = Ember.CollectionView.extend elementId: '' @@ -8,7 +9,9 @@ View = Ember.CollectionView.extend emptyView: Ember.View.extend templateName: 'repos-list/empty' - itemViewClass: Ember.View.extend + itemViewClass: Ember.View.extend Polling, + pollModels: 'repo' + repoBinding: 'content' classNames: ['repo'] classNameBindings: ['color', 'selected'] diff --git a/app/views/running-jobs.coffee b/app/views/running-jobs.coffee new file mode 100644 index 00000000..11c9a768 --- /dev/null +++ b/app/views/running-jobs.coffee @@ -0,0 +1,8 @@ +`import BasicView from 'travis/views/basic'` +`import Polling from 'travis/mixins/polling'` + +View = BasicView.extend Polling, + pollHook: (store) -> + @get('controller.store').find('job', {}) + +`export default View` diff --git a/tests/unit/components/running-jobs-item-test.coffee b/tests/unit/components/running-jobs-item-test.coffee new file mode 100644 index 00000000..6a10536f --- /dev/null +++ b/tests/unit/components/running-jobs-item-test.coffee @@ -0,0 +1,17 @@ +`import { test, moduleForComponent } from 'ember-qunit'` + +moduleForComponent 'running-jobs-item', { + # specify the other units that are required for this test + # needs: ['component:foo', 'helper:bar'] +} + +test 'it renders', (assert) -> + assert.expect 2 + + # creates the component instance + component = @subject() + assert.equal component._state, 'preRender' + + # renders the component to the page + @render() + assert.equal component._state, 'inDOM'