Commit cf26610f authored by Phil Hughes's avatar Phil Hughes

Merge branch '50904-stages-sidebar' into 'master'

Moves stages dropdown into the new vue app

See merge request gitlab-org/gitlab-ce!21971
parents c4d9f402 f72a1bf0
...@@ -24,7 +24,6 @@ export default class Job extends LogOutputBehaviours { ...@@ -24,7 +24,6 @@ export default class Job extends LogOutputBehaviours {
this.$document = $(document); this.$document = $(document);
this.$window = $(window); this.$window = $(window);
this.logBytes = 0; this.logBytes = 0;
this.updateDropdown = this.updateDropdown.bind(this);
this.$buildTrace = $('#build-trace'); this.$buildTrace = $('#build-trace');
this.$buildRefreshAnimation = $('.js-build-refresh'); this.$buildRefreshAnimation = $('.js-build-refresh');
...@@ -35,18 +34,12 @@ export default class Job extends LogOutputBehaviours { ...@@ -35,18 +34,12 @@ export default class Job extends LogOutputBehaviours {
clearTimeout(this.timeout); clearTimeout(this.timeout);
this.initSidebar(); this.initSidebar();
this.populateJobs(this.buildStage);
this.updateStageDropdownText(this.buildStage);
this.sidebarOnResize(); this.sidebarOnResize();
this.$document this.$document
.off('click', '.js-sidebar-build-toggle') .off('click', '.js-sidebar-build-toggle')
.on('click', '.js-sidebar-build-toggle', this.sidebarOnClick.bind(this)); .on('click', '.js-sidebar-build-toggle', this.sidebarOnClick.bind(this));
this.$document
.off('click', '.stage-item')
.on('click', '.stage-item', this.updateDropdown);
this.scrollThrottled = _.throttle(this.toggleScroll.bind(this), 100); this.scrollThrottled = _.throttle(this.toggleScroll.bind(this), 100);
this.$window this.$window
...@@ -194,20 +187,4 @@ export default class Job extends LogOutputBehaviours { ...@@ -194,20 +187,4 @@ export default class Job extends LogOutputBehaviours {
if (this.shouldHideSidebarForViewport()) this.toggleSidebar(); if (this.shouldHideSidebarForViewport()) this.toggleSidebar();
} }
// eslint-disable-next-line class-methods-use-this
populateJobs(stage) {
$('.build-job').hide();
$(`.build-job[data-stage="${stage}"]`).show();
}
// eslint-disable-next-line class-methods-use-this
updateStageDropdownText(stage) {
$('.stage-selection').text(stage);
}
updateDropdown(e) {
e.preventDefault();
const stage = e.currentTarget.text;
this.updateStageDropdownText(stage);
this.populateJobs(stage);
}
} }
<script> <script>
import _ from 'underscore';
import CiIcon from '~/vue_shared/components/ci_icon.vue'; import CiIcon from '~/vue_shared/components/ci_icon.vue';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import tooltip from '~/vue_shared/directives/tooltip'; import tooltip from '~/vue_shared/directives/tooltip';
...@@ -16,26 +17,39 @@ ...@@ -16,26 +17,39 @@
type: Array, type: Array,
required: true, required: true,
}, },
jobId: {
type: Number,
required: true,
},
},
methods: {
isJobActive(currentJobId) {
return this.jobId === currentJobId;
},
tooltipText(job) {
return `${_.escape(job.name)} - ${job.status.tooltip}`;
},
}, },
}; };
</script> </script>
<template> <template>
<div class="builds-container"> <div class="js-jobs-container builds-container">
<div <div
v-for="job in jobs"
:key="job.id"
class="build-job" class="build-job"
:class="{ retried: job.retried, active: isJobActive(job.id) }"
> >
<a <a
v-for="job in jobs"
:key="job.id"
v-tooltip v-tooltip
:href="job.path" :href="job.status.details_path"
:title="job.tooltip" :title="tooltipText(job)"
:class="{ active: job.active, retried: job.retried }" data-container="body"
> >
<icon <icon
v-if="job.active" v-if="isJobActive(job.id)"
name="arrow-right" name="arrow-right"
class="js-arrow-right" class="js-arrow-right icon-arrow-right"
/> />
<ci-icon :status="job.status" /> <ci-icon :status="job.status" />
......
<script> <script>
import _ from 'underscore'; import _ from 'underscore';
import { mapActions, mapState } from 'vuex';
import timeagoMixin from '~/vue_shared/mixins/timeago'; import timeagoMixin from '~/vue_shared/mixins/timeago';
import { timeIntervalInWords } from '~/lib/utils/datetime_utility'; import { timeIntervalInWords } from '~/lib/utils/datetime_utility';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
...@@ -7,26 +8,22 @@ ...@@ -7,26 +8,22 @@
import ArtifactsBlock from './artifacts_block.vue'; import ArtifactsBlock from './artifacts_block.vue';
import TriggerBlock from './trigger_block.vue'; import TriggerBlock from './trigger_block.vue';
import CommitBlock from './commit_block.vue'; import CommitBlock from './commit_block.vue';
import StagesDropdown from './stages_dropdown.vue';
import JobsContainer from './jobs_container.vue';
export default { export default {
name: 'SidebarDetailsBlock', name: 'JobSidebar',
components: { components: {
ArtifactsBlock, ArtifactsBlock,
CommitBlock, CommitBlock,
DetailRow, DetailRow,
Icon, Icon,
TriggerBlock, TriggerBlock,
StagesDropdown,
JobsContainer,
}, },
mixins: [timeagoMixin], mixins: [timeagoMixin],
props: { props: {
job: {
type: Object,
required: true,
},
isLoading: {
type: Boolean,
required: true,
},
runnerHelpUrl: { runnerHelpUrl: {
type: String, type: String,
required: false, required: false,
...@@ -39,9 +36,7 @@ ...@@ -39,9 +36,7 @@
}, },
}, },
computed: { computed: {
shouldRenderContent() { ...mapState(['job', 'isLoading', 'stages', 'jobs']),
return !this.isLoading && Object.keys(this.job).length > 0;
},
coverage() { coverage() {
return `${this.job.coverage}%`; return `${this.job.coverage}%`;
}, },
...@@ -97,20 +92,31 @@ ...@@ -97,20 +92,31 @@
}, },
hasStages() { hasStages() {
return ( return (
this.job && (this.job &&
this.job.pipeline && this.job.pipeline &&
this.job.pipeline.stages && this.job.pipeline.stages &&
this.job.pipeline.stages.length > 0 this.job.pipeline.stages.length > 0) ||
) || false; false
);
}, },
commit() { commit() {
return this.job.pipeline.commit || {}; return this.job.pipeline.commit || {};
}, },
}, },
methods: {
...mapActions(['fetchJobsForStage']),
},
}; };
</script> </script>
<template> <template>
<div> <aside
class="right-sidebar right-sidebar-expanded build-sidebar"
data-offset-top="101"
data-spy="affix"
>
<div class="sidebar-container">
<div class="blocks-container">
<template v-if="!isLoading">
<div class="block"> <div class="block">
<strong class="inline prepend-top-8"> <strong class="inline prepend-top-8">
{{ job.name }} {{ job.name }}
...@@ -137,7 +143,8 @@ ...@@ -137,7 +143,8 @@
<button <button
:aria-label="__('Toggle Sidebar')" :aria-label="__('Toggle Sidebar')"
type="button" type="button"
class="btn btn-blank gutter-toggle float-right d-block d-md-none js-sidebar-build-toggle" class="btn btn-blank gutter-toggle
float-right d-block d-md-none js-sidebar-build-toggle"
> >
<i <i
aria-hidden="true" aria-hidden="true"
...@@ -146,7 +153,6 @@ ...@@ -146,7 +153,6 @@
></i> ></i>
</button> </button>
</div> </div>
<template v-if="shouldRenderContent">
<div <div
v-if="job.retry_path || job.new_issue_path" v-if="job.retry_path || job.new_issue_path"
class="block retry-link" class="block retry-link"
...@@ -168,7 +174,7 @@ ...@@ -168,7 +174,7 @@
{{ __('Retry') }} {{ __('Retry') }}
</a> </a>
</div> </div>
<div :class="{block : renderBlock }"> <div :class="{ block : renderBlock }">
<p <p
v-if="job.merge_request" v-if="job.merge_request"
class="build-detail-row js-job-mr" class="build-detail-row js-job-mr"
...@@ -266,11 +272,26 @@ ...@@ -266,11 +272,26 @@
:commit="commit" :commit="commit"
:merge-request="job.merge_request" :merge-request="job.merge_request"
/> />
<stages-dropdown
:stages="stages"
:pipeline="job.pipeline"
@requestSidebarStageDropdown="fetchJobsForStage"
/>
</template> </template>
<gl-loading-icon <gl-loading-icon
v-if="isLoading" v-else
:size="2" :size="2"
class="prepend-top-10" class="prepend-top-10"
/> />
</div> </div>
<jobs-container
v-if="!isLoading && jobs.length"
:jobs="jobs"
:job-id="job.id"
/>
</div>
</aside>
</template> </template>
<script> <script>
import _ from 'underscore';
import CiIcon from '~/vue_shared/components/ci_icon.vue'; import CiIcon from '~/vue_shared/components/ci_icon.vue';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import { __ } from '~/locale';
import { sprintf, __ } from '~/locale';
export default { export default {
components: { components: {
...@@ -10,30 +10,14 @@ ...@@ -10,30 +10,14 @@
Icon, Icon,
}, },
props: { props: {
pipelineId: { pipeline: {
type: Number, type: Object,
required: true,
},
pipelinePath: {
type: String,
required: true,
},
pipelineRef: {
type: String,
required: true,
},
pipelineRefPath: {
type: String,
required: true, required: true,
}, },
stages: { stages: {
type: Array, type: Array,
required: true, required: true,
}, },
pipelineStatus: {
type: Object,
required: true,
},
}, },
data() { data() {
return { return {
...@@ -41,51 +25,68 @@ ...@@ -41,51 +25,68 @@
}; };
}, },
computed: { computed: {
pipelineLink() { hasRef() {
return sprintf(__('Pipeline %{pipelineLinkStart} #%{pipelineId} %{pipelineLinkEnd} from %{pipelineLinkRefStart} %{pipelineRef} %{pipelineLinkRefEnd}'), { return !_.isEmpty(this.pipeline.ref);
pipelineLinkStart: `<a href=${this.pipelinePath} class="js-pipeline-path link-commit">`, },
pipelineId: this.pipelineId, },
pipelineLinkEnd: '</a>', watch: {
pipelineLinkRefStart: `<a href=${this.pipelineRefPath} class="link-commit ref-name">`, // When the component is initially mounted it may start with an empty stages array.
pipelineRef: this.pipelineRef, // Once the prop is updated, we set the first stage as the selected one
pipelineLinkRefEnd: '</a>', stages(newVal) {
}, false); if (newVal.length) {
this.selectedStage = newVal[0].name;
}
}, },
}, },
methods: { methods: {
onStageClick(stage) { onStageClick(stage) {
// todo: consider moving into store
this.selectedStage = stage.name;
// update dropdown with jobs
// jobs container is a new component.
this.$emit('requestSidebarStageDropdown', stage); this.$emit('requestSidebarStageDropdown', stage);
this.selectedStage = stage.name;
}, },
}, },
}; };
</script> </script>
<template> <template>
<div class="block-last"> <div class="block-last dropdown">
<ci-icon :status="pipelineStatus" /> <ci-icon
:status="pipeline.details.status"
class="vertical-align-middle"
/>
<p v-html="pipelineLink"></p> {{ __('Pipeline') }}
<a
:href="pipeline.path"
class="js-pipeline-path link-commit"
>
#{{ pipeline.id }}
</a>
<template v-if="hasRef">
{{ __('from') }}
<a
:href="pipeline.ref.path"
class="link-commit ref-name"
>
{{ pipeline.ref.name }}
</a>
</template>
<div class="dropdown">
<button <button
type="button" type="button"
data-toggle="dropdown" data-toggle="dropdown"
class="js-selected-stage dropdown-menu-toggle prepend-top-8"
> >
{{ selectedStage }} {{ selectedStage }}
<icon name="chevron-down" /> <i class="fa fa-chevron-down" ></i>
</button> </button>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li <li
v-for="(stage, index) in stages" v-for="stage in stages"
:key="index" :key="stage.name"
> >
<button <button
type="button" type="button"
class="stage-item" class="js-stage-item stage-item"
@click="onStageClick(stage)" @click="onStageClick(stage)"
> >
{{ stage.name }} {{ stage.name }}
...@@ -93,5 +94,4 @@ ...@@ -93,5 +94,4 @@
</li> </li>
</ul> </ul>
</div> </div>
</div>
</template> </template>
import { mapState } from 'vuex'; import _ from 'underscore';
import { mapState, mapActions } from 'vuex';
import Vue from 'vue'; import Vue from 'vue';
import Job from '../job'; import Job from '../job';
import JobHeader from './components/header.vue'; import JobHeader from './components/header.vue';
import DetailsBlock from './components/sidebar_details_block.vue'; import Sidebar from './components/sidebar.vue';
import createStore from './store'; import createStore from './store';
export default () => { export default () => {
...@@ -13,6 +14,7 @@ export default () => { ...@@ -13,6 +14,7 @@ export default () => {
const store = createStore(); const store = createStore();
store.dispatch('setJobEndpoint', dataset.endpoint); store.dispatch('setJobEndpoint', dataset.endpoint);
store.dispatch('fetchJob'); store.dispatch('fetchJob');
// Header // Header
...@@ -43,17 +45,25 @@ export default () => { ...@@ -43,17 +45,25 @@ export default () => {
new Vue({ new Vue({
el: detailsBlockElement, el: detailsBlockElement,
components: { components: {
DetailsBlock, Sidebar,
}, },
store,
computed: { computed: {
...mapState(['job', 'isLoading']), ...mapState(['job']),
},
watch: {
job(newVal, oldVal) {
if (_.isEmpty(oldVal) && !_.isEmpty(newVal.pipeline)) {
this.fetchStages();
}
},
}, },
methods: {
...mapActions(['fetchStages']),
},
store,
render(createElement) { render(createElement) {
return createElement('details-block', { return createElement('sidebar', {
props: { props: {
isLoading: this.isLoading,
job: this.job,
runnerHelpUrl: dataset.runnerHelpUrl, runnerHelpUrl: dataset.runnerHelpUrl,
terminalPath: detailsBlockDataset.terminalPath, terminalPath: detailsBlockDataset.terminalPath,
}, },
......
...@@ -62,7 +62,9 @@ export const fetchJob = ({ state, dispatch }) => { ...@@ -62,7 +62,9 @@ export const fetchJob = ({ state, dispatch }) => {
}); });
}; };
export const receiveJobSuccess = ({ commit }, data) => commit(types.RECEIVE_JOB_SUCCESS, data); export const receiveJobSuccess = ({ commit }, data) => {
commit(types.RECEIVE_JOB_SUCCESS, data);
};
export const receiveJobError = ({ commit }) => { export const receiveJobError = ({ commit }) => {
commit(types.RECEIVE_JOB_ERROR); commit(types.RECEIVE_JOB_ERROR);
flash(__('An error occurred while fetching the job.')); flash(__('An error occurred while fetching the job.'));
...@@ -137,8 +139,11 @@ export const fetchStages = ({ state, dispatch }) => { ...@@ -137,8 +139,11 @@ export const fetchStages = ({ state, dispatch }) => {
dispatch('requestStages'); dispatch('requestStages');
axios axios
.get(state.stagesEndpoint) .get(state.job.pipeline.path)
.then(({ data }) => dispatch('receiveStagesSuccess', data)) .then(({ data }) => {
dispatch('receiveStagesSuccess', data.details.stages);
dispatch('fetchJobsForStage', data.details.stages[0]);
})
.catch(() => dispatch('receiveStagesError')); .catch(() => dispatch('receiveStagesError'));
}; };
export const receiveStagesSuccess = ({ commit }, data) => export const receiveStagesSuccess = ({ commit }, data) =>
...@@ -152,16 +157,23 @@ export const receiveStagesError = ({ commit }) => { ...@@ -152,16 +157,23 @@ export const receiveStagesError = ({ commit }) => {
* Jobs list on sidebar - depend on stages dropdown * Jobs list on sidebar - depend on stages dropdown
*/ */
export const requestJobsForStage = ({ commit }) => commit(types.REQUEST_JOBS_FOR_STAGE); export const requestJobsForStage = ({ commit }) => commit(types.REQUEST_JOBS_FOR_STAGE);
export const setSelectedStage = ({ commit }, stage) => commit(types.SET_SELECTED_STAGE, stage);
// On stage click, set selected stage + fetch job // On stage click, set selected stage + fetch job
export const fetchJobsForStage = ({ state, dispatch }, stage) => { export const fetchJobsForStage = ({ dispatch }, stage) => {
dispatch('setSelectedStage', stage);
dispatch('requestJobsForStage'); dispatch('requestJobsForStage');
axios axios
.get(state.stageJobsEndpoint) .get(stage.dropdown_path, {
.then(({ data }) => dispatch('receiveJobsForStageSuccess', data)) params: {
retried: 1,
},
})
.then(({ data }) => {
const retriedJobs = data.retried.map(job => Object.assign({}, job, { retried: true }));
const jobs = data.latest_statuses.concat(retriedJobs);
dispatch('receiveJobsForStageSuccess', jobs);
})
.catch(() => dispatch('receiveJobsForStageError')); .catch(() => dispatch('receiveJobsForStageError'));
}; };
export const receiveJobsForStageSuccess = ({ commit }, data) => export const receiveJobsForStageSuccess = ({ commit }, data) =>
......
...@@ -328,23 +328,6 @@ ...@@ -328,23 +328,6 @@
} }
} }
.build-dropdown {
margin: $gl-padding 0;
padding: 0;
.dropdown-menu-toggle {
margin-top: #{$gl-padding / 2};
}
svg {
position: relative;
top: 3px;
margin-right: 3px;
width: 14px;
height: 14px;
}
}
.builds-container { .builds-container {
background-color: $white-light; background-color: $white-light;
border-top: 1px solid $border-color; border-top: 1px solid $border-color;
...@@ -381,15 +364,11 @@ ...@@ -381,15 +364,11 @@
position: absolute; position: absolute;
left: 15px; left: 15px;
top: 20px; top: 20px;
display: none; display: block;
} }
&.active { &.active {
font-weight: $gl-font-weight-bold; font-weight: $gl-font-weight-bold;
.icon-arrow-right {
display: block;
}
} }
&.retried { &.retried {
......
%aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar.js-right-sidebar{ data: { "offset-top" => "101", "spy" => "affix" } }
.sidebar-container
.blocks-container
#js-details-block-vue{ data: { terminal_path: can?(current_user, :create_build_terminal, @build) && @build.has_terminal? ? terminal_project_job_path(@project, @build) : nil } }
- if @build.pipeline.stages_count > 1
.block-last.dropdown.build-dropdown
%div
%span{ class: "ci-status-icon-#{@build.pipeline.status}" }
= ci_icon_for_status(@build.pipeline.status)
Pipeline
= link_to "##{@build.pipeline.id}", project_pipeline_path(@project, @build.pipeline), class: 'link-commit'
from
= link_to "#{@build.pipeline.ref}", project_ref_path(@project, @build.pipeline.ref), class: 'link-commit ref-name'
%button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
%span.stage-selection More
= icon('chevron-down')
%ul.dropdown-menu
- @build.pipeline.legacy_stages.each do |stage|
%li
%a.stage-item= stage.name
.builds-container
- HasStatus::ORDERED_STATUSES.each do |build_status|
- builds.select{|build| build.status == build_status}.each do |build|
.build-job{ class: sidebar_build_class(build, @build), data: { stage: build.stage } }
- tooltip = sanitize(build.tooltip_message.dup)
= link_to(project_job_path(@project, build), data: { toggle: 'tooltip', title: tooltip, container: 'body' }) do
= sprite_icon('arrow-right', size:16, css_class: 'icon-arrow-right')
%span{ class: "ci-status-icon-#{build.status}" }
= ci_icon_for_status(build.status)
%span
- if build.name
= build.name
- else
= build.id
- if build.retried?
= sprite_icon('retry', size:16, css_class: 'icon-retry')
...@@ -93,7 +93,7 @@ ...@@ -93,7 +93,7 @@
- else - else
= render "empty_states" = render "empty_states"
= render "sidebar", builds: @builds #js-details-block-vue{ data: { terminal_path: can?(current_user, :create_build_terminal, @build) && @build.has_terminal? ? terminal_project_job_path(@project, @build) : nil } }
.js-build-options{ data: javascript_build_options } .js-build-options{ data: javascript_build_options }
......
...@@ -4310,9 +4310,6 @@ msgstr "" ...@@ -4310,9 +4310,6 @@ msgstr ""
msgid "Pipeline" msgid "Pipeline"
msgstr "" msgstr ""
msgid "Pipeline %{pipelineLinkStart} #%{pipelineId} %{pipelineLinkEnd} from %{pipelineLinkRefStart} %{pipelineRef} %{pipelineLinkRefEnd}"
msgstr ""
msgid "Pipeline Health" msgid "Pipeline Health"
msgstr "" msgstr ""
...@@ -7039,6 +7036,9 @@ msgstr "" ...@@ -7039,6 +7036,9 @@ msgstr ""
msgid "for this project" msgid "for this project"
msgstr "" msgstr ""
msgid "from"
msgstr ""
msgid "here" msgid "here"
msgstr "" msgstr ""
......
...@@ -60,7 +60,7 @@ describe 'Environment' do ...@@ -60,7 +60,7 @@ describe 'Environment' do
context 'with manual action' do context 'with manual action' do
let(:action) do let(:action) do
create(:ci_build, :manual, pipeline: pipeline, create(:ci_build, :manual, pipeline: pipeline,
name: 'deploy to production') name: 'deploy to production', environment: environment.name)
end end
context 'when user has ability to trigger deployment' do context 'when user has ability to trigger deployment' do
...@@ -73,12 +73,16 @@ describe 'Environment' do ...@@ -73,12 +73,16 @@ describe 'Environment' do
expect(page).to have_link(action.name.humanize) expect(page).to have_link(action.name.humanize)
end end
it 'does allow to play manual action' do it 'does allow to play manual action', :js do
expect(action).to be_manual expect(action).to be_manual
find('button.dropdown').click
expect { click_link(action.name.humanize) } expect { click_link(action.name.humanize) }
.not_to change { Ci::Pipeline.count } .not_to change { Ci::Pipeline.count }
wait_for_all_requests
expect(page).to have_content(action.name) expect(page).to have_content(action.name)
expect(action.reload).to be_pending expect(action.reload).to be_pending
end end
...@@ -165,10 +169,10 @@ describe 'Environment' do ...@@ -165,10 +169,10 @@ describe 'Environment' do
name: action.ref, project: project) name: action.ref, project: project)
end end
it 'allows to stop environment' do it 'allows to stop environment', :js do
click_button('Stop') click_button('Stop')
click_button('Stop environment') # confirm modal click_button('Stop environment') # confirm modal
wait_for_all_requests
expect(page).to have_content('close_app') expect(page).to have_content('close_app')
end end
end end
......
...@@ -38,9 +38,10 @@ describe 'User browses a job', :js do ...@@ -38,9 +38,10 @@ describe 'User browses a job', :js do
let!(:build) { create(:ci_build, :failed, :trace_artifact, pipeline: pipeline) } let!(:build) { create(:ci_build, :failed, :trace_artifact, pipeline: pipeline) }
it 'displays the failure reason' do it 'displays the failure reason' do
wait_for_all_requests
within('.builds-container') do within('.builds-container') do
build_link = first('.build-job > a') build_link = first('.build-job > a')
expect(build_link['data-title']).to eq('test - failed - (unknown failure)') expect(build_link['data-original-title']).to eq('test - failed - (unknown failure)')
end end
end end
end end
...@@ -49,9 +50,10 @@ describe 'User browses a job', :js do ...@@ -49,9 +50,10 @@ describe 'User browses a job', :js do
let!(:build) { create(:ci_build, :failed, :retried, :trace_artifact, pipeline: pipeline) } let!(:build) { create(:ci_build, :failed, :retried, :trace_artifact, pipeline: pipeline) }
it 'displays the failure reason and retried label' do it 'displays the failure reason and retried label' do
wait_for_all_requests
within('.builds-container') do within('.builds-container') do
build_link = first('.build-job > a') build_link = first('.build-job > a')
expect(build_link['data-title']).to eq('test - failed - (unknown failure) (retried)') expect(build_link['data-original-title']).to eq('test - failed - (unknown failure) (retried)')
end end
end end
end end
......
...@@ -134,23 +134,25 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do ...@@ -134,23 +134,25 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
expect(page).to have_content pipeline.commit.title expect(page).to have_content pipeline.commit.title
end end
it 'shows active job' do it 'shows active job', :js do
visit project_job_path(project, job) visit project_job_path(project, job)
wait_for_requests
expect(page).to have_selector('.build-job.active') expect(page).to have_selector('.build-job.active')
end end
end end
context 'sidebar' do context 'sidebar', :js do
let(:job) { create(:ci_build, :success, :trace_live, pipeline: pipeline, name: '<img src=x onerror=alert(document.domain)>') } let(:job) { create(:ci_build, :success, :trace_live, pipeline: pipeline, name: '<img src=x onerror=alert(document.domain)>') }
before do before do
visit project_job_path(project, job) visit project_job_path(project, job)
wait_for_requests
end end
it 'renders escaped tooltip name' do it 'renders escaped tooltip name' do
page.within('aside.right-sidebar') do page.within('aside.right-sidebar') do
expect(find('.active.build-job a')['data-title']).to eq('<img src="x"> - passed') expect(find('.active.build-job a')['data-original-title']).to eq('&lt;img src=x onerror=alert(document.domain)&gt; - passed')
end end
end end
end end
......
...@@ -57,25 +57,6 @@ describe('Job', () => { ...@@ -57,25 +57,6 @@ describe('Job', () => {
expect(job.buildStage).toBe('test'); expect(job.buildStage).toBe('test');
expect(job.state).toBe(''); expect(job.state).toBe('');
}); });
it('only shows the jobs matching the current stage', () => {
expect($('.build-job[data-stage="build"]').is(':visible')).toBe(false);
expect($('.build-job[data-stage="test"]').is(':visible')).toBe(true);
expect($('.build-job[data-stage="deploy"]').is(':visible')).toBe(false);
});
it('selects the current stage in the build dropdown menu', () => {
expect($('.stage-selection').text()).toBe('test');
});
it('updates the jobs when the build dropdown changes', () => {
$('.stage-item:contains("build")').click();
expect($('.stage-selection').text()).toBe('build');
expect($('.build-job[data-stage="build"]').is(':visible')).toBe(true);
expect($('.build-job[data-stage="test"]').is(':visible')).toBe(false);
expect($('.build-job[data-stage="deploy"]').is(':visible')).toBe(false);
});
}); });
describe('running build', () => { describe('running build', () => {
......
...@@ -2,7 +2,7 @@ import Vue from 'vue'; ...@@ -2,7 +2,7 @@ import Vue from 'vue';
import component from '~/jobs/components/jobs_container.vue'; import component from '~/jobs/components/jobs_container.vue';
import mountComponent from '../../helpers/vue_mount_component_helper'; import mountComponent from '../../helpers/vue_mount_component_helper';
describe('Artifacts block', () => { describe('Jobs List block', () => {
const Component = Vue.extend(component); const Component = Vue.extend(component);
let vm; let vm;
...@@ -16,8 +16,7 @@ describe('Artifacts block', () => { ...@@ -16,8 +16,7 @@ describe('Artifacts block', () => {
text: 'passed', text: 'passed',
tooltip: 'passed', tooltip: 'passed',
}, },
path: 'job/233432756', id: 233432756,
id: '233432756',
tooltip: 'build - passed', tooltip: 'build - passed',
retried: true, retried: true,
}; };
...@@ -33,8 +32,7 @@ describe('Artifacts block', () => { ...@@ -33,8 +32,7 @@ describe('Artifacts block', () => {
text: 'passed', text: 'passed',
tooltip: 'passed', tooltip: 'passed',
}, },
path: 'job/2322756', id: 2322756,
id: '2322756',
tooltip: 'build - passed', tooltip: 'build - passed',
active: true, active: true,
}; };
...@@ -50,8 +48,7 @@ describe('Artifacts block', () => { ...@@ -50,8 +48,7 @@ describe('Artifacts block', () => {
text: 'passed', text: 'passed',
tooltip: 'passed', tooltip: 'passed',
}, },
path: 'job/232153', id: 232153,
id: '232153',
tooltip: 'build - passed', tooltip: 'build - passed',
}; };
...@@ -62,14 +59,16 @@ describe('Artifacts block', () => { ...@@ -62,14 +59,16 @@ describe('Artifacts block', () => {
it('renders list of jobs', () => { it('renders list of jobs', () => {
vm = mountComponent(Component, { vm = mountComponent(Component, {
jobs: [job, retried, active], jobs: [job, retried, active],
jobId: 12313,
}); });
expect(vm.$el.querySelectorAll('a').length).toEqual(3); expect(vm.$el.querySelectorAll('a').length).toEqual(3);
}); });
it('renders arrow right when job is active', () => { it('renders arrow right when job id matches `jobId`', () => {
vm = mountComponent(Component, { vm = mountComponent(Component, {
jobs: [active], jobs: [active],
jobId: active.id,
}); });
expect(vm.$el.querySelector('a .js-arrow-right')).not.toBeNull(); expect(vm.$el.querySelector('a .js-arrow-right')).not.toBeNull();
...@@ -78,6 +77,7 @@ describe('Artifacts block', () => { ...@@ -78,6 +77,7 @@ describe('Artifacts block', () => {
it('does not render arrow right when job is not active', () => { it('does not render arrow right when job is not active', () => {
vm = mountComponent(Component, { vm = mountComponent(Component, {
jobs: [job], jobs: [job],
jobId: active.id,
}); });
expect(vm.$el.querySelector('a .js-arrow-right')).toBeNull(); expect(vm.$el.querySelector('a .js-arrow-right')).toBeNull();
...@@ -86,6 +86,7 @@ describe('Artifacts block', () => { ...@@ -86,6 +86,7 @@ describe('Artifacts block', () => {
it('renders job name when present', () => { it('renders job name when present', () => {
vm = mountComponent(Component, { vm = mountComponent(Component, {
jobs: [job], jobs: [job],
jobId: active.id,
}); });
expect(vm.$el.querySelector('a').textContent.trim()).toContain(job.name); expect(vm.$el.querySelector('a').textContent.trim()).toContain(job.name);
...@@ -95,6 +96,7 @@ describe('Artifacts block', () => { ...@@ -95,6 +96,7 @@ describe('Artifacts block', () => {
it('renders job id when job name is not available', () => { it('renders job id when job name is not available', () => {
vm = mountComponent(Component, { vm = mountComponent(Component, {
jobs: [retried], jobs: [retried],
jobId: active.id,
}); });
expect(vm.$el.querySelector('a').textContent.trim()).toContain(retried.id); expect(vm.$el.querySelector('a').textContent.trim()).toContain(retried.id);
...@@ -103,14 +105,16 @@ describe('Artifacts block', () => { ...@@ -103,14 +105,16 @@ describe('Artifacts block', () => {
it('links to the job page', () => { it('links to the job page', () => {
vm = mountComponent(Component, { vm = mountComponent(Component, {
jobs: [job], jobs: [job],
jobId: active.id,
}); });
expect(vm.$el.querySelector('a').getAttribute('href')).toEqual(job.path); expect(vm.$el.querySelector('a').getAttribute('href')).toEqual(job.status.details_path);
}); });
it('renders retry icon when job was retried', () => { it('renders retry icon when job was retried', () => {
vm = mountComponent(Component, { vm = mountComponent(Component, {
jobs: [retried], jobs: [retried],
jobId: active.id,
}); });
expect(vm.$el.querySelector('.js-retry-icon')).not.toBeNull(); expect(vm.$el.querySelector('.js-retry-icon')).not.toBeNull();
...@@ -119,6 +123,7 @@ describe('Artifacts block', () => { ...@@ -119,6 +123,7 @@ describe('Artifacts block', () => {
it('does not render retry icon when job was not retried', () => { it('does not render retry icon when job was not retried', () => {
vm = mountComponent(Component, { vm = mountComponent(Component, {
jobs: [job], jobs: [job],
jobId: active.id,
}); });
expect(vm.$el.querySelector('.js-retry-icon')).toBeNull(); expect(vm.$el.querySelector('.js-retry-icon')).toBeNull();
......
import Vue from 'vue'; import Vue from 'vue';
import sidebarDetailsBlock from '~/jobs/components/sidebar_details_block.vue'; import sidebarDetailsBlock from '~/jobs/components/sidebar.vue';
import job from '../mock_data'; import createStore from '~/jobs/store';
import mountComponent from '../../helpers/vue_mount_component_helper'; import job, { stages, jobsInStage } from '../mock_data';
import { mountComponentWithStore } from '../../helpers/vue_mount_component_helper';
import { trimText } from '../../helpers/vue_component_helper';
describe('Sidebar details block', () => { describe('Sidebar details block', () => {
let SidebarComponent; const SidebarComponent = Vue.extend(sidebarDetailsBlock);
let vm; let vm;
let store;
function trimWhitespace(element) {
return element.textContent.replace(/\s+/g, ' ').trim();
}
beforeEach(() => { beforeEach(() => {
SidebarComponent = Vue.extend(sidebarDetailsBlock); store = createStore();
}); });
afterEach(() => { afterEach(() => {
...@@ -21,19 +20,21 @@ describe('Sidebar details block', () => { ...@@ -21,19 +20,21 @@ describe('Sidebar details block', () => {
describe('when it is loading', () => { describe('when it is loading', () => {
it('should render a loading spinner', () => { it('should render a loading spinner', () => {
vm = mountComponent(SidebarComponent, { store.dispatch('requestJob');
job: {}, vm = mountComponentWithStore(SidebarComponent, { store });
isLoading: true,
});
expect(vm.$el.querySelector('.fa-spinner')).toBeDefined(); expect(vm.$el.querySelector('.fa-spinner')).toBeDefined();
}); });
}); });
describe('when there is no retry path retry', () => { describe('when there is no retry path retry', () => {
it('should not render a retry button', () => { it('should not render a retry button', () => {
vm = mountComponent(SidebarComponent, { const copy = Object.assign({}, job);
job: {}, delete copy.retry_path;
isLoading: false,
store.dispatch('receiveJobSuccess', copy);
vm = mountComponentWithStore(SidebarComponent, {
store,
}); });
expect(vm.$el.querySelector('.js-retry-job')).toBeNull(); expect(vm.$el.querySelector('.js-retry-job')).toBeNull();
...@@ -42,10 +43,8 @@ describe('Sidebar details block', () => { ...@@ -42,10 +43,8 @@ describe('Sidebar details block', () => {
describe('without terminal path', () => { describe('without terminal path', () => {
it('does not render terminal link', () => { it('does not render terminal link', () => {
vm = mountComponent(SidebarComponent, { store.dispatch('receiveJobSuccess', job);
job, vm = mountComponentWithStore(SidebarComponent, { store });
isLoading: false,
});
expect(vm.$el.querySelector('.js-terminal-link')).toBeNull(); expect(vm.$el.querySelector('.js-terminal-link')).toBeNull();
}); });
...@@ -53,10 +52,12 @@ describe('Sidebar details block', () => { ...@@ -53,10 +52,12 @@ describe('Sidebar details block', () => {
describe('with terminal path', () => { describe('with terminal path', () => {
it('renders terminal link', () => { it('renders terminal link', () => {
vm = mountComponent(SidebarComponent, { store.dispatch('receiveJobSuccess', job);
job, vm = mountComponentWithStore(SidebarComponent, {
isLoading: false, store,
props: {
terminalPath: 'job/43123/terminal', terminalPath: 'job/43123/terminal',
},
}); });
expect(vm.$el.querySelector('.js-terminal-link')).not.toBeNull(); expect(vm.$el.querySelector('.js-terminal-link')).not.toBeNull();
...@@ -64,10 +65,8 @@ describe('Sidebar details block', () => { ...@@ -64,10 +65,8 @@ describe('Sidebar details block', () => {
}); });
beforeEach(() => { beforeEach(() => {
vm = mountComponent(SidebarComponent, { store.dispatch('receiveJobSuccess', job);
job, vm = mountComponentWithStore(SidebarComponent, { store });
isLoading: false,
});
}); });
describe('actions', () => { describe('actions', () => {
...@@ -89,7 +88,7 @@ describe('Sidebar details block', () => { ...@@ -89,7 +88,7 @@ describe('Sidebar details block', () => {
describe('information', () => { describe('information', () => {
it('should render merge request link', () => { it('should render merge request link', () => {
expect(trimWhitespace(vm.$el.querySelector('.js-job-mr'))).toEqual('Merge Request: !2'); expect(trimText(vm.$el.querySelector('.js-job-mr').textContent)).toEqual('Merge Request: !2');
expect(vm.$el.querySelector('.js-job-mr a').getAttribute('href')).toEqual( expect(vm.$el.querySelector('.js-job-mr a').getAttribute('href')).toEqual(
job.merge_request.path, job.merge_request.path,
...@@ -97,43 +96,101 @@ describe('Sidebar details block', () => { ...@@ -97,43 +96,101 @@ describe('Sidebar details block', () => {
}); });
it('should render job duration', () => { it('should render job duration', () => {
expect(trimWhitespace(vm.$el.querySelector('.js-job-duration'))).toEqual( expect(trimText(vm.$el.querySelector('.js-job-duration').textContent)).toEqual(
'Duration: 6 seconds', 'Duration: 6 seconds',
); );
}); });
it('should render erased date', () => { it('should render erased date', () => {
expect(trimWhitespace(vm.$el.querySelector('.js-job-erased'))).toEqual('Erased: 3 weeks ago'); expect(trimText(vm.$el.querySelector('.js-job-erased').textContent)).toEqual(
'Erased: 3 weeks ago',
);
}); });
it('should render finished date', () => { it('should render finished date', () => {
expect(trimWhitespace(vm.$el.querySelector('.js-job-finished'))).toEqual( expect(trimText(vm.$el.querySelector('.js-job-finished').textContent)).toEqual(
'Finished: 3 weeks ago', 'Finished: 3 weeks ago',
); );
}); });
it('should render queued date', () => { it('should render queued date', () => {
expect(trimWhitespace(vm.$el.querySelector('.js-job-queued'))).toEqual('Queued: 9 seconds'); expect(trimText(vm.$el.querySelector('.js-job-queued').textContent)).toEqual(
'Queued: 9 seconds',
);
}); });
it('should render runner ID', () => { it('should render runner ID', () => {
expect(trimWhitespace(vm.$el.querySelector('.js-job-runner'))).toEqual( expect(trimText(vm.$el.querySelector('.js-job-runner').textContent)).toEqual(
'Runner: local ci runner (#1)', 'Runner: local ci runner (#1)',
); );
}); });
it('should render timeout information', () => { it('should render timeout information', () => {
expect(trimWhitespace(vm.$el.querySelector('.js-job-timeout'))).toEqual( expect(trimText(vm.$el.querySelector('.js-job-timeout').textContent)).toEqual(
'Timeout: 1m 40s (from runner)', 'Timeout: 1m 40s (from runner)',
); );
}); });
it('should render coverage', () => { it('should render coverage', () => {
expect(trimWhitespace(vm.$el.querySelector('.js-job-coverage'))).toEqual('Coverage: 20%'); expect(trimText(vm.$el.querySelector('.js-job-coverage').textContent)).toEqual(
'Coverage: 20%',
);
}); });
it('should render tags', () => { it('should render tags', () => {
expect(trimWhitespace(vm.$el.querySelector('.js-job-tags'))).toEqual('Tags: tag'); expect(trimText(vm.$el.querySelector('.js-job-tags').textContent)).toEqual('Tags: tag');
});
});
describe('stages dropdown', () => {
beforeEach(() => {
store.dispatch('receiveJobSuccess', job);
});
describe('while fetching stages', () => {
it('renders dropdown with More label', () => {
vm = mountComponentWithStore(SidebarComponent, { store });
expect(vm.$el.querySelector('.js-selected-stage').textContent.trim()).toEqual('More');
});
});
describe('with stages', () => {
beforeEach(() => {
store.dispatch('receiveStagesSuccess', stages);
vm = mountComponentWithStore(SidebarComponent, { store });
});
it('renders first stage as selected', () => {
expect(vm.$el.querySelector('.js-selected-stage').textContent.trim()).toEqual(
stages[0].name,
);
});
});
describe('without jobs for stages', () => {
beforeEach(() => {
store.dispatch('receiveJobSuccess', job);
store.dispatch('receiveStagesSuccess', stages);
vm = mountComponentWithStore(SidebarComponent, { store });
});
it('does not render job container', () => {
expect(vm.$el.querySelector('.js-jobs-container')).toBeNull();
});
});
describe('with jobs for stages', () => {
beforeEach(() => {
store.dispatch('receiveJobSuccess', job);
store.dispatch('receiveStagesSuccess', stages);
store.dispatch('receiveJobsForStageSuccess', jobsInStage.latest_statuses);
vm = mountComponentWithStore(SidebarComponent, { store });
});
it('renders list of jobs', () => {
expect(vm.$el.querySelector('.js-jobs-container')).not.toBeNull();
});
}); });
}); });
}); });
...@@ -8,19 +8,10 @@ describe('Artifacts block', () => { ...@@ -8,19 +8,10 @@ describe('Artifacts block', () => {
beforeEach(() => { beforeEach(() => {
vm = mountComponent(Component, { vm = mountComponent(Component, {
pipelineId: 28029444, pipeline: {
pipelinePath: 'pipeline/28029444', id: 28029444,
pipelineRef: '50101-truncated-job-information', details: {
pipelineRefPath: 'commits/50101-truncated-job-information', status: {
stages: [
{
name: 'build',
},
{
name: 'test',
},
],
pipelineStatus: {
details_path: '/gitlab-org/gitlab-ce/pipelines/28029444', details_path: '/gitlab-org/gitlab-ce/pipelines/28029444',
group: 'success', group: 'success',
has_details: true, has_details: true,
...@@ -29,6 +20,21 @@ describe('Artifacts block', () => { ...@@ -29,6 +20,21 @@ describe('Artifacts block', () => {
text: 'passed', text: 'passed',
tooltip: 'passed', tooltip: 'passed',
}, },
},
path: 'pipeline/28029444',
},
ref: {
path: 'commits/50101-truncated-job-information',
name: '50101-truncated-job-information',
},
stages: [
{
name: 'build',
},
{
name: 'test',
},
],
}); });
}); });
......
...@@ -20,7 +20,8 @@ export default { ...@@ -20,7 +20,8 @@ export default {
group: 'success', group: 'success',
has_details: true, has_details: true,
details_path: '/root/ci-mock/-/jobs/4757', details_path: '/root/ci-mock/-/jobs/4757',
favicon: '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png', favicon:
'/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
action: { action: {
icon: 'retry', icon: 'retry',
title: 'Retry', title: 'Retry',
...@@ -37,7 +38,8 @@ export default { ...@@ -37,7 +38,8 @@ export default {
username: 'root', username: 'root',
id: 1, id: 1,
state: 'active', state: 'active',
avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', avatar_url:
'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
web_url: 'http://localhost:3000/root', web_url: 'http://localhost:3000/root',
}, },
erase_path: '/root/ci-mock/-/jobs/4757/erase', erase_path: '/root/ci-mock/-/jobs/4757/erase',
...@@ -54,7 +56,8 @@ export default { ...@@ -54,7 +56,8 @@ export default {
username: 'root', username: 'root',
id: 1, id: 1,
state: 'active', state: 'active',
avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', avatar_url:
'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
web_url: 'http://localhost:3000/root', web_url: 'http://localhost:3000/root',
}, },
active: false, active: false,
...@@ -78,7 +81,8 @@ export default { ...@@ -78,7 +81,8 @@ export default {
group: 'success', group: 'success',
has_details: true, has_details: true,
details_path: '/root/ci-mock/pipelines/140', details_path: '/root/ci-mock/pipelines/140',
favicon: '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png', favicon:
'/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
}, },
duration: 6, duration: 6,
finished_at: '2017-06-01T17:32:00.042Z', finished_at: '2017-06-01T17:32:00.042Z',
...@@ -107,11 +111,14 @@ export default { ...@@ -107,11 +111,14 @@ export default {
username: 'root', username: 'root',
id: 1, id: 1,
state: 'active', state: 'active',
avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', avatar_url:
'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
web_url: 'http://localhost:3000/root', web_url: 'http://localhost:3000/root',
}, },
author_gravatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', author_gravatar_url:
commit_url: 'http://localhost:3000/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6', 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
commit_url:
'http://localhost:3000/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6',
commit_path: '/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6', commit_path: '/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6',
}, },
}, },
...@@ -125,3 +132,1029 @@ export default { ...@@ -125,3 +132,1029 @@ export default {
}, },
raw_path: '/root/ci-mock/builds/4757/raw', raw_path: '/root/ci-mock/builds/4757/raw',
}; };
export const stages = [
{
name: 'build',
title: 'build: running',
groups: [
{
name: 'build:linux',
size: 1,
status: {
icon: 'status_pending',
text: 'pending',
label: 'pending',
group: 'pending',
tooltip: 'pending',
has_details: true,
details_path: '/gitlab-org/gitlab-shell/-/jobs/1180',
illustration: {
image: 'illustrations/pending_job_empty.svg',
size: 'svg-430',
title: 'This job has not started yet',
content: 'This job is in pending state and is waiting to be picked by a runner',
},
favicon:
'/assets/ci_favicons/favicon_status_pending-5bdf338420e5221ca24353b6bff1c9367189588750632e9a871b7af09ff6a2ae.png',
action: {
icon: 'cancel',
title: 'Cancel',
path: '/gitlab-org/gitlab-shell/-/jobs/1180/cancel',
method: 'post',
},
},
jobs: [
{
id: 1180,
name: 'build:linux',
started: false,
build_path: '/gitlab-org/gitlab-shell/-/jobs/1180',
cancel_path: '/gitlab-org/gitlab-shell/-/jobs/1180/cancel',
playable: false,
created_at: '2018-09-28T11:09:57.229Z',
updated_at: '2018-09-28T11:09:57.503Z',
status: {
icon: 'status_pending',
text: 'pending',
label: 'pending',
group: 'pending',
tooltip: 'pending',
has_details: true,
details_path: '/gitlab-org/gitlab-shell/-/jobs/1180',
illustration: {
image: 'illustrations/pending_job_empty.svg',
size: 'svg-430',
title: 'This job has not started yet',
content: 'This job is in pending state and is waiting to be picked by a runner',
},
favicon:
'/assets/ci_favicons/favicon_status_pending-5bdf338420e5221ca24353b6bff1c9367189588750632e9a871b7af09ff6a2ae.png',
action: {
icon: 'cancel',
title: 'Cancel',
path: '/gitlab-org/gitlab-shell/-/jobs/1180/cancel',
method: 'post',
},
},
},
],
},
{
name: 'build:osx',
size: 1,
status: {
icon: 'status_success',
text: 'passed',
label: 'passed',
group: 'success',
tooltip: 'passed',
has_details: true,
details_path: '/gitlab-org/gitlab-shell/-/jobs/444',
illustration: {
image: 'illustrations/skipped-job_empty.svg',
size: 'svg-430',
title: 'This job does not have a trace.',
},
favicon:
'/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
action: {
icon: 'retry',
title: 'Retry',
path: '/gitlab-org/gitlab-shell/-/jobs/444/retry',
method: 'post',
},
},
jobs: [
{
id: 444,
name: 'build:osx',
started: '2018-05-18T05:32:20.655Z',
build_path: '/gitlab-org/gitlab-shell/-/jobs/444',
retry_path: '/gitlab-org/gitlab-shell/-/jobs/444/retry',
playable: false,
created_at: '2018-05-18T15:32:54.364Z',
updated_at: '2018-05-18T15:32:54.364Z',
status: {
icon: 'status_success',
text: 'passed',
label: 'passed',
group: 'success',
tooltip: 'passed',
has_details: true,
details_path: '/gitlab-org/gitlab-shell/-/jobs/444',
illustration: {
image: 'illustrations/skipped-job_empty.svg',
size: 'svg-430',
title: 'This job does not have a trace.',
},
favicon:
'/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
action: {
icon: 'retry',
title: 'Retry',
path: '/gitlab-org/gitlab-shell/-/jobs/444/retry',
method: 'post',
},
},
},
],
},
],
status: {
icon: 'status_running',
text: 'running',
label: 'running',
group: 'running',
tooltip: 'running',
has_details: true,
details_path: '/gitlab-org/gitlab-shell/pipelines/27#build',
illustration: null,
favicon:
'/assets/ci_favicons/favicon_status_running-9c635b2419a8e1ec991c993061b89cc5aefc0743bb238ecd0c381e7741a70e8c.png',
},
path: '/gitlab-org/gitlab-shell/pipelines/27#build',
dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=build',
},
{
name: 'test',
title: 'test: passed with warnings',
groups: [
{
name: 'jenkins',
size: 1,
status: {
icon: 'status_success',
text: 'passed',
label: null,
group: 'success',
tooltip: null,
has_details: false,
details_path: null,
illustration: null,
favicon:
'/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
},
jobs: [
{
id: 459,
name: 'jenkins',
started: '2018-05-18T09:32:20.658Z',
build_path: '/gitlab-org/gitlab-shell/-/jobs/459',
playable: false,
created_at: '2018-05-18T15:32:55.330Z',
updated_at: '2018-05-18T15:32:55.330Z',
status: {
icon: 'status_success',
text: 'passed',
label: null,
group: 'success',
tooltip: null,
has_details: false,
details_path: null,
illustration: null,
favicon:
'/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
},
},
],
},
{
name: 'rspec:linux',
size: 3,
status: {
icon: 'status_success',
text: 'passed',
label: 'passed',
group: 'success',
tooltip: 'passed',
has_details: false,
details_path: null,
illustration: null,
favicon:
'/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
},
jobs: [
{
id: 445,
name: 'rspec:linux 0 3',
started: '2018-05-18T07:32:20.655Z',
build_path: '/gitlab-org/gitlab-shell/-/jobs/445',
retry_path: '/gitlab-org/gitlab-shell/-/jobs/445/retry',
playable: false,
created_at: '2018-05-18T15:32:54.425Z',
updated_at: '2018-05-18T15:32:54.425Z',
status: {
icon: 'status_success',
text: 'passed',
label: 'passed',
group: 'success',
tooltip: 'passed',
has_details: true,
details_path: '/gitlab-org/gitlab-shell/-/jobs/445',
illustration: {
image: 'illustrations/skipped-job_empty.svg',
size: 'svg-430',
title: 'This job does not have a trace.',
},
favicon:
'/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
action: {
icon: 'retry',
title: 'Retry',
path: '/gitlab-org/gitlab-shell/-/jobs/445/retry',
method: 'post',
},
},
},
{
id: 446,
name: 'rspec:linux 1 3',
started: '2018-05-18T07:32:20.655Z',
build_path: '/gitlab-org/gitlab-shell/-/jobs/446',
retry_path: '/gitlab-org/gitlab-shell/-/jobs/446/retry',
playable: false,
created_at: '2018-05-18T15:32:54.506Z',
updated_at: '2018-05-18T15:32:54.506Z',
status: {
icon: 'status_success',
text: 'passed',
label: 'passed',
group: 'success',
tooltip: 'passed',
has_details: true,
details_path: '/gitlab-org/gitlab-shell/-/jobs/446',
illustration: {
image: 'illustrations/skipped-job_empty.svg',
size: 'svg-430',
title: 'This job does not have a trace.',
},
favicon:
'/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
action: {
icon: 'retry',
title: 'Retry',
path: '/gitlab-org/gitlab-shell/-/jobs/446/retry',
method: 'post',
},
},
},
{
id: 447,
name: 'rspec:linux 2 3',
started: '2018-05-18T07:32:20.656Z',
build_path: '/gitlab-org/gitlab-shell/-/jobs/447',
retry_path: '/gitlab-org/gitlab-shell/-/jobs/447/retry',
playable: false,
created_at: '2018-05-18T15:32:54.572Z',
updated_at: '2018-05-18T15:32:54.572Z',
status: {
icon: 'status_success',
text: 'passed',
label: 'passed',
group: 'success',
tooltip: 'passed',
has_details: true,
details_path: '/gitlab-org/gitlab-shell/-/jobs/447',
illustration: {
image: 'illustrations/skipped-job_empty.svg',
size: 'svg-430',
title: 'This job does not have a trace.',
},
favicon:
'/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
action: {
icon: 'retry',
title: 'Retry',
path: '/gitlab-org/gitlab-shell/-/jobs/447/retry',
method: 'post',
},
},
},
],
},
{
name: 'rspec:osx',
size: 1,
status: {
icon: 'status_success',
text: 'passed',
label: 'passed',
group: 'success',
tooltip: 'passed',
has_details: true,
details_path: '/gitlab-org/gitlab-shell/-/jobs/452',
illustration: {
image: 'illustrations/skipped-job_empty.svg',
size: 'svg-430',
title: 'This job does not have a trace.',
},
favicon:
'/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
action: {
icon: 'retry',
title: 'Retry',
path: '/gitlab-org/gitlab-shell/-/jobs/452/retry',
method: 'post',
},
},
jobs: [
{
id: 452,
name: 'rspec:osx',
started: '2018-05-18T07:32:20.657Z',
build_path: '/gitlab-org/gitlab-shell/-/jobs/452',
retry_path: '/gitlab-org/gitlab-shell/-/jobs/452/retry',
playable: false,
created_at: '2018-05-18T15:32:54.920Z',
updated_at: '2018-05-18T15:32:54.920Z',
status: {
icon: 'status_success',
text: 'passed',
label: 'passed',
group: 'success',
tooltip: 'passed',
has_details: true,
details_path: '/gitlab-org/gitlab-shell/-/jobs/452',
illustration: {
image: 'illustrations/skipped-job_empty.svg',
size: 'svg-430',
title: 'This job does not have a trace.',
},
favicon:
'/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
action: {
icon: 'retry',
title: 'Retry',
path: '/gitlab-org/gitlab-shell/-/jobs/452/retry',
method: 'post',
},
},
},
],
},
{
name: 'rspec:windows',
size: 3,
status: {
icon: 'status_success',
text: 'passed',
label: 'passed',
group: 'success',
tooltip: 'passed',
has_details: false,
details_path: null,
illustration: null,
favicon:
'/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
},
jobs: [
{
id: 448,
name: 'rspec:windows 0 3',
started: '2018-05-18T07:32:20.656Z',
build_path: '/gitlab-org/gitlab-shell/-/jobs/448',
retry_path: '/gitlab-org/gitlab-shell/-/jobs/448/retry',
playable: false,
created_at: '2018-05-18T15:32:54.639Z',
updated_at: '2018-05-18T15:32:54.639Z',
status: {
icon: 'status_success',
text: 'passed',
label: 'passed',
group: 'success',
tooltip: 'passed',
has_details: true,
details_path: '/gitlab-org/gitlab-shell/-/jobs/448',
illustration: {
image: 'illustrations/skipped-job_empty.svg',
size: 'svg-430',
title: 'This job does not have a trace.',
},
favicon:
'/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
action: {
icon: 'retry',
title: 'Retry',
path: '/gitlab-org/gitlab-shell/-/jobs/448/retry',
method: 'post',
},
},
},
{
id: 449,
name: 'rspec:windows 1 3',
started: '2018-05-18T07:32:20.656Z',
build_path: '/gitlab-org/gitlab-shell/-/jobs/449',
retry_path: '/gitlab-org/gitlab-shell/-/jobs/449/retry',
playable: false,
created_at: '2018-05-18T15:32:54.703Z',
updated_at: '2018-05-18T15:32:54.703Z',
status: {
icon: 'status_success',
text: 'passed',
label: 'passed',
group: 'success',
tooltip: 'passed',
has_details: true,
details_path: '/gitlab-org/gitlab-shell/-/jobs/449',
illustration: {
image: 'illustrations/skipped-job_empty.svg',
size: 'svg-430',
title: 'This job does not have a trace.',
},
favicon:
'/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
action: {
icon: 'retry',
title: 'Retry',
path: '/gitlab-org/gitlab-shell/-/jobs/449/retry',
method: 'post',
},
},
},
{
id: 451,
name: 'rspec:windows 2 3',
started: '2018-05-18T07:32:20.657Z',
build_path: '/gitlab-org/gitlab-shell/-/jobs/451',
retry_path: '/gitlab-org/gitlab-shell/-/jobs/451/retry',
playable: false,
created_at: '2018-05-18T15:32:54.853Z',
updated_at: '2018-05-18T15:32:54.853Z',
status: {
icon: 'status_success',
text: 'passed',
label: 'passed',
group: 'success',
tooltip: 'passed',
has_details: true,
details_path: '/gitlab-org/gitlab-shell/-/jobs/451',
illustration: {
image: 'illustrations/skipped-job_empty.svg',
size: 'svg-430',
title: 'This job does not have a trace.',
},
favicon:
'/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
action: {
icon: 'retry',
title: 'Retry',
path: '/gitlab-org/gitlab-shell/-/jobs/451/retry',
method: 'post',
},
},
},
],
},
{
name: 'spinach:linux',
size: 1,
status: {
icon: 'status_success',
text: 'passed',
label: 'passed',
group: 'success',
tooltip: 'passed',
has_details: true,
details_path: '/gitlab-org/gitlab-shell/-/jobs/453',
illustration: {
image: 'illustrations/skipped-job_empty.svg',
size: 'svg-430',
title: 'This job does not have a trace.',
},
favicon:
'/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
action: {
icon: 'retry',
title: 'Retry',
path: '/gitlab-org/gitlab-shell/-/jobs/453/retry',
method: 'post',
},
},
jobs: [
{
id: 453,
name: 'spinach:linux',
started: '2018-05-18T07:32:20.657Z',
build_path: '/gitlab-org/gitlab-shell/-/jobs/453',
retry_path: '/gitlab-org/gitlab-shell/-/jobs/453/retry',
playable: false,
created_at: '2018-05-18T15:32:54.993Z',
updated_at: '2018-05-18T15:32:54.993Z',
status: {
icon: 'status_success',
text: 'passed',
label: 'passed',
group: 'success',
tooltip: 'passed',
has_details: true,
details_path: '/gitlab-org/gitlab-shell/-/jobs/453',
illustration: {
image: 'illustrations/skipped-job_empty.svg',
size: 'svg-430',
title: 'This job does not have a trace.',
},
favicon:
'/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
action: {
icon: 'retry',
title: 'Retry',
path: '/gitlab-org/gitlab-shell/-/jobs/453/retry',
method: 'post',
},
},
},
],
},
{
name: 'spinach:osx',
size: 1,
status: {
icon: 'status_warning',
text: 'failed',
label: 'failed (allowed to fail)',
group: 'failed_with_warnings',
tooltip: 'failed - (unknown failure) (allowed to fail)',
has_details: true,
details_path: '/gitlab-org/gitlab-shell/-/jobs/454',
illustration: {
image: 'illustrations/skipped-job_empty.svg',
size: 'svg-430',
title: 'This job does not have a trace.',
},
favicon:
'/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png',
action: {
icon: 'retry',
title: 'Retry',
path: '/gitlab-org/gitlab-shell/-/jobs/454/retry',
method: 'post',
},
},
jobs: [
{
id: 454,
name: 'spinach:osx',
started: '2018-05-18T07:32:20.657Z',
build_path: '/gitlab-org/gitlab-shell/-/jobs/454',
retry_path: '/gitlab-org/gitlab-shell/-/jobs/454/retry',
playable: false,
created_at: '2018-05-18T15:32:55.053Z',
updated_at: '2018-05-18T15:32:55.053Z',
status: {
icon: 'status_warning',
text: 'failed',
label: 'failed (allowed to fail)',
group: 'failed_with_warnings',
tooltip: 'failed - (unknown failure) (allowed to fail)',
has_details: true,
details_path: '/gitlab-org/gitlab-shell/-/jobs/454',
illustration: {
image: 'illustrations/skipped-job_empty.svg',
size: 'svg-430',
title: 'This job does not have a trace.',
},
favicon:
'/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png',
action: {
icon: 'retry',
title: 'Retry',
path: '/gitlab-org/gitlab-shell/-/jobs/454/retry',
method: 'post',
},
},
callout_message: 'There is an unknown failure, please try again',
recoverable: true,
},
],
},
],
status: {
icon: 'status_warning',
text: 'passed',
label: 'passed with warnings',
group: 'success_with_warnings',
tooltip: 'passed',
has_details: true,
details_path: '/gitlab-org/gitlab-shell/pipelines/27#test',
illustration: null,
favicon:
'/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
},
path: '/gitlab-org/gitlab-shell/pipelines/27#test',
dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=test',
},
{
name: 'deploy',
title: 'deploy: running',
groups: [
{
name: 'production',
size: 1,
status: {
icon: 'status_created',
text: 'created',
label: 'created',
group: 'created',
tooltip: 'created',
has_details: true,
details_path: '/gitlab-org/gitlab-shell/-/jobs/457',
illustration: {
image: 'illustrations/job_not_triggered.svg',
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',
},
favicon:
'/assets/ci_favicons/favicon_status_created-4b975aa976d24e5a3ea7cd9a5713e6ce2cd9afd08b910415e96675de35f64955.png',
action: {
icon: 'cancel',
title: 'Cancel',
path: '/gitlab-org/gitlab-shell/-/jobs/457/cancel',
method: 'post',
},
},
jobs: [
{
id: 457,
name: 'production',
started: false,
build_path: '/gitlab-org/gitlab-shell/-/jobs/457',
cancel_path: '/gitlab-org/gitlab-shell/-/jobs/457/cancel',
playable: false,
created_at: '2018-05-18T15:32:55.259Z',
updated_at: '2018-09-28T11:09:57.454Z',
status: {
icon: 'status_created',
text: 'created',
label: 'created',
group: 'created',
tooltip: 'created',
has_details: true,
details_path: '/gitlab-org/gitlab-shell/-/jobs/457',
illustration: {
image: 'illustrations/job_not_triggered.svg',
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',
},
favicon:
'/assets/ci_favicons/favicon_status_created-4b975aa976d24e5a3ea7cd9a5713e6ce2cd9afd08b910415e96675de35f64955.png',
action: {
icon: 'cancel',
title: 'Cancel',
path: '/gitlab-org/gitlab-shell/-/jobs/457/cancel',
method: 'post',
},
},
},
],
},
{
name: 'staging',
size: 1,
status: {
icon: 'status_success',
text: 'passed',
label: 'passed',
group: 'success',
tooltip: 'passed',
has_details: true,
details_path: '/gitlab-org/gitlab-shell/-/jobs/455',
illustration: {
image: 'illustrations/skipped-job_empty.svg',
size: 'svg-430',
title: 'This job does not have a trace.',
},
favicon:
'/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
action: {
icon: 'retry',
title: 'Retry',
path: '/gitlab-org/gitlab-shell/-/jobs/455/retry',
method: 'post',
},
},
jobs: [
{
id: 455,
name: 'staging',
started: '2018-05-18T09:32:20.658Z',
build_path: '/gitlab-org/gitlab-shell/-/jobs/455',
retry_path: '/gitlab-org/gitlab-shell/-/jobs/455/retry',
playable: false,
created_at: '2018-05-18T15:32:55.119Z',
updated_at: '2018-05-18T15:32:55.119Z',
status: {
icon: 'status_success',
text: 'passed',
label: 'passed',
group: 'success',
tooltip: 'passed',
has_details: true,
details_path: '/gitlab-org/gitlab-shell/-/jobs/455',
illustration: {
image: 'illustrations/skipped-job_empty.svg',
size: 'svg-430',
title: 'This job does not have a trace.',
},
favicon:
'/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
action: {
icon: 'retry',
title: 'Retry',
path: '/gitlab-org/gitlab-shell/-/jobs/455/retry',
method: 'post',
},
},
},
],
},
{
name: 'stop staging',
size: 1,
status: {
icon: 'status_created',
text: 'created',
label: 'created',
group: 'created',
tooltip: 'created',
has_details: true,
details_path: '/gitlab-org/gitlab-shell/-/jobs/456',
illustration: {
image: 'illustrations/job_not_triggered.svg',
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',
},
favicon:
'/assets/ci_favicons/favicon_status_created-4b975aa976d24e5a3ea7cd9a5713e6ce2cd9afd08b910415e96675de35f64955.png',
action: {
icon: 'cancel',
title: 'Cancel',
path: '/gitlab-org/gitlab-shell/-/jobs/456/cancel',
method: 'post',
},
},
jobs: [
{
id: 456,
name: 'stop staging',
started: false,
build_path: '/gitlab-org/gitlab-shell/-/jobs/456',
cancel_path: '/gitlab-org/gitlab-shell/-/jobs/456/cancel',
playable: false,
created_at: '2018-05-18T15:32:55.205Z',
updated_at: '2018-09-28T11:09:57.396Z',
status: {
icon: 'status_created',
text: 'created',
label: 'created',
group: 'created',
tooltip: 'created',
has_details: true,
details_path: '/gitlab-org/gitlab-shell/-/jobs/456',
illustration: {
image: 'illustrations/job_not_triggered.svg',
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',
},
favicon:
'/assets/ci_favicons/favicon_status_created-4b975aa976d24e5a3ea7cd9a5713e6ce2cd9afd08b910415e96675de35f64955.png',
action: {
icon: 'cancel',
title: 'Cancel',
path: '/gitlab-org/gitlab-shell/-/jobs/456/cancel',
method: 'post',
},
},
},
],
},
],
status: {
icon: 'status_running',
text: 'running',
label: 'running',
group: 'running',
tooltip: 'running',
has_details: true,
details_path: '/gitlab-org/gitlab-shell/pipelines/27#deploy',
illustration: null,
favicon:
'/assets/ci_favicons/favicon_status_running-9c635b2419a8e1ec991c993061b89cc5aefc0743bb238ecd0c381e7741a70e8c.png',
},
path: '/gitlab-org/gitlab-shell/pipelines/27#deploy',
dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=deploy',
},
{
name: 'notify',
title: 'notify: manual action',
groups: [
{
name: 'slack',
size: 1,
status: {
icon: 'status_manual',
text: 'manual',
label: 'manual play action',
group: 'manual',
tooltip: 'manual action',
has_details: true,
details_path: '/gitlab-org/gitlab-shell/-/jobs/458',
illustration: {
image: 'illustrations/manual_action.svg',
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',
},
favicon:
'/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png',
action: {
icon: 'play',
title: 'Play',
path: '/gitlab-org/gitlab-shell/-/jobs/458/play',
method: 'post',
},
},
jobs: [
{
id: 458,
name: 'slack',
started: null,
build_path: '/gitlab-org/gitlab-shell/-/jobs/458',
play_path: '/gitlab-org/gitlab-shell/-/jobs/458/play',
playable: true,
created_at: '2018-05-18T15:32:55.303Z',
updated_at: '2018-05-18T15:34:08.535Z',
status: {
icon: 'status_manual',
text: 'manual',
label: 'manual play action',
group: 'manual',
tooltip: 'manual action',
has_details: true,
details_path: '/gitlab-org/gitlab-shell/-/jobs/458',
illustration: {
image: 'illustrations/manual_action.svg',
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',
},
favicon:
'/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png',
action: {
icon: 'play',
title: 'Play',
path: '/gitlab-org/gitlab-shell/-/jobs/458/play',
method: 'post',
},
},
},
],
},
],
status: {
icon: 'status_manual',
text: 'manual',
label: 'manual action',
group: 'manual',
tooltip: 'manual action',
has_details: true,
details_path: '/gitlab-org/gitlab-shell/pipelines/27#notify',
illustration: null,
favicon:
'/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png',
},
path: '/gitlab-org/gitlab-shell/pipelines/27#notify',
dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=notify',
},
];
export const jobsInStage = {
name: 'build',
title: 'build: running',
latest_statuses: [
{
id: 1180,
name: 'build:linux',
started: false,
build_path: '/gitlab-org/gitlab-shell/-/jobs/1180',
cancel_path: '/gitlab-org/gitlab-shell/-/jobs/1180/cancel',
playable: false,
created_at: '2018-09-28T11:09:57.229Z',
updated_at: '2018-09-28T11:09:57.503Z',
status: {
icon: 'status_pending',
text: 'pending',
label: 'pending',
group: 'pending',
tooltip: 'pending',
has_details: true,
details_path: '/gitlab-org/gitlab-shell/-/jobs/1180',
illustration: {
image: 'illustrations/pending_job_empty.svg',
size: 'svg-430',
title: 'This job has not started yet',
content: 'This job is in pending state and is waiting to be picked by a runner',
},
favicon:
'/assets/ci_favicons/favicon_status_pending-5bdf338420e5221ca24353b6bff1c9367189588750632e9a871b7af09ff6a2ae.png',
action: {
icon: 'cancel',
title: 'Cancel',
path: '/gitlab-org/gitlab-shell/-/jobs/1180/cancel',
method: 'post',
},
},
},
{
id: 444,
name: 'build:osx',
started: '2018-05-18T05:32:20.655Z',
build_path: '/gitlab-org/gitlab-shell/-/jobs/444',
retry_path: '/gitlab-org/gitlab-shell/-/jobs/444/retry',
playable: false,
created_at: '2018-05-18T15:32:54.364Z',
updated_at: '2018-05-18T15:32:54.364Z',
status: {
icon: 'status_success',
text: 'passed',
label: 'passed',
group: 'success',
tooltip: 'passed',
has_details: true,
details_path: '/gitlab-org/gitlab-shell/-/jobs/444',
illustration: {
image: 'illustrations/skipped-job_empty.svg',
size: 'svg-430',
title: 'This job does not have a trace.',
},
favicon:
'/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
action: {
icon: 'retry',
title: 'Retry',
path: '/gitlab-org/gitlab-shell/-/jobs/444/retry',
method: 'post',
},
},
},
],
retried: [
{
id: 443,
name: 'build:linux',
started: '2018-05-18T06:32:20.655Z',
build_path: '/gitlab-org/gitlab-shell/-/jobs/443',
retry_path: '/gitlab-org/gitlab-shell/-/jobs/443/retry',
playable: false,
created_at: '2018-05-18T15:32:54.296Z',
updated_at: '2018-05-18T15:32:54.296Z',
status: {
icon: 'status_success',
text: 'passed',
label: 'passed',
group: 'success',
tooltip: 'passed (retried)',
has_details: true,
details_path: '/gitlab-org/gitlab-shell/-/jobs/443',
illustration: {
image: 'illustrations/skipped-job_empty.svg',
size: 'svg-430',
title: 'This job does not have a trace.',
},
favicon:
'/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
action: {
icon: 'retry',
title: 'Retry',
path: '/gitlab-org/gitlab-shell/-/jobs/443/retry',
method: 'post',
},
},
},
],
status: {
icon: 'status_running',
text: 'running',
label: 'running',
group: 'running',
tooltip: 'running',
has_details: true,
details_path: '/gitlab-org/gitlab-shell/pipelines/27#build',
illustration: null,
favicon:
'/assets/ci_favicons/favicon_status_running-9c635b2419a8e1ec991c993061b89cc5aefc0743bb238ecd0c381e7741a70e8c.png',
},
path: '/gitlab-org/gitlab-shell/pipelines/27#build',
dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=build',
};
...@@ -27,7 +27,6 @@ import { ...@@ -27,7 +27,6 @@ import {
receiveStagesSuccess, receiveStagesSuccess,
receiveStagesError, receiveStagesError,
requestJobsForStage, requestJobsForStage,
setSelectedStage,
fetchJobsForStage, fetchJobsForStage,
receiveJobsForStageSuccess, receiveJobsForStageSuccess,
receiveJobsForStageError, receiveJobsForStageError,
...@@ -236,7 +235,8 @@ describe('Job State actions', () => { ...@@ -236,7 +235,8 @@ describe('Job State actions', () => {
}, },
{ {
payload: { payload: {
html: 'I, [2018-08-17T22:57:45.707325 #1841] INFO -- :', complete: true, html: 'I, [2018-08-17T22:57:45.707325 #1841] INFO -- :',
complete: true,
}, },
type: 'receiveTraceSuccess', type: 'receiveTraceSuccess',
}, },
...@@ -421,7 +421,9 @@ describe('Job State actions', () => { ...@@ -421,7 +421,9 @@ describe('Job State actions', () => {
let mock; let mock;
beforeEach(() => { beforeEach(() => {
mockedState.stagesEndpoint = `${TEST_HOST}/endpoint.json`; mockedState.job.pipeline = {
path: `${TEST_HOST}/endpoint.json/stages`,
};
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
}); });
...@@ -430,8 +432,10 @@ describe('Job State actions', () => { ...@@ -430,8 +432,10 @@ describe('Job State actions', () => {
}); });
describe('success', () => { describe('success', () => {
it('dispatches requestStages and receiveStagesSuccess ', done => { it('dispatches requestStages and receiveStagesSuccess, fetchJobsForStage ', done => {
mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(200, [{ id: 121212, name: 'build' }]); mock
.onGet(`${TEST_HOST}/endpoint.json/stages`)
.replyOnce(200, { details: { stages: [{ id: 121212, name: 'build' }] } });
testAction( testAction(
fetchStages, fetchStages,
...@@ -446,6 +450,10 @@ describe('Job State actions', () => { ...@@ -446,6 +450,10 @@ describe('Job State actions', () => {
payload: [{ id: 121212, name: 'build' }], payload: [{ id: 121212, name: 'build' }],
type: 'receiveStagesSuccess', type: 'receiveStagesSuccess',
}, },
{
payload: { id: 121212, name: 'build' },
type: 'fetchJobsForStage',
},
], ],
done, done,
); );
...@@ -516,24 +524,10 @@ describe('Job State actions', () => { ...@@ -516,24 +524,10 @@ describe('Job State actions', () => {
}); });
}); });
describe('setSelectedStage', () => {
it('should commit SET_SELECTED_STAGE mutation ', done => {
testAction(
setSelectedStage,
{ name: 'build' },
mockedState,
[{ type: types.SET_SELECTED_STAGE, payload: { name: 'build' } }],
[],
done,
);
});
});
describe('fetchJobsForStage', () => { describe('fetchJobsForStage', () => {
let mock; let mock;
beforeEach(() => { beforeEach(() => {
mockedState.stageJobsEndpoint = `${TEST_HOST}/endpoint.json`;
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
}); });
...@@ -542,19 +536,17 @@ describe('Job State actions', () => { ...@@ -542,19 +536,17 @@ describe('Job State actions', () => {
}); });
describe('success', () => { describe('success', () => {
it('dispatches setSelectedStage, requestJobsForStage and receiveJobsForStageSuccess ', done => { it('dispatches requestJobsForStage and receiveJobsForStageSuccess ', done => {
mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(200, [{ id: 121212, name: 'build' }]); mock
.onGet(`${TEST_HOST}/jobs.json`)
.replyOnce(200, { latest_statuses: [{ id: 121212, name: 'build' }], retried: [] });
testAction( testAction(
fetchJobsForStage, fetchJobsForStage,
null, { dropdown_path: `${TEST_HOST}/jobs.json` },
mockedState, mockedState,
[], [],
[ [
{
type: 'setSelectedStage',
payload: null,
},
{ {
type: 'requestJobsForStage', type: 'requestJobsForStage',
}, },
...@@ -570,20 +562,16 @@ describe('Job State actions', () => { ...@@ -570,20 +562,16 @@ describe('Job State actions', () => {
describe('error', () => { describe('error', () => {
beforeEach(() => { beforeEach(() => {
mock.onGet(`${TEST_HOST}/endpoint.json`).reply(500); mock.onGet(`${TEST_HOST}/jobs.json`).reply(500);
}); });
it('dispatches setSelectedStage, requestJobsForStage and receiveJobsForStageError', done => { it('dispatches requestJobsForStage and receiveJobsForStageError', done => {
testAction( testAction(
fetchJobsForStage, fetchJobsForStage,
null, { dropdown_path: `${TEST_HOST}/jobs.json` },
mockedState, mockedState,
[], [],
[ [
{
payload: null,
type: 'setSelectedStage',
},
{ {
type: 'requestJobsForStage', type: 'requestJobsForStage',
}, },
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment