diff --git a/app/components/build-repo-actions.coffee b/app/components/build-repo-actions.coffee
new file mode 100644
index 00000000..5c2c2278
--- /dev/null
+++ b/app/components/build-repo-actions.coffee
@@ -0,0 +1,9 @@
+`import Ember from 'ember'`
+`import RepoActionsItemComponentMixin from 'travis/utils/repo-actions-item-component-mixin'`
+
+BuildRepoActionsComponent = Ember.Component.extend(RepoActionsItemComponentMixin,
+ item: Ember.computed.alias('build')
+ type: 'build'
+)
+
+`export default BuildRepoActionsComponent`
diff --git a/app/components/job-repo-actions.coffee b/app/components/job-repo-actions.coffee
new file mode 100644
index 00000000..4b6deb59
--- /dev/null
+++ b/app/components/job-repo-actions.coffee
@@ -0,0 +1,9 @@
+`import Ember from 'ember'`
+`import RepoActionsItemComponentMixin from 'travis/utils/repo-actions-item-component-mixin'`
+
+JobRepoActionsComponent = Ember.Component.extend(RepoActionsItemComponentMixin,
+ item: Ember.computed.alias('job')
+ type: 'job'
+)
+
+`export default JobRepoActionsComponent`
diff --git a/app/components/repo-actions.coffee b/app/components/repo-actions.coffee
new file mode 100644
index 00000000..43d38649
--- /dev/null
+++ b/app/components/repo-actions.coffee
@@ -0,0 +1,15 @@
+`import Ember from 'ember'`
+
+RepoActionsComponent = Ember.Component.extend(
+ displayCodeClimate: (->
+ @get('repo.githubLanguage') == 'Ruby'
+ ).property('repo.githubLanguage')
+
+ actions:
+ codeClimatePopup: ->
+ $('.popup').removeClass('display')
+ $('#code-climate').addClass('display')
+ return false
+)
+
+`export default RepoActionsComponent`
diff --git a/app/models/build.coffee b/app/models/build.coffee
index b3b72b6f..39dcca4d 100644
--- a/app/models/build.coffee
+++ b/app/models/build.coffee
@@ -92,11 +92,13 @@ Build = Model.extend DurationCalculations,
@get('jobs').filterBy('canCancel').length
).property('jobs.@each.canCancel')
+ canRestart: Ember.computed.alias('isFinished')
+
cancel: (->
Ajax.post "/builds/#{@get('id')}/cancel"
)
- requeue: ->
+ restart: ->
Ajax.post "/builds/#{@get('id')}/restart"
formattedFinishedAt: (->
diff --git a/app/models/job.coffee b/app/models/job.coffee
index ca252075..37f08934 100644
--- a/app/models/job.coffee
+++ b/app/models/job.coffee
@@ -91,6 +91,8 @@ Job = Model.extend DurationCalculations,
!@get('isFinished')
).property('state')
+ canRestart: Ember.computed.alias('isFinished')
+
cancel: (->
Ajax.post "/jobs/#{@get('id')}/cancel"
)
@@ -103,7 +105,7 @@ Job = Model.extend DurationCalculations,
@clearLog()
@get('log').fetch()
- requeue: ->
+ restart: ->
Ajax.post "/jobs/#{@get('id')}/restart"
appendLog: (part) ->
diff --git a/app/models/user.coffee b/app/models/user.coffee
index 86c3f537..964098f1 100644
--- a/app/models/user.coffee
+++ b/app/models/user.coffee
@@ -54,6 +54,12 @@ User = Model.extend
permissions
).property()
+ hasAccessToRepo: (repo) ->
+ id = if repo.get then repo.get('id') else repo
+
+ if permissions = @get('permissions')
+ permissions.contains parseInt(id)
+
type: (->
'user'
).property()
diff --git a/app/templates/build.hbs b/app/templates/build.hbs
index 61f26288..837a6759 100644
--- a/app/templates/build.hbs
+++ b/app/templates/build.hbs
@@ -65,8 +65,7 @@
- {{view 'repo-actions'}}
-
+ {{repo-actions build=build repo=build.repo user=auth.currentUser}}
diff --git a/app/templates/components/build-repo-actions.hbs b/app/templates/components/build-repo-actions.hbs
new file mode 100644
index 00000000..9b00cd3b
--- /dev/null
+++ b/app/templates/components/build-repo-actions.hbs
@@ -0,0 +1,20 @@
+{{#if canCancel}}
+ {{#if cancelling}}
+
+ {{else}}
+
+
+
+ {{/if}}
+{{/if}}
+
+{{#if canRestart}}
+ {{#if restarting}}
+
+ {{else}}
+
+
+
+ {{/if}}
+{{/if}}
diff --git a/app/templates/components/code-climate-popup.hbs b/app/templates/components/code-climate-popup.hbs
new file mode 100644
index 00000000..372f41ef
--- /dev/null
+++ b/app/templates/components/code-climate-popup.hbs
@@ -0,0 +1,44 @@
+
diff --git a/app/templates/components/job-repo-actions.hbs b/app/templates/components/job-repo-actions.hbs
new file mode 100644
index 00000000..a02d9fd1
--- /dev/null
+++ b/app/templates/components/job-repo-actions.hbs
@@ -0,0 +1,19 @@
+{{#if canCancel}}
+ {{#if cancelling}}
+
+ {{else}}
+
+
+
+ {{/if}}
+{{/if}}
+
+{{#if canRestart}}
+ {{#if restarting}}
+
+ {{else}}
+
+
+
+ {{/if}}
+{{/if}}
diff --git a/app/templates/components/repo-actions.hbs b/app/templates/components/repo-actions.hbs
new file mode 100644
index 00000000..e6cb6c53
--- /dev/null
+++ b/app/templates/components/repo-actions.hbs
@@ -0,0 +1,33 @@
+
+
+ {{! TODO: when `component` helper is available we could just use
+ with a component name based on type that is passed here }}
+ {{#if job}}
+ {{job-repo-actions job=job user=user repo=repo}}
+ {{else}}
+ {{build-repo-actions build=build user=user repo=repo}}
+ {{/if}}
+
+ {{#if displayCodeClimate}}
+
+ {{/if}}
+
+
+
+
+
+
+{{code-climate-popup}}
diff --git a/app/templates/job.hbs b/app/templates/job.hbs
index 3bcc2155..95bb366e 100644
--- a/app/templates/job.hbs
+++ b/app/templates/job.hbs
@@ -55,7 +55,7 @@
- {{view 'repo-actions'}}
+ {{repo-actions job=job repo=job.repo user=auth.currentUser}}
diff --git a/app/templates/repos/show/actions.hbs b/app/templates/repos/show/actions.hbs
deleted file mode 100644
index fdc68e49..00000000
--- a/app/templates/repos/show/actions.hbs
+++ /dev/null
@@ -1,55 +0,0 @@
-
-
- {{#if view.displayCancelBuild}}
-
-
-
- {{/if}}
- {{#if view.displayCancelJob}}
-
-
-
- {{/if}}
- {{#if view.displayRequeueBuild}}
- {{#if view.requeueing}}
-
- {{else}}
-
-
-
- {{/if}}
- {{/if}}
- {{#if view.displayRequeueJob}}
- {{#if view.requeueing}}
-
- {{else}}
-
-
-
- {{/if}}
- {{/if}}
- {{#if view.displayCodeClimate}}
-
- {{/if}}
-
-
-
-
-
diff --git a/app/templates/repos/show/tools.hbs b/app/templates/repos/show/tools.hbs
index 66024bd4..9b990fdb 100644
--- a/app/templates/repos/show/tools.hbs
+++ b/app/templates/repos/show/tools.hbs
@@ -17,7 +17,6 @@
-
-
-
diff --git a/app/utils/repo-actions-item-component-mixin.coffee b/app/utils/repo-actions-item-component-mixin.coffee
new file mode 100644
index 00000000..20d69aa8
--- /dev/null
+++ b/app/utils/repo-actions-item-component-mixin.coffee
@@ -0,0 +1,50 @@
+`import Ember from 'ember'`
+
+Mixin = Ember.Mixin.create(
+ restarting: false
+ cancelling: false
+
+ userHasPermissionForRepo: (->
+ repo = @get('repo')
+ user = @get('user')
+ if user && repo
+ user.hasAccessToRepo(repo)
+ ).property('user.permissions.[]', 'repo', 'user')
+
+ canCancel: (->
+ @get('item.canCancel') && @get('userHasPermissionForRepo')
+ ).property('userHasPermissionForRepo', 'item.canCancel')
+
+ canRestart: (->
+ @get('item.canRestart') && @get('userHasPermissionForRepo')
+ ).property('userHasPermissionForRepo', 'item.canRestart')
+
+ actions:
+ restart: ->
+ return if @get('restarting')
+ @set('restarting', true)
+
+ onFinished = =>
+ @set('restarting', false)
+ @get('item').restart().then(onFinished, onFinished)
+
+ cancel: ->
+ return if @get('cancelling')
+ @set('cancelling', true)
+
+ type = @get('type')
+
+ @get('item').cancel().then =>
+ @set('cancelling', false)
+ Travis.flash(success: "#{type.capitalize()} has been successfully canceled.")
+ , (xhr) =>
+ @set('cancelling', false)
+ if xhr.status == 422
+ Travis.flash(error: "This #{type} can't be canceled")
+ else if xhr.status == 403
+ Travis.flash(error: "You don't have sufficient access to cancel this #{type}")
+ else
+ Travis.flash(error: "An error occured when canceling the #{type}")
+)
+
+`export default Mixin`
diff --git a/app/views/repo-actions.coffee b/app/views/repo-actions.coffee
deleted file mode 100644
index 53b98acf..00000000
--- a/app/views/repo-actions.coffee
+++ /dev/null
@@ -1,172 +0,0 @@
-`import Ember from 'ember'`
-`import { plainTextLog as plainTextLogUrl } from 'travis/utils/urls'`
-`import Job from 'travis/models/job'`
-`import config from 'travis/config/environment'`
-`import BasicView from 'travis/views/basic'`
-
-View = BasicView.extend
- templateName: 'repos/show/actions'
-
- repoBinding: 'controller.repo'
- buildBinding: 'controller.build'
- jobBinding: 'controller.job'
- tabBinding: 'controller.tab'
- currentUserBinding: 'controller.currentUser'
-
- actions:
- requeueBuild: ->
- if @get('canRequeueBuild')
- @requeue @get('build')
-
- requeueJob: ->
- if @get('canRequeueJob')
- @requeue @get('_job')
-
- cancelBuild: ->
- if @get('canCancelBuild')
- Travis.flash(notice: 'Build cancellation has been scheduled.')
- @get('build').cancel().then ->
- Travis.flash(success: 'Build has been successfully canceled.')
- , (xhr) ->
- if xhr.status == 422
- Travis.flash(error: 'This build can\'t be canceled')
- else if xhr.status == 403
- Travis.flash(error: 'You don\'t have sufficient access to cancel this build')
- else
- Travis.flash(error: 'An error occured when canceling the build')
-
-
- removeLog: ->
- @popupCloseAll()
- if @get('canRemoveLog')
- job = @get('_job') || @get('build.jobs.firstObject')
- job.removeLog().then ->
- Travis.flash(success: 'Log has been successfully removed.')
- , (xhr) ->
- if xhr.status == 409
- Travis.flash(error: 'Log can\'t be removed')
- else if xhr.status == 401
- Travis.flash(error: 'You don\'t have sufficient access to remove the log')
- else
- Travis.flash(error: 'An error occured when removing the log')
-
- cancelJob: ->
- if @get('canCancelJob')
- Travis.flash(notice: 'Job cancellation has been scheduled.')
- @get('_job').cancel().then ->
- Travis.flash(success: 'Job has been successfully canceled.')
- , (xhr) ->
- if xhr.status == 422
- Travis.flash(error: 'This job can\'t be canceled')
- else if xhr.status == 403
- Travis.flash(error: 'You don\'t have sufficient access to cancel this job')
- else
- Travis.flash(error: 'An error occured when canceling the job')
-
- codeClimatePopup: ->
- @popupCloseAll()
- console.log('repo-actions view')
- @popup('code-climate')
- return false
-
- hasPermission: (->
- if permissions = @get('currentUser.permissions')
- permissions.contains parseInt(@get('repo.id'))
- ).property('currentUser.permissions.length', 'repo.id')
-
- hasPushPermission: (->
- if permissions = @get('currentUser.pushPermissions')
- permissions.contains parseInt(@get('repo.id'))
- ).property('currentUser.pushPermissions.length', 'repo.id')
-
- displayRequeueBuild: (->
- @get('isBuildTab') && @get('build.isFinished')
- ).property('isBuildTab', 'build.isFinished')
-
- canRequeueBuild: (->
- @get('displayRequeueBuild') && @get('hasPermission')
- ).property('displayRequeueBuild', 'hasPermission')
-
- displayRequeueJob: (->
- @get('isJobTab') && @get('job.isFinished')
- ).property('isJobTab', 'job.isFinished')
-
- canRequeueJob: (->
- @get('displayRequeueJob') && @get('hasPermission')
- ).property('displayRequeueJob', 'hasPermission')
-
- showDownloadLog: (->
- @get('jobIdForLog') && (@get('isJobTab') || @get('isBuildTab'))
- ).property('jobIdForLog', 'isJobTab', 'isBuildTab')
-
- _job: (->
- if id = @get('jobIdForLog')
- store =Travis.__container__.lookup('store:main')
- store.find('job', id)
- store.recordForId('job', id)
- ).property('jobIdForLog')
-
- jobIdForLog: (->
- job = @get('job.id')
- unless job
- if @get('build.jobs.length') == 1
- job = @get('build.jobs').objectAt?(0).get?('id')
- job
- ).property('job.id', 'build.jobs.firstObject.id', 'build.jobs.length')
-
- plainTextLogUrl: (->
- if id = @get('jobIdForLog')
- url = plainTextLogUrl(id)
- if config.pro
- token = @get('job.log.token') || @get('build.jobs.firstObject.log.token')
- url += "&access_token=#{token}"
- url
- ).property('jobIdForLog', 'job.log.token', 'build.jobs.firstObject.log.token')
-
- canRemoveLog: (->
- @get('displayRemoveLog')
- ).property('displayRemoveLog')
-
- displayRemoveLog: (->
- if job = @get('_job')
- (@get('isJobTab') || (@get('isBuildTab') && @get('build.jobs.length') == 1)) &&
- job.get('canRemoveLog') && @get('hasPermission')
- ).property('isJobTab', 'isBuildTab', 'build.jobs.length', '_job.canRemoveLog', 'jobIdForLog', 'canRemoveLog', 'hasPermission')
-
- canCancelBuild: (->
- @get('displayCancelBuild') && @get('hasPermission')
- ).property('displayCancelBuild', 'hasPermission')
-
- displayCancelBuild: (->
- @get('isBuildTab') && @get('build.canCancel')
- ).property('isBuildTab', 'build.canCancel')
-
- canCancelJob: (->
- @get('displayCancelJob') && @get('hasPermission')
- ).property('displayCancelJob', 'hasPermission')
-
- displayCancelJob: (->
- @get('isJobTab') && @get('job.canCancel')
- ).property('isJobTab', 'job.canCancel')
-
- isJobTab: (->
- @get('tab') == 'job'
- ).property('tab', 'repo.id')
-
- isBuildTab: (->
- ['current', 'build'].indexOf(@get('tab')) > -1
- ).property('tab')
-
- displayCodeClimate: (->
- @get('repo.githubLanguage') == 'Ruby'
- ).property('repo.githubLanguage')
-
- requeueFinished: ->
- @set('requeueing', false)
-
- requeue: (thing) ->
- return if @get('requeueing')
- @set('requeueing', true)
- thing.requeue().then(this.requeueFinished.bind(this), this.requeueFinished.bind(this))
-
-`export default View`
diff --git a/tests/acceptance/dashboard-test.coffee b/tests/acceptance/dashboard-test.coffee
index 41ede825..f3b2a29a 100644
--- a/tests/acceptance/dashboard-test.coffee
+++ b/tests/acceptance/dashboard-test.coffee
@@ -63,5 +63,5 @@ test 'visiting /dashboard', ->
visit '/dashboard'
andThen ->
- equal find('.tiles .repo').length, 1, 'there should be one repo displayed on dashboard'
- equal find('.tiles .repo').text(), 'travis-web', 'travis-web repository should be displayed'
+ equal find('.dashboard-active .row').length, 1, 'there should be one repo displayed on dashboard'
+ equal find('.dashboard-active .row h2').text(), 'travis-web', 'travis-web repository should be displayed'
diff --git a/tests/unit/components/build-repo-actions-test.coffee b/tests/unit/components/build-repo-actions-test.coffee
new file mode 100644
index 00000000..ac57952c
--- /dev/null
+++ b/tests/unit/components/build-repo-actions-test.coffee
@@ -0,0 +1,58 @@
+`import { test, moduleForComponent } from 'ember-qunit'`
+
+moduleForComponent 'build-repo-actions', 'BuildRepoActionsComponent', {
+ # specify the other units that are required for this test
+ # needs: ['component:foo', 'helper:bar']
+}
+
+test 'it shows cancel button if canCancel is true', ->
+ component = @subject(canCancel: true)
+ @append()
+
+ ok component.$('a[title="Cancel Build"]').length, 'cancel link should be visible'
+
+test 'it shows restart button if canRestart is true', ->
+ component = @subject(canRestart: true)
+ @append()
+
+ ok component.$('a[title="Restart Build"]').length, 'restart link should be visible'
+
+test 'user can cancel if she has permissions to a repo and build is cancelable', ->
+ build = Ember.Object.create(canCancel: false, userHasPermissionForRepo: true)
+
+ component = @subject(build: build, userHasPermissionForRepo: false)
+ ok !component.get('canCancel')
+
+ component.set('userHasPermissionForRepo', true)
+ ok !component.get('canCancel')
+
+ build.set('canCancel', true)
+ ok component.get('canCancel')
+
+test 'user can restart if she has permissions to a repo and job is restartable', ->
+ build = Ember.Object.create(canRestart: false, userHasPermissionForRepo: true)
+
+ component = @subject(build: build, userHasPermissionForRepo: false)
+ ok !component.get('canRestart')
+
+ component.set('userHasPermissionForRepo', true)
+ ok !component.get('canRestart')
+
+ build.set('canRestart', true)
+ ok component.get('canRestart')
+
+test 'it properly checks for user permissions for a repo', ->
+ expect 3
+
+ repo = Ember.Object.create(id: 44)
+ user = Ember.Object.extend(
+ hasAccessToRepo: (repo) ->
+ ok repo.get('id', 44)
+ ok true, 'hasAccessToRepo was called'
+
+ false
+ ).create()
+
+ component = @subject(user: user, repo: repo)
+
+ ok !component.get('userHasPermissionForRepo'), 'user should not have access to a repo'
diff --git a/tests/unit/components/job-repo-actions-test.coffee b/tests/unit/components/job-repo-actions-test.coffee
new file mode 100644
index 00000000..e5ff256f
--- /dev/null
+++ b/tests/unit/components/job-repo-actions-test.coffee
@@ -0,0 +1,56 @@
+`import { test, moduleForComponent } from 'ember-qunit'`
+
+moduleForComponent 'job-repo-actions', 'JobRepoActionsComponent', {
+}
+
+test 'it shows cancel button if canCancel is true', ->
+ component = @subject(canCancel: true)
+ @append()
+
+ ok component.$('a[title="Cancel Job"]').length, 'cancel link should be visible'
+
+test 'it shows restart button if canRestart is true', ->
+ component = @subject(canRestart: true)
+ @append()
+
+ ok component.$('a[title="Restart Job"]').length, 'restart link should be visible'
+
+test 'user can cancel if she has permissions to a repo and job is cancelable', ->
+ job = Ember.Object.create(canCancel: false, userHasPermissionForRepo: true)
+
+ component = @subject(job: job, userHasPermissionForRepo: false)
+ ok !component.get('canCancel')
+
+ component.set('userHasPermissionForRepo', true)
+ ok !component.get('canCancel')
+
+ job.set('canCancel', true)
+ ok component.get('canCancel')
+
+test 'user can restart if she has permissions to a repo and job is restartable', ->
+ job = Ember.Object.create(canRestart: false, userHasPermissionForRepo: true)
+
+ component = @subject(job: job, userHasPermissionForRepo: false)
+ ok !component.get('canRestart')
+
+ component.set('userHasPermissionForRepo', true)
+ ok !component.get('canRestart')
+
+ job.set('canRestart', true)
+ ok component.get('canRestart')
+
+test 'it properly checks for user permissions for a repo', ->
+ expect 3
+
+ repo = Ember.Object.create(id: 44)
+ user = Ember.Object.extend(
+ hasAccessToRepo: (repo) ->
+ ok repo.get('id', 44)
+ ok true, 'hasAccessToRepo was called'
+
+ false
+ ).create()
+
+ component = @subject(user: user, repo: repo)
+
+ ok !component.get('userHasPermissionForRepo'), 'user should not have access to a repo'
diff --git a/tests/unit/components/repo-actions-test.coffee b/tests/unit/components/repo-actions-test.coffee
new file mode 100644
index 00000000..c9935252
--- /dev/null
+++ b/tests/unit/components/repo-actions-test.coffee
@@ -0,0 +1,25 @@
+`import { test, moduleForComponent } from 'ember-qunit'`
+
+moduleForComponent 'repo-actions', 'RepoActionsComponent', {
+ # specify the other units that are required for this test
+ needs: ['component:build-repo-actions', 'component:job-repo-actions']
+}
+
+test 'it displays code climate if the repo language is ruby', ->
+ # creates the component instance
+ repo = Ember.Object.create(githubLanguage: 'Ruby')
+
+ component = @subject(repo: repo)
+ @append()
+
+ ok component.get('displayCodeClimate'), 'component should try to display code climate'
+ ok component.$('a[name=code-climate]').length, 'component should render a code climate button'
+
+test 'it doesn\'t display code climate for other languages', ->
+ repo = Ember.Object.create(githubLanguage: 'Go')
+
+ component = @subject(repo: repo)
+ @append()
+
+ ok !component.get('displayCodeClimate'), 'component should not try to display code climate'
+ ok !component.$('a[name=code-climate]').length, 'component should not render a code climate button'