diff --git a/.localeapp/log.yml b/.localeapp/log.yml index 1c42b9c4..ea6770fd 100644 --- a/.localeapp/log.yml +++ b/.localeapp/log.yml @@ -1,3 +1,3 @@ --- -:polled_at: 1361792606 -:updated_at: 1361792606 +:polled_at: 1363093348 +:updated_at: 1363093348 diff --git a/.travis.yml b/.travis.yml index a220d0f9..c99ba3cc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ rvm: before_script: - "gem install travis-artifacts" - "bundle exec rakep" + - "phantomjs --version" env: global: @@ -16,14 +17,9 @@ env: script: "script/ci" after_script: - - "ENV=production bundle exec rakep" + - "bundle exec rakep" - "test $TEST_SUITE = \"ember\" && travis-artifacts upload --target-path assets/$TRAVIS_BRANCH --path public/scripts:scripts --path public/styles:styles" -matrix: - allow_failures: - - env: "TEST_SUITE=ember" - rvm: "1.9.3" - notifications: irc: "irc.freenode.org#travis" campfire: diff --git a/Assetfile b/Assetfile index 84d80092..da341d89 100644 --- a/Assetfile +++ b/Assetfile @@ -22,7 +22,7 @@ end output 'public/scripts' input assets.scripts do match '**/*.hbs' do - travis_handlebars :precompile => assets.production? + travis_handlebars :precompile => false # assets.production? concat 'templates.js' end @@ -64,7 +64,7 @@ input assets.scripts do if assets.production? match 'min/app.js' do strip_debug - uglify squeeze: true + # uglify squeeze: true concat 'app.js' end end diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..37eceb4e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,15 @@ +# Contributing to Travis-CI +Issues for any Travis-CI should be submitted to https://github.com/travis-ci/travis-ci/issues + +## Security Issues +***Any security issues should be submitted directly to [security@travis-ci.org](mailto:security@travis-ci.org)*** + +## Reporting Issues +- Explain what you expected to happen vs the actual results +- Include a screenshot if it helps illustrate the issue. https://github.com/blog/1347-issue-attachments +- What steps are required to reproduce the issue +- An example build that shows the issue + +## Submitting a PR to Travis-Web + +See testing and setup notes in the base [README](https://github.com/travis-ci/travis-web) diff --git a/Gemfile b/Gemfile index 0ecca317..e8365a53 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,5 @@ -ruby '1.9.3' rescue nil - -source :rubygems +source 'https://rubygems.org' +ruby '1.9.3' gem 'puma' gem 'rack-ssl', '~> 1.3' @@ -21,7 +20,7 @@ group :assets do end group :development, :test do - gem 'rake', '~> 0.9.2' + gem 'rake' gem 'localeapp' gem 'handlebars' gem 'localeapp-handlebars_i18n' diff --git a/Gemfile.lock b/Gemfile.lock index c5de1e09..91af9a57 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,60 +1,61 @@ GIT remote: git://github.com/livingsocial/rake-pipeline.git - revision: 50b8d77b703c96539a433ee53a680c43542aaa75 + revision: 65b1e744defa208e313703d89f3453447cc103b2 specs: rake-pipeline (0.8.0) json - rake (~> 0.9.0) + rake (~> 10.0.0) thor GIT remote: git://github.com/wycats/rake-pipeline-web-filters.git - revision: 1a6dc173776b188836aa2ce2ac35b61c7f7daafe + revision: fd8d838491bd6b8de0bab72d90115b9a4f2da8a1 specs: rake-pipeline-web-filters (0.6.0) rack rake-pipeline (~> 0.6) GEM - remote: http://rubygems.org/ + remote: https://rubygems.org/ specs: POpen4 (0.1.4) Platform (>= 0.4.0) open4 Platform (0.4.0) - backports (2.6.5) - chunky_png (1.2.6) - coderay (1.0.8) + backports (3.0.3) + chunky_png (1.2.7) + coderay (1.0.9) coffee-script (2.2.0) coffee-script-source execjs - coffee-script-source (1.4.0) + coffee-script-source (1.5.0) commonjs (0.2.6) compass (0.12.2) chunky_png (~> 1.2) fssm (>= 0.2.7) sass (~> 3.1) - diff-lcs (1.1.3) + diff-lcs (1.2.1) eventmachine (1.0.0) execjs (1.4.0) multi_json (~> 1.0) - foreman (0.60.2) + foreman (0.61.0) thor (>= 0.13.6) - fssm (0.2.9) - gli (2.5.2) - guard (1.5.4) - listen (>= 0.4.2) + fssm (0.2.10) + gli (2.5.4) + guard (1.6.2) + listen (>= 0.6.0) lumberjack (>= 1.0.2) pry (>= 0.9.10) + terminal-table (>= 1.4.3) thor (>= 0.14.6) - handlebars (0.3.1) + handlebars (0.4.0) commonjs (~> 0.2.3) - therubyracer (~> 0.10.0) - i18n (0.6.1) - json (1.7.5) - libv8 (3.3.10.4) - listen (0.6.0) - localeapp (0.6.8) + therubyracer (~> 0.11.1) + i18n (0.6.3) + json (1.7.7) + libv8 (3.11.8.13) + listen (0.7.3) + localeapp (0.6.9) gli i18n json @@ -64,46 +65,47 @@ GEM localeapp lumberjack (1.0.2) method_source (0.8.1) - mime-types (1.19) - multi_json (1.5.0) + mime-types (1.21) + multi_json (1.6.1) open4 (1.3.0) - pry (0.9.10) + pry (0.9.12) coderay (~> 1.0.5) method_source (~> 0.8) - slop (~> 3.3.1) + slop (~> 3.4) puma (1.6.3) rack (~> 1.2) - rack (1.4.1) + rack (1.5.2) rack-cache (1.2) rack (>= 0.4) rack-mobile-detect (0.4.0) rack rack-protection (1.3.2) rack - rack-ssl (1.3.2) + rack-ssl (1.3.3) rack rack-test (0.6.2) rack (>= 1.0) - rake (0.9.6) + rake (10.0.3) rake-pipeline-i18n-filters (0.0.5) rake-pipeline (~> 0.6) - rb-fsevent (0.9.2) - rerun (0.7.1) + rb-fsevent (0.9.3) + ref (1.0.2) + rerun (0.8.0) listen rest-client (1.6.7) mime-types (>= 1.16) - rspec (2.12.0) - rspec-core (~> 2.12.0) - rspec-expectations (~> 2.12.0) - rspec-mocks (~> 2.12.0) - rspec-core (2.12.2) - rspec-expectations (2.12.0) - diff-lcs (~> 1.1.3) - rspec-mocks (2.12.0) - sass (3.2.3) - sinatra (1.3.3) - rack (~> 1.3, >= 1.3.6) - rack-protection (~> 1.2) + rspec (2.13.0) + rspec-core (~> 2.13.0) + rspec-expectations (~> 2.13.0) + rspec-mocks (~> 2.13.0) + rspec-core (2.13.0) + rspec-expectations (2.13.0) + diff-lcs (>= 1.1.3, < 2.0) + rspec-mocks (2.13.0) + sass (3.2.6) + sinatra (1.3.5) + rack (~> 1.4) + rack-protection (~> 1.3) tilt (~> 1.3, >= 1.3.3) sinatra-contrib (1.3.2) backports (>= 2.0) @@ -112,10 +114,12 @@ GEM rack-test sinatra (~> 1.3.0) tilt (~> 1.3) - slop (3.3.3) - therubyracer (0.10.2) - libv8 (~> 3.3.10) - thor (0.16.0) + slop (3.4.3) + terminal-table (1.4.5) + therubyracer (0.11.4) + libv8 (~> 3.11.8.12) + ref + thor (0.17.0) tilt (1.3.3) uglifier (1.3.0) execjs (>= 0.3.0) @@ -140,7 +144,7 @@ DEPENDENCIES rack-mobile-detect rack-protection (~> 1.3) rack-ssl (~> 1.3) - rake (~> 0.9.2) + rake rake-pipeline! rake-pipeline-i18n-filters rake-pipeline-web-filters! diff --git a/assets/images/ui/log.fold.closed.3.png b/assets/images/ui/log.fold.closed.3.png new file mode 100644 index 00000000..304b09e7 Binary files /dev/null and b/assets/images/ui/log.fold.closed.3.png differ diff --git a/assets/scripts/app/app.coffee b/assets/scripts/app/app.coffee index a8a50350..42641989 100644 --- a/assets/scripts/app/app.coffee +++ b/assets/scripts/app/app.coffee @@ -1,64 +1,4 @@ -require 'auth' -require 'controllers' -require 'helpers' -require 'models' -require 'pusher' -require 'routes' -require 'slider' -require 'store' -require 'tailing' -require 'templates' -require 'views' - -require 'config/locales' -require 'data/sponsors' - -require 'travis/instrumentation' # $.mockjaxSettings.log = false # Ember.LOG_BINDINGS = true # Ember.ENV.RAISE_ON_DEPRECATION = true # Pusher.log = -> console.log(arguments) - -Travis.reopen - App: Em.Application.extend - autoinit: false - currentUserBinding: 'auth.user' - authStateBinding: 'auth.state' - - init: -> - @_super.apply this, arguments - - @store = Travis.Store.create() - @store.loadMany(Travis.Sponsor, Travis.SPONSORS) - - @slider = new Travis.Slider() - @pusher = new Travis.Pusher(Travis.config.pusher_key) - @tailing = new Travis.Tailing() - - @set('auth', Travis.Auth.create(app: this, endpoint: Travis.config.api_endpoint)) - - storeAfterSignInPath: (path) -> - @get('auth').storeAfterSignInPath(path) - - autoSignIn: (path) -> - @get('auth').autoSignIn() - - signIn: -> - @get('auth').signIn() - - signOut: -> - @get('auth').signOut() - @get('router').send('afterSignOut') - - receive: -> - @store.receive.apply(@store, arguments) - - toggleSidebar: -> - $('body').toggleClass('maximized') - # TODO gotta force redraws here :/ - element = $('') - $('#top .profile').append(element) - Em.run.later (-> element.remove()), 10 - element = $('') - $('#repo').append(element) - Em.run.later (-> element.remove()), 10 diff --git a/assets/scripts/app/auth.coffee b/assets/scripts/app/auth.coffee index ae0540d7..28b0d9e6 100644 --- a/assets/scripts/app/auth.coffee +++ b/assets/scripts/app/auth.coffee @@ -13,6 +13,8 @@ Travis.setLocale Travis.default_locale @set('state', 'signed-out') @set('user', undefined) + Travis.__container__.lookup('controller:currentUser').set('content', null) + Travis.__container__.lookup('router:main').send('afterSignOut') signIn: -> @set('state', 'signing-in') @@ -31,7 +33,7 @@ if user && token && @validateUser(user) { user: user, token: token } else - console.log('dropping user, no token') unless token? + # console.log('dropping user, no token') if token? storage.removeItem('travis.user') storage.removeItem('travis.token') null @@ -43,25 +45,30 @@ if user[field] true else - console.log("discarding user data, lacks #{field}") + # console.log("discarding user data, lacks #{field}") false setData: (data) -> @storeData(data, Travis.sessionStorage) @storeData(data, Travis.storage) unless @userDataFrom(Travis.storage) - @set('user', @loadUser(data.user)) + user = @loadUser(data.user) + # TODO: we should not use __container__ directly, how to do it better? + # A good answer seems to do auth in context of controller. + Travis.__container__.lookup('controller:currentUser').set('content', user) + @set('state', 'signed-in') Travis.setLocale(data.user.locale || Travis.default_locale) Travis.trigger('user:signed_in', data.user) - @get('app.router').send('afterSignIn', @readAfterSignInPath()) + Travis.__container__.lookup('router:main').send('afterSignIn', @readAfterSignInPath()) storeData: (data, storage) -> storage.setItem('travis.token', data.token) storage.setItem('travis.user', JSON.stringify(data.user)) loadUser: (user) -> - @app.store.load(Travis.User, user) - user = @app.store.find(Travis.User, user.id) + store = @app.store + store.load(Travis.User, user.id, user) + user = store.find(Travis.User, user.id) user.get('permissions') user diff --git a/assets/scripts/app/controllers.coffee b/assets/scripts/app/controllers.coffee index f0e1d3ac..475e997d 100644 --- a/assets/scripts/app/controllers.coffee +++ b/assets/scripts/app/controllers.coffee @@ -1,25 +1,40 @@ require 'helpers' require 'travis/ticker' -Travis.reopen - Controller: Em.Controller.extend() +Travis.Controller = Em.Controller.extend() +Travis.TopController = Em.Controller.extend + needs: ['currentUser'] + userBinding: 'controllers.currentUser' - TopController: Em.Controller.extend - userBinding: 'Travis.app.currentUser' +Travis.ApplicationController = Em.Controller.extend + templateName: 'layouts/home' - ApplicationController: Em.Controller.extend() - MainController: Em.Controller.extend() - StatsLayoutController: Em.Controller.extend() - ProfileLayoutController: Em.Controller.extend() - AuthLayoutController: Em.Controller.extend() + connectLayout: (name) -> + name = "layouts/#{name}" + if @get('templateName') != name + @set('templateName', name) + +Travis.MainController = Em.Controller.extend() +Travis.StatsLayoutController = Em.Controller.extend() +Travis.ProfileLayoutController = Em.Controller.extend() +Travis.AuthLayoutController = Em.Controller.extend() + +Travis.AccountProfileController = Em.Controller.extend + needs: ['currentUser'] + userBinding: 'controllers.currentUser' require 'controllers/accounts' +require 'controllers/build' require 'controllers/builds' require 'controllers/flash' require 'controllers/home' +require 'controllers/job' require 'controllers/profile' require 'controllers/repos' require 'controllers/repo' require 'controllers/running_jobs' require 'controllers/sidebar' require 'controllers/stats' +require 'controllers/current_user' +require 'controllers/account_index' + diff --git a/assets/scripts/app/controllers/account_index.coffee b/assets/scripts/app/controllers/account_index.coffee new file mode 100644 index 00000000..b5dda57a --- /dev/null +++ b/assets/scripts/app/controllers/account_index.coffee @@ -0,0 +1,10 @@ +Travis.AccountIndexController = Em.Controller.extend + needs: ['profile', 'currentUser'] + hooksBinding: 'controllers.profile.hooks' + userBinding: 'controllers.currentUser' + + sync: -> + @get('user').sync() + + toggle: (hook) -> + hook.toggle() diff --git a/assets/scripts/app/controllers/accounts.coffee b/assets/scripts/app/controllers/accounts.coffee index 7bc7dff0..213d2c85 100644 --- a/assets/scripts/app/controllers/accounts.coffee +++ b/assets/scripts/app/controllers/accounts.coffee @@ -1,8 +1,5 @@ Travis.AccountsController = Ember.ArrayController.extend tab: 'accounts' - init: -> - @_super() - findByLogin: (login) -> @find (account) -> account.get('login') == login diff --git a/assets/scripts/app/controllers/build.coffee b/assets/scripts/app/controllers/build.coffee new file mode 100644 index 00000000..56740440 --- /dev/null +++ b/assets/scripts/app/controllers/build.coffee @@ -0,0 +1,24 @@ +Travis.BuildController = Ember.Controller.extend + needs: ['repo'] + repoBinding: 'controllers.repo.repo' + buildBinding: 'controllers.repo.build' + commitBinding: 'build.commit' + lineNumberBinding: 'controllers.repo.lineNumber' + + currentItemBinding: 'build' + + loading: (-> + @get('build.isLoading') + ).property('build.isLoading') + + urlGithubCommit: (-> + Travis.Urls.githubCommit(@get('repo.slug'), @get('commit.sha')) + ).property('repo.slug', 'commit.sha') + + urlAuthor: (-> + Travis.Urls.email(@get('commit.authorEmail')) + ).property('commit.authorEmail') + + urlCommitter: (-> + Travis.Urls.email(@get('commit.committerEmail')) + ).property('commit.committerEmail') diff --git a/assets/scripts/app/controllers/builds.coffee b/assets/scripts/app/controllers/builds.coffee index 3f945639..b18a4898 100644 --- a/assets/scripts/app/controllers/builds.coffee +++ b/assets/scripts/app/controllers/builds.coffee @@ -1,5 +1,19 @@ Travis.BuildsController = Em.ArrayController.extend - # sortAscending: false + sortAscending: false + sortProperties: ['number'] - repo: 'parent.repo' - contentBinding: 'parent.builds' + needs: ['repo'] + + repoBinding: 'controllers.repo.repo' + contentBinding: 'controllers.repo.builds' + tabBinding: 'controllers.repo.tab' + isLoadedBinding: 'content.isLoaded' + + showMore: -> + id = @get('repo.id') + number = @get('lastObject.number') + @get('content').load Travis.Build.olderThanNumber(id, number, @get('tab')) + + displayShowMoreButton: (-> + @get('tab') != 'branches' + ).property('tab') diff --git a/assets/scripts/app/controllers/current_user.coffee b/assets/scripts/app/controllers/current_user.coffee new file mode 100644 index 00000000..548e06a5 --- /dev/null +++ b/assets/scripts/app/controllers/current_user.coffee @@ -0,0 +1,3 @@ +Travis.CurrentUserController = Em.ObjectController.extend + sync: -> + @get('content').sync() diff --git a/assets/scripts/app/controllers/flash.coffee b/assets/scripts/app/controllers/flash.coffee index 20e5b8a5..67ad6ae5 100644 --- a/assets/scripts/app/controllers/flash.coffee +++ b/assets/scripts/app/controllers/flash.coffee @@ -1,9 +1,12 @@ Travis.FlashController = Ember.ArrayController.extend - broadcastBinding: 'Travis.app.currentUser.broadcasts' + needs: ['currentUser'] + currentUserBinding: 'controllers.currentUser' + + broadcastBinding: 'currentUser.broadcasts' init: -> - @set('flashes', Ember.A()) @_super.apply this, arguments + @set('flashes', Ember.A()) content: (-> @get('unseenBroadcasts').concat(@get('flashes')) @@ -14,8 +17,8 @@ Travis.FlashController = Ember.ArrayController.extend ).property('broadcasts.isLoaded', 'broadcasts.length') broadcasts: (-> - if Travis.app.get('currentUser') then Travis.Broadcast.find() else Ember.A() - ).property('Travis.app.currentUser') + if @get('currentUser') then Travis.Broadcast.find() else Ember.A() + ).property('currentUser') loadFlashes: (msgs) -> for msg in msgs diff --git a/assets/scripts/app/controllers/job.coffee b/assets/scripts/app/controllers/job.coffee new file mode 100644 index 00000000..3f94343c --- /dev/null +++ b/assets/scripts/app/controllers/job.coffee @@ -0,0 +1,21 @@ +Travis.JobController = Em.Controller.extend + needs: ['repo'] + + jobBinding: 'controllers.repo.job' + repoBinding: 'controllers.repo.repo' + commitBinding: 'job.commit' + lineNumberBinding: 'controllers.repo.lineNumber' + + currentItemBinding: 'job' + + urlGithubCommit: (-> + Travis.Urls.githubCommit(@get('repo.slug'), @get('commit.sha')) + ).property('repo.slug', 'commit.sha') + + urlAuthor: (-> + Travis.Urls.email(@get('commit.authorEmail')) + ).property('commit.authorEmail') + + urlCommitter: (-> + Travis.Urls.email(@get('commit.committerEmail')) + ).property('commit.committerEmail') diff --git a/assets/scripts/app/controllers/profile.coffee b/assets/scripts/app/controllers/profile.coffee index 744214a0..fbac998a 100644 --- a/assets/scripts/app/controllers/profile.coffee +++ b/assets/scripts/app/controllers/profile.coffee @@ -1,21 +1,31 @@ Travis.ProfileController = Travis.Controller.extend name: 'profile' - userBinding: 'Travis.app.currentUser' - accountsBinding: 'Travis.app.router.accountsController' + + needs: ['currentUser', 'accounts'] + userBinding: 'controllers.currentUser' + accountsBinding: 'controllers.accounts' init: -> + @_super.apply this, arguments + self = this Travis.on("user:synced", (-> self.reloadHooks() )) account: (-> - login = @get('params.login') || Travis.app.get('currentUser.login') + login = @get('params.login') || @get('user.login') account = @get('accounts').filter((account) -> account if account.get('login') == login)[0] account.select() if account account ).property('accounts.length', 'params.login') + sync: -> + @get('user').sync() + + toggle: (hook) -> + hook.toggle() + activate: (action, params) -> @setParams(params || @get('params')) this["view#{$.camelize(action)}"]() @@ -25,7 +35,7 @@ Travis.ProfileController = Travis.Controller.extend @reloadHooks() reloadHooks: -> - @set('hooks', Travis.Hook.find(owner_name: @get('params.login') || Travis.app.get('currentUser.login'))) + @set('hooks', Travis.Hook.find(owner_name: @get('params.login') || @get('user.login'))) viewUser: -> @connectTab('user') @@ -33,7 +43,6 @@ Travis.ProfileController = Travis.Controller.extend connectTab: (tab) -> viewClass = Travis["#{$.camelize(tab)}View"] @set('tab', tab) - @connectOutlet(outletName: 'pane', controller: this, viewClass: viewClass) setParams: (params) -> @set('params', {}) diff --git a/assets/scripts/app/controllers/repo.coffee b/assets/scripts/app/controllers/repo.coffee index 671d0f70..bf5e0de7 100644 --- a/assets/scripts/app/controllers/repo.coffee +++ b/assets/scripts/app/controllers/repo.coffee @@ -1,20 +1,15 @@ Travis.RepoController = Travis.Controller.extend bindings: [] + needs: ['repos', 'currentUser'] + currentUserBinding: 'controllers.currentUser' + + isError: (-> @get('repo.isError') ).property('repo.isError') + slug: (-> @get('repo.slug') ).property('repo.slug') + isLoading: (-> @get('repo.isLoading') ).property('repo.isLoading') init: -> @_super.apply this, arguments Ember.run.later(@updateTimes.bind(this), Travis.INTERVALS.updateTimes) - @set 'builds', Em.ArrayProxy.create(Em.SortableMixin, - isLoadedBinding: 'content.isLoaded' - sortProperties: ['number'] - sortAscending: false - content: [] - isLoadingBinding: 'content.isLoading' - load: (records) -> - content = @get('content') - if content && content.load - content.load(records) - ) updateTimes: -> if builds = @get('builds') @@ -33,7 +28,7 @@ Travis.RepoController = Travis.Controller.extend this["view#{$.camelize(action)}"]() viewIndex: -> - @_bind('repo', 'controllers.reposController.firstObject') + @_bind('repo', 'controllers.repos.firstObject') @_bind('build', 'repo.lastBuild') @connectTab('current') @@ -43,15 +38,15 @@ Travis.RepoController = Travis.Controller.extend viewBuilds: -> @connectTab('builds') - @_bind('builds.content', 'repo.builds') + @_bind('builds', 'repo.builds') viewPullRequests: -> @connectTab('pull_requests') - @_bind('builds.content', 'repo.pullRequests') + @_bind('builds', 'repo.pullRequests') viewBranches: -> @connectTab('branches') - @_bind('builds.content', 'repo.branches') + @_bind('builds', 'repo.branches') viewEvents: -> @connectTab('events') @@ -64,12 +59,9 @@ Travis.RepoController = Travis.Controller.extend @_bind('build', 'job.build') @connectTab('job') - repoObserver: (-> - repo = @get('repo') - repo.select() if repo - ).observes('repo.id') - connectTab: (tab) -> + # TODO: such implementation seems weird now, because we render + # in the renderTemplate function in routes name = if tab == 'current' then 'build' else tab viewClass = if name in ['builds', 'branches', 'pull_requests'] Travis.BuildsView @@ -77,7 +69,6 @@ Travis.RepoController = Travis.Controller.extend Travis["#{$.camelize(name)}View"] @set('tab', tab) - @connectOutlet(outletName: 'pane', controller: this, viewClass: viewClass) _bind: (to, from) -> @bindings.push Ember.oneWay(this, to, from) @@ -85,3 +76,7 @@ Travis.RepoController = Travis.Controller.extend _unbind: -> binding.disconnect(this) for binding in @bindings @bindings.clear() + + urlGithub: (-> + Travis.Urls.githubRepo(@get('repo.slug')) + ).property('repo.slug') diff --git a/assets/scripts/app/controllers/repos.coffee b/assets/scripts/app/controllers/repos.coffee index 06445ebd..cfc0c0da 100644 --- a/assets/scripts/app/controllers/repos.coffee +++ b/assets/scripts/app/controllers/repos.coffee @@ -3,10 +3,30 @@ require 'travis/limited_array' Travis.ReposController = Ember.ArrayController.extend defaultTab: 'recent' isLoadedBinding: 'content.isLoaded' + needs: ['currentUser', 'repo'] + currentUserBinding: 'controllers.currentUser' + 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') init: -> + @_super.apply this, arguments Ember.run.later(@updateTimes.bind(this), Travis.INTERVALS.updateTimes) + recentRepos: (-> + Travis.Repo.find() + Travis.LimitedArray.create + content: Em.ArrayProxy.extend(Em.SortableMixin).create( + sortProperties: ['sortOrder'] + content: Travis.Repo.withLastBuild() + isLoadedBinding: 'content.isLoaded' + ) + limit: 30 + ).property() + updateTimes: -> if content = @get('content') content.forEach (r) -> r.updateTimes() @@ -19,18 +39,10 @@ Travis.ReposController = Ember.ArrayController.extend this["view#{$.camelize(tab)}"](params) viewRecent: -> - content = Travis.LimitedArray.create - content: Em.ArrayProxy.extend(Em.SortableMixin).create( - sortProperties: ['sortOrder'] - content: Travis.Repo.find() - isLoadedBinding: 'content.isLoaded' - ) - limit: 30 - @set('content', content) - # @set('content', Travis.Repo.find()) + @set('content', @get('recentRepos')) viewOwned: -> - @set('content', Travis.Repo.accessibleBy(Travis.app.get('currentUser.login'))) + @set('content', Travis.Repo.accessibleBy(@get('currentUser.login'))) viewSearch: (params) -> @set('content', Travis.Repo.search(params.search)) diff --git a/assets/scripts/app/controllers/running_jobs.coffee b/assets/scripts/app/controllers/running_jobs.coffee index 11534d9d..2a010233 100644 --- a/assets/scripts/app/controllers/running_jobs.coffee +++ b/assets/scripts/app/controllers/running_jobs.coffee @@ -1,8 +1,9 @@ Travis.RunningJobsController = Em.ArrayProxy.extend Group: Em.Object.extend - repo: (-> @get('jobs.firstObject.repo') ).property('jobs.firstObject.repo') + slug: (-> @get('jobs.firstObject.repoSlug') ).property('jobs.firstObject.repoSlug') init: -> + @_super.apply this, arguments @set 'jobs', [] @set 'sortedJobs', Em.ArrayProxy.extend(Em.SortableMixin, @@ -39,7 +40,9 @@ Travis.RunningJobsController = Em.ArrayProxy.extend init: -> @_super.apply this, arguments - @addedJobs @get('content') if @get('content') + jobs = Travis.Job.running() + @set 'content', jobs + @addedJobs jobs contentArrayWillChange: (array, index, removedCount, addedCount) -> @_super.apply this, arguments diff --git a/assets/scripts/app/controllers/sidebar.coffee b/assets/scripts/app/controllers/sidebar.coffee index c8a9f644..42bfd638 100644 --- a/assets/scripts/app/controllers/sidebar.coffee +++ b/assets/scripts/app/controllers/sidebar.coffee @@ -1,15 +1,32 @@ Travis.reopen SidebarController: Em.ArrayController.extend init: -> + @_super.apply this, arguments @tickables = [] Travis.Ticker.create(target: this, interval: Travis.INTERVALS.sponsors) tick: -> tickable.tick() for tickable in @tickables - QueuesController: Em.ArrayController.extend() + QueuesController: Em.ArrayController.extend + init: -> + @_super.apply this, arguments + + queues = for queue in Travis.QUEUES + Travis.LimitedArray.create + content: Travis.Job.queued(queue.name), limit: 20 + id: "queue_#{queue.name}" + name: queue.display + @set 'content', queues + + showAll: (queue) -> + queue.showAll() WorkersController: Em.ArrayController.extend + init: -> + @_super.apply this, arguments + @set 'content', Travis.Worker.find() + groups: (-> if content = @get 'arrangedContent' groups = {} @@ -53,3 +70,22 @@ Travis.reopen end: -> @start() + @get('perPage') +Travis.DecksController = Travis.SponsorsController.extend + needs: ['sidebar'] + perPage: 1 + + init: -> + @_super.apply this, arguments + + @get('controllers.sidebar').tickables.push(this) + @set 'content', Travis.Sponsor.decks() + +Travis.LinksController = Travis.SponsorsController.extend + needs: ['sidebar'] + perPage: 6 + + init: -> + @_super.apply this, arguments + + @get('controllers.sidebar').tickables.push(this) + @set 'content', Travis.Sponsor.links() diff --git a/assets/scripts/app/controllers/stats.coffee b/assets/scripts/app/controllers/stats.coffee index b0b31704..3d6da596 100644 --- a/assets/scripts/app/controllers/stats.coffee +++ b/assets/scripts/app/controllers/stats.coffee @@ -2,7 +2,7 @@ Travis.StatsController = Travis.Controller.extend name: 'stats' init: -> - @_super('top') + @_super.apply this, arguments #@connectOutlet(outletName: 'main', controller: this, viewClass: Travis.StatsView) activate: (action, params) -> diff --git a/assets/scripts/app/helpers/helpers.coffee b/assets/scripts/app/helpers/helpers.coffee index c3f51333..a0f28ec5 100644 --- a/assets/scripts/app/helpers/helpers.coffee +++ b/assets/scripts/app/helpers/helpers.coffee @@ -1,4 +1,3 @@ -require 'travis/log' require 'config/emoij' @Travis.Helpers = @@ -40,16 +39,6 @@ require 'config/emoij' message = message.split(/\n/)[0] if options.short @_emojize(@_escape(message)).replace /\n/g, '
' - formatLog: (log, repo, item) -> - event = if item.constructor == Travis.Build - 'showBuild' - else - 'showJob' - - url = Travis.app.get('router').urlForEvent(event, repo, item) - - Travis.Log.filter(log, url) - pathFrom: (url) -> (url || '').split('/').pop() @@ -84,7 +73,7 @@ require 'config/emoij' string _nowUtc: -> - @_toUtc new Date() + @_toUtc Travis.currentDate() _toUtc: (date) -> Date.UTC date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds() diff --git a/assets/scripts/app/models.coffee b/assets/scripts/app/models.coffee index 28064194..8eab6753 100644 --- a/assets/scripts/app/models.coffee +++ b/assets/scripts/app/models.coffee @@ -1,6 +1,5 @@ require 'models/extensions' require 'models/account' -require 'models/artifact' require 'models/broadcast' require 'models/branch' require 'models/build' @@ -8,6 +7,7 @@ require 'models/commit' require 'models/event' require 'models/hook' require 'models/job' +require 'models/log' require 'models/repo' require 'models/sponsor' require 'models/user' diff --git a/assets/scripts/app/models/artifact.coffee b/assets/scripts/app/models/artifact.coffee deleted file mode 100644 index 26b87921..00000000 --- a/assets/scripts/app/models/artifact.coffee +++ /dev/null @@ -1,89 +0,0 @@ -require 'travis/model' - -@Travis.Artifact = Em.Object.extend - version: 1 # used to refresh log on requeue - body: null - isLoaded: false - - init: -> - @_super.apply this, arguments - - @addObserver 'job.id', @fetchBody - @fetchBody() - - @set 'queue', Ember.A([]) - @set 'parts', Ember.ArrayProxy.create(content: []) - - @addObserver 'body', @fetchWorker - @fetchWorker() - - id: (-> - @get('job.id') - ).property('job.id') - - clear: -> - @set('body', '') - @incrementProperty('version') - - fetchBody: -> - if jobId = @get('job.id') - @removeObserver 'job.id', @fetchBody - - self = this - Travis.ajax.ajax "/jobs/#{jobId}/log.txt?cors_hax=true", 'GET', - dataType: 'text' - contentType: 'text/plain' - success: (data, textStatus, xhr) -> - if xhr.status == 204 - logUrl = xhr.getResponseHeader('X-Log-Location') - - # For some reason not all browsers can fetch this header - unless logUrl - logUrl = self.s3Url("/jobs/#{jobId}/log.txt") - - $.ajax - url: logUrl - type: 'GET' - success: (data) -> - self.fetchedBody(data) - else - self.fetchedBody(data) - - s3Url: (path) -> - endpoint = Travis.config.api_endpoint - staging = if endpoint.match(/-staging/) then '-staging' else '' - host = Travis.config.api_endpoint.replace(/^https?:\/\//, '').split('.').slice(-2).join('.') - "https://s3.amazonaws.com/archive#{staging}.#{host}#{path}" - - - fetchedBody: (body) -> - @set 'body', body - @set 'isLoaded', true - - append: (body) -> - if @get('isInitialized') - @get('parts').pushObject body - @set('body', @get('body') + body) - else - @get('queue').pushObject(body) - - recordDidLoad: (-> - if @get('isLoaded') - if (body = @get 'body') && @get('parts.length') == 0 - @get('parts').pushObject body - - @set 'isInitialized', true - - queue = @get('queue') - if queue.get('length') > 0 - @append queue.toArray().join('') - ).observes('isLoaded') - - fetchWorker: -> - if !@get('workerName') && (body = @get('body')) - line = body.split("\n")[0] - if line && (match = line.match /Using worker: (.*)/) - if worker = match[1] - worker = worker.trim().split(':')[0] - @set('workerName', worker) - @removeObserver 'body', @fetchWorker diff --git a/assets/scripts/app/models/build.coffee b/assets/scripts/app/models/build.coffee index 089b111e..03b20781 100644 --- a/assets/scripts/app/models/build.coffee +++ b/assets/scripts/app/models/build.coffee @@ -2,32 +2,33 @@ require 'travis/model' @Travis.Build = Travis.Model.extend Travis.DurationCalculations, eventType: DS.attr('string') - repoId: DS.attr('number', key: 'repository_id') + repoId: DS.attr('number') commitId: DS.attr('number') state: DS.attr('string') number: DS.attr('number') branch: DS.attr('string') message: DS.attr('string') - _duration: DS.attr('number', key: 'duration') - startedAt: DS.attr('string', key: 'started_at') - finishedAt: DS.attr('string', key: 'finished_at') + _duration: DS.attr('number') + _config: DS.attr('object') + startedAt: DS.attr('string') + finishedAt: DS.attr('string') - repo: DS.belongsTo('Travis.Repo', key: 'repository_id') + repo: DS.belongsTo('Travis.Repo') commit: DS.belongsTo('Travis.Commit') - jobs: DS.hasMany('Travis.Job', key: 'job_ids') + jobs: DS.hasMany('Travis.Job') config: (-> - Travis.Helpers.compact(@get('data.config')) - ).property('data.config') + Travis.Helpers.compact(@get('_config')) + ).property('_config') isPullRequest: (-> @get('eventType') == 'pull_request' ).property('eventType') isMatrix: (-> - @get('data.job_ids.length') > 1 - ).property('data.job_ids.length') + @get('jobs.length') > 1 + ).property('jobs.length') isFinished: (-> @get('state') in ['passed', 'failed', 'errored', 'canceled'] @@ -73,6 +74,11 @@ require 'travis/model' branches: (options) -> @find repository_id: options.repoId, branches: true - olderThanNumber: (id, build_number) -> + olderThanNumber: (id, build_number, type) -> + console.log type # TODO fix this api and use some kind of pagination scheme - @find(url: "/builds", repository_id: id, after_number: build_number) + options = { repository_id: id, after_number: build_number } + if type? + options.event_type = type.replace(/s$/, '') # poor man's singularize + + @find(options) diff --git a/assets/scripts/app/models/commit.coffee b/assets/scripts/app/models/commit.coffee index 467d5006..9e3be52f 100644 --- a/assets/scripts/app/models/commit.coffee +++ b/assets/scripts/app/models/commit.coffee @@ -13,4 +13,4 @@ require 'travis/model' pullRequestTitle: DS.attr('string') pullRequestNumber: DS.attr('number') - build: DS.belongsTo('Travis.Build', key: 'buildId') + build: DS.belongsTo('Travis.Build') diff --git a/assets/scripts/app/models/job.coffee b/assets/scripts/app/models/job.coffee index 40313156..98334b06 100644 --- a/assets/scripts/app/models/job.coffee +++ b/assets/scripts/app/models/job.coffee @@ -1,7 +1,7 @@ require 'travis/model' @Travis.Job = Travis.Model.extend Travis.DurationCalculations, - repoId: DS.attr('number', key: 'repository_id') + repoId: DS.attr('number') buildId: DS.attr('number') commitId: DS.attr('number') logId: DS.attr('number') @@ -11,14 +11,23 @@ require 'travis/model' number: DS.attr('string') startedAt: DS.attr('string') finishedAt: DS.attr('string') - allowFailure: DS.attr('boolean', key: 'allow_failure') + allowFailure: DS.attr('boolean') repositorySlug: DS.attr('string') - repo: DS.belongsTo('Travis.Repo', key: 'repository_id') - build: DS.belongsTo('Travis.Build', key: 'build_id') - commit: DS.belongsTo('Travis.Commit', key: 'commit_id') + repo: DS.belongsTo('Travis.Repo') + build: DS.belongsTo('Travis.Build') + commit: DS.belongsTo('Travis.Commit') + + # this is a fake relationship just to get rid + # of ember data's bug: https://github.com/emberjs/data/issues/758 + # TODO: remove when this issue is fixed + fakeBuild: DS.belongsTo('Travis.Build') + + _config: DS.attr('object') + log: ( -> - Travis.Artifact.create(job: this) + @set('isLogAccessed', true) + Travis.Log.create(job: this) ).property() repoSlug: (-> @@ -30,15 +39,17 @@ require 'travis/model' ).property('repoSlug', 'repoId') config: (-> - Travis.Helpers.compact(@get('data.config')) - ).property('data.config') + Travis.Helpers.compact(@get('_config')) + ).property('_config') isFinished: (-> @get('state') in ['passed', 'failed', 'errored', 'canceled'] ).property('state') clearLog: -> - @get('log').clear() if @get('log.isLoaded') + # This is needed if we don't want to fetch log just to clear it + if @get('isLogAccessed') + @get('log').clear() sponsor: (-> worker = @get('log.workerName') @@ -70,17 +81,22 @@ require 'travis/model' requeue: -> Travis.ajax.post '/requests', job_id: @get('id') - appendLog: (text) -> - if log = @get('log') - log.append(text) + appendLog: (part) -> + @get('log').append part subscribe: -> - if id = @get('id') - Travis.app.pusher.subscribe "job-#{id}" + return if @get('subscribed') + @set('subscribed', true) + Travis.pusher.subscribe "job-#{@get('id')}" + + unsubscribe: -> + return unless @get('subscribed') + @set('subscribed', false) + Travis.pusher.unsubscribe "job-#{@get('id')}" onStateChange: (-> - if @get('state') == 'finished' && Travis.app - Travis.app.pusher.unsubscribe "job-#{@get('id')}" + if @get('state') == 'finished' && Travis.pusher + Travis.pusher.unsubscribe "job-#{@get('id')}" ).observes('state') isAttributeLoaded: (key) -> @@ -98,16 +114,16 @@ require 'travis/model' @Travis.Job.reopenClass queued: (queue) -> @find() - Travis.app.store.filter this, (job) -> + Travis.store.filter this, (job) -> queued = ['created', 'queued'].indexOf(job.get('state')) != -1 # TODO: why queue is sometimes just common instead of build.common? queued && (!queue || job.get('queue') == "builds.#{queue}" || job.get('queue') == queue) running: -> @find(state: 'started') - Travis.app.store.filter this, (job) -> + Travis.store.filter this, (job) -> job.get('state') == 'started' findMany: (ids) -> - Travis.app.store.findMany this, ids + Travis.store.findMany this, ids diff --git a/assets/scripts/app/models/log.coffee b/assets/scripts/app/models/log.coffee new file mode 100644 index 00000000..de96b6f6 --- /dev/null +++ b/assets/scripts/app/models/log.coffee @@ -0,0 +1,75 @@ +require 'travis/model' +require 'travis/chunk_buffer' + +@Travis.Log = Em.Object.extend + version: 0 # used to refresh log on requeue + isLoaded: false + length: 0 + + init: -> + @setParts() + @fetch() + + setParts: -> + #@set 'parts', Ember.ArrayProxy.create(content: []) + @set 'parts', Travis.ChunkBuffer.create(content: []) + + fetch: -> + console.log 'log model: fetching log' if Log.DEBUG + handlers = + json: (json) => @loadParts(json['log']['parts']) + text: (text) => @loadText(text) + Travis.Log.Request.create(id: id, handlers: handlers).run() if id = @get('job.id') + + clear: -> + @setParts() + @incrementProperty('version') + + append: (part) -> + @get('parts').pushObject(part) + + loadParts: (parts) -> + console.log 'log model: load parts' if Log.DEBUG + @append(part) for part in parts + @set('isLoaded', true) + + loadText: (text) -> + console.log 'log model: load text' if Log.DEBUG + number = -1 + @append(number: 1, content: text) + @set('isLoaded', true) + +Travis.Log.Request = Em.Object.extend + HEADERS: + accept: 'application/json; chunked=true; version=2, text/plain; version=2' + + run: -> + Travis.ajax.ajax "/jobs/#{@id}/log?cors_hax=true", 'GET', + dataType: 'text' + headers: @HEADERS + success: (body, status, xhr) => @handle(body, status, xhr) + + handle: (body, status, xhr) -> + if xhr.status == 204 + $.ajax(url: @redirectTo(xhr), type: 'GET', success: @handlers.text) + else if @isJson(xhr, body) + @handlers.json(JSON.parse(body)) + else + @handlers.text(body) + + redirectTo: (xhr) -> + # Firefox can't see the Location header on the xhr response due to the wrong + # status code 204. Should be some redirect code but that doesn't work with CORS. + xhr.getResponseHeader('Location') || @s3Url() + + s3Url: -> + endpoint = Travis.config.api_endpoint + staging = if endpoint.match(/-staging/) then '-staging' else '' + host = endpoint.replace(/^https?:\/\//, '').split('.').slice(-2).join('.') + "https://s3.amazonaws.com/archive#{staging}.#{host}#{path}/jobs/#{@id}/log.txt" + + isJson: (xhr, body) -> + # Firefox can't see the Content-Type header on the xhr response due to the wrong + # status code 204. Should be some redirect code but that doesn't work with CORS. + type = xhr.getResponseHeader('Content-Type') || '' + type.indexOf('json') > -1 || body.slice(0, 8) == '{"log":{' diff --git a/assets/scripts/app/models/repo.coffee b/assets/scripts/app/models/repo.coffee index ce37b7ea..34118023 100644 --- a/assets/scripts/app/models/repo.coffee +++ b/assets/scripts/app/models/repo.coffee @@ -9,6 +9,7 @@ require 'travis/model' lastBuildState: DS.attr('string') lastBuildStartedAt: DS.attr('string') lastBuildFinishedAt: DS.attr('string') + _lastBuildDuration: DS.attr('number') lastBuild: DS.belongsTo('Travis.Build') @@ -21,17 +22,14 @@ require 'travis/model' ).property('lastBuildId', 'lastBuildNumber') allBuilds: (-> - allBuilds = DS.RecordArray.create - type: Travis.Build - content: Ember.A([]) - store: @get('store') - @get('store').registerRecordArray(allBuilds, Travis.Build); - allBuilds + Travis.Build.find() ).property() builds: (-> id = @get('id') builds = Travis.Build.byRepoId id, event_type: 'push' + + # TODO: move to controller array = Travis.ExpandableRecordArray.create type: Travis.Build content: Ember.A([]) @@ -40,7 +38,7 @@ require 'travis/model' array.load(builds) id = @get('id') - array.observe(@get('allBuilds'), (build) -> build.get('repo.id') == id && !build.get('isPullRequest') ) + array.observe(@get('allBuilds'), (build) -> build.get('isLoaded') && build.get('eventType') && build.get('repo.id') == id && !build.get('isPullRequest') ) array ).property() @@ -56,7 +54,7 @@ require 'travis/model' array.load(builds) id = @get('id') - array.observe(@get('allBuilds'), (build) -> @get('repositoryId') == id && build.get('isPullRequest') ) + array.observe(@get('allBuilds'), (build) -> build.get('isLoaded') && build.get('eventType') && build.get('repo.id') == id && build.get('isPullRequest') ) array ).property() @@ -78,10 +76,10 @@ require 'travis/model' ).property('slug') lastBuildDuration: (-> - duration = @get('data.last_build_duration') + duration = @get('_lastBuildDuration') duration = Travis.Helpers.durationFrom(@get('lastBuildStartedAt'), @get('lastBuildFinishedAt')) unless duration duration - ).property('data.last_build_duration', 'lastBuildStartedAt', 'lastBuildFinishedAt') + ).property('_lastBuildDuration', 'lastBuildStartedAt', 'lastBuildFinishedAt') sortOrder: (-> # cuz sortAscending seems buggy when set to false @@ -118,6 +116,9 @@ require 'travis/model' search: (query) -> @find(search: query, orderBy: 'name') + withLastBuild: -> + @filter( (repo) -> repo.get('lastBuildId') ) + bySlug: (slug) -> repo = $.select(@find().toArray(), (repo) -> repo.get('slug') == slug) if repo.length > 0 then repo else @find(slug: slug) diff --git a/assets/scripts/app/models/sponsor.coffee b/assets/scripts/app/models/sponsor.coffee index 82b9564b..201173fb 100644 --- a/assets/scripts/app/models/sponsor.coffee +++ b/assets/scripts/app/models/sponsor.coffee @@ -4,10 +4,11 @@ require 'travis/model' type: DS.attr('string') url: DS.attr('string') link: DS.attr('string') + _image: DS.attr('string') image: (-> - "/images/sponsors/#{@get('data.image')}" - ).property('data.image') + "/images/sponsors/#{@get('_image')}" + ).property('_image') Travis.Sponsor.reopenClass decks: -> diff --git a/assets/scripts/app/models/user.coffee b/assets/scripts/app/models/user.coffee index 4128b8d2..a6f2779f 100644 --- a/assets/scripts/app/models/user.coffee +++ b/assets/scripts/app/models/user.coffee @@ -2,7 +2,7 @@ require 'travis/ajax' require 'travis/model' @Travis.User = Travis.Model.extend - _name: DS.attr('string', key: 'name') + _name: DS.attr('string') email: DS.attr('string') login: DS.attr('string') token: DS.attr('string') @@ -22,9 +22,11 @@ require 'travis/model' ).property('login', '_name') init: -> - @poll() if @get('isSyncing') @_super() + # TODO: the next line fails, check this + #@poll() if @get('isSyncing') + Ember.run.next this, -> transaction = @get('store').transaction() transaction.add this diff --git a/assets/scripts/app/models/worker.coffee b/assets/scripts/app/models/worker.coffee index 3458d852..737bf113 100644 --- a/assets/scripts/app/models/worker.coffee +++ b/assets/scripts/app/models/worker.coffee @@ -4,10 +4,7 @@ require 'travis/model' state: DS.attr('string') name: DS.attr('string') host: DS.attr('string') - - payload: (-> - @get('data.payload') - ).property('data.payload') + payload: DS.attr('object') number: (-> @get('name').match(/\d+$/)[0] diff --git a/assets/scripts/app/pusher.coffee b/assets/scripts/app/pusher.coffee index daa054ca..019c70ff 100644 --- a/assets/scripts/app/pusher.coffee +++ b/assets/scripts/app/pusher.coffee @@ -24,10 +24,12 @@ $.extend Travis.Pusher.prototype, @pusher.subscribeAll() subscribe: (channel) -> + console.log("subscribing to #{channel}") channel = @prefix(channel) @pusher.subscribe(channel).bind_all((event, data) => @receive(event, data)) unless @pusher?.channel(channel) unsubscribe: (channel) -> + console.log("unsubscribing from #{channel}") channel = @prefix(channel) @pusher.unsubscribe(channel) if @pusher?.channel(channel) @@ -45,7 +47,7 @@ $.extend Travis.Pusher.prototype, Travis.Job.find(data.job.id).clearLog() Ember.run.next -> - Travis.app.store.receive(event, data) + Travis.store.receive(event, data) normalize: (event, data) -> switch event diff --git a/assets/scripts/app/routes.coffee b/assets/scripts/app/routes.coffee index c0cd5130..c7cd5df0 100644 --- a/assets/scripts/app/routes.coffee +++ b/assets/scripts/app/routes.coffee @@ -1,374 +1,287 @@ require 'travis/location' +require 'travis/line_number_parser' +Ember.Router.reopen + location: (if testMode? then Ember.NoneLocation.create() else Travis.Location.create()) + + handleURL: (url) -> + url = url.replace(/#.*?$/, '') + try + @_super(url) + catch error + @_super('/not-found') + +# TODO: don't reopen Ember.Route to add events, there should be +# a better way (like "parent" resource for everything inside map) Ember.Route.reopen - enter: (router) -> - @_super(router) - _gaq.push(['_trackPageview', @absoluteRoute(router)]) if @get('isLeafRoute') && _gaq? + events: + afterSignIn: (path) -> + @routeTo(path) -defaultRoute = Ember.Route.extend - route: '/' - index: 1000 + afterSignOut: -> + @routeTo('/') -lineNumberRoute = Ember.Route.extend - route: '#L:number' - index: 1 - connectOutlets: (router) -> - router.saveLineNumberHash() - - dynamicSegmentPattern: "([0-9]+)" - -Travis.Router = Ember.Router.extend - location: 'travis' - # enableLogging: true - enableLogging: false - initialState: 'loading' - - showRoot: Ember.Route.transitionTo('root.home.show') - showStats: Ember.Route.transitionTo('root.stats') - - showRepo: Ember.Route.transitionTo('root.home.repo.show') - showBuilds: Ember.Route.transitionTo('root.home.repo.builds.index') - showBuild: Ember.Route.transitionTo('root.home.repo.builds.show') - showPullRequests: Ember.Route.transitionTo('root.home.repo.pullRequests') - showBranches: Ember.Route.transitionTo('root.home.repo.branches') - showEvents: Ember.Route.transitionTo('root.home.repo.events') - showJob: Ember.Route.transitionTo('root.home.repo.job') - - showProfile: Ember.Route.transitionTo('root.profile') - showAccount: Ember.Route.transitionTo('root.profile.account') - showUserProfile: Ember.Route.transitionTo('root.profile.account.profile') - - saveLineNumberHash: (path) -> - Ember.run.next this, -> - path = path || @get('location').getURL() - if match = path.match(/#L\d+$/) - @set 'repoController.lineNumberHash', match[0] - - reload: -> - console.log 'Triggering reload' - url = @get('location').getURL() - @transitionTo('loading') - # Without ember next @route sometimes hit the place where HistoryLocation - # does not have any state set up yet, so it's best to defer it a little bit. - Ember.run.next this, -> - @route(url) + routeTo: (path) -> + return unless path + @router.handleURL(path) + @router.location.setURL(path) signedIn: -> - !!Travis.app.get('auth.user') + @controllerFor('currentUser').get('content') - needsAuth: (path) -> - path.indexOf('/profile') == 0 - - afterSignOut: -> - @authorize('/') - - loading: Ember.Route.extend - routePath: (router, path) -> - router.saveLineNumberHash(path) - router.authorize(path) - Travis.app.autoSignIn() unless router.signedIn() + redirect: -> + if @get('needsAuth') + @authorize(@router.location.getURL()) + else + @_super.apply this, arguments + Travis.autoSignIn() unless @signedIn() authorize: (path) -> - if !@signedIn() && @needsAuth(path) - Travis.app.storeAfterSignInPath(path) - @transitionTo('root.auth') + if !@signedIn() + Travis.storeAfterSignInPath(path) + @transitionTo('auth') + +Travis.Router.reopen + transitionTo: -> + this.container.lookup('controller:repo').set('lineNumber', null) + + @_super.apply this, arguments + + +Travis.Router.map -> + @resource 'index', path: '/', -> + @route 'current', path: '/' + @resource 'repo', path: '/:owner/:name', -> + @route 'index', path: '/' + @resource 'build', path: '/builds/:build_id' + @resource 'job', path: '/jobs/:job_id' + @resource 'builds', path: '/builds' + @resource 'pullRequests', path: '/pull_requests' + @resource 'branches', path: '/branches' + + @route 'stats', path: '/stats' + @route 'auth', path: '/auth' + @route 'notFound', path: '/not-found' + + @resource 'profile', path: '/profile', -> + @route 'index', path: '/' + @resource 'account', path: '/:login', -> + @route 'index', path: '/' + @route 'profile', path: '/profile' + +Travis.ApplicationRoute = Ember.Route.extend Travis.LineNumberParser, + setupController: -> + @_super.apply this, arguments + + this.controllerFor('repo').set('lineNumber', @fetchLineNumber()) + +Travis.IndexCurrentRoute = Ember.Route.extend + renderTemplate: -> + @render 'repo' + @render 'build', outlet: 'pane', into: 'repo' + + setupController: -> + @container.lookup('controller:repo').activate('index') + +Travis.AbstractBuildsRoute = Ember.Route.extend + renderTemplate: -> + @render 'builds', outlet: 'pane', into: 'repo' + + setupController: -> + @container.lookup('controller:repo').activate(@get('contentType')) + +Travis.BuildsRoute = Travis.AbstractBuildsRoute.extend(contentType: 'builds') +Travis.PullRequestsRoute = Travis.AbstractBuildsRoute.extend(contentType: 'pull_requests') +Travis.BranchesRoute = Travis.AbstractBuildsRoute.extend(contentType: 'branches') + +Travis.BuildRoute = Ember.Route.extend + renderTemplate: -> + @render 'build', outlet: 'pane', into: 'repo' + + serialize: (model, params) -> + id = if model.get then model.get('id') else model + + { build_id: id } + + setupController: (controller, model) -> + model = Travis.Build.find(model) if model && !model.get + + repo = @container.lookup('controller:repo') + repo.set('build', model) + repo.activate('build') + +Travis.JobRoute = Ember.Route.extend + renderTemplate: -> + @render 'job', outlet: 'pane', into: 'repo' + + serialize: (model, params) -> + id = if model.get then model.get('id') else model + + { job_id: id } + + setupController: (controller, model) -> + model = Travis.Job.find(model) if model && !model.get + + repo = @container.lookup('controller:repo') + repo.set('job', model) + repo.activate('job') + +Travis.RepoIndexRoute = Ember.Route.extend + setupController: (controller, model) -> + @container.lookup('controller:repo').activate('current') + + renderTemplate: -> + @render 'build', outlet: 'pane', into: 'repo' + +Travis.RepoRoute = Ember.Route.extend + renderTemplate: -> + @render 'repo' + + setupController: (controller, model) -> + # TODO: if repo is just a data hash with id and slug load it + # as incomplete record + model = Travis.Repo.find(model.id) if model && !model.get + controller.set('repo', model) + + serialize: (repo) -> + slug = if repo.get then repo.get('slug') else repo.slug + [owner, name] = slug.split('/') + { owner: owner, name: name } + + deserialize: (params) -> + slug = "#{params.owner}/#{params.name}" + content = Ember.Object.create slug: slug, isLoaded: false, isLoading: true + proxy = Ember.ObjectProxy.create(content: content) + + repos = Travis.Repo.bySlug(slug) + + observer = -> + if repos.get 'isLoaded' + repos.removeObserver 'isLoaded', observer + proxy.set 'isLoading', false + + if repos.get('length') == 0 + # isError is also used in DS.Model, but maybe we should use something + # more focused like notFound later + proxy.set 'isError', true + else + proxy.set 'content', repos.objectAt(0) + + if repos.length + proxy.set('content', repos[0]) else - @transitionTo('root') - @route(path) + repos.addObserver 'isLoaded', observer - root: Ember.Route.extend - route: '/' - loading: Ember.State.extend() - afterSignIn: (-> ) + proxy - auth: Ember.Route.extend - route: '/auth' - customRegexp: /^\/?auth($|\/)/ - connectOutlets: (router) -> - router.get('applicationView').connectLayout 'simple' - $('body').attr('id', 'auth') - router.get('applicationController').connectOutlet('top', 'top') - router.get('applicationController').connectOutlet('main', 'signin') +Travis.IndexRoute = Ember.Route.extend + renderTemplate: -> + $('body').attr('id', 'home') - afterSignIn: (router, path) -> - router.route(path || '/') + @render 'repos', outlet: 'left' + @render 'sidebar', outlet: 'right' + @render 'top', outlet: 'top' + @render 'flash', outlet: 'flash' - stats: Ember.Route.extend - route: '/stats' - customRegexp: /^\/?stats($|\/)/ - connectOutlets: (router) -> - router.get('applicationView').connectLayout 'simple' - $('body').attr('id', 'stats') - router.get('applicationController').connectOutlet 'top', 'top' - router.get('applicationController').connectOutlet 'main', 'stats' + setupController: (controller)-> + @container.lookup('controller:repos').activate() + @container.lookup('controller:application').connectLayout 'home' - profile: Ember.Route.extend - initialState: 'index' - route: '/profile' +Travis.StatsRoute = Ember.Route.extend + renderTemplate: -> + $('body').attr('id', 'stats') - connectOutlets: (router) -> - router.get('applicationView').connectLayout 'profile' - $('body').attr('id', 'profile') - router.get('accountsController').set('content', Travis.Account.find()) - router.get('applicationController').connectOutlet 'top', 'top' - router.get('applicationController').connectOutlet 'left', 'accounts' - router.get('applicationController').connectOutlet 'flash', 'flash' + @render 'top', outlet: 'top' + @render 'stats' - index: Ember.Route.extend - route: '/' - connectOutlets: (router) -> - router.get('applicationController').connectOutlet 'main', 'profile' - router.get('profileController').activate 'hooks' + setupController: -> + @container.lookup('controller:application').connectLayout('simple') - account: Ember.Route.extend - initialState: 'index' - route: '/:login' +Travis.NotFoundRoute = Ember.Route.extend + renderTemplate: -> + $('body').attr('id', 'not-found') - connectOutlets: (router, account) -> - if account - params = { login: account.get('login') } - router.get('profileController').setParams(params) - else - router.send 'showProfile' + @render 'top', outlet: 'top' + @render 'not_found' - deserialize: (router, params) -> - controller = router.get('accountsController') + setupController: -> + @container.lookup('controller:application').connectLayout('simple') - unless controller.get 'content' - controller.set('content', Travis.Account.find()) +Travis.ProfileRoute = Ember.Route.extend + needsAuth: true - account = controller.findByLogin(params.login) + setupController: -> + @container.lookup('controller:application').connectLayout('profile') + @container.lookup('controller:accounts').set('content', Travis.Account.find()) - if account - account - else - deferred = $.Deferred() + renderTemplate: -> + $('body').attr('id', 'profile') - observer = -> - if account = controller.findByLogin(params.login) - controller.removeObserver 'content.length', observer - deferred.resolve account - controller.addObserver 'content.length', observer + @render 'top', outlet: 'top' + @render 'accounts', outlet: 'left' + @render 'flash', outlet: 'flash' + @render 'profile' - deferred.promise() +Travis.ProfileIndexRoute = Ember.Route.extend + setupController: -> + @container.lookup('controller:profile').activate 'hooks' - serialize: (router, account) -> - if account - { login: account.get('login') } - else - {} + renderTemplate: -> + @render 'hooks', outlet: 'pane', into: 'profile', controller: 'profile' - index: Ember.Route.extend - route: '/' - connectOutlets: (router) -> - router.get('profileController').activate 'hooks' +Travis.AccountRoute = Ember.Route.extend + setupController: (controller, account) -> + profileController = @container.lookup('controller:profile') + profileController.activate 'hooks' - profile: Ember.Route.extend - route: '/profile' + if account + params = { login: account.get('login') } + profileController.setParams(params) - connectOutlets: (router) -> - router.get('profileController').activate 'user' + deserialize: (params) -> + controller = @container.lookup('controller:accounts') + account = controller.findByLogin(params.login) - home: Ember.Route.extend - route: '/' - connectOutlets: (router) -> - router.get('applicationView').connectLayout 'home' - $('body').attr('id', 'home') - router.get('applicationController').connectOutlet 'left', 'repos' - router.get('applicationController').connectOutlet 'right', 'sidebar' - router.get('applicationController').connectOutlet 'top', 'top' - router.get('applicationController').connectOutlet 'main', 'repo' - router.get('applicationController').connectOutlet 'flash', 'flash' - router.get('reposController').activate() - router.get('repoController').set('repos', router.get('reposController')) + if account + account + else + content = Ember.Object.create(login: params.login) + proxy = Ember.ObjectProxy.create(content: content) - show: Ember.Route.extend - route: '/' - connectOutlets: (router) -> - router.get('repoController').activate('index') + observer = -> + if account = controller.findByLogin(params.login) + controller.removeObserver 'content.length', observer + proxy.set('content', account) + controller.addObserver 'content.length', observer - initialState: 'default' - default: defaultRoute - lineNumber: lineNumberRoute + proxy - showWithLineNumber: Ember.Route.extend - route: '/#/L:number' - connectOutlets: (router) -> - router.get('repoController').activate('index') + serialize: (account) -> + if account + { login: account.get('login') } + else + {} - repo: Ember.Route.extend - route: '/:owner/:name' - dynamicSegmentPattern: "([^/#]+)" +Travis.AccountIndexRoute = Ember.Route.extend + setupController: -> + @container.lookup('controller:profile').activate 'hooks' - connectOutlets: (router, repo) -> - if repo && repo.constructor != Travis.Repo - repo = Travis.Repo.find(repo.id) - router.get('repoController').set 'repo', repo + renderTemplate: -> + @render 'hooks', outlet: 'pane', into: 'profile' - deserialize: (router, params) -> - slug = "#{params.owner}/#{params.name}" - repos = Travis.Repo.bySlug(slug) - deferred = $.Deferred() +Travis.AccountProfileRoute = Ember.Route.extend + setupController: -> + @container.lookup('controller:profile').activate 'user' - observer = -> - if repos.get 'isLoaded' - repos.removeObserver 'isLoaded', observer - deferred.resolve repos.objectAt(0) + renderTemplate: -> + @render 'user', outlet: 'pane', into: 'profile' - if repos.length - deferred.resolve repos[0] - else - repos.addObserver 'isLoaded', observer +Travis.AuthRoute = Ember.Route.extend + renderTemplate: -> + $('body').attr('id', 'auth') - deferred.promise() + @render 'top', outlet: 'top' + @render 'auth.signin' - serialize: (router, repo) -> - if typeof repo == 'string' - [owner, name] = repo.split '/' - { owner: owner, name: name } - else if repo && repo.constructor == Travis.Repo - { owner: repo.get('owner'), name: repo.get('name') } - else if repo && repo.id && repo.slug - [owner, name] = repo.slug.split '/' - { owner: owner, name: name } - else - # TODO: it would be nice to handle 404 somehow - {} - - show: Ember.Route.extend - route: '/' - connectOutlets: (router) -> - router.get('repoController').activate('current') - - initialState: 'default' - default: defaultRoute - lineNumber: lineNumberRoute - - builds: Ember.Route.extend - route: '/builds' - - index: Ember.Route.extend - route: '/' - connectOutlets: (router, repo) -> - router.get('repoController').activate 'builds' - - show: Ember.Route.extend - route: '/:build_id' - connectOutlets: (router, build) -> - unless build.get - # TODO: apparently when I use id in url, it will pass it - # here, why doesn't it use deserialize? - build = Travis.Build.find(build) - router.get('repoController').set 'build', build - router.get('repoController').activate 'build' - - serialize: (router, build) -> - if build.get - { build_id: build.get('id') } - else - { build_id: build } - - deserialize: (router, params) -> - # Something is wrong here. If I don't use deferred, id is not - # initialized and url ends up being /jobs/null - # This should not be needed, as id should be immediately set on the - # record. - # TODO: find out why it happens - build = Travis.Build.find params.build_id - - if build.get 'id' - build - else - deferred = $.Deferred() - - observer = -> - if build.get 'id' - build.removeObserver 'id', observer - deferred.resolve build - - build.addObserver 'id', observer - - deferred.promise() - - # TODO: this is not dry, but for some weird - # reason Mixins don't play nice with Ember.Route - initialState: 'default' - default: defaultRoute - lineNumber: lineNumberRoute - dynamicSegmentPattern: "([^/#]+)" - - logRedirect: Ember.Route.extend - route: '/log.txt' - connectOutlets: (router) -> - build = router.get('repoController').get 'build' - - observer = -> - if logId = build.get('jobs.firstObject.log.id') - window.location = Travis.Urls.plainTextLog(logId) - - build.removeObserver('jobs.firstObject.log.id', observer) - - build.addObserver('jobs.firstObject.log.id', observer) - - pullRequests: Ember.Route.extend - route: '/pull_requests' - connectOutlets: (router, repo) -> - router.get('repoController').activate 'pull_requests' - - branches: Ember.Route.extend - route: '/branches' - connectOutlets: (router, repo) -> - router.get('repoController').activate 'branches' - - events: Ember.Route.extend - route: '/events' - connectOutlets: (router, repo) -> - router.get('repoController').activate 'events' - - job: Ember.Route.extend - route: '/jobs/:job_id' - dynamicSegmentPattern: "([^/#]+)" - connectOutlets: (router, job) -> - unless job.get - # In case I use id - job = Travis.Job.find(job) - router.get('repoController').set 'job', job - router.get('repoController').activate 'job' - - serialize: (router, job) -> - if job.get - { job_id: job.get('id') } - else - { job_id: job } - - deserialize: (router, params) -> - job = Travis.Job.find params.job_id - - if job.get 'id' - job - else - deferred = $.Deferred() - - observer = -> - if job.get 'id' - job.removeObserver 'id', observer - deferred.resolve job - job.addObserver 'id', observer - deferred.promise() - - initialState: 'default' - default: defaultRoute - lineNumber: lineNumberRoute - - logRedirect: Ember.Route.extend - route: '/log.txt' - connectOutlets: (router, job) -> - job = router.get('repoController').get 'job' - - observer = -> - if logId = job.get('log.id') - window.location = Travis.Urls.plainTextLog(logId) - - job.removeObserver('log.id', observer) - - job.addObserver('log.id', observer) + setupController: -> + @container.lookup('controller:application').connectLayout('simple') diff --git a/assets/scripts/app/store.coffee b/assets/scripts/app/store.coffee index 2ad698cb..92519e07 100644 --- a/assets/scripts/app/store.coffee +++ b/assets/scripts/app/store.coffee @@ -1,30 +1,23 @@ require 'store/rest_adapter' -DATA_PROXY = - get: (name) -> - @savedData[name] +coerceId = (id) -> if id == null then null else id+'' Travis.Store = DS.Store.extend - revision: 4 + revision: 12 adapter: Travis.RestAdapter.create() init: -> @_super.apply this, arguments @_loadedData = {} + @clientIdToComplete = {} - load: (type, id, hash) -> + load: (type, data, prematerialized) -> result = @_super.apply this, arguments - if result && result.clientId + if result && result.clientId && @clientIdToComplete[result.clientId] == undefined # I assume that everything that goes through load is complete record # representation, incomplete hashes from pusher go through merge() - record = @findByClientId type, result.clientId - record.set 'incomplete', false - record.set 'complete', true - # setting both incomplete and complete may be weird, but it's easier to - # work with both values. I need to check if record has already been completed - # and in order to do that, without having 'complete', I would need to check - # for incomplete == false, which looks worse + @clientIdToComplete[result.clientId] = true result @@ -34,38 +27,26 @@ Travis.Store = DS.Store.extend array.set('isLoaded', true) for array in @typeMapFor(type).recordArrays result - merge: (type, id, hash) -> - if hash == undefined - hash = id - primaryKey = type.proto().primaryKey - Ember.assert("A data hash was loaded for a record of type " + type.toString() + " but no primary key '" + primaryKey + "' was provided.", hash[primaryKey]) - id = hash[primaryKey] + merge: (type, data, incomplete) -> + id = coerceId data.id - typeMap = @typeMapFor(type) - dataCache = typeMap.cidToHash - clientId = typeMap.idToCid[id] - recordCache = @get('recordCache') - - if clientId != undefined - if (data = dataCache[clientId]) && (typeof data == 'object') - for key, value of hash - if ( descriptor = Object.getOwnPropertyDescriptor(data, key) ) && descriptor.set - Ember.set(data, key, value) - else - data[key] = value - else - dataCache[clientId] = hash - - if record = recordCache[clientId] - record.send('didChangeData') + typeMap = @typeMapFor(type) + clientId = typeMap.idToCid[id] + record = @recordCache[clientId] + if record + @get('adapter').merge(this, record, data) else - clientId = @pushHash(hash, id, type) + if (savedData = @clientIdToData[clientId]) && savedData.id? + $.extend(savedData, data) + else + result = @load(type, data, {id: data.id}) - if clientId - DATA_PROXY.savedData = hash - @updateRecordArrays(type, clientId, DATA_PROXY) + if result && result.clientId + clientId = result.clientId + if incomplete + @clientIdToComplete[result.clientId] = false - { id: id, clientId: clientId } + { clientId: clientId, id: id } isInStore: (type, id) -> !!@typeMapFor(type).idToCid[id] @@ -76,6 +57,7 @@ Travis.Store = DS.Store.extend mappings = @adapter.get('mappings') type = mappings[name] + if event == 'build:started' && data.build.commit # TODO: commit should be a sideload record on build, not mixed with it build = data.build @@ -96,8 +78,10 @@ Travis.Store = DS.Store.extend if event == 'job:log' - if job = @find(Travis.Job, data['job']['id']) - job.appendLog(data['job']['_log']) + console.log 'store: received job:log event', data if Log.DEBUG + data = data.job + job = @find(Travis.Job, data.id) + job.appendLog(number: parseInt(data.number), content: data._log) else if data[type.singularName()] @_loadOne(this, type, data) else if data[type.pluralName()] @@ -113,14 +97,29 @@ Travis.Store = DS.Store.extend if type == Travis.Build && (json.repository || json.repo) @loadIncomplete(Travis.Repo, json.repository || json.repo) - @loadIncomplete(type, json[root]) + result = @loadIncomplete(type, json[root]) + if result.id + @find(type, result.id) addLoadedData: (type, clientId, hash) -> id = hash.id @_loadedData[type.toString()] ||= {} loadedData = (@_loadedData[type][clientId] ||= []) - for key of hash - loadedData.pushObject key unless loadedData.contains(key) + + serializer = @get('adapter.serializer') + + Ember.get(type, 'attributes').forEach( (name, meta) -> + value = @extractAttribute(type, hash, name) + if value != undefined + loadedData.pushObject name unless loadedData.contains(name) + , serializer) + + Ember.get(type, 'relationshipsByName').forEach( (name, relationship) -> + key = @_keyForBelongsTo(type, relationship.key) + value = @extractBelongsTo(type, hash, key) + if value != undefined + loadedData.pushObject name unless loadedData.contains(name) + , serializer) isDataLoadedFor: (type, clientId, key) -> if recordsData = @_loadedData[type.toString()] @@ -130,39 +129,48 @@ Travis.Store = DS.Store.extend loadIncomplete: (type, hash, options) -> options ?= {} - id = hash.id + id = coerceId hash.id typeMap = @typeMapFor(type) - dataCache = typeMap.cidToHash + cidToData = @clientIdToData clientId = typeMap.idToCid[id] - if dataCache[clientId] && options.skipIfExists + if clientId && cidToData[clientId] && options.skipIfExists return - result = @merge(type, hash) - + result = @merge(type, hash, true) if result && result.clientId @addLoadedData(type, result.clientId, hash) - record = @findByClientId(type, result.clientId) - unless record.get('complete') - record.loadedAsIncomplete() + #@_updateRelationships(type, hash) - @_updateAssociations(type, type.singularName(), hash) + result - record + materializeRecord: (type, clientId, id) -> + record = @_super.apply this, arguments + + if @clientIdToComplete[clientId] != undefined && !@clientIdToComplete[clientId] + record.set 'incomplete', true + else + record.set 'incomplete', false + + record _loadMany: (store, type, json) -> root = type.pluralName() @adapter.sideload(store, type, json, root) @loadMany(type, json[root]) - _updateAssociations: (type, name, data) -> - Em.get(type, 'associationsByName').forEach (key, meta) => + _updateRelationships: (type, data) -> + Em.get(type, 'relationshipsByName').forEach (key, meta) => if meta.kind == 'belongsTo' id = data["#{key}_id"] if clientId = @typeMapFor(meta.type).idToCid[id] if parent = this.findByClientId(meta.type, clientId, id) dataProxy = parent.get('data') - if ids = dataProxy.get("#{name}_ids") - ids.pushObject(data.id) unless data.id in ids - parent.send('didChangeData'); + if ids = dataProxy['hasMany'][type.pluralName()] + unless data.id in ids + state = parent.get('stateManager.currentState.path') + unless state == "rootState.loaded.materializing" + parent.send('materializingData') + ids.pushObject(data.id) + parent.notifyPropertyChange('data') diff --git a/assets/scripts/app/store/rest_adapter.coffee b/assets/scripts/app/store/rest_adapter.coffee index 66aaba02..b240e3d4 100644 --- a/assets/scripts/app/store/rest_adapter.coffee +++ b/assets/scripts/app/store/rest_adapter.coffee @@ -1,7 +1,67 @@ require 'travis/ajax' require 'models' -@Travis.RestAdapter = DS.RESTAdapter.extend +DS.JSONTransforms['object'] = { + deserialize: (serialized) -> serialized + serialize: (deserialized) -> deserialized +} + +Travis.Serializer = DS.RESTSerializer.extend + # The next 3 methods specify the behavior of adding records to dirty sets + # (ie. which records will be treated as dirty on the next commit). We don't + # allow to change most of the records on the client, so for anything except + # the User, we ignore dirtyiness. + dirtyRecordsForAttributeChange: (dirtySet, record) -> + if record.constructor == Travis.User + @_super.apply this, arguments + + dirtyRecordsForBelongsToChange: (dirtySet, record) -> + if record.constructor == Travis.User + @_super.apply this, arguments + + dirtyRecordsForHasManyChange: (dirtySet, record) -> + if record.constructor == Travis.User + @_super.apply this, arguments + + merge: (record, serialized) -> + data = record.get('data') + + # TODO: write test that ensures that we go to materializingData + # only if we can + state = record.get('stateManager.currentState.path') + unless state == "rootState.loaded.materializing" + record.send('materializingData') + + record.eachAttribute( (name, attribute) -> + value = @extractAttribute(record.constructor, serialized, name) + if value != undefined + value = @deserializeValue(value, attribute.type) + if value != data.attributes[name] + record.materializeAttribute(name, value) + record.notifyPropertyChange(name) + , this) + + record.eachRelationship( (name, relationship) -> + if relationship.kind == 'belongsTo' + key = @_keyForBelongsTo(record.constructor, relationship.key) + value = @extractBelongsTo(record.constructor, serialized, key) + + if value != undefined && data.belongsTo[name] != value + record.materializeBelongsTo name, value + record.notifyPropertyChange(name) + else if relationship.kind == 'hasMany' + key = @_keyForHasMany(record.constructor, relationship.key) + value = @extractHasMany(record.constructor, serialized, key) + + if value != undefined + record.materializeHasMany name, value + record.notifyPropertyChange(name) + , this) + + record.notifyPropertyChange('data') + +Travis.RestAdapter = DS.RESTAdapter.extend + serializer: Travis.Serializer mappings: broadcasts: Travis.Broadcast repositories: Travis.Repo @@ -38,3 +98,53 @@ require 'models' return else @_super.apply this, arguments + + merge: (store, record, serialized) -> + @get('serializer').merge(record, serialized) + + didFindRecord: (store, type, payload, id) -> + if (type == Travis.Build || type == Travis.Job) && payload.commit? + payload.commits = payload.commit + delete payload.commit + + @_super.apply this, arguments + + didSaveRecord: (store, type, record, payload) -> + # API sometimes return { result: true } response + # which does not play nice with ember-data. For now + # let's just change payload to have serialized record + # included, but ideally it should be fixed in the API + # to be consistent across all the endpoints. + if payload?.result == true + payload = {} + payload[type.singularName()] = record.serialize() + + @_super(store, type, record, payload) + +Travis.RestAdapter.map 'Travis.Commit', {} + +Travis.RestAdapter.map 'Travis.Build', { + repoId: { key: 'repository_id' } + repo: { key: 'repository_id' } + _duration: { key: 'duration' } + jobs: { key: 'job_ids' } + _config: { key: 'config' } +} + +Travis.RestAdapter.map 'Travis.Repo', { + _lastBuildDuration: { key: 'last_build_duration' } +} + +Travis.RestAdapter.map 'Travis.Job', { + repoId: { key: 'repository_id' } + repo: { key: 'repository_id' } + _config: { key: 'config' } +} + +Travis.RestAdapter.map 'Travis.User', { + _name: { key: 'name' } +} + +Travis.RestAdapter.map 'Travis.Sponsor', { + _image: { key: 'image' } +} diff --git a/assets/scripts/app/tailing.coffee b/assets/scripts/app/tailing.coffee index 9c195fd4..fab3fec8 100644 --- a/assets/scripts/app/tailing.coffee +++ b/assets/scripts/app/tailing.coffee @@ -12,7 +12,7 @@ $.extend Travis.Tailing.prototype, @positionButton() Ember.run.later(@run.bind(this), @options.timeout) if @active() - toggle: (event) -> + toggle: -> if @active() then @stop() else @start() active: -> diff --git a/assets/scripts/app/templates/auth/signin.hbs b/assets/scripts/app/templates/auth/signin.hbs index a5609048..867fa2b5 100644 --- a/assets/scripts/app/templates/auth/signin.hbs +++ b/assets/scripts/app/templates/auth/signin.hbs @@ -6,6 +6,6 @@ {{else}}

