summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/pipelines/components/dag/dag.vue
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/pipelines/components/dag/dag.vue')
-rw-r--r--app/assets/javascripts/pipelines/components/dag/dag.vue117
1 files changed, 110 insertions, 7 deletions
diff --git a/app/assets/javascripts/pipelines/components/dag/dag.vue b/app/assets/javascripts/pipelines/components/dag/dag.vue
index 6e0d23ef87f..85163a666e2 100644
--- a/app/assets/javascripts/pipelines/components/dag/dag.vue
+++ b/app/assets/javascripts/pipelines/components/dag/dag.vue
@@ -1,19 +1,32 @@
<script>
-import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui';
+import { GlAlert, GlButton, GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui';
+import { isEmpty } from 'lodash';
import axios from '~/lib/utils/axios_utils';
import { __ } from '~/locale';
import DagGraph from './dag_graph.vue';
-import { DEFAULT, PARSE_FAILURE, LOAD_FAILURE, UNSUPPORTED_DATA } from './constants';
+import DagAnnotations from './dag_annotations.vue';
+import {
+ DEFAULT,
+ PARSE_FAILURE,
+ LOAD_FAILURE,
+ UNSUPPORTED_DATA,
+ ADD_NOTE,
+ REMOVE_NOTE,
+ REPLACE_NOTES,
+} from './constants';
import { parseData } from './parsing_utils';
export default {
// eslint-disable-next-line @gitlab/require-i18n-strings
name: 'Dag',
components: {
+ DagAnnotations,
DagGraph,
GlAlert,
GlLink,
GlSprintf,
+ GlEmptyState,
+ GlButton,
},
props: {
graphUrl: {
@@ -21,21 +34,43 @@ export default {
required: false,
default: '',
},
+ emptySvgPath: {
+ type: String,
+ required: true,
+ default: '',
+ },
+ dagDocPath: {
+ type: String,
+ required: true,
+ default: '',
+ },
},
data() {
return {
- showFailureAlert: false,
- showBetaInfo: true,
+ annotationsMap: {},
failureType: null,
graphData: null,
+ showFailureAlert: false,
+ showBetaInfo: true,
+ hasNoDependentJobs: false,
};
},
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.'),
- [UNSUPPORTED_DATA]: __('A DAG must have two dependent jobs to be visualized on this tab.'),
+ [UNSUPPORTED_DATA]: __('DAG visualization requires at least 3 dependent jobs.'),
[DEFAULT]: __('An unknown error occurred while loading this graph.'),
},
+ emptyStateTexts: {
+ title: __('Start using Directed Acyclic Graphs (DAG)'),
+ firstDescription: __(
+ "This pipeline does not use the %{codeStart}needs%{codeEnd} keyword and can't be represented as a directed acyclic graph.",
+ ),
+ secondDescription: __(
+ 'Using %{codeStart}needs%{codeEnd} allows jobs to run before their stage is reached, as soon as their individual dependencies are met, which speeds up your pipelines.',
+ ),
+ button: __('Learn more about job dependencies'),
+ },
computed: {
betaMessage() {
return __(
@@ -66,6 +101,9 @@ export default {
};
}
},
+ shouldDisplayAnnotations() {
+ return !isEmpty(this.annotationsMap);
+ },
shouldDisplayGraph() {
return Boolean(!this.showFailureAlert && this.graphData);
},
@@ -86,6 +124,9 @@ export default {
.catch(() => reportFailure(LOAD_FAILURE));
},
methods: {
+ addAnnotationToMap({ uid, source, target }) {
+ this.$set(this.annotationsMap, uid, { source, target });
+ },
processGraphData(data) {
let parsed;
@@ -96,11 +137,18 @@ export default {
return;
}
- if (parsed.links.length < 2) {
+ if (parsed.links.length === 1) {
this.reportFailure(UNSUPPORTED_DATA);
return;
}
+ // 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;
+ return;
+ }
+
this.graphData = parsed;
},
hideAlert() {
@@ -109,10 +157,28 @@ export default {
hideBetaInfo() {
this.showBetaInfo = false;
},
+ removeAnnotationFromMap({ uid }) {
+ this.$delete(this.annotationsMap, uid);
+ },
reportFailure(type) {
this.showFailureAlert = true;
this.failureType = type;
},
+ 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;
+ }
+ },
},
};
</script>
@@ -131,6 +197,43 @@ export default {
</template>
</gl-sprintf>
</gl-alert>
- <dag-graph v-if="shouldDisplayGraph" :graph-data="graphData" @onFailure="reportFailure" />
+ <div class="gl-relative">
+ <dag-annotations v-if="shouldDisplayAnnotations" :annotations="annotationsMap" />
+ <dag-graph
+ v-if="shouldDisplayGraph"
+ :graph-data="graphData"
+ @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>
+ </gl-sprintf>
+ </p>
+ </div>
+ </template>
+ <template #actions>
+ <gl-button :href="dagDocPath" target="__blank" variant="success">
+ {{ $options.emptyStateTexts.button }}
+ </gl-button>
+ </template>
+ </gl-empty-state>
+ </div>
</div>
</template>