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.
This commit is contained in:
Piotr Sarnacki 2015-04-27 12:31:48 +02:00
parent a304ce2640
commit a4a75912b0
13 changed files with 180 additions and 27 deletions

View File

@ -0,0 +1,8 @@
`import Ember from 'ember'`
`import Polling from 'travis/mixins/polling'`
RunningJobsItemComponent = Ember.Component.extend(Polling,
pollModels: 'job'
)
`export default RunningJobsItemComponent`

View File

@ -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 =>

63
app/mixins/polling.coffee Normal file
View File

@ -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`

View File

@ -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')

View File

@ -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`

View File

@ -0,0 +1 @@
{{yield}}

View File

@ -1,29 +1,7 @@
{{#if isLoaded}}
{{#if controller.length}}
{{#each job in controller}}
<div {{bind-attr class=":tile :tile--sidebar job.state"}}>
{{#if job.repo.slug}}
<span {{bind-attr class=":icon :icon--job job.state"}}></span>
{{#link-to "job" job.repo job}}{{job.repo.slug}}{{/link-to}}
{{/if}}
<p class="tile-title float-right">
<span class="icon icon--hash"></span>
{{#if job.repo.slug}}
{{#link-to "job" job.repo job}}{{job.number}}{{/link-to}}
{{/if}}
</p>
<p>
<span class="icon icon--clock"></span>
Duration:
<abbr class="duration" {{bind-attr title="job.startedAt"}}>
{{format-duration job.duration}}
</abbr>
</p>
</div>
{{running-jobs-item job=job}}
{{/each}}
{{else}}
<div class="spinner-container">There are no jobs running</div>

View File

@ -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')

18
app/views/builds.coffee Normal file
View File

@ -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`

View File

@ -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'

View File

@ -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']

View File

@ -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`

View File

@ -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'