Commit 6ccbe640 authored by GitLab Bot's avatar GitLab Bot

Merge remote-tracking branch 'upstream/master' into ce-to-ee-2018-10-12

parents e06e9796 1103f589
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
'headerActions', 'headerActions',
'headerTime', 'headerTime',
'shouldRenderCalloutMessage', 'shouldRenderCalloutMessage',
'jobHasStarted', 'shouldRenderTriggeredLabel',
'hasEnvironment', 'hasEnvironment',
'isJobStuck', 'isJobStuck',
'shouldRenderSharedRunnerLimitWarning', 'shouldRenderSharedRunnerLimitWarning',
...@@ -63,7 +63,7 @@ ...@@ -63,7 +63,7 @@
:user="job.user" :user="job.user"
:actions="headerActions" :actions="headerActions"
:has-sidebar-button="true" :has-sidebar-button="true"
:should-render-triggered-label="jobHasStarted" :should-render-triggered-label="shouldRenderTriggeredLabel"
:item-name="__('Job')" :item-name="__('Job')"
/> />
</div> </div>
......
...@@ -36,7 +36,7 @@ export default { ...@@ -36,7 +36,7 @@ export default {
}, },
}, },
computed: { computed: {
...mapState(['job', 'isLoading', 'stages', 'jobs']), ...mapState(['job', 'isLoading', 'stages', 'jobs', 'selectedStage']),
coverage() { coverage() {
return `${this.job.coverage}%`; return `${this.job.coverage}%`;
}, },
...@@ -276,6 +276,7 @@ export default { ...@@ -276,6 +276,7 @@ export default {
<stages-dropdown <stages-dropdown
:stages="stages" :stages="stages"
:pipeline="job.pipeline" :pipeline="job.pipeline"
:selected-stage="selectedStage"
@requestSidebarStageDropdown="fetchJobsForStage" @requestSidebarStageDropdown="fetchJobsForStage"
/> />
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
import _ from 'underscore'; 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';
export default { export default {
components: { components: {
...@@ -18,30 +17,20 @@ export default { ...@@ -18,30 +17,20 @@ export default {
type: Array, type: Array,
required: true, required: true,
}, },
selectedStage: {
type: String,
required: true,
}, },
data() {
return {
selectedStage: this.stages.length > 0 ? this.stages[0].name : __('More'),
};
}, },
computed: { computed: {
hasRef() { hasRef() {
return !_.isEmpty(this.pipeline.ref); return !_.isEmpty(this.pipeline.ref);
}, },
}, },
watch: {
// When the component is initially mounted it may start with an empty stages array.
// Once the prop is updated, we set the first stage as the selected one
stages(newVal) {
if (newVal.length) {
this.selectedStage = newVal[0].name;
}
},
},
methods: { methods: {
onStageClick(stage) { onStageClick(stage) {
this.$emit('requestSidebarStageDropdown', stage); this.$emit('requestSidebarStageDropdown', stage);
this.selectedStage = stage.name;
}, },
}, },
}; };
......
...@@ -141,8 +141,10 @@ export const fetchStages = ({ state, dispatch }) => { ...@@ -141,8 +141,10 @@ export const fetchStages = ({ state, dispatch }) => {
axios axios
.get(`${state.job.pipeline.path}.json`) .get(`${state.job.pipeline.path}.json`)
.then(({ data }) => { .then(({ data }) => {
// Set selected stage
dispatch('receiveStagesSuccess', data.details.stages); dispatch('receiveStagesSuccess', data.details.stages);
dispatch('fetchJobsForStage', data.details.stages[0]); const selectedStage = data.details.stages.find(stage => stage.name === state.selectedStage);
dispatch('fetchJobsForStage', selectedStage);
}) })
.catch(() => dispatch('receiveStagesError')); .catch(() => dispatch('receiveStagesError'));
}; };
...@@ -156,11 +158,12 @@ export const receiveStagesError = ({ commit }) => { ...@@ -156,11 +158,12 @@ 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 }, stage) =>
commit(types.REQUEST_JOBS_FOR_STAGE, stage);
// On stage click, set selected stage + fetch job // On stage click, set selected stage + fetch job
export const fetchJobsForStage = ({ dispatch }, stage) => { export const fetchJobsForStage = ({ dispatch }, stage) => {
dispatch('requestJobsForStage'); dispatch('requestJobsForStage', stage);
axios axios
.get(stage.dropdown_path, { .get(stage.dropdown_path, {
......
...@@ -22,10 +22,10 @@ export const shouldRenderCalloutMessage = state => ...@@ -22,10 +22,10 @@ export const shouldRenderCalloutMessage = state =>
!_.isEmpty(state.job.status) && !_.isEmpty(state.job.callout_message); !_.isEmpty(state.job.status) && !_.isEmpty(state.job.callout_message);
/** /**
* When job has not started the key will be `false` * When job has not started the key will be null
* When job started the key will be a string with a date. * When job started the key will be a string with a date.
*/ */
export const jobHasStarted = state => !(state.job.started === false); export const shouldRenderTriggeredLabel = state => _.isString(state.job.started);
export const hasEnvironment = state => !_.isEmpty(state.job.deployment_status); export const hasEnvironment = state => !_.isEmpty(state.job.deployment_status);
......
...@@ -53,6 +53,16 @@ export default { ...@@ -53,6 +53,16 @@ export default {
state.isLoading = false; state.isLoading = false;
state.hasError = false; state.hasError = false;
state.job = job; state.job = job;
/**
* We only update it on the first request
* The dropdown can be changed by the user
* after the first request,
* and we do not want to hijack that
*/
if (state.selectedStage === 'More' && job.stage) {
state.selectedStage = job.stage;
}
}, },
[types.RECEIVE_JOB_ERROR](state) { [types.RECEIVE_JOB_ERROR](state) {
state.isLoading = false; state.isLoading = false;
...@@ -81,8 +91,9 @@ export default { ...@@ -81,8 +91,9 @@ export default {
state.stages = []; state.stages = [];
}, },
[types.REQUEST_JOBS_FOR_STAGE](state) { [types.REQUEST_JOBS_FOR_STAGE](state, stage) {
state.isLoadingJobs = true; state.isLoadingJobs = true;
state.selectedStage = stage.name;
}, },
[types.RECEIVE_JOBS_FOR_STAGE_SUCCESS](state, jobs) { [types.RECEIVE_JOBS_FOR_STAGE_SUCCESS](state, jobs) {
state.isLoadingJobs = false; state.isLoadingJobs = false;
......
import { __ } from '~/locale';
export default () => ({ export default () => ({
jobEndpoint: null, jobEndpoint: null,
traceEndpoint: null, traceEndpoint: null,
...@@ -34,7 +36,7 @@ export default () => ({ ...@@ -34,7 +36,7 @@ export default () => ({
// sidebar dropdown // sidebar dropdown
isLoadingStages: false, isLoadingStages: false,
isLoadingJobs: false, isLoadingJobs: false,
selectedStage: null, selectedStage: __('More'),
stages: [], stages: [],
jobs: [], jobs: [],
}); });
...@@ -6,6 +6,7 @@ class BuildDetailsEntity < JobEntity ...@@ -6,6 +6,7 @@ class BuildDetailsEntity < JobEntity
expose :coverage, :erased_at, :duration expose :coverage, :erased_at, :duration
expose :tag_list, as: :tags expose :tag_list, as: :tags
expose :has_trace?, as: :has_trace expose :has_trace?, as: :has_trace
expose :stage
expose :user, using: UserEntity expose :user, using: UserEntity
expose :runner, using: RunnerEntity expose :runner, using: RunnerEntity
expose :pipeline, using: PipelineEntity expose :pipeline, using: PipelineEntity
......
---
title: Fixes triggered/created labeled in job header
merge_request:
author:
type: fixed
---
title: Load correct stage in the stages dropdown
merge_request: 22317
author:
type: fixed
...@@ -352,6 +352,10 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do ...@@ -352,6 +352,10 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
expect(json_response['has_trace']).to be true expect(json_response['has_trace']).to be true
end end
end end
it 'exposes the stage the job belongs to' do
expect(json_response['stage']).to eq('test')
end
end end
context 'when requesting JSON job is triggered' do context 'when requesting JSON job is triggered' do
......
...@@ -7,7 +7,8 @@ ...@@ -7,7 +7,8 @@
"artifact", "artifact",
"runner", "runner",
"runners", "runners",
"has_trace" "has_trace",
"stage"
], ],
"properties": { "properties": {
"artifact": { "$ref": "artifact.json" }, "artifact": { "$ref": "artifact.json" },
...@@ -16,6 +17,7 @@ ...@@ -16,6 +17,7 @@
"deployment_status": { "$ref": "deployment_status.json" }, "deployment_status": { "$ref": "deployment_status.json" },
"runner": { "$ref": "runner.json" }, "runner": { "$ref": "runner.json" },
"runners": { "$ref": "runners.json" }, "runners": { "$ref": "runners.json" },
"has_trace": { "type": "boolean" } "has_trace": { "type": "boolean" },
"stage": { "type": "string" }
} }
} }
...@@ -161,9 +161,9 @@ describe('Sidebar details block', () => { ...@@ -161,9 +161,9 @@ describe('Sidebar details block', () => {
vm = mountComponentWithStore(SidebarComponent, { store }); vm = mountComponentWithStore(SidebarComponent, { store });
}); });
it('renders first stage as selected', () => { it('renders value provided as selectedStage as selected', () => {
expect(vm.$el.querySelector('.js-selected-stage').textContent.trim()).toEqual( expect(vm.$el.querySelector('.js-selected-stage').textContent.trim()).toEqual(
stages[0].name, vm.selectedStage,
); );
}); });
}); });
......
...@@ -2,7 +2,7 @@ import Vue from 'vue'; ...@@ -2,7 +2,7 @@ import Vue from 'vue';
import component from '~/jobs/components/stages_dropdown.vue'; import component from '~/jobs/components/stages_dropdown.vue';
import mountComponent from '../../helpers/vue_mount_component_helper'; import mountComponent from '../../helpers/vue_mount_component_helper';
describe('Artifacts block', () => { describe('Stages Dropdown', () => {
const Component = Vue.extend(component); const Component = Vue.extend(component);
let vm; let vm;
...@@ -23,10 +23,6 @@ describe('Artifacts block', () => { ...@@ -23,10 +23,6 @@ describe('Artifacts block', () => {
}, },
path: 'pipeline/28029444', path: 'pipeline/28029444',
}, },
ref: {
path: 'commits/50101-truncated-job-information',
name: '50101-truncated-job-information',
},
stages: [ stages: [
{ {
name: 'build', name: 'build',
...@@ -35,6 +31,7 @@ describe('Artifacts block', () => { ...@@ -35,6 +31,7 @@ describe('Artifacts block', () => {
name: 'test', name: 'test',
}, },
], ],
selectedStage: 'deploy'
}); });
}); });
...@@ -53,17 +50,10 @@ describe('Artifacts block', () => { ...@@ -53,17 +50,10 @@ describe('Artifacts block', () => {
}); });
it('renders dropdown with stages', () => { it('renders dropdown with stages', () => {
expect(vm.$el.querySelector('.dropdown button').textContent).toContain('build'); expect(vm.$el.querySelector('.dropdown .js-stage-item').textContent).toContain('build');
}); });
it('updates selected stage on click', done => { it('rendes selected stage', () => {
vm.$el.querySelectorAll('.stage-item')[1].click(); expect(vm.$el.querySelector('.dropdown .js-selected-stage').textContent).toContain('deploy');
vm
.$nextTick()
.then(() => {
expect(vm.$el.querySelector('.dropdown button').textContent).toContain('test');
})
.then(done)
.catch(done.fail);
}); });
}); });
...@@ -424,6 +424,7 @@ describe('Job State actions', () => { ...@@ -424,6 +424,7 @@ describe('Job State actions', () => {
mockedState.job.pipeline = { mockedState.job.pipeline = {
path: `${TEST_HOST}/endpoint`, path: `${TEST_HOST}/endpoint`,
}; };
mockedState.selectedStage = 'deploy'
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
}); });
...@@ -435,7 +436,7 @@ describe('Job State actions', () => { ...@@ -435,7 +436,7 @@ describe('Job State actions', () => {
it('dispatches requestStages and receiveStagesSuccess, fetchJobsForStage ', done => { it('dispatches requestStages and receiveStagesSuccess, fetchJobsForStage ', done => {
mock mock
.onGet(`${TEST_HOST}/endpoint.json`) .onGet(`${TEST_HOST}/endpoint.json`)
.replyOnce(200, { details: { stages: [{ id: 121212, name: 'build' }] } }); .replyOnce(200, { details: { stages: [{ name: 'build' }, { name: 'deploy' }] } });
testAction( testAction(
fetchStages, fetchStages,
...@@ -447,11 +448,11 @@ describe('Job State actions', () => { ...@@ -447,11 +448,11 @@ describe('Job State actions', () => {
type: 'requestStages', type: 'requestStages',
}, },
{ {
payload: [{ id: 121212, name: 'build' }], payload: [{ name: 'build' }, { name: 'deploy' }],
type: 'receiveStagesSuccess', type: 'receiveStagesSuccess',
}, },
{ {
payload: { id: 121212, name: 'build' }, payload: { name: 'deploy' },
type: 'fetchJobsForStage', type: 'fetchJobsForStage',
}, },
], ],
...@@ -515,9 +516,9 @@ describe('Job State actions', () => { ...@@ -515,9 +516,9 @@ describe('Job State actions', () => {
it('should commit REQUEST_JOBS_FOR_STAGE mutation ', done => { it('should commit REQUEST_JOBS_FOR_STAGE mutation ', done => {
testAction( testAction(
requestJobsForStage, requestJobsForStage,
null, { name: 'deploy' },
mockedState, mockedState,
[{ type: types.REQUEST_JOBS_FOR_STAGE }], [{ type: types.REQUEST_JOBS_FOR_STAGE, payload: { name: 'deploy' } }],
[], [],
done, done,
); );
...@@ -549,6 +550,7 @@ describe('Job State actions', () => { ...@@ -549,6 +550,7 @@ describe('Job State actions', () => {
[ [
{ {
type: 'requestJobsForStage', type: 'requestJobsForStage',
payload: { dropdown_path: `${TEST_HOST}/jobs.json` },
}, },
{ {
payload: [{ id: 121212, name: 'build' }], payload: [{ id: 121212, name: 'build' }],
...@@ -574,6 +576,7 @@ describe('Job State actions', () => { ...@@ -574,6 +576,7 @@ describe('Job State actions', () => {
[ [
{ {
type: 'requestJobsForStage', type: 'requestJobsForStage',
payload: { dropdown_path: `${TEST_HOST}/jobs.json` },
}, },
{ {
type: 'receiveJobsForStageError', type: 'receiveJobsForStageError',
......
...@@ -77,18 +77,18 @@ describe('Job Store Getters', () => { ...@@ -77,18 +77,18 @@ describe('Job Store Getters', () => {
}); });
}); });
describe('jobHasStarted', () => { describe('shouldRenderTriggeredLabel', () => {
describe('when started equals false', () => { describe('when started equals null', () => {
it('returns false', () => { it('returns false', () => {
localState.job.started = false; localState.job.started = null;
expect(getters.jobHasStarted(localState)).toEqual(false); expect(getters.shouldRenderTriggeredLabel(localState)).toEqual(false);
}); });
}); });
describe('when started equals string', () => { describe('when started equals string', () => {
it('returns true', () => { it('returns true', () => {
localState.job.started = '2018-08-31T16:20:49.023Z'; localState.job.started = '2018-08-31T16:20:49.023Z';
expect(getters.jobHasStarted(localState)).toEqual(true); expect(getters.shouldRenderTriggeredLabel(localState)).toEqual(true);
}); });
}); });
}); });
......
...@@ -108,21 +108,33 @@ describe('Jobs Store Mutations', () => { ...@@ -108,21 +108,33 @@ describe('Jobs Store Mutations', () => {
}); });
describe('RECEIVE_JOB_SUCCESS', () => { describe('RECEIVE_JOB_SUCCESS', () => {
beforeEach(() => {
mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321 });
});
it('sets is loading to false', () => { it('sets is loading to false', () => {
mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321 });
expect(stateCopy.isLoading).toEqual(false); expect(stateCopy.isLoading).toEqual(false);
}); });
it('sets hasError to false', () => { it('sets hasError to false', () => {
mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321 });
expect(stateCopy.hasError).toEqual(false); expect(stateCopy.hasError).toEqual(false);
}); });
it('sets job data', () => { it('sets job data', () => {
mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321 });
expect(stateCopy.job).toEqual({ id: 1312321 }); expect(stateCopy.job).toEqual({ id: 1312321 });
}); });
it('sets selectedStage when the selectedStage is More', () => {
expect(stateCopy.selectedStage).toEqual('More');
mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321, stage: 'deploy' });
expect(stateCopy.selectedStage).toEqual('deploy');
});
it('does not set selectedStage when the selectedStage is not More', () => {
stateCopy.selectedStage = 'notify'
expect(stateCopy.selectedStage).toEqual('notify');
mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321, stage: 'deploy' });
expect(stateCopy.selectedStage).toEqual('notify');
});
}); });
describe('RECEIVE_JOB_ERROR', () => { describe('RECEIVE_JOB_ERROR', () => {
...@@ -200,9 +212,14 @@ describe('Jobs Store Mutations', () => { ...@@ -200,9 +212,14 @@ describe('Jobs Store Mutations', () => {
describe('REQUEST_JOBS_FOR_STAGE', () => { describe('REQUEST_JOBS_FOR_STAGE', () => {
it('sets isLoadingStages to true', () => { it('sets isLoadingStages to true', () => {
mutations[types.REQUEST_JOBS_FOR_STAGE](stateCopy); mutations[types.REQUEST_JOBS_FOR_STAGE](stateCopy, { name: 'deploy' });
expect(stateCopy.isLoadingJobs).toEqual(true); expect(stateCopy.isLoadingJobs).toEqual(true);
}); });
it('sets selectedStage', () => {
mutations[types.REQUEST_JOBS_FOR_STAGE](stateCopy, { name: 'deploy' });
expect(stateCopy.selectedStage).toEqual('deploy');
})
}); });
describe('RECEIVE_JOBS_FOR_STAGE_SUCCESS', () => { describe('RECEIVE_JOBS_FOR_STAGE_SUCCESS', () => {
......
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