debian-mirror-gitlab/app/assets/javascripts/pipelines/components/dag/dag.vue

249 lines
7 KiB
Vue
Raw Normal View History

2020-06-23 00:09:42 +05:30
<script>
2021-01-29 00:20:46 +05:30
import { GlAlert, GlButton, GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui';
2020-07-28 23:09:34 +05:30
import { isEmpty } from 'lodash';
2020-10-24 23:57:45 +05:30
import { fetchPolicies } from '~/lib/graphql';
2021-03-11 19:13:27 +05:30
import { __ } from '~/locale';
import { DEFAULT, PARSE_FAILURE, LOAD_FAILURE, UNSUPPORTED_DATA } from '../../constants';
2020-10-24 23:57:45 +05:30
import getDagVisData from '../../graphql/queries/get_dag_vis_data.query.graphql';
2021-01-03 14:25:43 +05:30
import { parseData } from '../parsing_utils';
2021-03-11 19:13:27 +05:30
import { ADD_NOTE, REMOVE_NOTE, REPLACE_NOTES } from './constants';
import DagAnnotations from './dag_annotations.vue';
import DagGraph from './dag_graph.vue';
2020-06-23 00:09:42 +05:30
export default {
// eslint-disable-next-line @gitlab/require-i18n-strings
name: 'Dag',
components: {
2020-07-28 23:09:34 +05:30
DagAnnotations,
2020-06-23 00:09:42 +05:30
DagGraph,
GlAlert,
2020-07-28 23:09:34 +05:30
GlButton,
2021-01-29 00:20:46 +05:30
GlEmptyState,
GlLink,
GlSprintf,
2020-06-23 00:09:42 +05:30
},
2020-10-24 23:57:45 +05:30
inject: {
2021-01-29 00:20:46 +05:30
aboutDagDocPath: {
default: null,
},
2020-10-24 23:57:45 +05:30
dagDocPath: {
default: null,
2020-06-23 00:09:42 +05:30
},
2020-07-28 23:09:34 +05:30
emptySvgPath: {
default: '',
},
2020-10-24 23:57:45 +05:30
pipelineIid: {
default: '',
},
pipelineProjectPath: {
2020-07-28 23:09:34 +05:30
default: '',
},
2020-06-23 00:09:42 +05:30
},
2020-10-24 23:57:45 +05:30
apollo: {
graphData: {
fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
query: getDagVisData,
variables() {
return {
projectPath: this.pipelineProjectPath,
iid: this.pipelineIid,
};
},
update(data) {
2021-04-29 21:17:54 +05:30
if (!data?.project?.pipeline) {
return this.graphData;
}
2020-10-24 23:57:45 +05:30
const {
stages: { nodes: stages },
} = data.project.pipeline;
const unwrappedGroups = stages
.map(({ name, groups: { nodes: groups } }) => {
2021-03-08 18:12:59 +05:30
return groups.map((group) => {
2020-10-24 23:57:45 +05:30
return { category: name, ...group };
});
})
.flat(2);
2021-03-08 18:12:59 +05:30
const nodes = unwrappedGroups.map((group) => {
2020-10-24 23:57:45 +05:30
const jobs = group.jobs.nodes.map(({ name, needs }) => {
2021-03-08 18:12:59 +05:30
return { name, needs: needs.nodes.map((need) => need.name) };
2020-10-24 23:57:45 +05:30
});
return { ...group, jobs };
});
return nodes;
},
error() {
this.reportFailure(LOAD_FAILURE);
},
},
},
2020-06-23 00:09:42 +05:30
data() {
return {
2020-07-28 23:09:34 +05:30
annotationsMap: {},
2020-06-23 00:09:42 +05:30
failureType: null,
graphData: null,
2020-07-28 23:09:34 +05:30
showFailureAlert: false,
hasNoDependentJobs: false,
2020-06-23 00:09:42 +05:30
};
},
errorTexts: {
[LOAD_FAILURE]: __('We are currently unable to fetch data for this graph.'),
[PARSE_FAILURE]: __('There was an error parsing the data for this graph.'),
2020-07-28 23:09:34 +05:30
[UNSUPPORTED_DATA]: __('DAG visualization requires at least 3 dependent jobs.'),
2020-06-23 00:09:42 +05:30
[DEFAULT]: __('An unknown error occurred while loading this graph.'),
},
2020-07-28 23:09:34 +05:30
emptyStateTexts: {
2021-01-29 00:20:46 +05:30
title: __('Speed up your pipelines with Needs relationships'),
2020-07-28 23:09:34 +05:30
firstDescription: __(
2021-01-29 00:20:46 +05:30
'Using the %{codeStart}needs%{codeEnd} keyword makes jobs run before their stage is reached. Jobs run as soon as their %{codeStart}needs%{codeEnd} relationships are met, which speeds up your pipelines.',
2020-07-28 23:09:34 +05:30
),
secondDescription: __(
2021-01-29 00:20:46 +05:30
"If you add %{codeStart}needs%{codeEnd} to jobs in your pipeline you'll be able to view the %{codeStart}needs%{codeEnd} relationships between jobs in this tab as a %{linkStart}Directed Acyclic Graph (DAG)%{linkEnd}.",
2020-07-28 23:09:34 +05:30
),
2021-01-29 00:20:46 +05:30
button: __('Learn more about Needs relationships'),
2020-07-28 23:09:34 +05:30
},
2020-06-23 00:09:42 +05:30
computed: {
failure() {
switch (this.failureType) {
case LOAD_FAILURE:
return {
text: this.$options.errorTexts[LOAD_FAILURE],
variant: 'danger',
};
case PARSE_FAILURE:
return {
text: this.$options.errorTexts[PARSE_FAILURE],
variant: 'danger',
};
case UNSUPPORTED_DATA:
return {
text: this.$options.errorTexts[UNSUPPORTED_DATA],
variant: 'info',
};
default:
return {
text: this.$options.errorTexts[DEFAULT],
2020-10-24 23:57:45 +05:30
variant: 'danger',
2020-06-23 00:09:42 +05:30
};
}
},
2020-10-24 23:57:45 +05:30
processedData() {
return this.processGraphData(this.graphData);
},
2020-07-28 23:09:34 +05:30
shouldDisplayAnnotations() {
return !isEmpty(this.annotationsMap);
},
2020-06-23 00:09:42 +05:30
shouldDisplayGraph() {
2020-10-24 23:57:45 +05:30
return Boolean(!this.showFailureAlert && !this.hasNoDependentJobs && this.graphData);
2020-06-23 00:09:42 +05:30
},
},
methods: {
2020-07-28 23:09:34 +05:30
addAnnotationToMap({ uid, source, target }) {
this.$set(this.annotationsMap, uid, { source, target });
},
2020-06-23 00:09:42 +05:30
processGraphData(data) {
let parsed;
try {
2020-10-24 23:57:45 +05:30
parsed = parseData(data);
2020-06-23 00:09:42 +05:30
} catch {
this.reportFailure(PARSE_FAILURE);
2020-10-24 23:57:45 +05:30
return {};
2020-06-23 00:09:42 +05:30
}
2020-07-28 23:09:34 +05:30
if (parsed.links.length === 1) {
2020-06-23 00:09:42 +05:30
this.reportFailure(UNSUPPORTED_DATA);
2020-10-24 23:57:45 +05:30
return {};
2020-06-23 00:09:42 +05:30
}
2020-07-28 23:09:34 +05:30
// If there are no links, we don't report failure
// as it simply means the user does not use job dependencies
if (parsed.links.length === 0) {
this.hasNoDependentJobs = true;
2020-10-24 23:57:45 +05:30
return {};
2020-07-28 23:09:34 +05:30
}
2020-10-24 23:57:45 +05:30
return parsed;
2020-06-23 00:09:42 +05:30
},
hideAlert() {
this.showFailureAlert = false;
},
2020-07-28 23:09:34 +05:30
removeAnnotationFromMap({ uid }) {
this.$delete(this.annotationsMap, uid);
},
2020-06-23 00:09:42 +05:30
reportFailure(type) {
this.showFailureAlert = true;
this.failureType = type;
},
2020-07-28 23:09:34 +05:30
updateAnnotation({ type, data }) {
switch (type) {
case ADD_NOTE:
this.addAnnotationToMap(data);
break;
case REMOVE_NOTE:
this.removeAnnotationFromMap(data);
break;
case REPLACE_NOTES:
this.annotationsMap = data;
break;
default:
break;
}
},
2020-06-23 00:09:42 +05:30
},
};
</script>
<template>
<div>
<gl-alert v-if="showFailureAlert" :variant="failure.variant" @dismiss="hideAlert">
{{ failure.text }}
</gl-alert>
2020-07-28 23:09:34 +05:30
<div class="gl-relative">
<dag-annotations v-if="shouldDisplayAnnotations" :annotations="annotationsMap" />
<dag-graph
v-if="shouldDisplayGraph"
2020-10-24 23:57:45 +05:30
:graph-data="processedData"
2020-07-28 23:09:34 +05:30
@onFailure="reportFailure"
@update-annotation="updateAnnotation"
/>
<gl-empty-state
v-else-if="hasNoDependentJobs"
:svg-path="emptySvgPath"
:title="$options.emptyStateTexts.title"
>
<template #description>
<div class="gl-text-left">
<p>
<gl-sprintf :message="$options.emptyStateTexts.firstDescription">
<template #code="{ content }">
<code>{{ content }}</code>
</template>
</gl-sprintf>
</p>
<p>
<gl-sprintf :message="$options.emptyStateTexts.secondDescription">
<template #code="{ content }">
<code>{{ content }}</code>
</template>
2021-01-29 00:20:46 +05:30
<template #link="{ content }">
<gl-link :href="aboutDagDocPath">{{ content }}</gl-link>
</template>
2020-07-28 23:09:34 +05:30
</gl-sprintf>
</p>
</div>
</template>
2020-10-24 23:57:45 +05:30
<template v-if="dagDocPath" #actions>
2020-07-28 23:09:34 +05:30
<gl-button :href="dagDocPath" target="__blank" variant="success">
{{ $options.emptyStateTexts.button }}
</gl-button>
</template>
</gl-empty-state>
</div>
2020-06-23 00:09:42 +05:30
</div>
</template>