diff --git a/Gemfile b/Gemfile index 7cf60c0b..66eaa70a 100644 --- a/Gemfile +++ b/Gemfile @@ -27,3 +27,8 @@ group :assets do gem 'tilt' gem 'guard' end + +group :development do + gem 'foreman' + gem 'rerun' +end diff --git a/Gemfile.lock b/Gemfile.lock index 2f2fa7ea..965acee7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -155,6 +155,8 @@ GEM multi_json (~> 1.0) faraday (0.8.4) multipart-post (~> 1.1) + foreman (0.59.0) + thor (>= 0.13.6) fssm (0.2.9) guard (1.3.3) listen (>= 0.4.2) @@ -215,6 +217,8 @@ GEM json (~> 1.4) redcarpet (2.1.1) redis (3.0.1) + rerun (0.7.1) + listen rollout (1.1.0) sass (3.2.1) signature (0.1.4) @@ -259,7 +263,7 @@ DEPENDENCIES bunny coffee-script compass - debugger + foreman gh! guard hubble! @@ -268,6 +272,7 @@ DEPENDENCIES rack-contrib! rake-pipeline! rake-pipeline-web-filters! + rerun sinatra sinatra-contrib tilt diff --git a/assets/javascripts/app/app.coffee b/assets/javascripts/app/app.coffee index 13ff4dc6..e9896f25 100644 --- a/assets/javascripts/app/app.coffee +++ b/assets/javascripts/app/app.coffee @@ -21,13 +21,13 @@ require 'data/sponsors' Travis.reopen App: Em.Application.extend + autoinit: false currentUserBinding: 'auth.user' accessTokenBinding: 'auth.user.accessToken' authStateBinding: 'auth.state' init: -> - @_super() - @connect() + @_super.apply this, arguments @store = Travis.Store.create() @store.loadMany(Travis.Sponsor, Travis.SPONSORS) @@ -35,7 +35,7 @@ Travis.reopen @set('auth', Travis.Auth.create(store: @store, endpoint: Travis.config.api_endpoint)) @slider = new Travis.Slider() - @routes = new Travis.Routes() + @pusher = new Travis.Pusher() @tailing = new Travis.Tailing() @@ -44,28 +44,17 @@ Travis.reopen signOut: -> @get('auth').signOut() - @routes.route('') - - render: (name, action, params) -> - layout = @connectLayout(name) - layout.activate(action, params || {}) - $('body').attr('id', name) receive: -> @store.receive.apply(@store, arguments) - connectLayout: (name) -> - unless @get('layout.name') == name - name = $.camelize(name) - viewClass = Travis["#{name}Layout"] - @layout = Travis["#{name}Controller"].create(parent: @controller) - @controller.connectOutlet(outletName: 'layout', controller: @layout, viewClass: viewClass) - @layout - - connect: -> - @controller = Em.Controller.create() - view = Em.View.create - template: Em.Handlebars.compile('{{outlet layout}}') - controller: @controller - view.appendTo(@get('rootElement') || 'body') + toggleSidebar: -> + $('body').toggleClass('maximized') + # TODO gotta force redraws here :/ + element = $('') + $('#top .profile').append(element) + Em.run.later (-> element.remove()), 10 + element = $('') + $('#repository').append(element) + Em.run.later (-> element.remove()), 10 diff --git a/assets/javascripts/app/controllers.coffee b/assets/javascripts/app/controllers.coffee index 52bf9244..99830b9b 100644 --- a/assets/javascripts/app/controllers.coffee +++ b/assets/javascripts/app/controllers.coffee @@ -3,16 +3,6 @@ require 'travis/ticker' Travis.reopen Controller: Em.Controller.extend - init: -> - for name in Array.prototype.slice.apply(arguments) - name = "#{$.camelize(name, false)}Controller" - klass = Travis[$.camelize(name)] || Em.Controller - this[name] = klass.create(parent: this, namespace: Travis, controllers: this) - - connectTop: -> - @connectOutlet(outletName: 'top', controller: @topController, viewClass: Travis.TopView) - @topController.set('tab', @get('name')) - connectOutlet: -> view = @_super.apply(this, arguments) @@ -28,6 +18,10 @@ Travis.reopen TopController: Em.Controller.extend userBinding: 'Travis.app.currentUser' + ApplicationController: Em.Controller.extend() + MainController: Em.Controller.extend() + StatsLayoutController: Em.Controller.extend() + ProfileLayoutController: Em.Controller.extend() require 'controllers/accounts' require 'controllers/builds' diff --git a/assets/javascripts/app/controllers/accounts.coffee b/assets/javascripts/app/controllers/accounts.coffee index 263dea52..a23d4427 100644 --- a/assets/javascripts/app/controllers/accounts.coffee +++ b/assets/javascripts/app/controllers/accounts.coffee @@ -2,6 +2,7 @@ Travis.AccountsController = Ember.ArrayController.extend defaultTab: 'accounts' init: -> + @_super() @activate(@defaultTab) activate: (tab, params) -> @@ -10,3 +11,6 @@ Travis.AccountsController = Ember.ArrayController.extend viewAccounts: -> @set('content', Travis.Account.filter()) + + findByLogin: (login) -> + @find (account) -> account.get('login') == 'login' diff --git a/assets/javascripts/app/controllers/home.coffee b/assets/javascripts/app/controllers/home.coffee index 62d30e91..c900505b 100644 --- a/assets/javascripts/app/controllers/home.coffee +++ b/assets/javascripts/app/controllers/home.coffee @@ -1,13 +1 @@ -Travis.HomeController = Travis.Controller.extend - name: 'home' - - init: -> - @_super('top', 'repositories', 'repository', 'sidebar') - - @connectTop() - @connectOutlet outletName: 'left', controller: @repositoriesController, viewClass: Travis.RepositoriesView - @connectOutlet outletName: 'main', controller: @repositoryController, viewClass: Travis.RepositoryView - @connectOutlet outletName: 'right', controller: @sidebarController, viewClass: Travis.SidebarView - - activate: (action, params) -> - @repositoryController.activate(action, params) +Travis.HomeController = Travis.Controller.extend() diff --git a/assets/javascripts/app/controllers/job.coffee b/assets/javascripts/app/controllers/job.coffee new file mode 100644 index 00000000..e69de29b diff --git a/assets/javascripts/app/controllers/profile.coffee b/assets/javascripts/app/controllers/profile.coffee index bdc6dd75..b6e18569 100644 --- a/assets/javascripts/app/controllers/profile.coffee +++ b/assets/javascripts/app/controllers/profile.coffee @@ -1,17 +1,11 @@ Travis.ProfileController = Travis.Controller.extend name: 'profile' userBinding: 'Travis.app.currentUser' - - init: -> - @_super('top', 'accounts') - @connectTop() - @connectOutlet outletName: 'left', controller: @accountsController, viewClass: Travis.AccountsView - @connectOutlet(outletName: 'main', controller: this, viewClass: Travis.ProfileView) - @accounts = @accountsController.get('content') + accountsBinding: 'Travis.app.router.accountsController' account: (-> login = @get('params.login') || Travis.app.get('currentUser.login') - @accounts.toArray().filter((account) -> account if account.get('login') == login)[0] + @get('accounts').filter((account) -> account if account.get('login') == login)[0] ).property('accounts.length', 'params.login') activate: (action, params) -> diff --git a/assets/javascripts/app/controllers/repository.coffee b/assets/javascripts/app/controllers/repository.coffee index 993c6c44..7d94f22e 100644 --- a/assets/javascripts/app/controllers/repository.coffee +++ b/assets/javascripts/app/controllers/repository.coffee @@ -3,7 +3,7 @@ Travis.RepositoryController = Travis.Controller.extend params: {} init: -> - @_super('builds', 'build', 'job') + @_super.apply this, arguments Ember.run.later(@updateTimes.bind(this), Travis.INTERVALS.updateTimes) updateTimes: -> diff --git a/assets/javascripts/app/controllers/stats.coffee b/assets/javascripts/app/controllers/stats.coffee index e721f882..b0b31704 100644 --- a/assets/javascripts/app/controllers/stats.coffee +++ b/assets/javascripts/app/controllers/stats.coffee @@ -3,8 +3,7 @@ Travis.StatsController = Travis.Controller.extend init: -> @_super('top') - @connectTop() - @connectOutlet(outletName: 'main', controller: this, viewClass: Travis.StatsView) + #@connectOutlet(outletName: 'main', controller: this, viewClass: Travis.StatsView) activate: (action, params) -> # noop diff --git a/assets/javascripts/app/models/worker.coffee b/assets/javascripts/app/models/worker.coffee index c722d08a..482a9a6f 100644 --- a/assets/javascripts/app/models/worker.coffee +++ b/assets/javascripts/app/models/worker.coffee @@ -18,10 +18,6 @@ require 'travis/model' @get('state') == 'working' ).property('state') - urlJob: (-> - "/#{@get('repository')}/jobs/#{@get('job_id')}" if @get('state') == 'working' - ).property('repository', 'job_id', 'state') - repository: (-> @get('payload.repository.slug') ).property('payload.repository.slug') @@ -29,3 +25,7 @@ require 'travis/model' job_id: (-> @get('payload.job.id') ).property('payload.job.id') + + job: (-> + Travis.Job.find @get('job_id') + ).property('job_id') diff --git a/assets/javascripts/app/pusher.coffee b/assets/javascripts/app/pusher.coffee index 12c911ed..c529b9a4 100644 --- a/assets/javascripts/app/pusher.coffee +++ b/assets/javascripts/app/pusher.coffee @@ -1,6 +1,7 @@ Travis.Pusher = -> @active_channels = [] if Travis.config.pusher?.key? + Pusher.warn = @warn @pusher = new Pusher(Travis.config.pusher.key) @subscribe(channel) for channel in Travis.Pusher.CHANNELS this @@ -13,7 +14,7 @@ $.extend Travis.Pusher.prototype, subscribe: (channel) -> if @pusher && @active_channels.indexOf(channel) == -1 @active_channels.push(channel) - @pusher.subscribe(@prefix(channel)).bind_all (event, data) => @receive(event, data) + @pusher.subscribe(@prefix(channel)).bind_all((event, data) => @receive(event, data)) unsubscribe: (channel) -> ix = @active_channels.indexOf(channel) @@ -40,3 +41,5 @@ $.extend Travis.Pusher.prototype, when 'worker:added', 'worker:updated', 'worker:removed' { worker: data } + warn: (type, error) -> + console.warn(error) unless error.data?.message && error.data.message.indexOf('No current subscription') > -1 diff --git a/assets/javascripts/app/routes.coffee b/assets/javascripts/app/routes.coffee index 14151207..59370c57 100644 --- a/assets/javascripts/app/routes.coffee +++ b/assets/javascripts/app/routes.coffee @@ -1,42 +1,225 @@ -Travis.Routes = -> - unless Travis.Routes.initialized - Ember.run.next => - Em.routes.set('usesHistory', true) - Em.routes.set('wantsHistory', true) - Em.routes.set('baseURI', @base_uri) +Travis.Router = Ember.Router.extend + location: 'history' + enableLogging: true + initialState: 'loading' - @add(route, target[0], target[1]) for route, target of Travis.ROUTES - Travis.Routes.initialized = true - -$.extend Travis.Routes.prototype, - base_uri: "#{document.location.protocol}//#{document.location.host}" - - add: (route, layout, action) -> - Em.routes.add route, (params) => - @action(layout, action, params) - - route: (location) -> - location = $(event.target).closest('a')[0].href.replace("#{@base_uri}/", '') if typeof location != 'string' - Em.routes.set('location', location) - - action: (name, action, params) -> - # this needs to be a global reference because Em.routes is global - Travis.app.render(name, action, params) if @before(name, action, params) - - before: (name, action, params) -> - if @requiresAuth(name, action, params) - @requireAuth(name, action, params) - else - true + goToRoot: Ember.Route.transitionTo('root.home.show') + goToStats: Ember.Route.transitionTo('root.stats') + showRepository: Ember.Route.transitionTo('root.home.repository.show') + showBuilds: Ember.Route.transitionTo('root.home.repository.builds.index') + showBuild: Ember.Route.transitionTo('root.home.repository.builds.show') + showPullRequests: Ember.Route.transitionTo('root.home.repository.pullRequests') + showBranches: Ember.Route.transitionTo('root.home.repository.branches') + showJob: Ember.Route.transitionTo('root.home.repository.job') + showProfile: Ember.Route.transitionTo('root.profile') + showAccount: Ember.Route.transitionTo('root.profile.account') + showUserProfile: Ember.Route.transitionTo('root.profile.account.profile') signedIn: -> !!Travis.app.get('currentUser') - requiresAuth: (name, action, params) -> - name == 'profile' and not @signedIn() + requiresAuth: (path) -> + # TODO: not sure what the path is at the moment + path == '/profile' && !@signedIn() - requireAuth: (name, action, params) -> - Travis.app.set('returnTo', [name, action, params]) - # Travis.app.render('auth', 'show') - @route('') - false + loading: Ember.Route.extend + routePath: (router, path) -> + router.set('lastAttemptedPath', path) + if router.requiresAuth(path) + router.send 'switchToUnauthenticated' + else + router.send 'switchToAuthenticated' + + switchToUnauthenticated: Ember.State.transitionTo('unauthenticated.index') + switchToAuthenticated: Ember.State.transitionTo('authenticated.index') + + unauthenticated: Ember.Route.extend + index: Ember.Route.extend + route: '/' + + connectOutlets: (router) -> + router.transitionTo('login') + + login: Ember.Route.extend + route: '/login' + + connectOutlets: (router) -> + router.get('applicationController').connectOutlet('login') + + authenticated: Ember.Route.extend + index: Ember.Route.extend + connectOutlets: (router) -> + router.transitionTo('root') + + path = router.get('lastAttemptedPath') + if path && path != '/' + router.route(path) + + root: Ember.Route.extend + initialState: 'home' + loading: Ember.State.extend() + + stats: Ember.Route.extend + route: '/stats' + connectOutlets: (router) -> + router.get('applicationController').connectOutlet 'statsLayout' + $('body').attr('id', 'stats') + router.get('statsLayoutController').connectOutlet 'top', 'top' + router.get('statsLayoutController').connectOutlet 'main', 'stats' + + profile: Ember.Route.extend + initialState: 'index' + route: '/profile' + connectOutlets: (router) -> + router.get('applicationController').connectOutlet 'profileLayout' + $('body').attr('id', 'profile') + router.get('profileLayoutController').connectOutlet 'top', 'top' + router.get('profileLayoutController').connectOutlet 'left', 'accounts' + + index: Ember.Route.extend + route: '/' + connectOutlets: (router) -> + router.get('profileLayoutController').connectOutlet 'main', 'profile' + router.get('profileController').activate 'hooks' + + account: Ember.Route.extend + initialState: 'index' + route: '/:login' + + connectOutlets: (router, account) -> + if account + params = { login: account.get('login') } + router.get('profileController').setParams(params) + else + router.send 'showProfile' + + deserialize: (router, params) -> + router.get('accountsController').findByLogin(params.login) + + serialize: (router, account) -> + if account + { login: account.get('login') } + else + {} + + index: Ember.Route.extend + route: '/' + connectOutlets: (router) -> + router.get('profileController').activate 'hooks' + + profile: Ember.Route.extend + route: '/profile' + + connectOutlets: (router) -> + router.get('profileController').activate 'user' + + home: Ember.Route.extend + initialState: 'show' + route: '/' + connectOutlets: (router) -> + router.get('applicationController').connectOutlet 'home' + $('body').attr('id', 'home') + router.get('homeController').connectOutlet 'left', 'repositories' + router.get('homeController').connectOutlet 'right', 'sidebar' + router.get('homeController').connectOutlet 'top', 'top' + router.get('homeController').connectOutlet 'main', 'repository' + + show: Ember.Route.extend + route: '/' + connectOutlets: (router) -> + router.get('repositoryController').activate('index') + + repository: Ember.Route.extend + initialState: 'show' + route: '/:owner/:name' + + connectOutlets: (router, repository) -> + params = { owner: repository.get('owner'), name: repository.get('name') } + + # TODO: we can just pass objects instead of params now, I'm leaving this + # to not have to rewrite too much, but it would be nice to fix this + # later + router.get('repositoryController').setParams(params) + + deserialize: (router, params) -> + slug = "#{params.owner}/#{params.name}" + repos = Travis.Repository.bySlug(slug) + deferred = $.Deferred() + + observer = -> + if repos.get 'isLoaded' + repos.removeObserver 'isLoaded', observer + deferred.resolve repos.objectAt(0) + + repos.addObserver 'isLoaded', observer + + deferred.promise() + + serialize: (router, repository) -> + if repository + { owner: repository.get('owner'), name: repository.get('name') } + else + {} + + show: Ember.Route.extend + route: '/' + connectOutlets: (router) -> + router.get('repositoryController').activate('current') + + builds: Ember.Route.extend + route: '/builds' + initialState: 'index' + + index: Ember.Route.extend + route: '/' + connectOutlets: (router, repository) -> + router.get('repositoryController').activate 'builds' + + show: Ember.Route.extend + route: '/:build_id' + connectOutlets: (router, build) -> + router.get('repositoryController').activate 'build', id: build.get('id') + + 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 + deferred = $.Deferred() + + observer = -> + if build.get 'id' + build.removeObserver 'id', observer + deferred.resolve build + + build.addObserver 'id', observer + + deferred.promise() + + pullRequests: Ember.Route.extend + route: '/pull_requests' + connectOutlets: (router, repository) -> + router.get('repositoryController').activate 'pull_requests' + + branches: Ember.Route.extend + route: '/branches' + connectOutlets: (router, repository) -> + router.get('repositoryController').activate 'branches' + + job: Ember.Route.extend + route: '/jobs/:job_id' + connectOutlets: (router, job) -> + router.get('repositoryController').activate 'job', id: job.get('id') + + deserialize: (router, params) -> + job = Travis.Job.find params.job_id + deferred = $.Deferred() + + observer = -> + if job.get 'id' + job.removeObserver 'id', observer + deferred.resolve job + job.addObserver 'id', observer + deferred.promise() diff --git a/assets/javascripts/app/templates/application.hbs b/assets/javascripts/app/templates/application.hbs new file mode 100644 index 00000000..c6ff6c5b --- /dev/null +++ b/assets/javascripts/app/templates/application.hbs @@ -0,0 +1,6 @@ +{{! TODO apparently styles assume that there is more divs here... + I should fix that later }} +