diff --git a/.jshintrc b/.jshintrc index d7283329..98bf8385 100644 --- a/.jshintrc +++ b/.jshintrc @@ -3,7 +3,8 @@ "document", "window", "-Promise", - "jQuery" + "jQuery", + "Visibility" ], "browser": true, "boss": true, diff --git a/.travis.yml b/.travis.yml index 28361939..b6f36c35 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,8 +16,8 @@ matrix: fast_finish: true -#addons: -# sauce_connect: true +addons: + sauce_connect: true sudo: false diff --git a/.watchmanconfig b/.watchmanconfig new file mode 100644 index 00000000..e7834e3e --- /dev/null +++ b/.watchmanconfig @@ -0,0 +1,3 @@ +{ + "ignore_dirs": ["tmp", "dist"] +} diff --git a/app/adapters/application.coffee b/app/adapters/application.coffee index 3ec51701..d36bca6d 100644 --- a/app/adapters/application.coffee +++ b/app/adapters/application.coffee @@ -2,6 +2,8 @@ `import config from 'travis/config/environment'` Adapter = DS.ActiveModelAdapter.extend + auth: Ember.inject.service() + host: config.apiEndpoint coalesceFindRequests: true @@ -12,13 +14,13 @@ Adapter = DS.ActiveModelAdapter.extend hash.headers['accept'] = 'application/json; version=2' - if token = Travis.sessionStorage.getItem('travis.token') + if token = @get('auth').token() hash.headers['Authorization'] ||= "token #{token}" hash findMany: (store, type, ids) -> - @ajax(@buildURL(type.typeKey), 'GET', data: { ids: ids }) + @ajax(@buildURL(type.modelName), 'GET', data: { ids: ids }) handleResponse: (status, headers, payload) -> if status > 299 diff --git a/app/adapters/hook.js b/app/adapters/hook.js new file mode 100644 index 00000000..153c036b --- /dev/null +++ b/app/adapters/hook.js @@ -0,0 +1,9 @@ +import ApplicationAdapter from 'travis/adapters/application'; + +export default ApplicationAdapter.extend({ + updateRecord(store, type, snapshot) { + return this._super(...arguments).then( (data) => { + return { hook: { id: snapshot.id } }; + }); + } +}); diff --git a/app/adapters/repo.js b/app/adapters/repo.js new file mode 100644 index 00000000..b869bcba --- /dev/null +++ b/app/adapters/repo.js @@ -0,0 +1,26 @@ +import V3Adapter from 'travis/adapters/v3'; + +export default V3Adapter.extend({ + defaultSerializer: '-repo', + + buildUrl(modelName, id, snapshot, requestType, query) { + var url = this._super(...arguments); + + return url; + }, + + ajaxOptions(url, type, options) { + var hash = options || {}; + if(!hash.data) { + hash.data = {}; + } + + if(hash.data.include) { + hash.data.include += ',repository.default_branch,branch.last_build,build.commit'; + } else { + hash.data.include = 'repository.default_branch,branch.last_build,build.commit'; + } + + return this._super(url, type, hash); + } +}); diff --git a/app/adapters/ssh-key.coffee b/app/adapters/ssh-key.coffee index f4bad312..1c052a97 100644 --- a/app/adapters/ssh-key.coffee +++ b/app/adapters/ssh-key.coffee @@ -19,6 +19,7 @@ Adapter = ApplicationAdapter.extend id = Ember.get(record, 'id') - this.ajax(this.urlPrefix() + '/ssh_key/' + id, "PATCH", { data: data }) + this.ajax(this.urlPrefix() + '/ssh_key/' + id, "PATCH", { data: data }).then (data) -> + data.ssh_key `export default Adapter` diff --git a/app/adapters/v3.js b/app/adapters/v3.js new file mode 100644 index 00000000..32f40c33 --- /dev/null +++ b/app/adapters/v3.js @@ -0,0 +1,58 @@ +import Ember from 'ember'; +import DS from 'ember-data'; +import config from 'travis/config/environment'; + +export default DS.RESTAdapter.extend({ + auth: Ember.inject.service(), + host: config.apiEndpoint, + + sortQueryParams: false, + coalesceFindRequests: false, + headers: { + 'Travis-API-Version': '3', + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + + ajaxOptions: function(url, type, options) { + var hash = this._super(...arguments); + + hash.headers = hash.headers || {}; + + var token; + if(token = this.get('auth').token()) { + hash.headers['Authorization'] = "token " + token; + } + + return hash; + }, + + // TODO: I shouldn't override this method as it's private, a better way would + // be to create my own URL generator + _buildURL: function(modelName, id) { + var url = []; + var host = Ember.get(this, 'host'); + var prefix = this.urlPrefix(); + var path; + + if (modelName) { + path = this.pathForType(modelName, id); + if (path) { url.push(path); } + } + + if (id) { url.push(encodeURIComponent(id)); } + if (prefix) { url.unshift(prefix); } + + url = url.join('/'); + if (!host && url && url.charAt(0) !== '/') { + url = '/' + url; + } + + return url; + }, + + pathForType: function(modelName, id) { + var underscored = Ember.String.underscore(modelName); + return id ? underscored : Ember.String.pluralize(underscored); + } +}); diff --git a/app/components/caches-item.coffee b/app/components/caches-item.coffee index c6fa0dbf..00c96a9a 100644 --- a/app/components/caches-item.coffee +++ b/app/components/caches-item.coffee @@ -1,7 +1,7 @@ `import Ember from 'ember'` -`import Ajax from 'travis/utils/ajax'` CachesItemComponent = Ember.Component.extend + ajax: Ember.inject.service() tagName: 'li' classNames: ['cache-item'] @@ -20,7 +20,7 @@ CachesItemComponent = Ember.Component.extend deletingDone = => @set('isDeleting', false) repo = @get('repo') - Ajax.ajax("/repos/#{repo.get('id')}/caches", "DELETE", data: data).then(deletingDone, deletingDone).then => + @get('ajax').ajax("/repos/#{repo.get('id')}/caches", "DELETE", data: data).then(deletingDone, deletingDone).then => @get('caches').removeObject(@get('cache')) diff --git a/app/components/no-builds.coffee b/app/components/no-builds.coffee index 57050edf..66e72d8a 100644 --- a/app/components/no-builds.coffee +++ b/app/components/no-builds.coffee @@ -1,10 +1,8 @@ `import Ember from 'ember'` -`import Ajax from 'travis/utils/ajax'` `import config from 'travis/config/environment'` NoBuildsComponent = Ember.Component.extend - - actions: + actions: triggerBuild: () -> @set('isLoading', true) apiEndpoint = config.apiEndpoint diff --git a/app/components/repos-list-item.coffee b/app/components/repos-list-item.coffee index e6c5da71..8b6c804e 100644 --- a/app/components/repos-list-item.coffee +++ b/app/components/repos-list-item.coffee @@ -15,6 +15,10 @@ ReposListItemComponent = Ember.Component.extend Polling, @get('repo') == @get('selectedRepo') ).property('selectedRepo') + color: (-> + colorForState(@get('repo.defaultBranch.lastBuild.state')) + ).property('repo.defaultBranch.lastBuild.state') + scrollTop: (-> if (window.scrollY > 0) $('html, body').animate({scrollTop: 0}, 200) diff --git a/app/components/repos-list.coffee b/app/components/repos-list.coffee deleted file mode 100644 index 3dcf8f8c..00000000 --- a/app/components/repos-list.coffee +++ /dev/null @@ -1,6 +0,0 @@ -`import Ember from 'ember'` - -ReposListComponent = Ember.Component.extend - tagName: 'ul' - -`export default ReposListComponent` diff --git a/app/components/repos-list.js b/app/components/repos-list.js new file mode 100644 index 00000000..af03eeae --- /dev/null +++ b/app/components/repos-list.js @@ -0,0 +1,7 @@ +import Ember from 'ember'; + +var ReposListComponent = Ember.Component.extend({ + tagName: 'ul' +}); + +export default ReposListComponent; diff --git a/app/components/travis-status.coffee b/app/components/travis-status.coffee index 3ae86598..29269e19 100644 --- a/app/components/travis-status.coffee +++ b/app/components/travis-status.coffee @@ -1,5 +1,4 @@ `import Ember from 'ember'` -`import Ajax from 'travis/utils/ajax'` `import config from 'travis/config/environment'` TravisStatusComponent = Ember.Component.extend diff --git a/app/controllers/account.coffee b/app/controllers/account.coffee index f1ddb699..27aea603 100644 --- a/app/controllers/account.coffee +++ b/app/controllers/account.coffee @@ -2,8 +2,7 @@ Controller = Ember.Controller.extend allHooks: [] - needs: ['currentUser'] - userBinding: 'controllers.currentUser.model' + userBinding: 'auth.currentUser' init: -> @_super.apply this, arguments @@ -22,7 +21,7 @@ Controller = Ember.Controller.extend reloadHooks: -> if login = @get('model.login') - hooks = @store.find('hook', all: true, owner_name: login) + hooks = @store.query('hook', all: true, owner_name: login) hooks.then () -> hooks.set('isLoaded', true) diff --git a/app/controllers/accounts/info.coffee b/app/controllers/accounts/info.coffee index 8d51d797..179d835f 100644 --- a/app/controllers/accounts/info.coffee +++ b/app/controllers/accounts/info.coffee @@ -1,7 +1,7 @@ `import Ember from 'ember'` Controller = Ember.Controller.extend - needs: ['currentUser', 'repos'] - userBinding: 'controllers.currentUser' + repos: Ember.inject.controller() + userBinding: 'auth.currentUser' `export default Controller` diff --git a/app/controllers/build.coffee b/app/controllers/build.coffee index 6470f9a0..b01148ff 100644 --- a/app/controllers/build.coffee +++ b/app/controllers/build.coffee @@ -3,11 +3,11 @@ `import GithubUrlPropertievs from 'travis/mixins/github-url-properties'` Controller = Ember.Controller.extend GithubUrlPropertievs, - needs: ['repo', 'currentUser'] - repoBinding: 'controllers.repo.repo' + repoController: Ember.inject.controller('repo') + repoBinding: 'repoController.repo' commitBinding: 'build.commit' - currentUserBinding: 'controllers.currentUser.model' - tabBinding: 'controllers.repo.tab' + currentUserBinding: 'auth.currentUser' + tabBinding: 'repoController.tab' sendFaviconStateChanges: true currentItemBinding: 'build' diff --git a/app/controllers/builds.coffee b/app/controllers/builds.coffee index 956c9dbb..17649392 100644 --- a/app/controllers/builds.coffee +++ b/app/controllers/builds.coffee @@ -4,10 +4,10 @@ Controller = Ember.ArrayController.extend sortAscending: false sortProperties: ['number'] - needs: ['repo'] + repoController: Ember.inject.controller('repo') - repoBinding: 'controllers.repo.repo' - tabBinding: 'controllers.repo.tab' + repoBinding: 'repoController.repo' + tabBinding: 'repoController.tab' isLoadedBinding: 'content.isLoaded' isLoadingBinding: 'content.isLoading' @@ -53,7 +53,7 @@ Controller = Ember.ArrayController.extend if type? options.event_type = type.replace(/s$/, '') # poor man's singularize - @store.find('build', options) + @store.query('build', options) actions: showMoreBuilds: -> diff --git a/app/controllers/caches.coffee b/app/controllers/caches.coffee index 70940d3a..9827c927 100644 --- a/app/controllers/caches.coffee +++ b/app/controllers/caches.coffee @@ -1,9 +1,10 @@ `import Ember from 'ember'` -`import Ajax from 'travis/utils/ajax'` Controller = Ember.Controller.extend - needs: ['repo'] - repo: Ember.computed.alias('controllers.repo.repo') + ajax: Ember.inject.service() + + repoController: Ember.inject.controller('repo') + repo: Ember.computed.alias('repoController.repo') isDeleting: false @@ -21,7 +22,7 @@ Controller = Ember.Controller.extend deletingDone = => @set('isDeleting', false) repo = @get('repo') - Ajax.ajax("/repos/#{@get('repo.id')}/caches", "DELETE").then(deletingDone, deletingDone).then => + @get('ajax').ajax("/repos/#{@get('repo.id')}/caches", "DELETE").then(deletingDone, deletingDone).then => @set('model', {}) `export default Controller` diff --git a/app/controllers/env-var.coffee b/app/controllers/env-var.coffee deleted file mode 100644 index b793b857..00000000 --- a/app/controllers/env-var.coffee +++ /dev/null @@ -1,48 +0,0 @@ -`import Ember from 'ember'` -`import Validations from 'travis/utils/validations'` - -Controller = Ember.ObjectController.extend Validations, - isEditing: false - isDeleting: false - - validates: - name: ['presence'] - - actionType: 'Save' - showValueField: Ember.computed.alias('public') - - value: ( (key, value) -> - if arguments.length == 2 - @get('model').set('value', value) - value - else if @get('public') - @get('model.value') - else - '••••••••••••••••' - ).property('model.value', 'public') - - actions: - delete: -> - return if @get('isDeleting') - @set('isDeleting', true) - - @get('model').destroyRecord() - - edit: -> - @set('isEditing', true) - - cancel: -> - @set('isEditing', false) - @get('model').revert() - - save: -> - return if @get('isSaving') - - if @isValid() - env_var = @get('model') - - # TODO: handle errors - env_var.save().then => - @set('isEditing', false) - -`export default Controller` diff --git a/app/controllers/env-vars.coffee b/app/controllers/env-vars.coffee deleted file mode 100644 index 2580ccad..00000000 --- a/app/controllers/env-vars.coffee +++ /dev/null @@ -1,6 +0,0 @@ -`import Ember from 'ember'` - -Controller = Ember.ArrayController.extend - vars: Ember.computed.alias('model') - -`export default Controller` diff --git a/app/controllers/env-vars/new.coffee b/app/controllers/env-vars/new.coffee deleted file mode 100644 index 0317eeb5..00000000 --- a/app/controllers/env-vars/new.coffee +++ /dev/null @@ -1,45 +0,0 @@ -`import Validations from 'travis/utils/validations'` - -Controller = Ember.Controller.extend Validations, - needs: ['repo'] - repo: Ember.computed.alias('controllers.repo.repo') - - isSaving: false - - validates: - name: ['presence'] - - actionType: 'Add' - showValueField: true - - reset: -> - @setProperties(name: null, value: null, public: null) - - actions: - cancel: -> - @reset() - @transitionToRoute('env_vars') - - save: -> - return if @get('isSaving') - @set('isSaving', true) - - if @isValid() - env_var = @store.createRecord('env_var', - name: @get('name') - value: @get('value') - public: @get('public') - repo: @get('repo') - ) - - self = this - env_var.save().then => - @set('isSaving', false) - @reset() - self.transitionToRoute('env_vars') - , => - @set('isSaving', false) - else - @set('isSaving', false) - -`export default Controller` diff --git a/app/controllers/first-sync.coffee b/app/controllers/first-sync.coffee index 6ca0d82b..c518ebc4 100644 --- a/app/controllers/first-sync.coffee +++ b/app/controllers/first-sync.coffee @@ -1,8 +1,7 @@ `import Ember from 'ember'` Controller = Ember.Controller.extend - needs: ['currentUser'] - user: Ember.computed.alias('controllers.currentUser.model') + user: Ember.computed.alias('auth.currentUser') isSyncing: Ember.computed.alias('user.isSyncing') diff --git a/app/controllers/job.coffee b/app/controllers/job.coffee index fb45da9c..496a2247 100644 --- a/app/controllers/job.coffee +++ b/app/controllers/job.coffee @@ -2,13 +2,13 @@ `import { githubCommit } from 'travis/utils/urls'` Controller = Ember.Controller.extend - needs: ['repo', 'currentUser'] + repoController: Ember.inject.controller('repo') - repoBinding: 'controllers.repo.repo' + repoBinding: 'repoController.repo' commitBinding: 'job.commit' annotationsBinding: 'job.annotations' - currentUserBinding: 'controllers.currentUser.model' - tabBinding: 'controllers.repo.tab' + currentUserBinding: 'auth.currentUser' + tabBinding: 'repoController.tab' currentItemBinding: 'job' diff --git a/app/controllers/owner.coffee b/app/controllers/owner.coffee index ca59030c..0f3970d3 100644 --- a/app/controllers/owner.coffee +++ b/app/controllers/owner.coffee @@ -1,5 +1,4 @@ `import Ember from 'ember'` -`import Ajax from 'travis/utils/ajax'` Controller = Ember.Controller.extend isLoading: false diff --git a/app/controllers/owner/repositories.coffee b/app/controllers/owner/repositories.coffee index e771a62a..cf07a5b0 100644 --- a/app/controllers/owner/repositories.coffee +++ b/app/controllers/owner/repositories.coffee @@ -1,5 +1,4 @@ `import Ember from 'ember'` -`import Ajax from 'travis/utils/ajax'` Controller = Ember.Controller.extend isLoading: false @@ -13,14 +12,13 @@ Controller = Ember.Controller.extend item ).sortBy('default_branch.last_build.finished_at').reverse() repos - ).property('model') # running: (-> # data = @get('model') - # repos = data.repositories.filter (item, index) -> - # if item.default_branch.last_build != null + # repos = data.repositories.filter (item, index) -> + # if item.default_branch.last_build != null # if item.default_branch.last_build.state == 'started' # item # repos diff --git a/app/controllers/owner/running.coffee b/app/controllers/owner/running.coffee index 33f2ac9f..3b170877 100644 --- a/app/controllers/owner/running.coffee +++ b/app/controllers/owner/running.coffee @@ -1,5 +1,4 @@ `import Ember from 'ember'` -`import Ajax from 'travis/utils/ajax'` Controller = Ember.Controller.extend isLoading: false diff --git a/app/controllers/profile.coffee b/app/controllers/profile.coffee index d5dd0c5d..867f4c7c 100644 --- a/app/controllers/profile.coffee +++ b/app/controllers/profile.coffee @@ -3,16 +3,17 @@ Controller = Ember.Controller.extend name: 'profile' - needs: ['currentUser', 'accounts', 'account'] - userBinding: 'controllers.currentUser.model' - accountBinding: 'controllers.account.model' + accountController: Ember.inject.controller('account') + accountsController: Ember.inject.controller('accounts') + userBinding: 'auth.currentUser' + accountBinding: 'accountController.model' activate: (action, params) -> this["view_#{action}".camelize()]() viewHooks: -> @connectTab('hooks') - @get('controllers.account').reloadHooks() + @get('accountController').reloadHooks() viewUser: -> @connectTab('user') diff --git a/app/controllers/repo.coffee b/app/controllers/repo.coffee index 4c4be4cd..8987b66d 100644 --- a/app/controllers/repo.coffee +++ b/app/controllers/repo.coffee @@ -2,13 +2,15 @@ `import { githubRepo } from 'travis/utils/urls'` Controller = Ember.Controller.extend - needs: ['repos', 'currentUser', 'build', 'job'] - currentUserBinding: 'controllers.currentUser.model' + jobController: Ember.inject.controller('job') + buildController: Ember.inject.controller('build') + reposController: Ember.inject.controller('repos') + currentUserBinding: 'auth.currentUser' classNames: ['repo'] - build: Ember.computed.alias('controllers.build.build') - job: Ember.computed.alias('controllers.job.job') + build: Ember.computed.alias('buildController.build') + job: Ember.computed.alias('jobController.job') slug: (-> @get('repo.slug') ).property('repo.slug') isLoading: (-> @get('repo.isLoading') ).property('repo.isLoading') @@ -75,15 +77,15 @@ Controller = Ember.Controller.extend Ember.run.scheduleOnce('actions', this, @_lastBuildDidChange); _lastBuildDidChange: -> - build = @get('repo.lastBuild') + build = @get('repo.defaultBranch.lastBuild') @set('build', build) stopObservingLastBuild: -> - @removeObserver('repo.lastBuild', this, 'lastBuildDidChange') + @removeObserver('repo.defaultBranch.lastBuild', this, 'lastBuildDidChange') observeLastBuild: -> @lastBuildDidChange() - @addObserver('repo.lastBuild', this, 'lastBuildDidChange') + @addObserver('repo.defaultBranch.lastBuild', this, 'lastBuildDidChange') connectTab: (tab) -> # TODO: such implementation seems weird now, because we render diff --git a/app/controllers/repos.coffee b/app/controllers/repos.coffee deleted file mode 100644 index 2dded81f..00000000 --- a/app/controllers/repos.coffee +++ /dev/null @@ -1,132 +0,0 @@ -`import Ember from 'ember'` -`import limit from 'travis/utils/computed-limit'` -`import Repo from 'travis/models/repo'` - -Controller = Ember.Controller.extend - contentBinding: 'repos' - actions: - activate: (name) -> - @activate(name) - - showRunningJobs: -> - @activate('running') - - showMyRepositories: -> - # this is a bit of a hack. I don't want to switch URL for 'running' - # so depending on current state I'm either just switching back or - # redirecting - if @get('tab') == 'running' - @activate('owned') - else - @transitionToRoute('main.repositories') - - - tabOrIsLoadedDidChange: (-> - @possiblyRedirectToGettingStartedPage() - ).observes('isLoaded', 'tab', 'repos.length') - - possiblyRedirectToGettingStartedPage: -> - Ember.run.scheduleOnce 'routerTransitions', this, -> - if @get('tab') == 'owned' && @get('isLoaded') && @get('repos.length') == 0 - @container.lookup('router:main').send('redirectToGettingStarted') - - isLoadedBinding: 'repos.isLoaded' - needs: ['currentUser', 'repo'] - currentUserBinding: 'controllers.currentUser.model' - selectedRepo: (-> - # we need to observe also repo.content here, because we use - # ObjectProxy in repo controller - # TODO: get rid of ObjectProxy there - @get('controllers.repo.repo.content') || @get('controllers.repo.repo') - ).property('controllers.repo.repo', 'controllers.repo.repo.content') - - startedJobsCount: Ember.computed.alias('runningJobs.length') - allJobsCount: (-> - @get('startedJobsCount') + @get('queuedJobs.length') - ).property('startedJobsCount', 'queuedJobs.length') - - init: -> - @_super.apply this, arguments - if !Ember.testing - Visibility.every @config.intervals.updateTimes, @updateTimes.bind(this) - - runningJobs: (-> - result = @store.filter('job', {}, (job) -> - ['queued', 'started', 'received'].indexOf(job.get('state')) != -1 - ) - result.set('isLoaded', false) - result.then => - result.set('isLoaded', true) - result - ).property() - - queuedJobs: (-> - result = @get('store').filter('job', {}, (job) -> - ['created'].indexOf(job.get('state')) != -1 - ) - result.set('isLoaded', false) - result.then => - result.set('isLoaded', true) - result - ).property() - - recentRepos: (-> - # I return an empty array here, because we're removing left sidebar, but - # I don't want to refactor too much code (it will be all changed anyway - # when we switch to new dashboard) - [] - ).property() - - updateTimes: -> - if repos = @get('repos') - repos.forEach (r) -> r.updateTimes() - - activate: (tab, params) -> - @set('sortProperties', ['sortOrder']) - @set('tab', tab) - this["view_#{tab}".camelize()](params) - - viewOwned: -> - @set('repos', @get('userRepos')) - - viewRunning: -> - - userRepos: (-> - if login = @get('currentUser.login') - Repo.accessibleBy(@store, login) - else - [] - ).property('currentUser.login') - - viewSearch: (phrase) -> - @set('search', phrase) - @set('repos', Repo.search(@store, phrase)) - - searchObserver: (-> - search = @get('search') - if search - @searchFor search - ).observes('search') - - searchFor: (phrase) -> - Ember.run.cancel(@searchLater) if @searchLater - @searchLater = Ember.run.later(this, (-> - @transitionTo('main.search', phrase.replace(/\//g, '%2F')) - ), 500) - - noReposMessage: (-> - tab = @get('tab') - - if tab == 'owned' - 'You don\'t have any repos set up on Travis CI' - else if tab == 'recent' - 'Repositories could not be loaded' - else - 'Could not find any repos' - ).property('tab') - - showRunningJobs: (-> - @get('tab') == 'running' - ).property('tab') - -`export default Controller` diff --git a/app/controllers/repos.js b/app/controllers/repos.js new file mode 100644 index 00000000..cd46c791 --- /dev/null +++ b/app/controllers/repos.js @@ -0,0 +1,234 @@ +import Ember from 'ember'; +import limit from 'travis/utils/computed-limit'; +import Repo from 'travis/models/repo'; + +var sortCallback = function(repo1, repo2) { + // this function could be made simpler, but I think it's clearer this way + // what're we really trying to achieve + + var lastBuild1 = repo1.get('defaultBranch.lastBuild'); + var lastBuild2 = repo2.get('defaultBranch.lastBuild'); + + if(!lastBuild1 && !lastBuild2) { + // if both repos lack builds, put newer repo first + return repo1.get('id') > repo2.get('id') ? -1 : 1; + } else if(lastBuild1 && !lastBuild2) { + // if only repo1 has a build, it goes first + return -1; + } else if(lastBuild2 && !lastBuild1) { + // if only repo2 has a build, it goes first + return 1; + } + + var finishedAt1 = lastBuild1.get('finishedAt'); + var finishedAt2 = lastBuild2.get('finishedAt'); + + if(finishedAt1) { + finishedAt1 = new Date(finishedAt1); + } + if(finishedAt2) { + finishedAt2 = new Date(finishedAt2); + } + + if(finishedAt1 && finishedAt2) { + // if both builds finished, put newer first + return finishedAt1.getTime() > finishedAt2.getTime() ? -1 : 1; + } else if(finishedAt1 && !finishedAt2) { + // if repo1 finished, but repo2 didn't, put repo2 first + return 1; + } else if(finishedAt2 && !finishedAt1) { + // if repo2 finisher, but repo1 didn't, put repo1 first + return -1; + } else { + // none of the builds finished, put newer build first + return lastBuild1.get('id') > lastBuild2.get('id') ? -1 : 1; + } + + throw "should not happen"; +}; + + + +var Controller = Ember.Controller.extend({ + ajax: Ember.inject.service(), + + actions: { + activate: function(name) { + return this.activate(name); + }, + showRunningJobs: function() { + return this.activate('running'); + }, + showMyRepositories: function() { + if (this.get('tab') === 'running') { + return this.activate('owned'); + } else { + return this.transitionToRoute('main.repositories'); + } + } + }, + + tabOrIsLoadedDidChange: function() { + return this.possiblyRedirectToGettingStartedPage(); + }.observes('isLoaded', 'tab', 'repos.length'), + + possiblyRedirectToGettingStartedPage() { + return Ember.run.scheduleOnce('routerTransitions', this, function() { + if (this.get('tab') === 'owned' && this.get('isLoaded') && this.get('repos.length') === 0) { + return this.container.lookup('router:main').send('redirectToGettingStarted'); + } + }); + }, + + isLoaded: false, + repoController: Ember.inject.controller('repo'), + currentUserBinding: 'auth.currentUser', + + selectedRepo: function() { + return this.get('repoController.repo.content') || this.get('repoController.repo'); + }.property('repoController.repo', 'repoController.repo.content'), + + startedJobsCount: Ember.computed.alias('runningJobs.length'), + + allJobsCount: function() { + return this.get('startedJobsCount') + this.get('queuedJobs.length'); + }.property('startedJobsCount', 'queuedJobs.length'), + + init() { + this._super.apply(this, arguments); + if (!Ember.testing) { + return Visibility.every(this.config.intervals.updateTimes, this.updateTimes.bind(this)); + } + }, + + runningJobs: function() { + var result; + + result = this.store.filter('job', {}, function(job) { + return ['queued', 'started', 'received'].indexOf(job.get('state')) !== -1; + }); + result.set('isLoaded', false); + result.then(function() { + return result.set('isLoaded', true); + }); + + return result; + }.property(), + + queuedJobs: function() { + var result; + result = this.get('store').filter('job', {}, function(job) { + return ['created'].indexOf(job.get('state')) !== -1; + }); + result.set('isLoaded', false); + result.then(function() { + result.set('isLoaded', true); + }); + + return result; + }.property(), + + recentRepos: function() { + return []; + }.property(), + + updateTimes() { + var repos; + if (repos = this.get('repos')) { + return repos.forEach(function(r) { + return r.updateTimes(); + }); + } + }, + + activate(tab, params) { + this.set('sortProperties', ['sortOrder']); + this.set('tab', tab); + return this[("view_" + tab).camelize()](params); + }, + + viewOwned() { + var repos, user; + + if (repos = this.get('ownedRepos')) { + return this.set('_repos', repos); + } else if (!this.get('fetchingOwnedRepos')) { + this.set('fetchingOwnedRepos', true); + this.set('isLoaded', false); + + if (user = this.get('currentUser')) { + user.get('_rawPermissions').then( (data) => { + repos = Repo.accessibleBy(this.store, data.pull).then( + (reposRecordArray) => { + this.set('isLoaded', true); + this.set('_repos', reposRecordArray); + this.set('ownedRepos', reposRecordArray); + this.set('fetchingOwnedRepos', false); + return reposRecordArray; + }); + }); + } + } + }, + + viewRunning() {}, + + viewSearch(phrase) { + this.set('search', phrase); + this.set('isLoaded', false); + Repo.search(this.store, this.get('ajax'), phrase).then( (reposRecordArray) => { + this.set('isLoaded', true); + this.set('_repos', reposRecordArray); + }); + }, + + searchObserver: function() { + var search; + search = this.get('search'); + if (search) { + return this.searchFor(search); + } + }.observes('search'), + + searchFor(phrase) { + if (this.searchLater) { + Ember.run.cancel(this.searchLater); + } + this.searchLater = Ember.run.later(this, (function() { + this.transitionTo('main.search', phrase.replace(/\//g, '%2F')); + }), 500); + }, + + noReposMessage: function() { + var tab; + tab = this.get('tab'); + if (tab === 'owned') { + return 'You don\'t have any repos set up on Travis CI'; + } else if (tab === 'recent') { + return 'Repositories could not be loaded'; + } else { + return 'Could not find any repos'; + } + }.property('tab'), + + showRunningJobs: function() { + return this.get('tab') === 'running'; + }.property('tab'), + + repos: function() { + var repos = this.get('_repos'); + + if(repos && repos.toArray) { + repos = repos.toArray(); + } + + if(repos && repos.sort) { + return repos.sort(sortCallback); + } else { + return []; + } + }.property('_repos.[]', '_repos.@each.lastBuildFinishedAt', + '_repos.@each.lastBuildId') +}); + +export default Controller; diff --git a/app/controllers/requests.coffee b/app/controllers/requests.coffee index 759b7268..40826f98 100644 --- a/app/controllers/requests.coffee +++ b/app/controllers/requests.coffee @@ -1,12 +1,11 @@ `import Ember from 'ember'` Controller = Ember.ArrayController.extend - needs: ['repo'] - repo: Ember.computed.alias('controllers.repo.repo') + repoController: Ember.inject.controller('repo') lintUrl: (-> - slug = @get('repo.slug') + slug = @get('repoController.repo.slug') "https://lint.travis-ci.org/#{slug}" - ).property('repo.slug') + ).property('repoController.repo.slug') `export default Controller` diff --git a/app/controllers/settings/index.coffee b/app/controllers/settings/index.coffee deleted file mode 100644 index ca04a09b..00000000 --- a/app/controllers/settings/index.coffee +++ /dev/null @@ -1,22 +0,0 @@ -`import Ember from 'ember'` - -Controller = Ember.Controller.extend - settings: Ember.computed.alias('model.settings') - - settingsChanged: (-> - value = @get('settings.maximum_number_of_builds') - console.log value - if parseInt(value) > 0 || value == '0' || value == 0 - @set('settings.maximum_number_of_builds_valid', '') - @get('model').saveSettings(@get('settings')).then null, -> - Travis.flash(error: 'There was an error while saving settings. Please try again.') - else - @set('settings.maximum_number_of_builds_valid', 'invalid') - ).observes('settings.maximum_number_of_builds') - - actions: - save: -> - @get('model').saveSettings(@get('settings')).then null, -> - Travis.flash(error: 'There was an error while saving settings. Please try again.') - -`export default Controller` diff --git a/app/controllers/top.coffee b/app/controllers/top.coffee index 93598ca2..e532a19c 100644 --- a/app/controllers/top.coffee +++ b/app/controllers/top.coffee @@ -1,12 +1,11 @@ `import Ember from 'ember'` -`import Ajax from 'travis/utils/ajax'` `import config from 'travis/config/environment'` Controller = Ember.Controller.extend - needs: ['currentUser'] - userBinding: 'controllers.currentUser.model' + userBinding: 'auth.currentUser' store: Ember.inject.service() + storage: Ember.inject.service() currentUserBinding: 'auth.currentUser' userName: (-> @@ -18,6 +17,8 @@ Controller = Ember.Controller.extend ).property('user.gravatarId') defineTowerColor: (broadcastArray) -> + return '' unless broadcastArray + if broadcastArray.length if broadcastArray.findBy('category', 'warning') return 'warning' @@ -39,7 +40,7 @@ Controller = Ember.Controller.extend options.type = 'GET' options.headers = { Authorization: "token #{@auth.token()}" } - seenBroadcasts = Travis.storage.getItem('travis.seen_broadcasts') + seenBroadcasts = @get('storage').getItem('travis.seen_broadcasts') if seenBroadcasts seenBroadcasts = JSON.parse(seenBroadcasts) else @@ -73,13 +74,13 @@ Controller = Ember.Controller.extend markBroadcastAsSeen: (broadcast) -> id = broadcast.get('id').toString() - seenBroadcasts = Travis.storage.getItem('travis.seen_broadcasts') + seenBroadcasts = @get('storage').getItem('travis.seen_broadcasts') if seenBroadcasts - seenBroadcasts = JSON.parse(seenBroadcasts) + seenBroadcasts = JSON.parse(seenBroadcasts) else seenBroadcasts = [] seenBroadcasts.push(id) - Travis.storage.setItem('travis.seen_broadcasts', JSON.stringify(seenBroadcasts)) + @get('storage').setItem('travis.seen_broadcasts', JSON.stringify(seenBroadcasts)) @get('broadcasts.content').removeObject(broadcast) @set('broadcasts.lastBroadcastStatus', @defineTowerColor(@get('broadcasts.content'))) return false diff --git a/app/helpers/format-commit.coffee b/app/helpers/format-commit.coffee index 1e488ac0..727fcf60 100644 --- a/app/helpers/format-commit.coffee +++ b/app/helpers/format-commit.coffee @@ -1,6 +1,6 @@ `import { safe, formatCommit as formatCommitHelper } from 'travis/utils/helpers'` -helper = Ember.HTMLBars.makeBoundHelper (params) -> +helper = Ember.Helper.helper (params) -> commit = params[0] safe formatCommitHelper(commit.get('sha'), commit.get('branch')) if commit diff --git a/app/helpers/format-duration.coffee b/app/helpers/format-duration.coffee index 9e39dc53..f3e8c0d4 100644 --- a/app/helpers/format-duration.coffee +++ b/app/helpers/format-duration.coffee @@ -1,7 +1,7 @@ `import { timeInWords, safe } from 'travis/utils/helpers'` `import Ember from "ember"` -helper = Ember.HTMLBars.makeBoundHelper (params) -> +helper = Ember.Helper.helper (params) -> safe timeInWords(params[0]) `export default helper` diff --git a/app/helpers/format-message.coffee b/app/helpers/format-message.coffee index ac38c784..d7b84077 100644 --- a/app/helpers/format-message.coffee +++ b/app/helpers/format-message.coffee @@ -1,7 +1,7 @@ `import { formatMessage as _formatMessage, safe } from 'travis/utils/helpers'` `import Ember from "ember"` -helper = Ember.HTMLBars.makeBoundHelper (params, hash) -> +helper = Ember.Helper.helper (params, hash) -> safe _formatMessage(params[0], hash) `export default helper` diff --git a/app/helpers/format-sha.coffee b/app/helpers/format-sha.coffee index d0c622bd..848379a2 100644 --- a/app/helpers/format-sha.coffee +++ b/app/helpers/format-sha.coffee @@ -1,7 +1,7 @@ `import { formatSha as _formatSha, safe } from 'travis/utils/helpers'` `import Ember from "ember"` -helper = Ember.HTMLBars.makeBoundHelper (params) -> +helper = Ember.Helper.helper (params) -> safe _formatSha(params[0]) `export default helper` diff --git a/app/helpers/format-time.coffee b/app/helpers/format-time.coffee index 73078609..fd404877 100644 --- a/app/helpers/format-time.coffee +++ b/app/helpers/format-time.coffee @@ -1,7 +1,7 @@ `import { timeAgoInWords, safe } from 'travis/utils/helpers'` `import Ember from "ember"` -helper = Ember.HTMLBars.makeBoundHelper (params) -> +helper = Ember.Helper.helper (params) -> safe timeAgoInWords(params[0]) || '-' `export default helper` diff --git a/app/helpers/github-commit-link.coffee b/app/helpers/github-commit-link.coffee index 1b50db32..2b7bd197 100644 --- a/app/helpers/github-commit-link.coffee +++ b/app/helpers/github-commit-link.coffee @@ -1,7 +1,7 @@ `import { formatCommit, safe } from 'travis/utils/helpers'` `import { githubCommit as githubCommitUrl } from 'travis/utils/urls'` -helper = Ember.HTMLBars.makeBoundHelper (params) -> +helper = Ember.Helper.helper (params) -> slug = params[0] commitSha = params[1] return '' unless commitSha diff --git a/app/helpers/humanize-state.coffee b/app/helpers/humanize-state.coffee index f7083b15..1a7989a4 100644 --- a/app/helpers/humanize-state.coffee +++ b/app/helpers/humanize-state.coffee @@ -1,7 +1,7 @@ `import { safe } from 'travis/utils/helpers'` `import Ember from "ember"` -helper = Ember.HTMLBars.makeBoundHelper (params) -> +helper = Ember.Helper.helper (params) -> state = params[0] if state == 'received' 'booting' diff --git a/app/helpers/landing-page-last-build-time.coffee b/app/helpers/landing-page-last-build-time.coffee index 83b57769..13373acd 100644 --- a/app/helpers/landing-page-last-build-time.coffee +++ b/app/helpers/landing-page-last-build-time.coffee @@ -1,7 +1,7 @@ `import { timeAgoInWords, safe } from 'travis/utils/helpers'` `import Ember from "ember"` -helper = Ember.HTMLBars.makeBoundHelper (params) -> +helper = Ember.Helper.helper (params) -> safe timeAgoInWords(params[0]) || 'currently running' `export default helper` diff --git a/app/helpers/pretty-date.coffee b/app/helpers/pretty-date.coffee index 455e9a2b..5336ed5d 100644 --- a/app/helpers/pretty-date.coffee +++ b/app/helpers/pretty-date.coffee @@ -1,7 +1,7 @@ `import { timeAgoInWords, safe } from 'travis/utils/helpers'` `import Ember from "ember"` -helper = Ember.HTMLBars.makeBoundHelper (params) -> +helper = Ember.Helper.helper (params) -> safe moment(params[0]).format('MMMM D, YYYY H:mm:ss') || '-' `export default helper` diff --git a/app/helpers/short-compare-shas.coffee b/app/helpers/short-compare-shas.coffee index 5d462170..faa35ec7 100644 --- a/app/helpers/short-compare-shas.coffee +++ b/app/helpers/short-compare-shas.coffee @@ -1,7 +1,7 @@ `import { pathFrom } from 'travis/utils/helpers'` `import Ember from "ember"` -helper = Ember.HTMLBars.makeBoundHelper (params) -> +helper = Ember.Helper.helper (params) -> url = params[0] path = pathFrom(url) if path.indexOf('...') >= 0 diff --git a/app/helpers/travis-mb.coffee b/app/helpers/travis-mb.coffee index 069b1ba8..58e14edd 100644 --- a/app/helpers/travis-mb.coffee +++ b/app/helpers/travis-mb.coffee @@ -4,4 +4,4 @@ fn = (size) -> if size (size / 1024 / 1024).toFixed(2) -`export default Ember.HTMLBars.makeBoundHelper(fn)` +`export default Ember.Helper.helper(fn)` diff --git a/app/initializers/auth.coffee b/app/initializers/auth.coffee index 5f18a20f..50e34625 100644 --- a/app/initializers/auth.coffee +++ b/app/initializers/auth.coffee @@ -1,16 +1,11 @@ -`import Auth from 'travis/utils/auth'` `import TestAuth from 'travis/utils/test-auth'` initialize = (container, app) -> - app.register 'auth:main', if Ember.testing then TestAuth else Auth - - app.inject('route', 'auth', 'auth:main') - app.inject('controller', 'auth', 'auth:main') - app.inject('application', 'auth', 'auth:main') - app.inject('component', 'auth', 'auth:main') - app.inject('service:flashes', 'auth', 'auth:main') - - app.inject('auth', 'store', 'service:store') + app.inject('route', 'auth', 'service:auth') + app.inject('controller', 'auth', 'service:auth') + app.inject('application', 'auth', 'service:auth') + app.inject('component', 'auth', 'service:auth') + app.inject('service:flashes', 'auth', 'service:auth') AuthInitializer = name: 'auth' diff --git a/app/initializers/pusher.coffee b/app/initializers/pusher.coffee index 857d2d41..920c3e88 100644 --- a/app/initializers/pusher.coffee +++ b/app/initializers/pusher.coffee @@ -2,12 +2,7 @@ `import TravisPusher from 'travis/utils/pusher'` initialize = (registry, application) -> - if config.pusher.key - application.pusher = new TravisPusher(config.pusher) - - application.register 'pusher:main', application.pusher, { instantiate: false } - - application.inject('route', 'pusher', 'pusher:main') + null PusherInitializer = name: 'pusher' diff --git a/app/initializers/services.coffee b/app/initializers/services.coffee index cb283735..ce0f365b 100644 --- a/app/initializers/services.coffee +++ b/app/initializers/services.coffee @@ -1,16 +1,11 @@ -`import Slider from 'travis/utils/slider'` `import Tailing from 'travis/utils/tailing'` `import ToTop from 'travis/utils/to-top'` `import config from 'travis/config/environment'` initialize = (container, application) -> - application.slider = new Slider(application.storage) application.tailing = new Tailing($(window), '#tail', '#log') application.toTop = new ToTop($(window), '.to-top', '#log-container') - application.register 'slider:main', application.slider, { instantiate: false } - application.inject('controller', 'slider', 'slider:main') - Initializer = name: 'services' initialize: initialize diff --git a/app/initializers/storage.coffee b/app/initializers/storage.coffee deleted file mode 100644 index f375e1e3..00000000 --- a/app/initializers/storage.coffee +++ /dev/null @@ -1,58 +0,0 @@ -`import Ember from 'ember'` - -Storage = Em.Object.extend - init: -> - @set('storage', {}) - key: (key) -> - "__#{key.replace('.', '__')}" - getItem: (k) -> - return @get("storage.#{@key(k)}") - setItem: (k,v) -> - @set("storage.#{@key(k)}", v) - removeItem: (k) -> - @setItem(k, null) - clear: -> - @set('storage', {}) - -sessionStorage = (-> - storage = null - try - # firefox will not throw error on access for sessionStorage var, - # you need to actually get something from session - window.sessionStorage.getItem('foo') - storage = window.sessionStorage - catch err - storage = Storage.create() - - storage -)() - -storage = (-> - storage = null - try - storage = window.localStorage || throw('no storage') - catch err - storage = Storage.create() - - storage -)() - -initialize = (container, application) -> - application.register 'storage:main', storage, { instantiate: false } - application.register 'sessionStorage:main', sessionStorage, { instantiate: false } - - application.inject('auth', 'storage', 'storage:main') - application.inject('auth', 'sessionStorage', 'sessionStorage:main') - - # I still use Travis.storage in some places which are not that easy to - # refactor - application.storage = storage - application.sessionStorage = sessionStorage - -StorageInitializer = - name: 'storage' - before: 'services' - initialize: initialize - -`export {initialize}` -`export default StorageInitializer` diff --git a/app/instance-initializers/pusher.coffee b/app/instance-initializers/pusher.coffee index b3e5c38e..9afd0788 100644 --- a/app/instance-initializers/pusher.coffee +++ b/app/instance-initializers/pusher.coffee @@ -1,5 +1,17 @@ +`import config from 'travis/config/environment'` +`import TravisPusher from 'travis/utils/pusher'` + initialize = (data) -> - data.application.pusher.store = data.container.lookup('service:store') + application = data.application + + if config.pusher.key + application.pusher = new TravisPusher(config.pusher, data.container.lookup('service:ajax')) + + application.register 'pusher:main', application.pusher, { instantiate: false } + + application.inject('route', 'pusher', 'pusher:main') + + application.pusher.store = data.container.lookup('service:store') PusherInitializer = name: 'pusher' diff --git a/app/models/branch.coffee b/app/models/branch.coffee index 72aaea3d..3a8e6405 100644 --- a/app/models/branch.coffee +++ b/app/models/branch.coffee @@ -2,25 +2,11 @@ `import Model from 'travis/models/model'` Branch = Model.extend - repositoryId: DS.attr('number') - commitId: DS.attr('number') - state: DS.attr() - number: DS.attr('number') - branch: DS.attr() - message: DS.attr() - result: DS.attr('number') - duration: DS.attr('number') - startedAt: DS.attr() - finishedAt: DS.attr() + name: DS.attr('string') + defaultBranch: DS.attr('boolean') - commit: DS.belongsTo('commit') - - repo: (-> - @store.find('repo', @get('repositoryId')) if @get('repositoryId') - ).property('repositoryId') - - updateTimes: -> - @notifyPropertyChange 'started_at' - @notifyPropertyChange 'finished_at' + lastBuild: DS.belongsTo('build') + builds: DS.hasMany('builds', inverse: 'branch') + repo: DS.belongsTo('repo', inverse: 'defaultBranch') `export default Branch` diff --git a/app/models/broadcast.coffee b/app/models/broadcast.coffee index 65b95456..d4895b3b 100644 --- a/app/models/broadcast.coffee +++ b/app/models/broadcast.coffee @@ -18,7 +18,7 @@ Broadcast = Model.extend Broadcast.reopenClass seen: (-> - seenBroadcasts = Travis.storage.getItem('travis.seen_broadcasts') + seenBroadcasts = Travis.lookup('service:storage').getItem('travis.seen_broadcasts') seenBroadcasts = JSON.parse(seenBroadcasts) if seenBroadcasts? Ember.A(seenBroadcasts || []) ).property() diff --git a/app/models/build.coffee b/app/models/build.coffee index 621305bf..a4ceb34e 100644 --- a/app/models/build.coffee +++ b/app/models/build.coffee @@ -1,26 +1,28 @@ `import { durationFrom, configKeys, compact } from 'travis/utils/helpers'` -`import Ajax from 'travis/utils/ajax'` `import configKeysMap from 'travis/utils/keys-map'` `import Ember from 'ember'` `import Model from 'travis/models/model'` `import DurationCalculations from 'travis/utils/duration-calculations'` Build = Model.extend DurationCalculations, + ajax: Ember.inject.service() + state: DS.attr() number: DS.attr('number') - branch: DS.attr('string') message: DS.attr('string') _duration: DS.attr('number') _config: DS.attr('object') _startedAt: DS.attr() - _finishedAt: DS.attr() + _finishedAt: DS.attr('string') pullRequest: DS.attr('boolean') pullRequestTitle: DS.attr() pullRequestNumber: DS.attr('number') eventType: DS.attr('string') + repositoryId: DS.attr('number') + branch: DS.belongsTo('branch', async: false, inverse: 'builds') repo: DS.belongsTo('repo', async: true) - commit: DS.belongsTo('commit', async: true) + commit: DS.belongsTo('commit', async: false) jobs: DS.hasMany('job', async: true) config: (-> @@ -90,11 +92,11 @@ Build = Model.extend DurationCalculations, canRestart: Ember.computed.alias('isFinished') cancel: (-> - Ajax.post "/builds/#{@get('id')}/cancel" + @get('ajax').post "/builds/#{@get('id')}/cancel" ) restart: -> - Ajax.post "/builds/#{@get('id')}/restart" + @get('ajax').post "/builds/#{@get('id')}/restart" formattedFinishedAt: (-> if finishedAt = @get('finishedAt') diff --git a/app/models/job.coffee b/app/models/job.coffee index e72304e8..b1cd747a 100644 --- a/app/models/job.coffee +++ b/app/models/job.coffee @@ -1,5 +1,4 @@ `import { durationFrom, configKeys, compact } from 'travis/utils/helpers'` -`import Ajax from 'travis/utils/ajax'` `import configKeysMap from 'travis/utils/keys-map'` `import Ember from 'ember'` `import Model from 'travis/models/model'` @@ -7,6 +6,7 @@ `import DurationCalculations from 'travis/utils/duration-calculations'` Job = Model.extend DurationCalculations, + ajax: Ember.inject.service() logId: DS.attr() queue: DS.attr() @@ -24,13 +24,15 @@ Job = Model.extend DurationCalculations, build: DS.belongsTo('build', async: true) commit: DS.belongsTo('commit', async: true) + branch: Ember.computed.alias('build.branch') + annotations: DS.hasMany('annotation') _config: DS.attr('object') log: ( -> @set('isLogAccessed', true) - Log.create(job: this) + Log.create(job: this, ajax: @get('ajax')) ).property() startedAt: (-> @@ -94,11 +96,11 @@ Job = Model.extend DurationCalculations, canRestart: Ember.computed.alias('isFinished') cancel: (-> - Ajax.post "/jobs/#{@get('id')}/cancel" + @get('ajax').post "/jobs/#{@get('id')}/cancel" ) removeLog: -> - Ajax.patch("/jobs/#{@get('id')}/log").then => + @get('ajax').patch("/jobs/#{@get('id')}/log").then => @reloadLog() reloadLog: -> @@ -106,7 +108,7 @@ Job = Model.extend DurationCalculations, @get('log').fetch() restart: -> - Ajax.post "/jobs/#{@get('id')}/restart" + @get('ajax').post "/jobs/#{@get('id')}/restart" appendLog: (part) -> @get('log').append part diff --git a/app/models/log.coffee b/app/models/log.coffee index f3c8e28c..bef4f2a3 100644 --- a/app/models/log.coffee +++ b/app/models/log.coffee @@ -1,5 +1,4 @@ `import Model from 'travis/models/model'` -`import Ajax from 'travis/utils/ajax'` `import Job from 'travis/models/job'` `import Ember from 'ember'` `import config from 'travis/config/environment'` @@ -9,7 +8,7 @@ Request = Ember.Object.extend accept: 'application/json; chunked=true; version=2, text/plain; version=2' run: -> - Ajax.ajax "/jobs/#{@id}/log?cors_hax=true", 'GET', + @get('ajax').ajax "/jobs/#{@id}/log?cors_hax=true", 'GET', dataType: 'text' headers: @HEADERS success: (body, status, xhr) => Ember.run(this, -> @handle(body, status, xhr)) @@ -50,7 +49,7 @@ Log = Ember.Object.extend data['part_numbers'] = partNumbers if partNumbers data['after'] = after if after - Ajax.ajax "/jobs/#{@get('job.id')}/log", 'GET', + @get('ajax').ajax "/jobs/#{@get('job.id')}/log", 'GET', dataType: 'json' headers: accept: 'application/json; chunked=true; version=2' @@ -81,7 +80,8 @@ Log = Ember.Object.extend @set('removed', true) @loadParts(json['log']['parts']) text: (text) => @loadText(text) - Request.create(id: id, handlers: handlers, log: this).run() if id = @get('job.id') + if id = @get('job.id') + Request.create(id: id, handlers: handlers, log: this, ajax: @get('ajax')).run() clear: -> @clearParts() diff --git a/app/models/repo.coffee b/app/models/repo.coffee index f91d0a76..b57af656 100644 --- a/app/models/repo.coffee +++ b/app/models/repo.coffee @@ -1,40 +1,28 @@ `import ExpandableRecordArray from 'travis/utils/expandable-record-array'` `import Model from 'travis/models/model'` -`import Ajax from 'travis/utils/ajax'` # TODO: Investigate for some weird reason if I use durationFrom here not durationFromHelper, # the function stops being visible inside computed properties. `import { durationFrom as durationFromHelper } from 'travis/utils/helpers'` `import Build from 'travis/models/build'` Repo = Model.extend + ajax: Ember.inject.service() + slug: DS.attr() description: DS.attr() private: DS.attr('boolean') - lastBuildNumber: DS.attr('number') - lastBuildState: DS.attr() - lastBuildStartedAt: DS.attr() - lastBuildFinishedAt: DS.attr() githubLanguage: DS.attr() - _lastBuildDuration: DS.attr('number') - lastBuildLanguage: DS.attr() active: DS.attr() - lastBuildId: DS.attr('number') - lastBuildHash: (-> - { - id: @get('lastBuildId') - number: @get('lastBuildNumber') - repo: this - } - ).property('lastBuildId', 'lastBuildNumber') - lastBuild: (-> - if id = @get('lastBuildId') - @store.find('build', id) - @store.recordForId('build', id) - ).property('lastBuildId') + #lastBuild: DS.belongsTo('build') + defaultBranch: DS.belongsTo('branch', async: false) + + # just for sorting + lastBuildFinishedAt: Ember.computed.oneWay('defaultBranch.lastBuild.finishedAt') + lastBuildId: Ember.computed.oneWay('defaultBranch.lastBuild.id') withLastBuild: -> - @filter( (repo) -> repo.get('lastBuildId') ) + @filter( (repo) -> repo.get('defaultBranch.lastBuild') ) sshKey: (-> @store.find('ssh_key', @get('id')) @@ -51,7 +39,7 @@ Repo = Model.extend builds: (-> id = @get('id') builds = @store.filter('build', event_type: ['push', 'api'], repository_id: id, (b) -> - b.get('repo.id') == id && (b.get('eventType') == 'push' || b.get('eventType') == 'api') + b.get('repositoryId')+'' == id+'' && (b.get('eventType') == 'push' || b.get('eventType') == 'api') ) # TODO: move to controller @@ -68,7 +56,7 @@ Repo = Model.extend pullRequests: (-> id = @get('id') builds = @store.filter('build', event_type: 'pull_request', repository_id: id, (b) -> - b.get('repo.id') == id && b.get('eventType') == 'pull_request' + b.get('repositoryId')+'' == id+'' && b.get('eventType') == 'pull_request' ) # TODO: move to controller @@ -85,7 +73,7 @@ Repo = Model.extend ).property() branches: (-> - builds = @store.find 'build', repository_id: @get('id'), branches: true + builds = @store.query 'build', repository_id: @get('id'), branches: true builds.then -> builds.set 'isLoaded', true @@ -101,27 +89,13 @@ Repo = Model.extend (@get('slug') || '').split('/')[1] ).property('slug') - lastBuildDuration: (-> - duration = @get('_lastBuildDuration') - duration = durationFromHelper(@get('lastBuildStartedAt'), @get('lastBuildFinishedAt')) unless duration - duration - ).property('_lastBuildDuration', 'lastBuildStartedAt', 'lastBuildFinishedAt') - sortOrderForLandingPage: (-> - state = @get('lastBuildState') + state = @get('defaultBranch.lastBuild.state') if state != 'passed' && state != 'failed' 0 else - parseInt(@get('lastBuildId')) - ).property('lastBuildId', 'lastBuildState') - - sortOrder: (-> - # cuz sortAscending seems buggy when set to false - if lastBuildFinishedAt = @get('lastBuildFinishedAt') - - new Date(lastBuildFinishedAt).getTime() - else - - new Date('9999').getTime() - parseInt(@get('lastBuildId')) - ).property('lastBuildFinishedAt', 'lastBuildId') + parseInt(@get('defaultBranch.lastBuild.id')) + ).property('defaultBranch.lastBuild.id', 'defaultBranch.lastBuild.state') stats: (-> if @get('slug') @@ -132,43 +106,58 @@ Repo = Model.extend ).property('slug') updateTimes: -> - @notifyPropertyChange 'lastBuildDuration' + if lastBuild = @get('defaultBranch.lastBuild') + lastBuild.updateTimes() regenerateKey: (options) -> - Ajax.ajax '/repos/' + @get('id') + '/key', 'post', options + @get('ajax').ajax '/repos/' + @get('id') + '/key', 'post', options fetchSettings: -> - Ajax.ajax('/repos/' + @get('id') + '/settings', 'get', forceAuth: true).then (data) -> + @get('ajax').ajax('/repos/' + @get('id') + '/settings', 'get', forceAuth: true).then (data) -> data['settings'] saveSettings: (settings) -> - Ajax.ajax('/repos/' + @get('id') + '/settings', 'patch', data: { settings: settings }) + @get('ajax').ajax('/repos/' + @get('id') + '/settings', 'patch', data: { settings: settings }) Repo.reopenClass recent: -> @find() - accessibleBy: (store, login) -> - repos = store.find('repo', { member: login, orderBy: 'name' }) + accessibleBy: (store, reposIds) -> + # this fires only for authenticated users and with API v3 that means getting + # only repos of currently logged in owner, but in the future it would be + # nice to not use that as it may change in the future + repos = store.filter('repo', (repo) -> + reposIds.indexOf(parseInt(repo.get('id'))) != -1 + ) - repos.then () -> - repos.set('isLoaded', true) + promise = new Ember.RSVP.Promise (resolve, reject) -> + store.query('repo', { 'repository.active': 'true' }).then( -> + resolve(repos) + , -> + reject() + ) - repos + promise - search: (store, query) -> - promise = store.find('repo', search: query, orderBy: 'name') + search: (store, ajax, query) -> + queryString = $.param(search: query, orderBy: 'name', limit: 5) + promise = ajax.ajax("/repos?#{queryString}", 'get') result = Ember.ArrayProxy.create(content: []) - promise.then -> - result.pushObjects(promise.get('content').toArray()) - result.set('isLoaded', true) + promise.then (data, status, xhr) -> + promises = data.repos.map (repoData) -> + store.findRecord('repo', repoData.id).then (record) -> + result.pushObject(record) + result.set('isLoaded', true) + record - result + Ember.RSVP.allSettled(promises).then -> + result withLastBuild: (store) -> repos = store.filter('repo', {}, (build) -> - build.get('lastBuildId') + build.get('defaultBranch.lastBuild') ) repos.then () -> @@ -176,20 +165,24 @@ Repo.reopenClass repos - bySlug: (store, slug) -> - # first check if there is a repo with a given slug already ordered - repos = store.all('repo').filterBy('slug', slug) - if repos.get('length') > 0 - repos - else - store.find('repo', { slug: slug }) - fetchBySlug: (store, slug) -> - repos = @bySlug(store, slug) + repos = store.peekAll('repo').filterBy('slug', slug) if repos.get('length') > 0 repos.get('firstObject') else - repos.then (repos) -> + adapter = store.adapterFor('repo') + modelClass = store.modelFor('repo') + adapter.findRecord(store, modelClass, slug).then (payload) -> + serializer = store.serializerFor('repo') + modelClass = store.modelFor('repo') + result = serializer.normalizeResponse(store, modelClass, payload, null, 'findRecord') + + repo = store.push(data: result.data) + for record in result.included + r = store.push(data: record) + + repo + , -> error = new Error('repo not found') error.slug = slug Ember.get(repos, 'firstObject') || throw(error) diff --git a/app/models/user.coffee b/app/models/user.coffee index a3524584..910a6663 100644 --- a/app/models/user.coffee +++ b/app/models/user.coffee @@ -1,9 +1,13 @@ `import Ember from 'ember'` `import Model from 'travis/models/model'` -`import Ajax from 'travis/utils/ajax'` `import config from 'travis/config/environment'` User = Model.extend + ajax: Ember.inject.service() + + # TODO: this totally not should be needed here + sessionStorage: Ember.inject.service() + name: DS.attr() email: DS.attr() login: DS.attr() @@ -27,7 +31,7 @@ User = Model.extend ).property() _rawPermissions: (-> - Ajax.get('/users/permissions') + @get('ajax').get('/users/permissions') ).property() permissions: (-> @@ -72,12 +76,12 @@ User = Model.extend sync: -> self = this - Ajax.post('/users/sync', {}, -> + @get('ajax').post('/users/sync', {}, -> self.setWithSession('isSyncing', true) ) poll: -> - Ajax.get '/users', (data) => + @get('ajax').get '/users', (data) => if data.user.is_syncing self = this setTimeout -> @@ -88,12 +92,12 @@ User = Model.extend @setWithSession('syncedAt', data.user.synced_at) Travis.trigger('user:synced', data.user) - @store.findQuery('account', {}) + @store.query('account', {}) setWithSession: (name, value) -> @set(name, value) - user = JSON.parse(Travis.sessionStorage.getItem('travis.user')) + user = JSON.parse(@get('sessionStorage').getItem('travis.user')) user[name.underscore()] = @get(name) - Travis.sessionStorage.setItem('travis.user', JSON.stringify(user)) + @get('sessionStorage').setItem('travis.user', JSON.stringify(user)) `export default User` diff --git a/app/router.coffee b/app/router.coffee index d42fb3f1..fab55d51 100644 --- a/app/router.coffee +++ b/app/router.coffee @@ -13,7 +13,7 @@ Router = Ember.Router.extend # # we should probably think about a more general way to # do this, location should not know about auth status - Location.create(auth: @container.lookup('auth:main')) + Location.create(auth: @container.lookup('service:auth')) ).property() # TODO: this is needed, because in the original version diff --git a/app/routes/accounts.coffee b/app/routes/accounts.coffee index 127fbb41..7753daba 100644 --- a/app/routes/accounts.coffee +++ b/app/routes/accounts.coffee @@ -2,7 +2,7 @@ Route = TravisRoute.extend model: -> - @store.find('account', { all: true }) + @store.query('account', { all: true }) setupController: (controller, model) -> user = model.filterBy('type', 'user')[0] diff --git a/app/routes/application.coffee b/app/routes/application.coffee index ed97af61..f108409e 100644 --- a/app/routes/application.coffee +++ b/app/routes/application.coffee @@ -21,7 +21,7 @@ Route = TravisRoute.extend BuildFaviconMixin, @get('stylesheetsManager').disable('dashboard') if !config.pro - repos = @get('store').all('repo') + repos = @get('store').peekAll('repo') repos.forEach (repo) => @subscribeToRepo(repo) diff --git a/app/routes/caches.coffee b/app/routes/caches.coffee index a792236f..753454f2 100644 --- a/app/routes/caches.coffee +++ b/app/routes/caches.coffee @@ -1,8 +1,9 @@ `import Ember from 'ember'` `import TravisRoute from 'travis/routes/basic'` -`import Ajax from 'travis/utils/ajax'` Route = TravisRoute.extend + ajax: Ember.inject.service() + needsAuth: true setupController: (controller) -> @_super.apply this, arguments @@ -10,7 +11,7 @@ Route = TravisRoute.extend model: -> repo = @modelFor('repo') - Ajax.get("/repos/#{repo.get('id')}/caches").then( (data) -> + @get('ajax').get("/repos/#{repo.get('id')}/caches").then( (data) -> caches = {} data["caches"].forEach (cacheData) -> diff --git a/app/routes/first-sync.coffee b/app/routes/first-sync.coffee index bb3699f3..7611a866 100644 --- a/app/routes/first-sync.coffee +++ b/app/routes/first-sync.coffee @@ -15,7 +15,7 @@ Route = SimpleLayoutRoute.extend if !controller.get('isSyncing') self = this Ember.run.later this, -> - @store.find('repo', member: @get('controller.user.login')).then( (repos) -> + @store.query('repo', member: @get('controller.user.login')).then( (repos) -> if repos.get('length') self.transitionTo('main') else diff --git a/app/routes/getting-started.coffee b/app/routes/getting-started.coffee index 52b7cc75..929e99f1 100644 --- a/app/routes/getting-started.coffee +++ b/app/routes/getting-started.coffee @@ -1,5 +1,7 @@ `import TravisRoute from 'travis/routes/basic'` -Route = TravisRoute.extend() +Route = TravisRoute.extend + setupController: (controller)-> + @container.lookup('controller:repos').activate('owned') `export default Route` diff --git a/app/routes/home.coffee b/app/routes/home.coffee index 260b0e8e..a1de26bc 100644 --- a/app/routes/home.coffee +++ b/app/routes/home.coffee @@ -21,10 +21,10 @@ Route = BasicRoute.extend @_super.apply this, arguments loadMoreRepos: -> - @store.find('build').then (builds) => + @store.findAll('build').then (builds) => repoIds = builds.mapBy('data.repo').uniq() repos = @get('repos.repos') - @store.find('repo', ids: repoIds).then (reposFromRequest) => + @store.query('repo', ids: repoIds).then (reposFromRequest) => reposFromRequest.toArray().forEach (repo) -> repos.pushObject(repo) unless repos.contains(repo) diff --git a/app/routes/job.coffee b/app/routes/job.coffee index e207df5f..e6320fa8 100644 --- a/app/routes/job.coffee +++ b/app/routes/job.coffee @@ -18,16 +18,18 @@ Route = TravisRoute.extend @controllerFor('job').set('job', model) repo.activate('job') - if build = model.get('build') - build = @store.recordForId('build', build.get('id')) - buildController = @controllerFor('build') + buildController = @controllerFor('build') - # this is a hack to not set favicon changes from build - # controller while we're viewing job, this should go away - # after refactoring of controllers - buildController.set('sendFaviconStateChanges', false) + model.get('repo') + if buildPromise = model.get('build') + buildPromise.then (build) => + build = @store.recordForId('build', build.get('id')) + buildController.set('build', build) - buildController.set('build', build) + # this is a hack to not set favicon changes from build + # controller while we're viewing job, this should go away + # after refactoring of controllers + buildController.set('sendFaviconStateChanges', false) model: (params) -> @store.find('job', params.job_id) diff --git a/app/routes/main-tab.coffee b/app/routes/main-tab.coffee index 6cf21fb6..b98e7587 100644 --- a/app/routes/main-tab.coffee +++ b/app/routes/main-tab.coffee @@ -11,13 +11,11 @@ Route = TravisRoute.extend @controllerFor('repo').activate('index') @controllerFor('repos').activate(@get('reposTabName')) - @currentRepoDidChange() - if repos = @controllerFor('repos').get('repos') - repos.addObserver('firstObject', this, 'currentRepoDidChange') + @setCurrentRepoObservers() deactivate: -> - if repos = @controllerFor('repos').get('repos') - repos.removeObserver('firstObject', this, 'currentRepoDidChange') + if repos = @controllerFor('repos') + repos.removeObserver('repos.firstObject', this, 'currentRepoDidChange') @_super.apply(this, arguments) @@ -25,6 +23,11 @@ Route = TravisRoute.extend if repo = @controllerFor('repos').get('repos.firstObject') @controllerFor('repo').set('repo', repo) + setCurrentRepoObservers: -> + @currentRepoDidChange() + if repos = @controllerFor('repos') + repos.addObserver('repos.firstObject', this, 'currentRepoDidChange') + actions: redirectToGettingStarted: -> @transitionTo('getting_started') diff --git a/app/routes/main.coffee b/app/routes/main.coffee index bbcc1151..46afa39f 100644 --- a/app/routes/main.coffee +++ b/app/routes/main.coffee @@ -14,6 +14,6 @@ Route = TravisRoute.extend setupController: (controller)-> # TODO: this is redundant with repositories and recent routes - @container.lookup('controller:repos').activate('owned') + #@container.lookup('controller:repos').activate('owned') `export default Route` diff --git a/app/routes/main/search.coffee b/app/routes/main/search.coffee index db0e011d..58d48426 100644 --- a/app/routes/main/search.coffee +++ b/app/routes/main/search.coffee @@ -10,8 +10,7 @@ Route = MainTabRoute.extend @controllerFor('repo').activate('index') @controllerFor('repos').activate('search', searchPhrase) - @currentRepoDidChange() - @controllerFor('repos').addObserver('firstObject', this, 'currentRepoDidChange') + @setCurrentRepoObservers() model: (params) -> params.phrase.replace(/%2F/g, '/') diff --git a/app/routes/owner.coffee b/app/routes/owner.coffee index 926a6de7..98a6d17a 100644 --- a/app/routes/owner.coffee +++ b/app/routes/owner.coffee @@ -1,6 +1,5 @@ `import Ember from 'ember'` `import TravisRoute from 'travis/routes/basic'` -`import Ajax from 'travis/utils/ajax'` `import config from 'travis/config/environment'` Route = TravisRoute.extend diff --git a/app/routes/owner/repositories.coffee b/app/routes/owner/repositories.coffee index 38707fe1..8a647b9b 100644 --- a/app/routes/owner/repositories.coffee +++ b/app/routes/owner/repositories.coffee @@ -1,6 +1,5 @@ `import Ember from 'ember'` `import TravisRoute from 'travis/routes/basic'` -`import Ajax from 'travis/utils/ajax'` `import config from 'travis/config/environment'` Route = TravisRoute.extend diff --git a/app/routes/owner/running.coffee b/app/routes/owner/running.coffee index 11fca69b..47ed43a3 100644 --- a/app/routes/owner/running.coffee +++ b/app/routes/owner/running.coffee @@ -1,6 +1,5 @@ `import Ember from 'ember'` `import TravisRoute from 'travis/routes/basic'` -`import Ajax from 'travis/utils/ajax'` `import config from 'travis/config/environment'` Route = TravisRoute.extend diff --git a/app/routes/repo.coffee b/app/routes/repo.coffee index ef9a1b95..9bf034dc 100644 --- a/app/routes/repo.coffee +++ b/app/routes/repo.coffee @@ -1,7 +1,9 @@ `import TravisRoute from 'travis/routes/basic'` `import Repo from 'travis/models/repo'` +`import Ember from 'ember'` Route = TravisRoute.extend + store: Ember.inject.service() titleToken: (model) -> model.get('slug') @@ -9,9 +11,10 @@ Route = TravisRoute.extend @render 'repo', into: 'main' setupController: (controller, model) -> + @container.lookup('controller:repos').activate('owned') # TODO: if repo is just a data hash with id and slug load it # as incomplete record - model = @store.find('repo', model.id) if model && !model.get + model = @get('store').find('repo', model.id) if model && !model.get controller.set('repo', model) serialize: (repo) -> @@ -21,7 +24,7 @@ Route = TravisRoute.extend model: (params) -> slug = "#{params.owner}/#{params.name}" - Repo.fetchBySlug(@store, slug) + Repo.fetchBySlug(@get('store'), slug) resetController: -> @controllerFor('repo').deactivate() diff --git a/app/routes/repo/index.coffee b/app/routes/repo/index.coffee index 0715b315..e607a1fe 100644 --- a/app/routes/repo/index.coffee +++ b/app/routes/repo/index.coffee @@ -6,7 +6,7 @@ Route = TravisRoute.extend @controllerFor('repo').activate('current') renderTemplate: -> - if @modelFor('repo').get('lastBuildId') + if @modelFor('repo').get('defaultBranch.lastBuild') @render 'build' else @render 'builds/not_found' diff --git a/app/routes/requests.coffee b/app/routes/requests.coffee index 511f9161..2e21f91b 100644 --- a/app/routes/requests.coffee +++ b/app/routes/requests.coffee @@ -7,6 +7,6 @@ Route = TravisRoute.extend @controllerFor('repo').activate('requests') model: -> - @store.find 'request', repository_id: @modelFor('repo').get('id') + @store.query 'request', repository_id: @modelFor('repo').get('id') `export default Route` diff --git a/app/routes/settings.coffee b/app/routes/settings.coffee index 0945c35e..628b4443 100644 --- a/app/routes/settings.coffee +++ b/app/routes/settings.coffee @@ -1,8 +1,9 @@ `import TravisRoute from 'travis/routes/basic'` -`import Ajax from 'travis/utils/ajax'` `import config from 'travis/config/environment'` Route = TravisRoute.extend + ajax: Ember.inject.service() + needsAuth: true setupController: (controller, model) -> @_super.apply(this, arguments) @@ -27,7 +28,7 @@ Route = TravisRoute.extend fetchSshKey: () -> repo = @modelFor('repo') - Ajax.get "/repos/#{repo.get('id')}/key", (data) => + @get('ajax').get "/repos/#{repo.get('id')}/key", (data) => Ember.Object.create(fingerprint: data.fingerprint) fetchRepositoryActiveFlag: -> diff --git a/app/routes/ssh-key.coffee b/app/routes/ssh-key.coffee index f97d323b..0657952d 100644 --- a/app/routes/ssh-key.coffee +++ b/app/routes/ssh-key.coffee @@ -1,8 +1,9 @@ `import Ember from 'ember'` -`import Ajax from 'travis/utils/ajax'` `import TravisRoute from 'travis/routes/basic'` Route = TravisRoute.extend + ajax: Ember.inject.service() + titleToken: 'Ssh Keys' model: (params) -> @@ -17,7 +18,7 @@ Route = TravisRoute.extend afterModel: (model, transition) -> repo = @modelFor('repo') - Ajax.get "/repos/#{repo.get('id')}/key", (data) => + @get('ajax').get "/repos/#{repo.get('id')}/key", (data) => @defaultKey = Ember.Object.create(fingerprint: data.fingerprint) setupController: (controller, model) -> diff --git a/app/serializers/application.coffee b/app/serializers/application.coffee index b1916d37..1e23b40b 100644 --- a/app/serializers/application.coffee +++ b/app/serializers/application.coffee @@ -1,7 +1,7 @@ `import DS from 'ember-data'` +`import V2FallbackSerializer from 'travis/serializers/v2_fallback'` -Serializer = DS.ActiveModelSerializer.extend - defaultSerializer: 'application' - serializer: 'application' +Serializer = V2FallbackSerializer.extend + isNewSerializerAPI: true `export default Serializer` diff --git a/app/serializers/branch.js b/app/serializers/branch.js new file mode 100644 index 00000000..14bbc2c0 --- /dev/null +++ b/app/serializers/branch.js @@ -0,0 +1,12 @@ +import Ember from 'ember'; +import V2FallbackSerializer from 'travis/serializers/v2_fallback'; + +export default V2FallbackSerializer.extend({ + extractAttributes(klass, payload) { + payload.id = payload['@href']; + return this._super(...arguments); + }, + extractId(modelClass, resourceHash) { + return resourceHash.id || resourceHash['@href']; + } +}); diff --git a/app/serializers/build.coffee b/app/serializers/build.coffee deleted file mode 100644 index c663c045..00000000 --- a/app/serializers/build.coffee +++ /dev/null @@ -1,19 +0,0 @@ -`import Ember from 'ember'` -`import ApplicationSerializer from 'travis/serializers/application'` - -Serializer = ApplicationSerializer.extend - attrs: { - repo: { key: 'repository_id' } - _config: { key: 'config' } - _finishedAt: { key: 'finished_at' } - _startedAt: { key: 'started_at' } - _duration: { key: 'duration' } - } - - extractSingle: (store, primaryType, rawPayload, recordId) -> - if commit = rawPayload.commit - rawPayload.commits = [commit] - - @_super(store, primaryType, rawPayload, recordId) - -`export default Serializer` diff --git a/app/serializers/build.js b/app/serializers/build.js new file mode 100644 index 00000000..32d42cdf --- /dev/null +++ b/app/serializers/build.js @@ -0,0 +1,109 @@ +import Ember from 'ember'; +import V2FallbackSerializer from 'travis/serializers/v2_fallback'; + +var Serializer = V2FallbackSerializer.extend({ + isNewSerializerAPI: true, + attrs: { + _config: { + key: 'config' + }, + _finished_at: { + key: 'finished_at' + }, + _started_at: { + key: 'started_at' + }, + _duration: { + key: 'duration' + } + }, + + extractRelationships: function(modelClass, resourceHash) { + var result; + result = this._super(modelClass, resourceHash); + return result; + }, + + normalizeArrayResponse: function(store, primaryModelClass, payload, id, requestType) { + var result; + if (payload.commits) { + payload.builds.forEach(function(build) { + var commit, commit_id; + commit_id = build.commit_id; + if (commit = payload.commits.findBy('id', commit_id)) { + build.commit = commit; + return delete build.commit_id; + } + }); + } + return this._super.apply(this, arguments); + }, + + keyForV2Relationship: function(key, typeClass, method) { + if(key === 'jobs') { + return 'job_ids'; + } else if (key === 'repo') { + return 'repository_id'; + } else if (key === 'commit') { + return key; + } else { + return this._super.apply(this, arguments); + } + }, + + keyForRelationship(key, typeClass, method) { + if (key === 'repo') { + return 'repository'; + } else { + return this._super.apply(this, arguments); + } + }, + + normalize: function(modelClass, resourceHash) { + var data, href, id, repoId, result; + + // TODO: remove this after switching to V3 entirely + if(!resourceHash['@type'] && resourceHash.commit && resourceHash.commit.branch_is_default) { + let build = resourceHash.build, + commit = resourceHash.commit; + let branch = { + name: commit.branch, + default_branch: commit.branch_is_default, + "@href": `/repo/${build.repository_id}/branch/${commit.branch}` + }; + resourceHash.build.branch = branch; + } + + // fix pusher payload, it doesn't include a branch record: + if(!resourceHash['@type'] && resourceHash.build && + resourceHash.repository && resourceHash.repository.default_branch) { + let branchName = resourceHash.build.branch, + repository = resourceHash.repository, + defaultBranchName = repository.default_branch.name; + + resourceHash.build.branch = { + name: branchName, + default_branch: branchName === defaultBranchName, + '@href': `/repo/${repository.id}/branch/${branchName}` + }; + + repository.default_branch['@href'] = `/repo/${repository.id}/branch/${defaultBranchName}`; + } + + result = this._super(modelClass, resourceHash); + + data = result.data; + + if (repoId = resourceHash.repository_id) { + data.attributes.repositoryId = repoId; + } else if (resourceHash.repository) { + if (href = resourceHash.repository['@href']) { + id = href.match(/\d+/)[0]; + data.attributes.repositoryId = id; + } + } + return result; + } +}); + +export default Serializer; diff --git a/app/serializers/env-var.coffee b/app/serializers/env-var.coffee index 859d7156..ebda37fe 100644 --- a/app/serializers/env-var.coffee +++ b/app/serializers/env-var.coffee @@ -6,4 +6,7 @@ Serializer = ApplicationSerializer.extend repo: { key: 'repository_id' } } + serialize: (snapshot, options) -> + return { env_var: this._super(snapshot, options) } + `export default Serializer` diff --git a/app/serializers/hook.js b/app/serializers/hook.js new file mode 100644 index 00000000..33df0b84 --- /dev/null +++ b/app/serializers/hook.js @@ -0,0 +1,7 @@ +import ApplicationSerializer from 'travis/serializers/application'; + +export default ApplicationSerializer.extend({ + serialize(snapshot, options) { + return { hook: this._super(...arguments) }; + } +}); diff --git a/app/serializers/job.coffee b/app/serializers/job.coffee index cb30d176..4ca81dfb 100644 --- a/app/serializers/job.coffee +++ b/app/serializers/job.coffee @@ -1,18 +1,30 @@ `import Ember from 'ember'` -`import ApplicationSerializer from 'travis/serializers/application'` +`import V2FallbackSerializer from 'travis/serializers/v2_fallback'` -Serializer = ApplicationSerializer.extend +Serializer = V2FallbackSerializer.extend + isNewSerializerAPI: true attrs: { - repo: { key: 'repository_id' } _config: { key: 'config' } - _finishedAt: { key: 'finished_at' } - _startedAt: { key: 'started_at' } + _finished_at: { key: 'finished_at' } + _started_at: { key: 'started_at' } } - extractSingle: (store, primaryType, rawPayload, recordId) -> - if commit = rawPayload.commit - rawPayload.commits = [commit] + keyForV2Relationship: (key, typeClass, method) -> + if key == 'repo' + 'repository' + else + @_super.apply(this, arguments) - @_super(store, primaryType, rawPayload, recordId) + keyForV2Relationship: (key, typeClass, method) -> + if key == 'repo' + 'repository_id' + else + @_super.apply(this, arguments) + + normalize: (modelClass, resourceHash) -> + if resourceHash.commit + resourceHash.commit['type'] = 'commit' + + @_super(modelClass, resourceHash) `export default Serializer` diff --git a/app/serializers/repo.coffee b/app/serializers/repo.coffee deleted file mode 100644 index ebdb0287..00000000 --- a/app/serializers/repo.coffee +++ /dev/null @@ -1,9 +0,0 @@ -`import Ember from 'ember'` -`import ApplicationSerializer from 'travis/serializers/application'` - -Serializer = ApplicationSerializer.extend - attrs: { - _lastBuildDuration: { key: 'last_build_duration' } - } - -`export default Serializer` diff --git a/app/serializers/repo.js b/app/serializers/repo.js new file mode 100644 index 00000000..98b08e20 --- /dev/null +++ b/app/serializers/repo.js @@ -0,0 +1,16 @@ +import Ember from 'ember'; +import V2FallbackSerializer from 'travis/serializers/v2_fallback'; + +var Serializer = V2FallbackSerializer.extend({ + isNewSerializerAPI: true, + + normalizeResponse(store, primaryModelClass, payload, id, requestType) { + if(!id && requestType === 'findRecord') { + id = payload.id; + } + + return this._super(store, primaryModelClass, payload, id, requestType); + } +}); + +export default Serializer; diff --git a/app/serializers/request.coffee b/app/serializers/request.coffee deleted file mode 100644 index 0f019e16..00000000 --- a/app/serializers/request.coffee +++ /dev/null @@ -1,11 +0,0 @@ -`import Ember from 'ember'` -`import ApplicationSerializer from 'travis/serializers/application'` - -Serializer = ApplicationSerializer.extend - attrs: { - branchName: { key: 'branch' } - tagName: { key: 'tag' } - repo: { key: 'repository_id' } - } - -`export default Serializer` diff --git a/app/serializers/request.js b/app/serializers/request.js new file mode 100644 index 00000000..a1f1effe --- /dev/null +++ b/app/serializers/request.js @@ -0,0 +1,35 @@ +import Ember from 'ember'; +import V2FallbackSerializer from 'travis/serializers/v2_fallback'; + +var Serializer = V2FallbackSerializer.extend({ + isNewSerializerAPI: true, + attrs: { + branch_name: { key: 'branch' }, + tag_name: { key: 'tag' } + }, + + keyForV2Relationship: function(key, typeClass, method) { + if (key === 'repo') { + return 'repository_id'; + } else { + return this._super.apply(this, arguments); + } + }, + + normalizeArrayResponse: function(store, primaryModelClass, payload, id, requestType) { + var result; + if (payload.commits) { + payload.requests.forEach(function(request) { + var commit, commit_id; + commit_id = request.commit_id; + if (commit = payload.commits.findBy('id', commit_id)) { + request.commit = commit; + return delete request.commit_id; + } + }); + } + return this._super.apply(this, arguments); + } +}); + +export default Serializer; diff --git a/app/serializers/ssh_key.js b/app/serializers/ssh_key.js new file mode 100644 index 00000000..b6d77b1a --- /dev/null +++ b/app/serializers/ssh_key.js @@ -0,0 +1,7 @@ +import ApplicationSerializer from 'travis/serializers/application'; + +export default ApplicationSerializer.extend({ + serialize(snapshot, options) { + return { ssh_key: this._super(...arguments) }; + } +}); diff --git a/app/serializers/v2_fallback.js b/app/serializers/v2_fallback.js new file mode 100644 index 00000000..7defd47b --- /dev/null +++ b/app/serializers/v2_fallback.js @@ -0,0 +1,94 @@ +import Ember from 'ember'; +import V3Serializer from 'travis/serializers/v3'; + +export default V3Serializer.extend({ + isNewSerializerAPI: true, + + extractRelationships(modelClass, resourceHash) { + if(resourceHash['@type']) { + return this._super(...arguments); + } else { + let relationships = {}; + + modelClass.eachRelationship((key, relationshipMeta) => { + // V2 API payload + let relationship = null; + let relationshipKey = this.keyForV2Relationship(key, relationshipMeta.kind, 'deserialize'); + let alternativeRelationshipKey = key.underscore(); + + if (resourceHash.hasOwnProperty(alternativeRelationshipKey) || resourceHash.hasOwnProperty(relationshipKey)) { + let data = null; + let relationshipHash = resourceHash[alternativeRelationshipKey] || resourceHash[relationshipKey]; + if (relationshipMeta.kind === 'belongsTo') { + data = this.extractRelationship(relationshipMeta.type, relationshipHash); + } else if (relationshipMeta.kind === 'hasMany') { + data = relationshipHash.map((item) => this.extractRelationship(relationshipMeta.type, item)); + } + relationship = { data }; + } + + if (relationship) { + relationships[key] = relationship; + } + }); + + return relationships; + } + }, + + normalize(modelClass, resourceHash) { + if(resourceHash['@type']) { + return this._super(...arguments); + } else { + var modelKey = modelClass.modelName; + var attributes = resourceHash[modelKey]; + if(attributes) { + for(var key in attributes) { + resourceHash[key] = attributes[key]; + } + + resourceHash['type'] = modelKey; + delete resourceHash[modelKey]; + } + + let { data, included } = this._super(...arguments); + if(!included) { + included = []; + } + let store = this.store; + + if(data.relationships) { + Object.keys(data.relationships).forEach(function (key) { + let relationship = data.relationships[key]; + let process = function(data) { + if(Object.keys(data).sort()+'' !== 'id,type' || (data['@href'] && data.type === 'branch')) { + // no need to add records if they have only id and type + let type = key === 'defaultBranch' ? 'branch' : key.singularize(); + let serializer = store.serializerFor(type); + let modelClass = store.modelFor(type); + let normalized = serializer.normalize(modelClass, data); + included.push(normalized.data); + if(normalized.included) { + normalized.included.forEach(function(item) { + included.push(item); + }); + } + } + }; + + if(Array.isArray(relationship.data)) { + relationship.data.forEach(process); + } else if(relationship && relationship.data) { + process(relationship.data); + } + }); + } + + return { data, included }; + } + }, + + keyForV2Relationship(key, typeClass, method) { + return key.underscore() + '_id'; + } +}); diff --git a/app/serializers/v3.js b/app/serializers/v3.js new file mode 100644 index 00000000..856e5b5b --- /dev/null +++ b/app/serializers/v3.js @@ -0,0 +1,186 @@ +import Ember from 'ember'; +import DS from 'ember-data'; + +var traverse = function(object, callback) { + if(!object) { + return; + } + + if(typeof(object) === 'object' && !Ember.isArray(object)) { + callback(object); + } + + if(Ember.isArray(object)) { + for(let item of object) { + traverse(item, callback); + } + } else if(typeof object === 'object') { + for(let key in object) { + if(object.hasOwnProperty(key)) { + let item = object[key]; + traverse(item, callback); + } + } + } +}; + +export default DS.JSONSerializer.extend({ + isNewSerializerAPI: true, + + extractRelationship(type, hash) { + if(hash && !hash.id && hash['@href']) { + hash.id = hash['@href']; + } + + let relationshipHash = this._super(...arguments); + if(relationshipHash && relationshipHash['@type']) { + relationshipHash.type = relationshipHash['@type']; + } else if(relationshipHash && !relationshipHash.type) { + relationshipHash.type = type; + } + return relationshipHash; + }, + + extractRelationships() { + let relationships = this._super(...arguments); + return relationships; + }, + + keyForRelationship(key, typeClass, method) { + if(key && key.underscore) { + return key.underscore(); + } else { + return key; + } + }, + + extractAttributes(modelClass, resourceHash) { + let attributes = this._super(...arguments); + for(let key in attributes) { + if(key.startsWith('@')) { + delete attributes.key; + } + } + + return attributes; + }, + + normalizeResponse(store, primaryModelClass, payload, id, requestType) { + this._fixReferences(payload); + return this._super(...arguments); + }, + + normalizeArrayResponse(store, primaryModelClass, payload, id, requestType) { + let documentHash = { + data: null, + included: [] + }; + + let meta = this.extractMeta(store, primaryModelClass, payload); + if (meta) { + Ember.assert('The `meta` returned from `extractMeta` has to be an object, not "' + Ember.typeOf(meta) + '".', Ember.typeOf(meta) === 'object'); + documentHash.meta = meta; + } + + let items, type; + if(type = payload['@type']) { + items = payload[type]; + } else { + items = payload[primaryModelClass.modelName.underscore() + 's']; + } + + documentHash.data = items.map((item) => { + let { data, included } = this.normalize(primaryModelClass, item); + if (included) { + documentHash.included.push(...included); + } + return data; + }); + + return documentHash; + }, + + normalize(modelClass, resourceHash) { + let { data, included } = this._super(...arguments); + if(!included) { + included = []; + } + let store = this.store; + + if(data.relationships) { + Object.keys(data.relationships).forEach(function (key) { + let relationship = data.relationships[key]; + let process = function(data) { + if(data['@representation'] !== 'standard') { + return; + } + let type = data['@type']; + let serializer = store.serializerFor(type); + let modelClass = store.modelFor(type); + let normalized = serializer.normalize(modelClass, data); + included.push(normalized.data); + if(normalized.included) { + normalized.included.forEach(function(item) { + included.push(item); + }); + } + }; + + if(Array.isArray(relationship.data)) { + relationship.data.forEach(process); + } else if(relationship && relationship.data) { + process(relationship.data); + } + }); + } + + return { data, included }; + }, + + keyForAttribute(key, method) { + if(method === 'deserialize') { + return Ember.String.underscore(key); + } else { + return Ember.String.camelize(key); + } + }, + + _fixReferences(payload) { + let byHref = {}, href, records; + if(payload['@type']) { + // API V3 doesn't return all of the objects in a full representation + // If an object is present in one place in the response, all of the + // other occurences will be just references of a kind - they will just + // include @href property. + // + // I don't want to identify records by href in ember-data, so here I'll + // set an id and a @type field on all of the references. + // + // First we need to group all of the items in the response by href: + traverse(payload, (item) => { + if(href = item['@href']) { + if(records = byHref[href]) { + records.push(item); + } else { + byHref[href] = [item]; + } + } + }); + + // Then we can choose a record with an id for each href and put the id + // in all of the other occurences. + for(let href in byHref) { + records = byHref[href]; + let recordWithAnId = records.find( (record) => record.id ); + if(recordWithAnId) { + for(let record of records) { + record.id = recordWithAnId.id; + //record['@type'] = recordWithAnId['@type']; + } + } + } + } + + return payload; + } +}); diff --git a/app/utils/ajax.coffee b/app/services/ajax.coffee similarity index 96% rename from app/utils/ajax.coffee rename to app/services/ajax.coffee index dc8457d6..b52b0eb3 100644 --- a/app/utils/ajax.coffee +++ b/app/services/ajax.coffee @@ -7,7 +7,9 @@ default_options = accepts: json: 'application/json; version=2' -ajax = Em.Object.create +Ajax = Ember.Service.extend + auth: Ember.inject.service() + publicEndpoints: [/\/repos\/?.*/, /\/builds\/?.*/, /\/jobs\/?.*/] privateEndpoints: [/\/repos\/\d+\/caches/] @@ -42,8 +44,8 @@ ajax = Em.Object.create endpoint = config.apiEndpoint || '' options = options || {} - token = Travis.sessionStorage.getItem('travis.token') - if token && (ajax.needsAuth(method, url) || options.forceAuth) + token = Ember.get(this, 'auth').token() + if token && (@needsAuth(method, url) || options.forceAuth) options.headers ||= {} options.headers['Authorization'] ||= "token #{token}" @@ -66,9 +68,9 @@ ajax = Em.Object.create error = options.error || (->) options.error = (data, status, xhr) => + console.log "[ERROR] API responded with an error (#{status}): #{JSON.stringify(data)}" Travis.lookup('controller:flash').pushObject(data.flash) if data?.flash delete data.flash if data? - console.log "[ERROR] API responded with an error (#{status}): #{JSON.stringify(data)}" error.apply(this, arguments) options = $.extend(options, default_options) @@ -153,4 +155,4 @@ ajax = Em.Object.create return promise -`export default ajax` +`export default Ajax` diff --git a/app/utils/auth.coffee b/app/services/auth.coffee similarity index 80% rename from app/utils/auth.coffee rename to app/services/auth.coffee index 8b7fdf0b..416c30b9 100644 --- a/app/utils/auth.coffee +++ b/app/services/auth.coffee @@ -1,7 +1,11 @@ `import config from 'travis/config/environment'` -`import Ajax from 'travis/utils/ajax'` -Auth = Ember.Object.extend +Auth = Ember.Service.extend + store: Ember.inject.service(), + storage: Ember.inject.service(), + sessionStorage: Ember.inject.service(), + ajax: Ember.inject.service() + state: "signed-out" receivingEnd: "#{location.protocol}//#{location.host}" @@ -9,20 +13,20 @@ Auth = Ember.Object.extend window.addEventListener('message', (e) => @receiveMessage(e)) token: -> - Travis.sessionStorage.getItem('travis.token') + @get('sessionStorage').getItem('travis.token') endpoint: (-> config.apiEndpoint ).property(), signOut: -> - @storage.removeItem('travis.user') - @storage.removeItem('travis.token') - @sessionStorage.clear() + @get('storage').removeItem('travis.user') + @get('storage').removeItem('travis.token') + @get('sessionStorage').clear() @set('state', 'signed-out') @set('user', undefined) if user = @get('currentUser') - @store.unloadAll('user') + @get('store').unloadAll('user') @set('currentUser', null) @sendToApp('afterSignOut') Travis.trigger('user:signed_out') @@ -36,7 +40,7 @@ Auth = Ember.Object.extend $('