Skip to content

Commit 1903522

Browse files
just-at-ubercodemmaaliakseireact
authored
Feature/Directed Asyclic Graph (#241)
* Pr branch (#214) * added simple route to tree graph * added new component for workflow graph * now displaying a svg circle, added some styling * added a simple line generator to display grah * moved code from js project to graph vue component, added new data in data.json * removed old file, fixed click issues by a temporay hack' * trying to integrate cytoscape graph@ * removed and changed some imports to make cytoscape graph work * updated store import * added loading of workflow to history component for testing * removed typescript files * added new test data * added demo-data * added store * added event function map but in pure js * added old tree component which will be updated * changed name of store * updated npm package of cytoscape to 3.16.0 * renamed tree vue file * changed to receive events as props@ * history now loads workflow container instead of graph * modiefied graph container to receive new props and set a small delay of displaying graph * changed with and height of graph to fix problem with zoom * now looping over events instead of workflow@ * added a new event map to get event info * changed all eventtypes to map to servers json format * updated store to receive both workflow id and child id * updated graph component to send correct data to store * added a function to set route to child * changed the way childworkflow route is set in event map * changed how parent is routed to * changed variable name for parent route * removed old function for routing * removed home btn * added a simpler way to change the route@ * added query to route to route to the graph format and not grid * renamed showGraph variable to showTimeLine * renamed variables and functions from showGraph to showTimeline * toggling with flag instead of true false variable * added a function setWorkflow which rezises the split window based on the variable graphView * removed old link to tree graph * added a refresh btn which forces a reload of the graph container * sending isworkflow running to the history component from index * refresh functioning, only on mount needs to bw fixed * removed debugging msg * Refresh btn now only shows if there is more data to reload * only sending the prop (events) to graphView if it has been fetched and has length * removed redundant code and changed variable names to better represent their functionalities * revomed debugging code * displaying graph on right side works but is hacky - needs to be fixed * added flexbox to the container when the dag graph is displayed to layout the slider on the right * added functionality which check route query to determine how to split the window@ * new name for class to match current class naming style * removed commented code and console log * changed to listen to query updates rather than store to update currently selected node * removed node list from graph and not used function * removed commented code * added new function zoomtonode which is called on mount, if a eventId exist on query * removed old console log * removed not used method in store set rendered nodes * removed functionality regarding rendered nodes (only relevant when we have a node list to the graph * removed old console logs * removed another debug console log * added omit to router query when routing from/to child to avoid trying to display a node with same eventId which doesnt exist@ * added a check to see if eventId is definied on graph mount * update route query when a node is clicked * added a watch function which watches changes in event id query and then scrolls the grid to that event * removed old on click functioanlity and added new to allow deselection of node * removed eventlist and moved childbtn to topbar * removed event list and all css linked to it and node list * added and extra if statement to check if you have already clicked on bg * removed old getters and setters in vue store * added functionality to check if a node is clicked or not * added matching top bar to canvas (same as grid) * added new btn styling to match rest of the page, using the predefinted mixins@ * added proper buttons for parent and child routes@ * removed unecessary css code * styled refresh button * added a check to enableSplitting to not do it on graph view * removed clickinfo from eventmap * updated name of event info and removed click info * removed old static workflow loading functionality and its corresponding data and props * removed old event map functions and renamed the current one * removed old code and renamed some variables * removed old demo data * changed child btn text to To Child * changed variable names for better readability. Added overflow ellipse for workflow name text * added new variables names to store for better readability regarding childworkflow * added functionality to show child btn for previous execution run id * functionality for changing the name of the parent btn, and only use run id as route * updated continued as new event to return run ID * added a new mutation to update route for new executions * Change cytoscape version * added functionality to make graph display child btn on grid click * refractored current code for better readability * removed onNodeClick function which is not necessary anymore * added an extra check to make sure refresh btn is not showed unecessarily * updated even connection functionality to not break for different chunk sizes * removed old console log * removed legend imports in graph component * added legend to graph container * added graph legend component and changed its styling a bit * small html and css fixes * moved legend to bottom of graph * removed chunckWorfklow and code that is not used * removed old graph file * fixed pr comment on this=self, and simplified zoom * removed unused import * passing eventId as prop instead of listening to changes in query * removed unnecessary async prefix for viewInit * update to check for prop and not for route query * changed from let to const for zoom lvl * added map mupations to connect to store * resolved merge conflicts * added demo data to gitignore * commented out demo data * making sure cytoscape version stays on 3.16.0 * added a new file for calculating the layout using timestamps * added a new file which keeps track of events and nodes and builds the graph * removed unused function * Order nodes by timestamp in graph visualization * Enable chrono edges * Avoid overlapping edges * Apply lint --fix * Disable chrono edges for large workflows * Fix timeline elements test * Use showGraph url parameter in tests Co-authored-by: Aliaksei Talkachou <[email protected]> * removing git ignore file * updating package-lock and updating ordering in cytoscape component * refactoring get-event-connections. fixing lint. adding missing licenses. * minor refactorings * minor refactoring * updating start date in license headers * moving files to a more logical structure. * small refactor * small refactor for legend * adding license header * fixing lint * moving constants * small rename refactor for graphView values. * adjusting line in legend * moving constants out of helper into constants file. * moving comments for constants. * moving more constants out * reordering constants * minor refactoring. removing some warnings in console. * adjusting whitespace in legend * fixing lint * minor refactoring of CytoscapeLayout for better readability. * minor refactor * minor refactoring * adjusting ordering * breaking apart cytoscape layout into smaller helpers * moving graph styles from helpers to constants. * fixed unnecessary code * moving out helper function * removing route.query * setting split direction to computed field * adjusting name for split size constant * fixing error in console * removing unused packages. * breaking apart graph helper into smaller files. * adding license headers * fixing lint * removing unused prop * moving to computed prop * moving constant. moving to computed field * simplifying code * reference helper * removing unused variables * moving out more constants * separating get event connections into smaller files. * minor refactoring * moving more constants out * fixing lint * updating comment * tweaking vuex store to have graph namespace for application separation. * fixing lint * removing direct import reference to store in components. * addressing PR comments * added comment in code. Co-authored-by: Emma Tysk <[email protected]> Co-authored-by: Aliaksei Talkachou <[email protected]>
1 parent f7d92c3 commit 1903522

File tree

65 files changed

+3228
-230
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+3228
-230
lines changed

client/main.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ import WorkflowList from './routes/domain/workflow-list';
5555
import WorkflowSummary from './routes/workflow/summary';
5656
import WorkflowTabs from './routes/workflow';
5757

58+
import store from './store/index.js';
59+
5860
import { http, injectMomentDurationFormat, jsonTryParse } from '~helpers';
5961

6062
const routeOpts = {
@@ -188,6 +190,7 @@ const routeOpts = {
188190
format: query.format || 'grid',
189191
runId: params.runId,
190192
showGraph: Boolean(query.showGraph) === true,
193+
graphView: query.graphView,
191194
workflowId: encodeURIComponent(params.workflowId),
192195
}),
193196
},
@@ -345,6 +348,7 @@ if (typeof mocha === 'undefined') {
345348
new Vue({
346349
el: 'main',
347350
router,
351+
store,
348352
template: '<App/>',
349353
components: { App },
350354
});
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
<script>
2+
// Copyright (c) 2020-2021 Uber Technologies Inc.
3+
//
4+
//
5+
// Permission is hereby granted, free of charge, to any person obtaining a copy
6+
// of this software and associated documentation files (the "Software"), to deal
7+
// in the Software without restriction, including without limitation the rights
8+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
// copies of the Software, and to permit persons to whom the Software is
10+
// furnished to do so, subject to the following conditions:
11+
//
12+
// The above copyright notice and this permission notice shall be included in
13+
// all copies or substantial portions of the Software.
14+
//
15+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
// THE SOFTWARE.
22+
23+
import { LEGEND_CONNECTION_LIST } from '../constants';
24+
25+
export default {
26+
name: 'graph-legend',
27+
data() {
28+
return {
29+
isHidden: true,
30+
connectionList: LEGEND_CONNECTION_LIST,
31+
};
32+
},
33+
methods: {
34+
onClick() {
35+
this.isHidden = !this.isHidden;
36+
},
37+
},
38+
};
39+
</script>
40+
41+
<template>
42+
<div v-on:click="onClick" class="graph-legend">
43+
<div v-if="isHidden" class="legend-preview">
44+
<div class="arrow-container">
45+
<span class="arrow direct"></span>
46+
<hr class="direct" />
47+
</div>
48+
</div>
49+
<div v-if="!isHidden">
50+
<transition-group appear name="fade">
51+
<div v-for="connection in connectionList" :key="connection.name">
52+
<div class="legend-example">
53+
<div class="arrow-container">
54+
<span :class="connection.name" class="pic arrow"></span>
55+
<hr :class="connection.name" />
56+
</div>
57+
<div class="text">{{ connection.text }}</div>
58+
</div>
59+
<hr class="divider" />
60+
</div>
61+
</transition-group>
62+
</div>
63+
</div>
64+
</template>
65+
66+
<style scoped lang="stylus">
67+
@require '../../../../../styles/definitions.styl';
68+
.graph-legend {
69+
background-color: white;
70+
border: 1px solid #eaeaea;
71+
box-shadow: 0px 0px 9px 0px rgba(232, 232, 232, 1);
72+
width: fit-content;
73+
max-width: 200px;
74+
padding: 10px 15px;
75+
position: absolute;
76+
right: inline-spacing-large;
77+
bottom: inline-spacing-large;
78+
cursor: pointer;
79+
z-index: 10;
80+
81+
&-example {
82+
display: flex;
83+
flex-direction: column;
84+
text-align: left;
85+
margin: inline-spacing-large;
86+
}
87+
&-preview {
88+
margin: inline-spacing-small inline-spacing-large;
89+
}
90+
}
91+
92+
hr {
93+
width: 40px;
94+
margin-left: 0;
95+
margin-top: 8px;
96+
97+
&.direct {
98+
border-top: 2px solid uber-black-90;
99+
}
100+
101+
&.chron {
102+
border-top: 2px solid secondary-color;
103+
}
104+
105+
&.inferred {
106+
border-top: 2px solid uber-yellow ;
107+
}
108+
}
109+
110+
hr.divider {
111+
border: 0;
112+
border-top: 1px solid #eaeaea;
113+
width: 100%;
114+
padding: 0;
115+
116+
&:last-child {
117+
display:none;
118+
}
119+
}
120+
121+
.fade-enter-active, .fade-leave-active {
122+
transition: opacity 1s;
123+
}
124+
125+
.fade-enter, .fade-leave-to {
126+
opacity: 0;
127+
}
128+
129+
.text {
130+
margin: 10px 0;
131+
}
132+
133+
.arrow-container {
134+
display: flex;
135+
align-items: center;
136+
}
137+
138+
.arrow {
139+
border-top: 6px solid transparent;
140+
border-bottom: 6px solid transparent;
141+
142+
&.direct {
143+
border-right: 6px solid uber-black-90;
144+
}
145+
146+
&.chron {
147+
border-right: 6px solid secondary-color;
148+
}
149+
150+
&.inferred {
151+
border-right: 6px solid uber-yellow;
152+
}
153+
}
154+
</style>
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
<script>
2+
// Copyright (c) 2020-2021 Uber Technologies Inc.
3+
//
4+
//
5+
// Permission is hereby granted, free of charge, to any person obtaining a copy
6+
// of this software and associated documentation files (the "Software"), to deal
7+
// in the Software without restriction, including without limitation the rights
8+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
// copies of the Software, and to permit persons to whom the Software is
10+
// furnished to do so, subject to the following conditions:
11+
//
12+
// The above copyright notice and this permission notice shall be included in
13+
// all copies or substantial portions of the Software.
14+
//
15+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
// THE SOFTWARE.
22+
23+
import cytoscape from 'cytoscape';
24+
import omit from 'lodash-es/omit';
25+
import { mapMutations } from 'vuex';
26+
import { getGraphPanCenter, selectNode } from '../helpers';
27+
import cytoscapeLayout from '../helpers/cytoscape-layout';
28+
import {
29+
CYTOSCAPE_DEFAULT_OPTIONS,
30+
GRAPH_ZOOM_DEFAULT,
31+
GRAPH_ZOOM_MAX,
32+
GRAPH_ZOOM_MIN,
33+
} from '../constants';
34+
35+
cytoscape.use(cytoscapeLayout);
36+
37+
export default {
38+
name: 'graph',
39+
props: ['events', 'selectedEventId'],
40+
watch: {
41+
selectedEventId(id) {
42+
this.selectNode(id);
43+
},
44+
},
45+
methods: {
46+
...mapMutations(['childRoute', 'toggleChildBtn']),
47+
zoomToNode(node) {
48+
const pan = getGraphPanCenter({
49+
boundingBox: node.boundingBox(),
50+
height: this.cy.height(),
51+
width: this.cy.width(),
52+
});
53+
54+
// Pan the graph to view node
55+
this.cy.animate({
56+
zoom: GRAPH_ZOOM_DEFAULT,
57+
pan,
58+
});
59+
},
60+
updateChildBtn(node) {
61+
const nodeData = node.data();
62+
63+
if (nodeData.childRoute) {
64+
this.childRoute({ route: nodeData.childRoute, btnText: 'To child' });
65+
} else if (nodeData.newExecutionRunId) {
66+
this.childRoute({
67+
route: nodeData.newExecutionRunId,
68+
btnText: 'Next execution',
69+
});
70+
} else {
71+
this.toggleChildBtn();
72+
}
73+
},
74+
selectNode(id) {
75+
// Update graph to render nodes around the select one
76+
this.updateView(id);
77+
78+
// Deselect all previously selected nodes
79+
this.cy.$(':selected').deselect();
80+
81+
// Mark current node as selected
82+
const node =
83+
id !== null && id !== undefined ? this.cy.nodes('node#' + id) : null;
84+
85+
if (node && node.length) {
86+
// Zoom to the selected node
87+
node.select();
88+
this.updateChildBtn(node);
89+
this.zoomToNode(node);
90+
}
91+
},
92+
initView(elements) {
93+
if (this.cy) {
94+
this.cy.unmount();
95+
this.cy.destroy();
96+
this.cy = null;
97+
}
98+
99+
// Create cy instance
100+
const container = this.$refs.cy;
101+
const cy = cytoscape({
102+
...CYTOSCAPE_DEFAULT_OPTIONS,
103+
container,
104+
elements,
105+
});
106+
107+
cy.minZoom(GRAPH_ZOOM_MIN);
108+
cy.maxZoom(GRAPH_ZOOM_MAX);
109+
110+
cy.on('mouseover', 'node', function(e) {
111+
container.style.cursor = 'pointer';
112+
});
113+
cy.on('mouseout', 'node', function(e) {
114+
container.style.cursor = 'default';
115+
});
116+
117+
// Register click event
118+
cy.on('tap', ({ target: eventTarget }) => {
119+
// Tap on background
120+
if (eventTarget === cy) {
121+
if (this.selectedEventId) {
122+
this.$router.replace({ query: omit(this.$route.query, 'eventId') });
123+
this.$store.commit('toggleChildBtn');
124+
}
125+
}
126+
// Tap on a node that is not already selected
127+
else if (eventTarget.isNode() && !eventTarget.selected()) {
128+
const nodeData = eventTarget.data();
129+
130+
this.$router.replace({
131+
query: { ...this.$route.query, eventId: nodeData.id },
132+
});
133+
}
134+
});
135+
cy.mount(container);
136+
this.cy = cy;
137+
this.cy.fit();
138+
},
139+
updateView(id = null) {
140+
if (this.cy && id === null) {
141+
// Do nothing if graph is rendered and user clicks somewhere between nodes
142+
return;
143+
}
144+
145+
const {
146+
shouldRedraw,
147+
elements,
148+
previousExecutionRunId,
149+
parentWorkflowExecution,
150+
sliceIndices,
151+
} = selectNode({
152+
events: this.events,
153+
selectedEventId: id,
154+
sliceIndices: this.sliceIndices,
155+
});
156+
157+
this.sliceIndices = sliceIndices;
158+
159+
if (!shouldRedraw) {
160+
return;
161+
}
162+
163+
// Currently viewing a child workflow, show parent button
164+
if (previousExecutionRunId) {
165+
this.$store.commit('previousExecutionRoute', previousExecutionRunId);
166+
} else if (parentWorkflowExecution) {
167+
this.$store.commit('parentRoute', parentWorkflowExecution);
168+
}
169+
170+
this.initView(elements);
171+
},
172+
},
173+
mounted() {
174+
this.sliceIndices = null;
175+
this.selectNode(this.selectedEventId);
176+
},
177+
};
178+
</script>
179+
180+
<template>
181+
<div id="cytoscape">
182+
<div ref="cy" id="cy"></div>
183+
</div>
184+
</template>
185+
186+
<style scoped>
187+
#cytoscape {
188+
width: 100%;
189+
height: 100%;
190+
}
191+
#cy {
192+
width: 100%;
193+
height: 100%;
194+
}
195+
</style>

0 commit comments

Comments
 (0)