Commit ca520887 authored by Andrew Fontaine's avatar Andrew Fontaine

Merge branch '221225-dag-basic-annotations' into 'master'

Add annotation component for DAG

See merge request gitlab-org/gitlab!35240
parents b27ccc99 773c29ee
...@@ -8,3 +8,8 @@ export const DEFAULT = 'default'; ...@@ -8,3 +8,8 @@ export const DEFAULT = 'default';
export const IS_HIGHLIGHTED = 'dag-highlighted'; export const IS_HIGHLIGHTED = 'dag-highlighted';
export const LINK_SELECTOR = 'dag-link'; export const LINK_SELECTOR = 'dag-link';
export const NODE_SELECTOR = 'dag-node'; export const NODE_SELECTOR = 'dag-node';
/* Annotation types */
export const ADD_NOTE = 'add';
export const REMOVE_NOTE = 'remove';
export const REPLACE_NOTES = 'replace';
<script> <script>
import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui'; import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui';
import { isEmpty } from 'lodash';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import { __ } from '~/locale'; import { __ } from '~/locale';
import DagGraph from './dag_graph.vue'; 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'; import { parseData } from './parsing_utils';
export default { export default {
// eslint-disable-next-line @gitlab/require-i18n-strings // eslint-disable-next-line @gitlab/require-i18n-strings
name: 'Dag', name: 'Dag',
components: { components: {
DagAnnotations,
DagGraph, DagGraph,
GlAlert, GlAlert,
GlLink, GlLink,
...@@ -24,10 +35,11 @@ export default { ...@@ -24,10 +35,11 @@ export default {
}, },
data() { data() {
return { return {
showFailureAlert: false, annotationsMap: {},
showBetaInfo: true,
failureType: null, failureType: null,
graphData: null, graphData: null,
showFailureAlert: false,
showBetaInfo: true,
}; };
}, },
errorTexts: { errorTexts: {
...@@ -66,6 +78,9 @@ export default { ...@@ -66,6 +78,9 @@ export default {
}; };
} }
}, },
shouldDisplayAnnotations() {
return !isEmpty(this.annotationsMap);
},
shouldDisplayGraph() { shouldDisplayGraph() {
return Boolean(!this.showFailureAlert && this.graphData); return Boolean(!this.showFailureAlert && this.graphData);
}, },
...@@ -86,6 +101,9 @@ export default { ...@@ -86,6 +101,9 @@ export default {
.catch(() => reportFailure(LOAD_FAILURE)); .catch(() => reportFailure(LOAD_FAILURE));
}, },
methods: { methods: {
addAnnotationToMap({ uid, source, target }) {
this.$set(this.annotationsMap, uid, { source, target });
},
processGraphData(data) { processGraphData(data) {
let parsed; let parsed;
...@@ -109,10 +127,28 @@ export default { ...@@ -109,10 +127,28 @@ export default {
hideBetaInfo() { hideBetaInfo() {
this.showBetaInfo = false; this.showBetaInfo = false;
}, },
removeAnnotationFromMap({ uid }) {
this.$delete(this.annotationsMap, uid);
},
reportFailure(type) { reportFailure(type) {
this.showFailureAlert = true; this.showFailureAlert = true;
this.failureType = type; 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> </script>
...@@ -131,6 +167,14 @@ export default { ...@@ -131,6 +167,14 @@ export default {
</template> </template>
</gl-sprintf> </gl-sprintf>
</gl-alert> </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"
@on-failure="reportFailure"
@update-annotation="updateAnnotation"
/>
</div>
</div> </div>
</template> </template>
<script>
import { GlButton } from '@gitlab/ui';
import { __ } from '~/locale';
export default {
name: 'DagAnnotations',
components: {
GlButton,
},
props: {
annotations: {
type: Object,
required: true,
},
},
data() {
return {
showList: true,
};
},
computed: {
linkText() {
return this.showList ? __('Hide list') : __('Show list');
},
shouldShowLink() {
return Object.keys(this.annotations).length > 1;
},
wrapperClasses() {
return [
'gl-display-flex',
'gl-flex-direction-column',
'gl-absolute',
'gl-right-1',
'gl-top-0',
'gl-w-max-content',
'gl-px-5',
'gl-py-4',
'gl-rounded-base',
'gl-bg-white',
].join(' ');
},
},
methods: {
toggleList() {
this.showList = !this.showList;
},
},
};
</script>
<template>
<div :class="wrapperClasses">
<div v-if="showList">
<div
v-for="note in annotations"
:key="note.uid"
class="gl-display-flex gl-align-items-center"
>
<div
data-testid="dag-color-block"
class="gl-w-6 gl-h-5"
:style="{
background: `linear-gradient(0.25turn, ${note.source.color} 40%, ${note.target.color} 60%)`,
}"
></div>
<div data-testid="dag-note-text" class="gl-px-2 gl-font-base gl-align-items-center">
{{ note.source.name }}{{ note.target.name }}
</div>
</div>
</div>
<gl-button v-if="shouldShowLink" variant="link" @click="toggleList">{{ linkText }}</gl-button>
</div>
</template>
<script> <script>
import * as d3 from 'd3'; import * as d3 from 'd3';
import { uniqueId } from 'lodash'; import { uniqueId } from 'lodash';
import { LINK_SELECTOR, NODE_SELECTOR, PARSE_FAILURE } from './constants';
import { import {
LINK_SELECTOR,
NODE_SELECTOR,
PARSE_FAILURE,
ADD_NOTE,
REMOVE_NOTE,
REPLACE_NOTES,
} from './constants';
import {
currentIsLive,
getLiveLinksAsDict,
highlightLinks, highlightLinks,
restoreLinks, restoreLinks,
toggleLinkHighlight, toggleLinkHighlight,
...@@ -55,8 +64,8 @@ export default { ...@@ -55,8 +64,8 @@ export default {
data() { data() {
return { return {
color: () => {}, color: () => {},
width: 0,
height: 0, height: 0,
width: 0,
}; };
}, },
mounted() { mounted() {
...@@ -65,7 +74,7 @@ export default { ...@@ -65,7 +74,7 @@ export default {
try { try {
countedAndTransformed = this.transformData(this.graphData); countedAndTransformed = this.transformData(this.graphData);
} catch { } catch {
this.$emit('onFailure', PARSE_FAILURE); this.$emit('on-failure', PARSE_FAILURE);
return; return;
} }
...@@ -95,17 +104,33 @@ export default { ...@@ -95,17 +104,33 @@ export default {
}, },
appendLinkInteractions(link) { appendLinkInteractions(link) {
const { baseOpacity } = this.$options.viewOptions;
return link return link
.on('mouseover', highlightLinks) .on('mouseover', (d, idx, collection) => {
.on('mouseout', restoreLinks.bind(null, this.$options.viewOptions.baseOpacity)) if (currentIsLive(idx, collection)) {
.on('click', toggleLinkHighlight.bind(null, this.$options.viewOptions.baseOpacity)); return;
}
this.$emit('update-annotation', { type: ADD_NOTE, data: d });
highlightLinks(d, idx, collection);
})
.on('mouseout', (d, idx, collection) => {
if (currentIsLive(idx, collection)) {
return;
}
this.$emit('update-annotation', { type: REMOVE_NOTE, data: d });
restoreLinks(baseOpacity);
})
.on('click', (d, idx, collection) => {
toggleLinkHighlight(baseOpacity, d, idx, collection);
this.$emit('update-annotation', { type: REPLACE_NOTES, data: getLiveLinksAsDict() });
});
}, },
appendNodeInteractions(node) { appendNodeInteractions(node) {
return node.on( return node.on('click', (d, idx, collection) => {
'click', togglePathHighlights(this.$options.viewOptions.baseOpacity, d, idx, collection);
togglePathHighlights.bind(null, this.$options.viewOptions.baseOpacity), this.$emit('update-annotation', { type: REPLACE_NOTES, data: getLiveLinksAsDict() });
); });
}, },
appendLabelAsForeignObject(d, i, n) { appendLabelAsForeignObject(d, i, n) {
...@@ -271,6 +296,11 @@ export default { ...@@ -271,6 +296,11 @@ export default {
.attr('y2', d => d.y1 - 4); .attr('y2', d => d.y1 - 4);
}, },
initColors() {
const colorFn = d3.scaleOrdinal(this.$options.gitLabColorRotation);
return ({ name }) => colorFn(name);
},
labelNodes(svg, nodeData) { labelNodes(svg, nodeData) {
return svg return svg
.append('g') .append('g')
...@@ -282,11 +312,6 @@ export default { ...@@ -282,11 +312,6 @@ export default {
.each(this.appendLabelAsForeignObject); .each(this.appendLabelAsForeignObject);
}, },
initColors() {
const colorFn = d3.scaleOrdinal(this.$options.gitLabColorRotation);
return ({ name }) => colorFn(name);
},
transformData(parsed) { transformData(parsed) {
const baseLayout = createSankey()(parsed); const baseLayout = createSankey()(parsed);
const cleanedNodes = removeOrphanNodes(baseLayout.nodes); const cleanedNodes = removeOrphanNodes(baseLayout.nodes);
......
...@@ -5,10 +5,20 @@ export const highlightIn = 1; ...@@ -5,10 +5,20 @@ export const highlightIn = 1;
export const highlightOut = 0.2; export const highlightOut = 0.2;
const getCurrent = (idx, collection) => d3.select(collection[idx]); const getCurrent = (idx, collection) => d3.select(collection[idx]);
const currentIsLive = (idx, collection) => getCurrent(idx, collection).classed(IS_HIGHLIGHTED); const getLiveLinks = () => d3.selectAll(`.${LINK_SELECTOR}.${IS_HIGHLIGHTED}`);
const getOtherLinks = () => d3.selectAll(`.${LINK_SELECTOR}:not(.${IS_HIGHLIGHTED})`); const getOtherLinks = () => d3.selectAll(`.${LINK_SELECTOR}:not(.${IS_HIGHLIGHTED})`);
const getNodesNotLive = () => d3.selectAll(`.${NODE_SELECTOR}:not(.${IS_HIGHLIGHTED})`); const getNodesNotLive = () => d3.selectAll(`.${NODE_SELECTOR}:not(.${IS_HIGHLIGHTED})`);
export const getLiveLinksAsDict = () => {
return Object.fromEntries(
getLiveLinks()
.data()
.map(d => [d.uid, d]),
);
};
export const currentIsLive = (idx, collection) =>
getCurrent(idx, collection).classed(IS_HIGHLIGHTED);
const backgroundLinks = selection => selection.style('stroke-opacity', highlightOut); const backgroundLinks = selection => selection.style('stroke-opacity', highlightOut);
const backgroundNodes = selection => selection.attr('stroke', '#f2f2f2'); const backgroundNodes = selection => selection.attr('stroke', '#f2f2f2');
const foregroundLinks = selection => selection.style('stroke-opacity', highlightIn); const foregroundLinks = selection => selection.style('stroke-opacity', highlightIn);
...@@ -16,10 +26,10 @@ const foregroundNodes = selection => selection.attr('stroke', d => d.color); ...@@ -16,10 +26,10 @@ const foregroundNodes = selection => selection.attr('stroke', d => d.color);
const renewLinks = (selection, baseOpacity) => selection.style('stroke-opacity', baseOpacity); const renewLinks = (selection, baseOpacity) => selection.style('stroke-opacity', baseOpacity);
const renewNodes = selection => selection.attr('stroke', d => d.color); const renewNodes = selection => selection.attr('stroke', d => d.color);
const getAllLinkAncestors = node => { export const getAllLinkAncestors = node => {
if (node.targetLinks) { if (node.targetLinks) {
return node.targetLinks.flatMap(n => { return node.targetLinks.flatMap(n => {
return [n.uid, ...getAllLinkAncestors(n.source)]; return [n, ...getAllLinkAncestors(n.source)];
}); });
} }
...@@ -59,8 +69,8 @@ const highlightPath = (parentLinks, parentNodes) => { ...@@ -59,8 +69,8 @@ const highlightPath = (parentLinks, parentNodes) => {
backgroundNodes(getNodesNotLive()); backgroundNodes(getNodesNotLive());
/* highlight correct links */ /* highlight correct links */
parentLinks.forEach(id => { parentLinks.forEach(({ uid }) => {
foregroundLinks(d3.select(`#${id}`)).classed(IS_HIGHLIGHTED, true); foregroundLinks(d3.select(`#${uid}`)).classed(IS_HIGHLIGHTED, true);
}); });
/* highlight correct nodes */ /* highlight correct nodes */
...@@ -69,9 +79,22 @@ const highlightPath = (parentLinks, parentNodes) => { ...@@ -69,9 +79,22 @@ const highlightPath = (parentLinks, parentNodes) => {
}); });
}; };
const restoreNodes = () => {
/*
When paths are unclicked, they can take down nodes that
are still in use for other paths. This checks the live paths and
rehighlights their nodes.
*/
getLiveLinks().each(d => {
foregroundNodes(d3.select(`#${d.source.uid}`)).classed(IS_HIGHLIGHTED, true);
foregroundNodes(d3.select(`#${d.target.uid}`)).classed(IS_HIGHLIGHTED, true);
});
};
const restorePath = (parentLinks, parentNodes, baseOpacity) => { const restorePath = (parentLinks, parentNodes, baseOpacity) => {
parentLinks.forEach(id => { parentLinks.forEach(({ uid }) => {
renewLinks(d3.select(`#${id}`), baseOpacity).classed(IS_HIGHLIGHTED, false); renewLinks(d3.select(`#${uid}`), baseOpacity).classed(IS_HIGHLIGHTED, false);
}); });
parentNodes.forEach(id => { parentNodes.forEach(id => {
...@@ -86,14 +109,10 @@ const restorePath = (parentLinks, parentNodes, baseOpacity) => { ...@@ -86,14 +109,10 @@ const restorePath = (parentLinks, parentNodes, baseOpacity) => {
backgroundLinks(getOtherLinks()); backgroundLinks(getOtherLinks());
backgroundNodes(getNodesNotLive()); backgroundNodes(getNodesNotLive());
restoreNodes();
}; };
export const restoreLinks = (baseOpacity, d, idx, collection) => { export const restoreLinks = baseOpacity => {
/* in this case, it has just been clicked */
if (currentIsLive(idx, collection)) {
return;
}
/* /*
if there exist live links, reset to highlight out / pale if there exist live links, reset to highlight out / pale
otherwise, reset to base otherwise, reset to base
...@@ -111,11 +130,12 @@ export const restoreLinks = (baseOpacity, d, idx, collection) => { ...@@ -111,11 +130,12 @@ export const restoreLinks = (baseOpacity, d, idx, collection) => {
export const toggleLinkHighlight = (baseOpacity, d, idx, collection) => { export const toggleLinkHighlight = (baseOpacity, d, idx, collection) => {
if (currentIsLive(idx, collection)) { if (currentIsLive(idx, collection)) {
restorePath([d.uid], [d.source.uid, d.target.uid], baseOpacity); restorePath([d], [d.source.uid, d.target.uid], baseOpacity);
restoreNodes();
return; return;
} }
highlightPath([d.uid], [d.source.uid, d.target.uid]); highlightPath([d], [d.source.uid, d.target.uid]);
}; };
export const togglePathHighlights = (baseOpacity, d, idx, collection) => { export const togglePathHighlights = (baseOpacity, d, idx, collection) => {
......
---
title: Add annotation component for DAG
merge_request: 35240
author:
type: added
...@@ -11709,6 +11709,9 @@ msgstr "" ...@@ -11709,6 +11709,9 @@ msgstr ""
msgid "Hide host keys manual input" msgid "Hide host keys manual input"
msgstr "" msgstr ""
msgid "Hide list"
msgstr ""
msgid "Hide marketing-related entries from help" msgid "Hide marketing-related entries from help"
msgstr "" msgstr ""
...@@ -20783,6 +20786,9 @@ msgstr "" ...@@ -20783,6 +20786,9 @@ msgstr ""
msgid "Show latest version" msgid "Show latest version"
msgstr "" msgstr ""
msgid "Show list"
msgstr ""
msgid "Show me everything" msgid "Show me everything"
msgstr "" msgstr ""
......
import { shallowMount, mount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
import DagAnnotations from '~/pipelines/components/dag/dag_annotations.vue';
import { singleNote, multiNote } from './mock_data';
describe('The DAG annotations', () => {
let wrapper;
const getColorBlock = () => wrapper.find('[data-testid="dag-color-block"]');
const getAllColorBlocks = () => wrapper.findAll('[data-testid="dag-color-block"]');
const getTextBlock = () => wrapper.find('[data-testid="dag-note-text"]');
const getAllTextBlocks = () => wrapper.findAll('[data-testid="dag-note-text"]');
const getToggleButton = () => wrapper.find(GlButton);
const createComponent = (propsData = {}, method = shallowMount) => {
if (wrapper?.destroy) {
wrapper.destroy();
}
wrapper = method(DagAnnotations, {
propsData,
data() {
return {
showList: true,
};
},
});
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('when there is one annotation', () => {
const currentNote = singleNote['dag-link103'];
beforeEach(() => {
createComponent({ annotations: singleNote });
});
it('displays the color block', () => {
expect(getColorBlock().exists()).toBe(true);
});
it('displays the text block', () => {
expect(getTextBlock().exists()).toBe(true);
expect(getTextBlock().text()).toBe(`${currentNote.source.name}${currentNote.target.name}`);
});
it('does not display the list toggle link', () => {
expect(getToggleButton().exists()).toBe(false);
});
});
describe('when there are multiple annoataions', () => {
beforeEach(() => {
createComponent({ annotations: multiNote });
});
it('displays a color block for each link', () => {
expect(getAllColorBlocks().length).toBe(Object.keys(multiNote).length);
});
it('displays a text block for each link', () => {
expect(getAllTextBlocks().length).toBe(Object.keys(multiNote).length);
Object.values(multiNote).forEach((item, idx) => {
expect(
getAllTextBlocks()
.at(idx)
.text(),
).toBe(`${item.source.name}${item.target.name}`);
});
});
it('displays the list toggle link', () => {
expect(getToggleButton().exists()).toBe(true);
expect(getToggleButton().text()).toBe('Hide list');
});
});
describe('the list toggle', () => {
beforeEach(() => {
createComponent({ annotations: multiNote }, mount);
});
describe('clicking hide', () => {
it('hides listed items and changes text to show', () => {
expect(getAllTextBlocks().length).toBe(Object.keys(multiNote).length);
expect(getToggleButton().text()).toBe('Hide list');
getToggleButton().trigger('click');
return wrapper.vm.$nextTick().then(() => {
expect(getAllTextBlocks().length).toBe(0);
expect(getToggleButton().text()).toBe('Show list');
});
});
});
describe('clicking show', () => {
it('shows listed items and changes text to hide', () => {
getToggleButton().trigger('click');
getToggleButton().trigger('click');
return wrapper.vm.$nextTick().then(() => {
expect(getAllTextBlocks().length).toBe(Object.keys(multiNote).length);
expect(getToggleButton().text()).toBe('Hide list');
});
});
});
});
});
import { mount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import DagGraph from '~/pipelines/components/dag/dag_graph.vue'; import DagGraph from '~/pipelines/components/dag/dag_graph.vue';
import { IS_HIGHLIGHTED, LINK_SELECTOR, NODE_SELECTOR } from '~/pipelines/components/dag/constants'; import { IS_HIGHLIGHTED, LINK_SELECTOR, NODE_SELECTOR } from '~/pipelines/components/dag/constants';
import { highlightIn, highlightOut } from '~/pipelines/components/dag/interactions'; import { highlightIn, highlightOut } from '~/pipelines/components/dag/interactions';
...@@ -19,7 +19,7 @@ describe('The DAG graph', () => { ...@@ -19,7 +19,7 @@ describe('The DAG graph', () => {
wrapper.destroy(); wrapper.destroy();
} }
wrapper = mount(DagGraph, { wrapper = shallowMount(DagGraph, {
attachToDocument: true, attachToDocument: true,
propsData, propsData,
data() { data() {
......
...@@ -5,14 +5,18 @@ import waitForPromises from 'helpers/wait_for_promises'; ...@@ -5,14 +5,18 @@ import waitForPromises from 'helpers/wait_for_promises';
import { GlAlert } from '@gitlab/ui'; import { GlAlert } from '@gitlab/ui';
import Dag from '~/pipelines/components/dag/dag.vue'; import Dag from '~/pipelines/components/dag/dag.vue';
import DagGraph from '~/pipelines/components/dag/dag_graph.vue'; import DagGraph from '~/pipelines/components/dag/dag_graph.vue';
import DagAnnotations from '~/pipelines/components/dag/dag_annotations.vue';
import { import {
ADD_NOTE,
REMOVE_NOTE,
REPLACE_NOTES,
DEFAULT, DEFAULT,
PARSE_FAILURE, PARSE_FAILURE,
LOAD_FAILURE, LOAD_FAILURE,
UNSUPPORTED_DATA, UNSUPPORTED_DATA,
} from '~/pipelines/components/dag//constants'; } from '~/pipelines/components/dag//constants';
import { mockBaseData, tooSmallGraph, unparseableGraph } from './mock_data'; import { mockBaseData, tooSmallGraph, unparseableGraph, singleNote, multiNote } from './mock_data';
describe('Pipeline DAG graph wrapper', () => { describe('Pipeline DAG graph wrapper', () => {
let wrapper; let wrapper;
...@@ -20,6 +24,7 @@ describe('Pipeline DAG graph wrapper', () => { ...@@ -20,6 +24,7 @@ describe('Pipeline DAG graph wrapper', () => {
const getAlert = () => wrapper.find(GlAlert); const getAlert = () => wrapper.find(GlAlert);
const getAllAlerts = () => wrapper.findAll(GlAlert); const getAllAlerts = () => wrapper.findAll(GlAlert);
const getGraph = () => wrapper.find(DagGraph); const getGraph = () => wrapper.find(DagGraph);
const getNotes = () => wrapper.find(DagAnnotations);
const getErrorText = type => wrapper.vm.$options.errorTexts[type]; const getErrorText = type => wrapper.vm.$options.errorTexts[type];
const dataPath = '/root/test/pipelines/90/dag.json'; const dataPath = '/root/test/pipelines/90/dag.json';
...@@ -134,4 +139,53 @@ describe('Pipeline DAG graph wrapper', () => { ...@@ -134,4 +139,53 @@ describe('Pipeline DAG graph wrapper', () => {
}); });
}); });
}); });
describe('annotations', () => {
beforeEach(() => {
mock.onGet(dataPath).replyOnce(200, mockBaseData);
createComponent({ graphUrl: dataPath }, mount);
});
it('toggles on link mouseover and mouseout', () => {
const currentNote = singleNote['dag-link103'];
expect(getNotes().exists()).toBe(false);
return wrapper.vm
.$nextTick()
.then(waitForPromises)
.then(() => {
getGraph().vm.$emit('update-annotation', { type: ADD_NOTE, data: currentNote });
return wrapper.vm.$nextTick();
})
.then(() => {
expect(getNotes().exists()).toBe(true);
getGraph().vm.$emit('update-annotation', { type: REMOVE_NOTE, data: currentNote });
return wrapper.vm.$nextTick();
})
.then(() => {
expect(getNotes().exists()).toBe(false);
});
});
it('toggles on node and link click', () => {
expect(getNotes().exists()).toBe(false);
return wrapper.vm
.$nextTick()
.then(waitForPromises)
.then(() => {
getGraph().vm.$emit('update-annotation', { type: REPLACE_NOTES, data: multiNote });
return wrapper.vm.$nextTick();
})
.then(() => {
expect(getNotes().exists()).toBe(true);
getGraph().vm.$emit('update-annotation', { type: REPLACE_NOTES, data: {} });
return wrapper.vm.$nextTick();
})
.then(() => {
expect(getNotes().exists()).toBe(false);
});
});
});
}); });
...@@ -388,3 +388,43 @@ export const parsedData = { ...@@ -388,3 +388,43 @@ export const parsedData = {
}, },
], ],
}; };
export const singleNote = {
'dag-link103': {
uid: 'dag-link103',
source: {
name: 'canary_a',
color: '#b31756',
},
target: {
name: 'production_a',
color: '#b24800',
},
},
};
export const multiNote = {
...singleNote,
'dag-link104': {
uid: 'dag-link104',
source: {
name: 'build_a',
color: '#e17223',
},
target: {
name: 'test_c',
color: '#006887',
},
},
'dag-link105': {
uid: 'dag-link105',
source: {
name: 'test_c',
color: '#006887',
},
target: {
name: 'post_test_c',
color: '#3547de',
},
},
};
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