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>