added empty state & YAML error state

This commit is contained in:
Phil Hughes 2018-05-29 10:35:13 +01:00
parent b4ef2aad02
commit 782c31a494
No known key found for this signature in database
GPG key ID: 32245528C52E0F9F
10 changed files with 88 additions and 18 deletions

View file

@ -1,10 +1,12 @@
<script> <script>
import { mapActions, mapGetters, mapState } from 'vuex'; import { mapActions, mapGetters, mapState } from 'vuex';
import { sprintf, __ } from '../../../locale';
import LoadingIcon from '../../../vue_shared/components/loading_icon.vue'; import LoadingIcon from '../../../vue_shared/components/loading_icon.vue';
import Icon from '../../../vue_shared/components/icon.vue'; import Icon from '../../../vue_shared/components/icon.vue';
import CiIcon from '../../../vue_shared/components/ci_icon.vue'; import CiIcon from '../../../vue_shared/components/ci_icon.vue';
import Tabs from '../../../vue_shared/components/tabs/tabs'; import Tabs from '../../../vue_shared/components/tabs/tabs';
import Tab from '../../../vue_shared/components/tabs/tab.vue'; import Tab from '../../../vue_shared/components/tabs/tab.vue';
import EmptyState from '../../../pipelines/components/empty_state.vue';
import JobsList from '../jobs/list.vue'; import JobsList from '../jobs/list.vue';
export default { export default {
@ -15,10 +17,23 @@ export default {
Tabs, Tabs,
Tab, Tab,
JobsList, JobsList,
EmptyState,
}, },
computed: { computed: {
...mapState(['pipelinesEmptyStateSvgPath']),
...mapGetters(['currentProject']),
...mapGetters('pipelines', ['jobsCount', 'failedJobsCount', 'failedStages', 'pipelineFailed']), ...mapGetters('pipelines', ['jobsCount', 'failedJobsCount', 'failedStages', 'pipelineFailed']),
...mapState('pipelines', ['isLoadingPipeline', 'latestPipeline', 'stages', 'isLoadingJobs']), ...mapState('pipelines', ['isLoadingPipeline', 'latestPipeline', 'stages', 'isLoadingJobs']),
ciLintText() {
return sprintf(
__('You can also test your .gitlab-ci.yml in the %{linkStart}Lint%{linkEnd}'),
{
linkStart: `<a href="${this.currentProject.web_url}/-/ci/lint">`,
linkEnd: '</a>',
},
false,
);
},
}, },
created() { created() {
this.fetchLatestPipeline(); this.fetchLatestPipeline();
@ -32,12 +47,13 @@ export default {
<template> <template>
<div class="ide-pipeline"> <div class="ide-pipeline">
<loading-icon <loading-icon
v-if="isLoadingPipeline && !latestPipeline" v-if="isLoadingPipeline && latestPipeline === null"
class="prepend-top-default" class="prepend-top-default"
size="2" size="2"
/> />
<template v-else-if="latestPipeline"> <template v-else-if="latestPipeline !== null">
<header <header
v-if="latestPipeline"
class="ide-tree-header ide-pipeline-header" class="ide-tree-header ide-pipeline-header"
> >
<ci-icon <ci-icon
@ -61,7 +77,31 @@ export default {
</a> </a>
</span> </span>
</header> </header>
<tabs class="ide-pipeline-list"> <empty-state
v-if="latestPipeline === false"
help-page-path="a"
:empty-state-svg-path="pipelinesEmptyStateSvgPath"
:can-set-ci="true"
/>
<div
v-else-if="latestPipeline.yamlError"
class="bs-callout bs-callout-danger"
>
<p class="append-bottom-0">
Found errors in your .gitlab-ci.yml:
</p>
<p class="append-bottom-0">
{{ latestPipeline.yamlError }}
</p>
<p
class="append-bottom-0"
v-html="ciLintText"
></p>
</div>
<tabs
v-else
class="ide-pipeline-list"
>
<tab <tab
:active="!pipelineFailed" :active="!pipelineFailed"
> >
@ -105,6 +145,7 @@ export default {
.ide-pipeline { .ide-pipeline {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%;
} }
.ide-pipeline-list { .ide-pipeline-list {
@ -121,4 +162,9 @@ export default {
.ide-pipeline-header .ci-status-icon { .ide-pipeline-header .ci-status-icon {
display: flex; display: flex;
} }
.ide-pipeline .empty-state {
margin-top: auto;
margin-bottom: auto;
}
</style> </style>

View file

@ -21,6 +21,7 @@ export function initIde(el) {
emptyStateSvgPath: el.dataset.emptyStateSvgPath, emptyStateSvgPath: el.dataset.emptyStateSvgPath,
noChangesStateSvgPath: el.dataset.noChangesStateSvgPath, noChangesStateSvgPath: el.dataset.noChangesStateSvgPath,
committedStateSvgPath: el.dataset.committedStateSvgPath, committedStateSvgPath: el.dataset.committedStateSvgPath,
pipelinesEmptyStateSvgPath: el.dataset.pipelinesEmptyStateSvgPath,
}); });
}, },
render(createElement) { render(createElement) {

View file

@ -19,12 +19,14 @@ export const receiveLatestPipelineError = ({ commit, dispatch }) => {
dispatch('stopPipelinePolling'); dispatch('stopPipelinePolling');
}; };
export const receiveLatestPipelineSuccess = ({ rootGetters, commit }, { pipelines }) => { export const receiveLatestPipelineSuccess = ({ rootGetters, commit }, { pipelines }) => {
let lastCommitPipeline = false;
if (pipelines && pipelines.length) { if (pipelines && pipelines.length) {
const lastCommitHash = rootGetters.lastCommit && rootGetters.lastCommit.id; const lastCommitHash = rootGetters.lastCommit && rootGetters.lastCommit.id;
const lastCommitPipeline = pipelines.find(pipeline => pipeline.commit.id === lastCommitHash); lastCommitPipeline = pipelines.find(pipeline => pipeline.commit.id === lastCommitHash);
}
commit(types.RECEIVE_LASTEST_PIPELINE_SUCCESS, lastCommitPipeline); commit(types.RECEIVE_LASTEST_PIPELINE_SUCCESS, lastCommitPipeline);
}
}; };
export const fetchLatestPipeline = ({ dispatch, rootGetters }) => { export const fetchLatestPipeline = ({ dispatch, rootGetters }) => {

View file

@ -19,6 +19,7 @@ export default {
details: { details: {
status: pipeline.details.status, status: pipeline.details.status,
}, },
yamlError: pipeline.yaml_errors,
}; };
state.stages = pipeline.details.stages.map((stage, i) => { state.stages = pipeline.details.stages.map((stage, i) => {
const foundStage = state.stages.find(s => s.id === i); const foundStage = state.stages.find(s => s.id === i);
@ -32,6 +33,8 @@ export default {
jobs: foundStage ? foundStage.jobs : [], jobs: foundStage ? foundStage.jobs : [],
}; };
}); });
} else {
state.latestPipeline = false;
} }
}, },
[types.REQUEST_JOBS](state, id) { [types.REQUEST_JOBS](state, id) {

View file

@ -1,5 +1,5 @@
export default () => ({ export default () => ({
isLoadingPipeline: false, isLoadingPipeline: true,
isLoadingJobs: false, isLoadingJobs: false,
latestPipeline: null, latestPipeline: null,
stages: [], stages: [],

View file

@ -114,12 +114,13 @@ export default {
}, },
[types.SET_EMPTY_STATE_SVGS]( [types.SET_EMPTY_STATE_SVGS](
state, state,
{ emptyStateSvgPath, noChangesStateSvgPath, committedStateSvgPath }, { emptyStateSvgPath, noChangesStateSvgPath, committedStateSvgPath, pipelinesEmptyStateSvgPath },
) { ) {
Object.assign(state, { Object.assign(state, {
emptyStateSvgPath, emptyStateSvgPath,
noChangesStateSvgPath, noChangesStateSvgPath,
committedStateSvgPath, committedStateSvgPath,
pipelinesEmptyStateSvgPath,
}); });
}, },
[types.TOGGLE_FILE_FINDER](state, fileFindVisible) { [types.TOGGLE_FILE_FINDER](state, fileFindVisible) {

View file

@ -3,7 +3,8 @@
#ide.ide-loading{ data: {"empty-state-svg-path" => image_path('illustrations/multi_file_editor_empty.svg'), #ide.ide-loading{ data: {"empty-state-svg-path" => image_path('illustrations/multi_file_editor_empty.svg'),
"no-changes-state-svg-path" => image_path('illustrations/multi-editor_no_changes_empty.svg'), "no-changes-state-svg-path" => image_path('illustrations/multi-editor_no_changes_empty.svg'),
"committed-state-svg-path" => image_path('illustrations/multi-editor_all_changes_committed_empty.svg') } } "committed-state-svg-path" => image_path('illustrations/multi-editor_all_changes_committed_empty.svg'),
"pipelines-empty-state-svg-path": image_path('illustrations/pipelines_empty.svg') } }
.text-center .text-center
= icon('spinner spin 2x') = icon('spinner spin 2x')
%h2.clgray= _('Loading the GitLab IDE...') %h2.clgray= _('Loading the GitLab IDE...')

View file

@ -20,12 +20,14 @@ export const pipelines = [
ref: 'master', ref: 'master',
sha: '123', sha: '123',
status: 'failed', status: 'failed',
commit: { id: '123' },
}, },
{ {
id: 2, id: 2,
ref: 'master', ref: 'master',
sha: '213', sha: '213',
status: 'success', status: 'success',
commit: { id: '213' },
}, },
]; ];

View file

@ -65,15 +65,28 @@ describe('IDE pipelines actions', () => {
}); });
describe('receiveLatestPipelineSuccess', () => { describe('receiveLatestPipelineSuccess', () => {
it('commits pipeline', done => { const rootGetters = {
testAction( lastCommit: { id: '123' },
receiveLatestPipelineSuccess, };
let commit;
beforeEach(() => {
commit = jasmine.createSpy('commit');
});
it('commits pipeline', () => {
receiveLatestPipelineSuccess({ rootGetters, commit }, { pipelines });
expect(commit.calls.argsFor(0)).toEqual([
types.RECEIVE_LASTEST_PIPELINE_SUCCESS,
pipelines[0], pipelines[0],
mockedState, ]);
[{ type: types.RECEIVE_LASTEST_PIPELINE_SUCCESS, payload: pipelines[0] }], });
[],
done, it('commits false when there are no pipelines', () => {
); receiveLatestPipelineSuccess({ rootGetters, commit }, { pipelines: [] });
expect(commit.calls.argsFor(0)).toEqual([types.RECEIVE_LASTEST_PIPELINE_SUCCESS, false]);
}); });
}); });

View file

@ -47,13 +47,14 @@ describe('IDE pipelines mutations', () => {
path: 'test', path: 'test',
commit: { id: '123' }, commit: { id: '123' },
details: { status: jasmine.any(Object) }, details: { status: jasmine.any(Object) },
yamlError: undefined,
}); });
}); });
it('does not set latest pipeline if pipeline is null', () => { it('does not set latest pipeline if pipeline is null', () => {
mutations[types.RECEIVE_LASTEST_PIPELINE_SUCCESS](mockedState, null); mutations[types.RECEIVE_LASTEST_PIPELINE_SUCCESS](mockedState, null);
expect(mockedState.latestPipeline).toEqual(null); expect(mockedState.latestPipeline).toEqual(false);
}); });
it('sets stages', () => { it('sets stages', () => {