diff --git a/assets/scripts/app/models/artifact.coffee b/assets/scripts/app/models/artifact.coffee index 0497af38..8cf48fd9 100644 --- a/assets/scripts/app/models/artifact.coffee +++ b/assets/scripts/app/models/artifact.coffee @@ -6,6 +6,9 @@ require 'travis/model' @_super.apply this, arguments @set 'queue', Ember.A([]) + @addObserver 'body', @fetchWorker + @fetchWorker() + append: (body) -> if @get('isLoaded') @set('body', @get('body') + body) @@ -18,3 +21,12 @@ require 'travis/model' if queue.get('length') > 0 @append queue.toArray().join('') ).observes('isLoaded') + + fetchWorker: -> + if 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/job.coffee b/assets/scripts/app/models/job.coffee index 99aa875a..24b95724 100644 --- a/assets/scripts/app/models/job.coffee +++ b/assets/scripts/app/models/job.coffee @@ -30,8 +30,13 @@ require 'travis/model' ).property('data.config') sponsor: (-> - @get('data.sponsor') - ).property('data.sponsor') + worker = @get('log.workerName') + if worker && worker.length + Travis.WORKERS[worker] || { + name: "Travis Pro" + url: "http://travis-ci.com" + } + ).property('log.workerName') configValues: (-> if config = @get('config') diff --git a/assets/scripts/app/models/worker.coffee b/assets/scripts/app/models/worker.coffee index 6164da13..2beb1e6b 100644 --- a/assets/scripts/app/models/worker.coffee +++ b/assets/scripts/app/models/worker.coffee @@ -19,8 +19,8 @@ require 'travis/model' ).property('state') repo: (-> - Travis.Repo.find(@get('payload.repo.id')) - ).property('payload.repo.id') + Travis.Repo.find(@get('payload.repository.id') || @get('payload.repo.id')) + ).property('payload.repository.id', 'payload.repo.id') job_id: (-> @get('payload.job.id') diff --git a/assets/scripts/app/routes.coffee b/assets/scripts/app/routes.coffee index 6bf2ac83..274b9d1b 100644 --- a/assets/scripts/app/routes.coffee +++ b/assets/scripts/app/routes.coffee @@ -1,5 +1,116 @@ +require 'travis/location' + +defaultRoute = Ember.Route.extend + route: '/' + index: 1000 + +lineNumberRoute = Ember.Route.extend + route: '#L:number' + index: 1 + routeMatcher: Ember.computed(-> + if route = @get 'route' + Ember._RouteMatcher.create + route: route + # TODO: overriding such things is not cool, I need to check what's the status of + # router rewrite and make sure we can do such stuff without overriding anything + init: -> + escapeForRegex = (text) -> + text.replace(/[\-\[\]{}()*+?.,\\\^\$|#\s]/g, "\\$&") + + route = @route + identifiers = [] + count = 1 + + if route.charAt(0) == '/' + route = @route = route.substr(1) + + escaped = escapeForRegex(route) + + regex = escaped.replace /:([a-z_]+)(?=$|\/)/gi, (match, id) -> + identifiers[count++] = id + "([0-9]+)" + + @identifiers = identifiers + @regex = new RegExp(regex) + ).cacheable() + + +nonHashRouteMatcher = Ember.computed(-> + if route = @get 'route' + Ember._RouteMatcher.create + route: route + # TODO: overriding such things is not cool, I need to check what's the status of + # router rewrite and make sure we can do such stuff without overriding anything + init: -> + escapeForRegex = (text) -> + text.replace(/[\-\[\]{}()*+?.,\\\^\$|#\s]/g, "\\$&") + + route = @route + identifiers = [] + count = 1 + + if route.charAt(0) == '/' + route = @route = route.substr(1) + + escaped = escapeForRegex(route) + + regex = escaped.replace /:([a-z_]+)(?=$|\/)/gi, (match, id) -> + identifiers[count++] = id + "([^/#]+)" + + @identifiers = identifiers + @regex = new RegExp("^/?" + regex) +).cacheable() + +resolvePath = (manager, path) -> + if @get('isLeafRoute') + return Ember.A() + + childStates = @get('childStates') + + childStates = Ember.A(childStates.filterProperty('isRoutable')) + + childStates = childStates.sort (a, b) -> + aDynamicSegments = a.get('routeMatcher.identifiers.length') + bDynamicSegments = b.get('routeMatcher.identifiers.length') + aRoute = a.get('route') + bRoute = b.get('route') + aIndex = a.get('index') + bIndex = b.get('index') + + if aIndex && bIndex + return aIndex - bIndex + + if aRoute.indexOf(bRoute) == 0 + return -1 + else if bRoute.indexOf(aRoute) == 0 + return 1 + + if aDynamicSegments != bDynamicSegments + return aDynamicSegments - bDynamicSegments + + return b.get('route.length') - a.get('route.length') + + match = null + console.log(childStates.map( (s) -> s.get('route'))) + state = childStates.find (state) -> + matcher = state.get('routeMatcher') + if match = matcher.match(path) + match + + Ember.assert("Could not find state for path " + path, !!state) + + resolvedState = Ember._ResolvedState.create + manager: manager + state: state + match: match + + states = state.resolvePath(manager, match.remaining) + + Ember.A([resolvedState]).pushObjects(states) + Travis.Router = Ember.Router.extend - location: 'history' + location: 'travis' enableLogging: true initialState: 'loading' @@ -25,6 +136,10 @@ Travis.Router = Ember.Router.extend loading: Ember.Route.extend routePath: (router, path) -> + if match = path.match(/#.*$/) + router.set 'lineNumberHash', match[0] + + sessionStorage.setItem('travis.path', path) if router.needsAuth(path) router.transitionTo('root.auth') @@ -37,10 +152,12 @@ Travis.Router = Ember.Router.extend path = sessionStorage.getItem('travis.path') sessionStorage.removeItem('travis.path') router.transitionTo('root') - router.route(path) if path + if path + router.route(path) + else + router.route('/') root: Ember.Route.extend - initialState: 'home' loading: Ember.State.extend() auth: Ember.Route.extend @@ -108,7 +225,6 @@ Travis.Router = Ember.Router.extend router.get('profileController').activate 'user' home: Ember.Route.extend - initialState: 'show' route: '/' connectOutlets: (router) -> router.get('applicationController').connectOutlet 'home' @@ -124,9 +240,19 @@ Travis.Router = Ember.Router.extend connectOutlets: (router) -> router.get('repoController').activate('index') + initialState: 'default' + default: defaultRoute + lineNumber: lineNumberRoute + resolvePath: resolvePath + + showWithLineNumber: Ember.Route.extend + route: '/#/L:number' + connectOutlets: (router) -> + router.get('repoController').activate('index') + repo: Ember.Route.extend - initialState: 'show' route: '/:owner/:name' + routeMatcher: nonHashRouteMatcher connectOutlets: (router, repo) -> router.get('repoController').set 'repo', repo @@ -156,9 +282,13 @@ Travis.Router = Ember.Router.extend connectOutlets: (router) -> router.get('repoController').activate('current') + initialState: 'default' + default: defaultRoute + lineNumber: lineNumberRoute + resolvePath: resolvePath + builds: Ember.Route.extend route: '/builds' - initialState: 'index' index: Ember.Route.extend route: '/' @@ -199,6 +329,14 @@ Travis.Router = Ember.Router.extend 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 + routeMatcher: nonHashRouteMatcher + resolvePath: resolvePath + pullRequests: Ember.Route.extend route: '/pull_requests' connectOutlets: (router, repo) -> @@ -210,7 +348,6 @@ Travis.Router = Ember.Router.extend router.get('repoController').activate 'branches' job: Ember.Route.extend - route: '/jobs/:job_id' connectOutlets: (router, job) -> unless job.get @@ -235,3 +372,9 @@ Travis.Router = Ember.Router.extend deferred.resolve job job.addObserver 'id', observer deferred.promise() + + initialState: 'default' + default: defaultRoute + lineNumber: lineNumberRoute + routeMatcher: nonHashRouteMatcher + resolvePath: resolvePath diff --git a/assets/scripts/app/views/job.coffee b/assets/scripts/app/views/job.coffee index 3ca6acb3..0f75b20c 100644 --- a/assets/scripts/app/views/job.coffee +++ b/assets/scripts/app/views/job.coffee @@ -48,6 +48,26 @@ templateName: 'jobs/log' logBinding: 'job.log' + scrollTo: (hash) -> + $('body').scrollTop $(hash).offset().top + Travis.app.router.set 'lineNumberHash', null + + didInsertElement: -> + @_super.apply this, arguments + + if hash = Travis.app.router.get 'lineNumberHash' + self = this + + checker = -> + return if self.get('isDestroyed') + + if $(hash).length + self.scrollTo(hash) + else + setTimeout checker, 100 + + checker() + click: (event) -> $(event.target).closest('.fold').toggleClass('open') diff --git a/assets/scripts/app/views/repo/show.coffee b/assets/scripts/app/views/repo/show.coffee index f716c9e1..78322cf8 100644 --- a/assets/scripts/app/views/repo/show.coffee +++ b/assets/scripts/app/views/repo/show.coffee @@ -90,7 +90,8 @@ ).property('tab') hasPushPermissions: (-> - Travis.app.get('currentUser.permissions').indexOf(@get('repo.id')) > -1 + if permissions = Travis.app.get('currentUser.permissions') + permissions.indexOf(@get('repo.id')) > -1 ).property('Travis.app.currentUser.permissions.length', 'repo.id') branches: (-> diff --git a/assets/scripts/data/sponsors.coffee b/assets/scripts/data/sponsors.coffee index 400e541b..ad52c3ca 100644 --- a/assets/scripts/data/sponsors.coffee +++ b/assets/scripts/data/sponsors.coffee @@ -41,4 +41,41 @@ { type: 'silver', link: "Tupalo: Discover, review & share local businesses." } ] - +@Travis.WORKERS = { + "jvm-otp1.worker.travis-ci.org": + name: "Travis Pro" + url: "http://travis-ci.com" + "jvm-otp2.worker.travis-ci.org": + name: "Transloadit" + url: "http://transloadit.com" + "ppp1.worker.travis-ci.org": + name: "Travis Pro" + url: "http://beta.travis-ci.com" + "ppp2.worker.travis-ci.org": + name: "EnterpriseRails" + url: "http://www.enterprise-rails.com" + "ppp3.worker.travis-ci.org": + name: "Alchemy CMS" + url: "http://alchemy-cms.com/" + "rails1.worker.travis-ci.org": + name: "EnterpriseRails" + url: "http://www.enterprise-rails.com" + "ruby1.worker.travis-ci.org": + name: "Engine Yard" + url: "http://www.engineyard.com" + "ruby2.worker.travis-ci.org": + name: "EnterpriseRails" + url: "http://www.enterprise-rails.com" + "ruby3.worker.travis-ci.org": + name: "Railslove" + url: "http://railslove.de" + "ruby4.worker.travis-ci.org": + name: "Engine Yard" + url: "http://www.engineyard.com" + "spree.worker.travis-ci.org": + name: "Spree" + url: "http://spreecommerce.com" + "staging.worker.travis-ci.org": + name: "EnterpriseRails" + url: "http://www.enterprise-rails.com" +} diff --git a/assets/scripts/lib/travis/log.coffee b/assets/scripts/lib/travis/log.coffee index 99d7b8e4..be3b274c 100644 --- a/assets/scripts/lib/travis/log.coffee +++ b/assets/scripts/lib/travis/log.coffee @@ -27,7 +27,7 @@ result = '' $.each log.trim().split('\n'), (ix, line) -> number = ix + 1 - path = Travis.Log.location().substr(1).replace(/\/L\d+/, '') + '/L' + number + path = Travis.Log.location().substr(1).replace(/L\d+/, '') + 'L' + number result += '

%@%@

\n'.fmt(path, path, number, number, line) result.trim() diff --git a/public/scripts/app.js b/public/scripts/app.js index 23347985..fd1bcd26 100644 --- a/public/scripts/app.js +++ b/public/scripts/app.js @@ -29619,4 +29619,4 @@ var _require=function(){function c(a,c){document.addEventListener?a.addEventList ++g&&setTimeout(c,0)})}}(); (function(){!window.WebSocket&&window.MozWebSocket&&(window.WebSocket=window.MozWebSocket);if(window.WebSocket)Pusher.Transport=window.WebSocket,Pusher.TransportType="native";var c=(document.location.protocol=="http:"?Pusher.cdn_http:Pusher.cdn_https)+Pusher.VERSION,a=[];window.JSON||a.push(c+"/json2"+Pusher.dependency_suffix+".js");if(!window.WebSocket)window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION=!0,a.push(c+"/flashfallback"+Pusher.dependency_suffix+".js");var b=function(){return window.WebSocket?function(){Pusher.ready()}: function(){window.WebSocket?(Pusher.Transport=window.WebSocket,Pusher.TransportType="flash",window.WEB_SOCKET_SWF_LOCATION=c+"/WebSocketMain.swf",WebSocket.__addTask(function(){Pusher.ready()}),WebSocket.__initialize()):(Pusher.Transport=null,Pusher.TransportType="none",Pusher.ready())}}(),e=function(a){var b=function(){document.body?a():setTimeout(b,0)};b()},g=function(){e(b)};a.length>0?_require(a,g):g()})(); -;minispade.register('app', "(function() {(function() {\nminispade.require('auth');\nminispade.require('controllers');\nminispade.require('helpers');\nminispade.require('models');\nminispade.require('pusher');\nminispade.require('routes');\nminispade.require('slider');\nminispade.require('store');\nminispade.require('tailing');\nminispade.require('templates');\nminispade.require('views');\nminispade.require('config/locales');\nminispade.require('data/sponsors');\n\n Travis.reopen({\n App: Em.Application.extend({\n autoinit: false,\n currentUserBinding: 'auth.user',\n authStateBinding: 'auth.state',\n init: function() {\n this._super.apply(this, arguments);\n this.store = Travis.Store.create();\n this.store.loadMany(Travis.Sponsor, Travis.SPONSORS);\n this.set('auth', Travis.Auth.create({\n app: this,\n endpoint: Travis.config.api_endpoint\n }));\n this.slider = new Travis.Slider();\n this.pusher = new Travis.Pusher(Travis.config.pusher);\n return this.tailing = new Travis.Tailing();\n },\n signIn: function() {\n return this.get('auth').signIn();\n },\n signOut: function() {\n this.get('auth').signOut();\n return this.get('router').send('showRoot');\n },\n receive: function() {\n return this.store.receive.apply(this.store, arguments);\n },\n toggleSidebar: function() {\n var element;\n $('body').toggleClass('maximized');\n element = $('');\n $('#top .profile').append(element);\n Em.run.later((function() {\n return element.remove();\n }), 10);\n element = $('');\n $('#repo').append(element);\n return Em.run.later((function() {\n return element.remove();\n }), 10);\n }\n })\n });\n\n}).call(this);\n\n})();\n//@ sourceURL=app");minispade.register('auth', "(function() {(function() {\n\n this.Travis.Auth = Ember.Object.extend({\n iframe: $('