client/containers/workflow-history/components/workflow-graph/index.vue (210 lines of code) (raw):
<script>
// Copyright (c) 2020-2024 Uber Technologies Inc.
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import omit from 'lodash-es/omit';
import Graph from './components/graph.vue';
import GraphLegend from './components/graph-legend.vue';
import { GRAPH_SHOW_DELAY } from './constants';
export default {
name: 'workflow-graph',
props: ['events', 'isWorkflowRunning', 'selectedEventId'],
components: {
Graph,
GraphLegend,
},
data() {
return {
isGraphLoading: true,
forceRefresh: true,
eventsSnapShot: [],
};
},
methods: {
delayedShow() {
setTimeout(() => {
this.isGraphLoading = false;
}, GRAPH_SHOW_DELAY);
},
updateRoute(route) {
const params = route.workflowId
? { workflowId: route.workflowId, runId: route.runId }
: { runId: route };
this.$router.replace({
params,
query: omit(this.$route.query, 'eventId'),
});
},
reloadWorkflow() {
this.eventsSnapShot = this.events;
this.isGraphLoading = true;
// NOTE: This is needed because the graph library being used is not vue reactive (jquery based).
// You can read about this approach here: https://michaelnthiessen.com/force-re-render/
this.forceRefresh = !this.forceRefresh;
this.delayedShow();
},
},
mounted() {
this.delayedShow();
this.eventsSnapShot = this.events;
this.$store.commit('resetGraphState');
},
computed: {
parentRoute() {
return this.$store.getters.parentRoute;
},
parentBtnText() {
return this.$store.getters.parentBtnText;
},
hasChildBtn() {
return this.$store.getters.hasChildBtn;
},
childBtnText() {
return this.$store.getters.childBtnText;
},
childRoute() {
return this.$store.getters.childRoute;
},
hasAllEvents() {
return this.eventsSnapShot.length === this.events.length;
},
workflowName() {
return this.events[0] && this.events[0].details.workflowType.name;
},
},
};
</script>
<template>
<div class="tree-graph">
<div id="canvas">
<div class="thead" ref="thead">
<div
class="aside-left"
v-if="parentRoute"
v-on:click="updateRoute(parentRoute)"
>
{{ parentBtnText }}
</div>
<div class="aside-center">
{{ workflowName }}
</div>
<div
class="aside-right"
v-if="hasChildBtn"
v-on:click="updateRoute(childRoute)"
>
{{ childBtnText }}
</div>
</div>
<div v-if="isGraphLoading" id="loading"></div>
<div class="refresh" v-if="!hasAllEvents" v-on:click="reloadWorkflow()">
Refresh
</div>
<GraphLegend />
<Graph
:key="forceRefresh"
v-if="!isGraphLoading"
:events="events"
:selected-event-id="selectedEventId"
/>
</div>
</div>
</template>
<style scoped lang="stylus">
@require '../../../../styles/definitions.styl';
.tree-graph {
width: 100%;
height: 100%;
}
div.thead {
.aside-right {
margin: inline-spacing-small inline-spacing-small inline-spacing-small auto;
action-button();
}
.aside-center {
flex: 1;
font-weight: 500;
text-align: center;
margin: auto;
padding: 0 inline-spacing-small;
text-overflow: ellipsis;
overflow: hidden;
}
.aside-left {
margin: inline-spacing-small auto inline-spacing-small inline-spacing-small;
action-button();
}
}
.thead {
background-color: uber-white-10;
box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.2);
padding: 0 inline-spacing-large;
position: absolute;
display: flex;
align-items: center;
justify-content: center;
top: 0;
left: 0;
z-index: 2;
width: 100%;
height: 60px;
}
#canvas {
width: 100%;
height: 100%;
background-color: white;
box-shadow: 0px 0px 9px 0px rgba(232, 232, 232, 1);
border: 1px solid #eaeaea;
overflow: hidden;
position: relative;
}
.refresh {
action-button();
icon-refresh();
position: absolute;
top: 'calc(%s + %s)' % (60px inline-spacing-large);
right: inline-spacing-large;
z-index: 3;
}
/* ---- Loading icon ---- */
#loading {
display: inline-block;
width: 50px;
height: 50px;
border: 3px solid #11939A;
border-radius: 50%;
border-top-color: #fff;
animation: spin 1s ease-in-out infinite;
position: absolute;
top: 50%;
left: 50%;
margin: -25px 0 0 -25px;
}
@keyframes spin {
to {
-webkit-transform: rotate(360deg);
}
}
@keyframes spin {
to {
-webkit-transform: rotate(360deg);
}
}
</style>