diff options
-rw-r--r-- | app/assets/javascripts/jobs/components/header.vue | 8 | ||||
-rw-r--r-- | app/assets/javascripts/vue_shared/components/header_ci_component.vue | 12 | ||||
-rw-r--r-- | app/assets/stylesheets/framework/images.scss | 11 | ||||
-rw-r--r-- | app/serializers/job_entity.rb | 2 | ||||
-rw-r--r-- | app/views/projects/jobs/_empty_state.html.haml | 17 | ||||
-rw-r--r-- | app/views/projects/jobs/show.html.haml | 76 | ||||
-rw-r--r-- | features/steps/shared/builds.rb | 2 | ||||
-rw-r--r-- | spec/factories/ci/builds.rb | 25 | ||||
-rw-r--r-- | spec/features/projects/jobs/user_browses_job_spec.rb | 2 | ||||
-rw-r--r-- | spec/features/projects/jobs_spec.rb | 28 | ||||
-rw-r--r-- | spec/javascripts/jobs/header_spec.js | 3 | ||||
-rw-r--r-- | spec/javascripts/vue_shared/components/header_ci_component_spec.js | 81 | ||||
-rw-r--r-- | spec/lib/gitlab/cycle_analytics/events_spec.rb | 14 | ||||
-rw-r--r-- | spec/requests/api/jobs_spec.rb | 4 |
14 files changed, 191 insertions, 94 deletions
diff --git a/app/assets/javascripts/jobs/components/header.vue b/app/assets/javascripts/jobs/components/header.vue index 6d671845f8e..c660828b30e 100644 --- a/app/assets/javascripts/jobs/components/header.vue +++ b/app/assets/javascripts/jobs/components/header.vue @@ -30,6 +30,9 @@ shouldRenderContent() { return !this.isLoading && Object.keys(this.job).length; }, + jobStarted() { + return this.job.started; + }, }, methods: { getActions() { @@ -63,8 +66,9 @@ :time="job.created_at" :user="job.user" :actions="actions" - :hasSidebarButton="true" - /> + :has-sidebar-button="true" + :should-render-triggered-label="jobStarted" + /> <loading-icon v-if="isLoading" size="2" diff --git a/app/assets/javascripts/vue_shared/components/header_ci_component.vue b/app/assets/javascripts/vue_shared/components/header_ci_component.vue index d305bd6acdc..2209bc0f9cf 100644 --- a/app/assets/javascripts/vue_shared/components/header_ci_component.vue +++ b/app/assets/javascripts/vue_shared/components/header_ci_component.vue @@ -45,6 +45,11 @@ export default { required: false, default: false, }, + shouldRenderTriggeredLabel: { + type: Boolean, + required: false, + default: true, + }, }, directives: { @@ -82,7 +87,12 @@ export default { {{itemName}} #{{itemId}} </strong> - triggered + <template v-if="shouldRenderTriggeredLabel"> + triggered + </template> + <template v-else> + created + </template> <timeago-tooltip :time="time" /> diff --git a/app/assets/stylesheets/framework/images.scss b/app/assets/stylesheets/framework/images.scss index aa2d30a3cef..fd5c3c81a53 100644 --- a/app/assets/stylesheets/framework/images.scss +++ b/app/assets/stylesheets/framework/images.scss @@ -20,10 +20,13 @@ width: 100%; } - &.svg-250 { - img, - svg { - width: 250px; + $image-widths: 250 306 394; + @each $width in $image-widths { + &.svg-#{$width} { + img, + svg { + width: #{$width + 'px'}; + } } } } diff --git a/app/serializers/job_entity.rb b/app/serializers/job_entity.rb index 72e56a2c77f..523b522d449 100644 --- a/app/serializers/job_entity.rb +++ b/app/serializers/job_entity.rb @@ -4,6 +4,8 @@ class JobEntity < Grape::Entity expose :id expose :name + expose :started?, as: :started + expose :build_path do |build| build.target_url || path_to(:namespace_project_job, build) end diff --git a/app/views/projects/jobs/_empty_state.html.haml b/app/views/projects/jobs/_empty_state.html.haml new file mode 100644 index 00000000000..c66313bdbf3 --- /dev/null +++ b/app/views/projects/jobs/_empty_state.html.haml @@ -0,0 +1,17 @@ +- illustration = local_assigns.fetch(:illustration) +- illustration_size = local_assigns.fetch(:illustration_size) +- title = local_assigns.fetch(:title) +- content = local_assigns.fetch(:content) +- action = local_assigns.fetch(:action, nil) + +.row.empty-state + .col-xs-12 + .svg-content{ class: illustration_size } + = image_tag illustration + .col-xs-12 + .text-content + %h4.text-center= title + %p= content + - if action + .text-center + = action diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml index fd24bbbb9ba..8b05440fc78 100644 --- a/app/views/projects/jobs/show.html.haml +++ b/app/views/projects/jobs/show.html.haml @@ -54,41 +54,53 @@ Job has been erased by #{link_to(@build.erased_by_name, user_path(@build.erased_by))} #{time_ago_with_tooltip(@build.erased_at)} - else Job has been erased #{time_ago_with_tooltip(@build.erased_at)} + - if @build.started? + .build-trace-container.prepend-top-default + .top-bar.js-top-bar + .js-truncated-info.truncated-info.hidden-xs.pull-left.hidden< + Showing last + %span.js-truncated-info-size.truncated-info-size>< + of log - + %a.js-raw-link.raw-link{ href: raw_project_job_path(@project, @build) }>< Complete Raw - .build-trace-container.prepend-top-default - .top-bar.js-top-bar - .js-truncated-info.truncated-info.hidden-xs.pull-left.hidden< - Showing last - %span.js-truncated-info-size.truncated-info-size>< - of log - - %a.js-raw-link.raw-link{ href: raw_project_job_path(@project, @build) }>< Complete Raw + .controllers.pull-right + - if @build.has_trace? + = link_to raw_project_job_path(@project, @build), + title: 'Show complete raw', + data: { placement: 'top', container: 'body' }, + class: 'js-raw-link-controller has-tooltip controllers-buttons' do + = icon('file-text-o') - .controllers.pull-right - - if @build.has_trace? - = link_to raw_project_job_path(@project, @build), - title: 'Show complete raw', - data: { placement: 'top', container: 'body' }, - class: 'js-raw-link-controller has-tooltip controllers-buttons' do - = icon('file-text-o') - - - if @build.erasable? && can?(current_user, :erase_build, @build) - = link_to erase_project_job_path(@project, @build), - method: :post, - data: { confirm: 'Are you sure you want to erase this build?', placement: 'top', container: 'body' }, - title: 'Erase job log', - class: 'has-tooltip js-erase-link controllers-buttons' do - = icon('trash') - .has-tooltip.controllers-buttons{ title: 'Scroll to top', data: { placement: 'top', container: 'body'} } - %button.js-scroll-up.btn-scroll.btn-transparent.btn-blank{ type: 'button', disabled: true } - = custom_icon('scroll_up') - .has-tooltip.controllers-buttons{ title: 'Scroll to bottom', data: { placement: 'top', container: 'body'} } - %button.js-scroll-down.btn-scroll.btn-transparent.btn-blank{ type: 'button', disabled: true } - = custom_icon('scroll_down') - - %pre.build-trace#build-trace - %code.bash.js-build-output - .build-loader-animation.js-build-refresh + - if @build.erasable? && can?(current_user, :erase_build, @build) + = link_to erase_project_job_path(@project, @build), + method: :post, + data: { confirm: 'Are you sure you want to erase this build?', placement: 'top', container: 'body' }, + title: 'Erase job log', + class: 'has-tooltip js-erase-link controllers-buttons' do + = icon('trash') + .has-tooltip.controllers-buttons{ title: 'Scroll to top', data: { placement: 'top', container: 'body'} } + %button.js-scroll-up.btn-scroll.btn-transparent.btn-blank{ type: 'button', disabled: true } + = custom_icon('scroll_up') + .has-tooltip.controllers-buttons{ title: 'Scroll to bottom', data: { placement: 'top', container: 'body'} } + %button.js-scroll-down.btn-scroll.btn-transparent.btn-blank{ type: 'button', disabled: true } + = custom_icon('scroll_down') + %pre.build-trace#build-trace + %code.bash.js-build-output + .build-loader-animation.js-build-refresh + - elsif @build.playable? + = render 'empty_state', + illustration: 'illustrations/manual_action.svg', + illustration_size: 'svg-394', + title: _('This job requires a manual action'), + content: _('This job depends on a user to trigger its process. Often they are used to deploy code to production environments.'), + action: ( link_to _('Trigger this manual action'), play_project_job_path(@project, @build), class: 'btn btn-primary', title: _('Trigger this manual action') ) + - else + = render 'empty_state', + illustration: 'illustrations/job_not_triggered.svg', + illustration_size: 'svg-306', + title: _('This job has not been triggered yet'), + content: _('This job depends on upstream jobs that need to succeed in order for this job to be triggered.') = render "sidebar" diff --git a/features/steps/shared/builds.rb b/features/steps/shared/builds.rb index c267195f0e8..a3e4459f169 100644 --- a/features/steps/shared/builds.rb +++ b/features/steps/shared/builds.rb @@ -11,7 +11,7 @@ module SharedBuilds step 'project has a recent build' do @pipeline = create(:ci_empty_pipeline, project: @project, sha: @project.commit.sha, ref: 'master') - @build = create(:ci_build, :coverage, pipeline: @pipeline) + @build = create(:ci_build, :running, :coverage, pipeline: @pipeline) end step 'recent build is successful' do diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index dc1d88c92dc..6f66468570f 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -7,12 +7,10 @@ FactoryBot.define do stage_idx 0 ref 'master' tag false - status 'pending' - created_at 'Di 29. Okt 09:50:00 CET 2013' - started_at 'Di 29. Okt 09:51:28 CET 2013' - finished_at 'Di 29. Okt 09:53:28 CET 2013' commands 'ls -a' protected false + created_at 'Di 29. Okt 09:50:00 CET 2013' + pending options do { @@ -29,23 +27,37 @@ FactoryBot.define do pipeline factory: :ci_pipeline + trait :started do + started_at 'Di 29. Okt 09:51:28 CET 2013' + end + + trait :finished do + started + finished_at 'Di 29. Okt 09:53:28 CET 2013' + end + trait :success do + finished status 'success' end trait :failed do + finished status 'failed' end trait :canceled do + finished status 'canceled' end trait :skipped do + started status 'skipped' end trait :running do + started status 'running' end @@ -114,11 +126,6 @@ FactoryBot.define do build.project ||= build.pipeline.project end - factory :ci_not_started_build do - started_at nil - finished_at nil - end - trait :tag do tag true end diff --git a/spec/features/projects/jobs/user_browses_job_spec.rb b/spec/features/projects/jobs/user_browses_job_spec.rb index 5d9208ebadd..4c49cff30d4 100644 --- a/spec/features/projects/jobs/user_browses_job_spec.rb +++ b/spec/features/projects/jobs/user_browses_job_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe 'User browses a job', :js do - let!(:build) { create(:ci_build, :coverage, pipeline: pipeline) } + let!(:build) { create(:ci_build, :running, :coverage, pipeline: pipeline) } let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.sha, ref: 'master') } let(:project) { create(:project, :repository, namespace: user.namespace) } let(:user) { create(:user) } diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index f8ea1a52656..9a6b27c00f8 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -369,6 +369,34 @@ feature 'Jobs' do end end end + + context 'Playable manual action' do + let(:job) { create(:ci_build, :playable, pipeline: pipeline) } + + before do + project.add_developer(user) + visit project_job_path(project, job) + end + + it 'shows manual action empty state' do + expect(page).to have_content('This job requires a manual action') + expect(page).to have_content('This job depends on a user to trigger its process. Often they are used to deploy code to production environments.') + expect(page).to have_link('Trigger this manual action') + end + end + + context 'Non triggered job' do + let(:job) { create(:ci_build, :created, pipeline: pipeline) } + + before do + visit project_job_path(project, job) + end + + it 'shows manual action empty state' do + expect(page).to have_content('This job has not been triggered yet') + expect(page).to have_content('This job depends on upstream jobs that need to succeed in order for this job to be triggered.') + end + end end describe "POST /:project/jobs/:id/cancel", :js do diff --git a/spec/javascripts/jobs/header_spec.js b/spec/javascripts/jobs/header_spec.js index 4a210faa017..83395ea451e 100644 --- a/spec/javascripts/jobs/header_spec.js +++ b/spec/javascripts/jobs/header_spec.js @@ -1,5 +1,6 @@ import Vue from 'vue'; import headerComponent from '~/jobs/components/header.vue'; +import mountComponent from '../helpers/vue_mount_component_helper'; describe('Job details header', () => { let HeaderComponent; @@ -35,7 +36,7 @@ describe('Job details header', () => { isLoading: false, }; - vm = new HeaderComponent({ propsData: props }).$mount(); + vm = mountComponent(HeaderComponent, props); }); afterEach(() => { diff --git a/spec/javascripts/vue_shared/components/header_ci_component_spec.js b/spec/javascripts/vue_shared/components/header_ci_component_spec.js index b4553acb341..b378a0bd896 100644 --- a/spec/javascripts/vue_shared/components/header_ci_component_spec.js +++ b/spec/javascripts/vue_shared/components/header_ci_component_spec.js @@ -1,5 +1,6 @@ import Vue from 'vue'; import headerCi from '~/vue_shared/components/header_ci_component.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; describe('Header CI Component', () => { let HeaderCi; @@ -8,7 +9,6 @@ describe('Header CI Component', () => { beforeEach(() => { HeaderCi = Vue.extend(headerCi); - props = { status: { group: 'failed', @@ -45,54 +45,65 @@ describe('Header CI Component', () => { ], hasSidebarButton: true, }; - - vm = new HeaderCi({ - propsData: props, - }).$mount(); }); afterEach(() => { vm.$destroy(); }); - it('should render status badge', () => { - expect(vm.$el.querySelector('.ci-failed')).toBeDefined(); - expect(vm.$el.querySelector('.ci-status-icon-failed svg')).toBeDefined(); - expect( - vm.$el.querySelector('.ci-failed').getAttribute('href'), - ).toEqual(props.status.details_path); - }); + describe('render', () => { + beforeEach(() => { + vm = mountComponent(HeaderCi, props); + }); - it('should render item name and id', () => { - expect(vm.$el.querySelector('strong').textContent.trim()).toEqual('job #123'); - }); + it('should render status badge', () => { + expect(vm.$el.querySelector('.ci-failed')).toBeDefined(); + expect(vm.$el.querySelector('.ci-status-icon-failed svg')).toBeDefined(); + expect( + vm.$el.querySelector('.ci-failed').getAttribute('href'), + ).toEqual(props.status.details_path); + }); - it('should render timeago date', () => { - expect(vm.$el.querySelector('time')).toBeDefined(); - }); + it('should render item name and id', () => { + expect(vm.$el.querySelector('strong').textContent.trim()).toEqual('job #123'); + }); - it('should render user icon and name', () => { - expect(vm.$el.querySelector('.js-user-link').textContent.trim()).toEqual(props.user.name); - }); + it('should render timeago date', () => { + expect(vm.$el.querySelector('time')).toBeDefined(); + }); - it('should render provided actions', () => { - expect(vm.$el.querySelector('.btn').tagName).toEqual('BUTTON'); - expect(vm.$el.querySelector('.btn').textContent.trim()).toEqual(props.actions[0].label); - expect(vm.$el.querySelector('.link').tagName).toEqual('A'); - expect(vm.$el.querySelector('.link').textContent.trim()).toEqual(props.actions[1].label); - expect(vm.$el.querySelector('.link').getAttribute('href')).toEqual(props.actions[0].path); - }); + it('should render user icon and name', () => { + expect(vm.$el.querySelector('.js-user-link').textContent.trim()).toEqual(props.user.name); + }); + + it('should render provided actions', () => { + expect(vm.$el.querySelector('.btn').tagName).toEqual('BUTTON'); + expect(vm.$el.querySelector('.btn').textContent.trim()).toEqual(props.actions[0].label); + expect(vm.$el.querySelector('.link').tagName).toEqual('A'); + expect(vm.$el.querySelector('.link').textContent.trim()).toEqual(props.actions[1].label); + expect(vm.$el.querySelector('.link').getAttribute('href')).toEqual(props.actions[0].path); + }); - it('should show loading icon', (done) => { - vm.actions[0].isLoading = true; + it('should show loading icon', (done) => { + vm.actions[0].isLoading = true; - Vue.nextTick(() => { - expect(vm.$el.querySelector('.btn .fa-spinner').getAttribute('style')).toBeFalsy(); - done(); + Vue.nextTick(() => { + expect(vm.$el.querySelector('.btn .fa-spinner').getAttribute('style')).toBeFalsy(); + done(); + }); + }); + + it('should render sidebar toggle button', () => { + expect(vm.$el.querySelector('.js-sidebar-build-toggle')).toBeDefined(); }); }); - it('should render sidebar toggle button', () => { - expect(vm.$el.querySelector('.js-sidebar-build-toggle')).toBeDefined(); + describe('shouldRenderTriggeredLabel', () => { + it('should rendered created keyword when the shouldRenderTriggeredLabel is false', () => { + vm = mountComponent(HeaderCi, { ...props, shouldRenderTriggeredLabel: false }); + + expect(vm.$el.textContent).toContain('created'); + expect(vm.$el.textContent).not.toContain('triggered'); + }); }); }); diff --git a/spec/lib/gitlab/cycle_analytics/events_spec.rb b/spec/lib/gitlab/cycle_analytics/events_spec.rb index 28ea7d4c303..38a47a159e1 100644 --- a/spec/lib/gitlab/cycle_analytics/events_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/events_spec.rb @@ -122,17 +122,18 @@ describe 'cycle analytics events' do let(:stage) { :test } let(:merge_request) { MergeRequest.first } + let!(:pipeline) do create(:ci_pipeline, ref: merge_request.source_branch, sha: merge_request.diff_head_sha, - project: context.project, + project: project, head_pipeline_of: merge_request) end before do - create(:ci_build, pipeline: pipeline, status: :success, author: user) - create(:ci_build, pipeline: pipeline, status: :success, author: user) + create(:ci_build, :success, pipeline: pipeline, author: user) + create(:ci_build, :success, pipeline: pipeline, author: user) pipeline.run! pipeline.succeed! @@ -219,17 +220,18 @@ describe 'cycle analytics events' do describe '#staging_events' do let(:stage) { :staging } let(:merge_request) { MergeRequest.first } + let!(:pipeline) do create(:ci_pipeline, ref: merge_request.source_branch, sha: merge_request.diff_head_sha, - project: context.project, + project: project, head_pipeline_of: merge_request) end before do - create(:ci_build, pipeline: pipeline, status: :success, author: user) - create(:ci_build, pipeline: pipeline, status: :success, author: user) + create(:ci_build, :success, pipeline: pipeline, author: user) + create(:ci_build, :success, pipeline: pipeline, author: user) pipeline.run! pipeline.succeed! diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb index e77745acbb7..805496e4a54 100644 --- a/spec/requests/api/jobs_spec.rb +++ b/spec/requests/api/jobs_spec.rb @@ -11,7 +11,7 @@ describe API::Jobs do ref: project.default_branch) end - let!(:job) { create(:ci_build, pipeline: pipeline) } + let!(:job) { create(:ci_build, :success, pipeline: pipeline) } let(:user) { create(:user) } let(:api_user) { user } @@ -443,7 +443,7 @@ describe API::Jobs do context 'user with :update_build persmission' do it 'cancels running or pending job' do expect(response).to have_gitlab_http_status(201) - expect(project.builds.first.status).to eq('canceled') + expect(project.builds.first.status).to eq('success') end end |