Sign in

- Please sign in with GitHub. + Please sign in with GitHub.

{{/if}} diff --git a/assets/scripts/app/templates/builds/list.hbs b/assets/scripts/app/templates/builds/list.hbs index 6dcdcd4f..d0a928d2 100644 --- a/assets/scripts/app/templates/builds/list.hbs +++ b/assets/scripts/app/templates/builds/list.hbs @@ -1,4 +1,4 @@ -{{#if builds.isLoaded}} +{{#if content.isLoaded}} @@ -21,14 +21,14 @@ - {{#each build in builds}} + {{#each build in controller}} {{#view Travis.BuildsItemView contextBinding="build"}} - {{#if commit.pullRequestNumber}} + {{#if view.isPullRequestsList}}
{{#if id}} - + {{#linkTo "build" repo this}} {{number}} - + {{/linkTo}} {{/if}} @@ -42,7 +42,7 @@ {{commit.committerName}} #{{commit.pullRequestNumber}} @@ -59,9 +59,11 @@ {{/each}}
-

- {{view view.ShowMoreButton}} -

+ {{#if displayShowMoreButton}} +

+ {{view view.ShowMoreButton}} +

+ {{/if}} {{else}}
Loading
-{{/if}} \ No newline at end of file +{{/if}} diff --git a/assets/scripts/app/templates/builds/show.hbs b/assets/scripts/app/templates/builds/show.hbs index b43c996c..447999d3 100644 --- a/assets/scripts/app/templates/builds/show.hbs +++ b/assets/scripts/app/templates/builds/show.hbs @@ -1,15 +1,17 @@ -{{#with view}} - {{#if loading}} - Loading - {{else}} +{{#if loading}} + Loading +{{else}} + {{#if build}}
{{t builds.name}}
- {{#with build}} - {{number}} - {{/with}} + {{#if build.id}} + {{#if build.repo.slug}} + {{#linkTo build repo build}}{{build.number}}{{/linkTo}} + {{/if}} + {{/if}}
{{t builds.state}}
{{capitalize build.state}}
@@ -19,30 +21,32 @@
{{formatDuration build.duration}}
-
-
{{t builds.commit}}
-
{{formatCommit build.commit}}
- {{#if build.isPullRequest}} -
{{t builds.pullRequest}}
-
#{{build.commit.pullRequestNumber}} {{build.commit.pullRequestTitle}}
- {{else}} - {{#if commit.compareUrl}} -
{{t builds.compare}}
-
{{pathFrom build.commit.compareUrl}}
+ {{#if commit}} +
+
{{t builds.commit}}
+
{{formatCommit build.commit}}
+ {{#if build.isPullRequest}} +
{{t builds.pullRequest}}
+
#{{build.commit.pullRequestNumber}} {{build.commit.pullRequestTitle}}
+ {{else}} + {{#if commit.compareUrl}} +
{{t builds.compare}}
+
{{pathFrom build.commit.compareUrl}}
+ {{/if}} {{/if}} - {{/if}} - {{#if commit.authorName}} -
{{t builds.author}}
-
{{build.commit.authorName}}
- {{/if}} - {{#if commit.committerName}} -
{{t builds.committer}}
-
{{build.commit.committerName}}
- {{/if}} -
+ {{#if commit.authorName}} +
{{t builds.author}}
+
{{build.commit.authorName}}
+ {{/if}} + {{#if commit.committerName}} +
{{t builds.committer}}
+
{{build.commit.committerName}}
+ {{/if}} +
+ {{/if}}
{{t builds.message}}
-
{{{formatMessage build.commit.message}}}
+
{{formatMessage build.commit.message}}
{{#unless isMatrix}}
{{t builds.config}}
@@ -54,7 +58,9 @@ {{view Travis.JobsView jobsBinding="build.requiredJobs" required="true"}} {{view Travis.JobsView jobsBinding="build.allowedFailureJobs"}} {{else}} - {{view Travis.LogView contextBinding="build.jobs.firstObject"}} + {{view Travis.LogView jobBinding="build.jobs.firstObject"}} {{/if}} + {{else}} + There are no builds for this repository. {{/if}} -{{/with}} +{{/if}} diff --git a/assets/scripts/app/templates/jobs/list.hbs b/assets/scripts/app/templates/jobs/list.hbs index a59ff266..71aca67c 100644 --- a/assets/scripts/app/templates/jobs/list.hbs +++ b/assets/scripts/app/templates/jobs/list.hbs @@ -24,7 +24,9 @@ {{#if job.id}} - {{number}} + {{#if job.repo.slug}} + {{#linkTo "job" repo job}}{{number}}{{/linkTo}} + {{/if}} {{/if}} diff --git a/assets/scripts/app/templates/jobs/log.hbs b/assets/scripts/app/templates/jobs/log.hbs index 36979890..fa050efb 100644 --- a/assets/scripts/app/templates/jobs/log.hbs +++ b/assets/scripts/app/templates/jobs/log.hbs @@ -1,13 +1,7 @@ -{{view.logSubscriber}} +{{#if view.log.isLoaded}} + {{view Travis.PreView logBinding="view.log"}} -{{#if view.job.log.isLoaded}} - {{! this #with + #if is needed because I want to rerender 'pre' when log changes to properly clean it up, - this should probably be refactored to use container view}} - {{#with view.job.log}} - {{view Travis.PreView logBinding="view.context.log" logUrlBinding="view.logUrl"}} - {{/with}} - - {{#if sponsor.name}} + {{#if view.job.sponsor.name}}

-  
-  
-
+
+ + + + +

+
+  {{#if view.limited}}
+    

+ This log is too long to be displayed. Please reduce the verbosity of your + build or download the the raw log. +

+ {{/if}} +
diff --git a/assets/scripts/app/templates/jobs/running/group.hbs b/assets/scripts/app/templates/jobs/running/group.hbs index dd249448..f7c1b793 100644 --- a/assets/scripts/app/templates/jobs/running/group.hbs +++ b/assets/scripts/app/templates/jobs/running/group.hbs @@ -5,9 +5,9 @@ diff --git a/assets/scripts/app/templates/jobs/show.hbs b/assets/scripts/app/templates/jobs/show.hbs index f49abed4..1d7a9989 100644 --- a/assets/scripts/app/templates/jobs/show.hbs +++ b/assets/scripts/app/templates/jobs/show.hbs @@ -1,23 +1,25 @@ -{{#with view}} - {{#if job.isLoaded}} -
-
-
-
Job
-
- - {{#if job.id}} - {{job.number}} +{{#if job.isLoaded}} +
+
+
+
Job
+
+ + {{#if job.id}} + {{#if job.repo.slug}} + {{#linkTo job repo job}}{{job.number}}{{/linkTo}} {{/if}} -
-
{{t jobs.state}}
-
{{capitalize job.state}}
-
{{t jobs.finished_at}}
-
{{formatTime job.finishedAt}}
-
{{t jobs.duration}}
-
{{formatDuration job.duration}}
-
+ {{/if}} +
+
{{t jobs.state}}
+
{{capitalize job.state}}
+
{{t jobs.finished_at}}
+
{{formatTime job.finishedAt}}
+
{{t jobs.duration}}
+
{{formatDuration job.duration}}
+
+ {{#if commit}}
{{t jobs.commit}}
{{formatCommit commit}}
@@ -34,18 +36,18 @@
{{commit.committerName}}
{{/if}}
+ {{/if}} -
{{t jobs.message}}
-
{{formatMessage commit.message}}
-
{{t jobs.config}}
-
{{formatConfig job.config}}
-
+
{{t jobs.message}}
+
{{formatMessage commit.message}}
+
{{t jobs.config}}
+
{{formatConfig job.config}}
+
- {{view Travis.LogView contextBinding="job"}}} - - {{else}} -
- Loading -
- {{/if}} -{{/with}} + {{view Travis.LogView jobBinding="job"}} + +{{else}} +
+ Loading +
+{{/if}} diff --git a/assets/scripts/app/templates/layouts/profile.hbs b/assets/scripts/app/templates/layouts/profile.hbs index dfd3d499..53b66ed7 100644 --- a/assets/scripts/app/templates/layouts/profile.hbs +++ b/assets/scripts/app/templates/layouts/profile.hbs @@ -18,7 +18,7 @@ -
+
 
diff --git a/assets/scripts/app/templates/layouts/sidebar.hbs b/assets/scripts/app/templates/layouts/sidebar.hbs index 188c02f5..9383426f 100644 --- a/assets/scripts/app/templates/layouts/sidebar.hbs +++ b/assets/scripts/app/templates/layouts/sidebar.hbs @@ -4,7 +4,7 @@
-
+
 
diff --git a/assets/scripts/app/templates/layouts/top.hbs b/assets/scripts/app/templates/layouts/top.hbs index 0c1ec213..1e614260 100644 --- a/assets/scripts/app/templates/layouts/top.hbs +++ b/assets/scripts/app/templates/layouts/top.hbs @@ -1,13 +1,13 @@ - +{{#linkTo index.current}}

Travis

-
+{{/linkTo}} diff --git a/assets/scripts/app/templates/repos/show.hbs b/assets/scripts/app/templates/repos/show.hbs index 00d52e5b..63ce6e62 100644 --- a/assets/scripts/app/templates/repos/show.hbs +++ b/assets/scripts/app/templates/repos/show.hbs @@ -1,27 +1,29 @@ -
+
{{#if view.isEmpty}} {{view Travis.ReposEmptyView}} {{else}} - {{#if view.repo.isLoaded}} - {{#with view.repo}} -

- {{slug}} -

- -

{{description}}

- - {{view Travis.RepoShowStatsView}} - {{view Travis.RepoShowTabsView}} - {{view Travis.RepoShowToolsView}} - {{/with}} - + {{#if isError}} + The repository at {{slug}} was not found. {{else}} - Loading - {{/if}} + {{#if repo.isLoaded}} + {{#with repo}} +

+ {{slug}} +

-
- {{outlet pane}} -
+

{{description}}

+ + {{view Travis.RepoShowTabsView}} + {{view Travis.RepoShowToolsView}} + {{/with}} + +
+ {{outlet pane}} +
+ {{else}} + Loading + {{/if}} + {{/if}} {{/if}}
diff --git a/assets/scripts/app/templates/repos/show/stats.hbs b/assets/scripts/app/templates/repos/show/stats.hbs deleted file mode 100644 index dfbb633e..00000000 --- a/assets/scripts/app/templates/repos/show/stats.hbs +++ /dev/null @@ -1,17 +0,0 @@ - - - diff --git a/assets/scripts/app/templates/repos/show/tabs.hbs b/assets/scripts/app/templates/repos/show/tabs.hbs index f906d3d9..75029f23 100644 --- a/assets/scripts/app/templates/repos/show/tabs.hbs +++ b/assets/scripts/app/templates/repos/show/tabs.hbs @@ -1,60 +1,60 @@ diff --git a/assets/scripts/app/templates/sponsors/decks.hbs b/assets/scripts/app/templates/sponsors/decks.hbs index 08f81627..22eeb210 100644 --- a/assets/scripts/app/templates/sponsors/decks.hbs +++ b/assets/scripts/app/templates/sponsors/decks.hbs @@ -1,4 +1,4 @@ -

{{t layouts.application.sponsers}}

+

{{t layouts.application.sponsors}}