Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Graph view: use initialOptions to save & restore view state on navigation #1717

Merged
merged 3 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes.d/1717.feat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
More view options are now remembered & restored when navigating between workflows.
162 changes: 105 additions & 57 deletions src/views/Graph.vue
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@
import { useJobTheme } from '@/composables/localStorage'
import graphqlMixin from '@/mixins/graphql'
import subscriptionComponentMixin from '@/mixins/subscriptionComponent'
import {
initialOptions,
useInitialOptions
} from '@/utils/initialOptions'
import SubscriptionQuery from '@/model/SubscriptionQuery.model'
// import CylcTreeCallback from '@/services/treeCallback'
import GraphNode from '@/components/cylc/GraphNode.vue'
Expand Down Expand Up @@ -224,9 +228,34 @@
}
},

setup () {
props: { initialOptions },

setup (props, { emit }) {
/**
* The transpose toggle state.
* If true layout is left-right, else top-bottom
* @type {import('vue').Ref<boolean>}
*/
const transpose = useInitialOptions('transpose', { props, emit }, false)

/**
* The auto-refresh toggle state.
* If true the graph layout will be updated on a timer
* @type {import('vue').Ref<boolean>}
*/
const autoRefresh = useInitialOptions('autoRefresh', { props, emit }, true)

/**
* The node spacing state.
* @type {import('vue').Ref<number>}
*/
const spacing = useInitialOptions('spacing', { props, emit }, 1.5)

return {
jobTheme: useJobTheme(),
transpose,
autoRefresh,
spacing
}
},

Expand All @@ -236,8 +265,6 @@
orientation: 'TB',
// the auto-refresh timer
refreshTimer: null,
// the spacing between nodes
spacing: 1.5,
// the nodes end edges we render to the graph
graphNodes: [],
graphEdges: [],
Expand All @@ -249,13 +276,50 @@
graphID: null,
// instance of system which provides pan/zoom/navigation support
panZoomWidget: null,
// if true layout is left-right is false it is top-bottom
transpose: false,
// if true the graph layout will be updated on a timer
autoRefresh: true,
// true if layout is in progress
updating: false,
controlGroups: [
// supports loading graph when component is mounted and autoRefresh is off.
// true if page is loading for the first time and nodeDimensions are yet to be calculated
initialLoad: true,
}
},

mounted () {
// compile & instantiate graphviz wasm
/** @type {Promise<Graphviz>} */
this.graphviz = Graphviz.load()
// allow render to happen before we go configuring svgPanZoom
this.$nextTick(() => {
this.refresh()
this.updateTimer()
})
this.mountSVGPanZoom()
},

beforeUnmount () {
clearInterval(this.refreshTimer)
},

computed: {
...mapGetters('workflows', ['getNodes']),
query () {
return new SubscriptionQuery(
QUERY,
this.variables,
'workflow',
[],
/* isDelta */ true,
/* isGlobalCallback */ true
)
},
workflowIDs () {
return [this.workflowId]
},
workflows () {
return this.getNodes('workflow', this.workflowIDs)
},
controlGroups () {
return [
{
title: 'Graph',
controls: [
Expand All @@ -270,14 +334,14 @@
title: 'Auto Refresh',
icon: mdiTimer,
action: 'toggle',
value: true,
value: this.autoRefresh,
key: 'autoRefresh'
},
{
title: 'Transpose',
icon: mdiFileRotateRight,
action: 'toggle',
value: false,
value: this.transpose,
key: 'transpose'
},
{
Expand All @@ -300,42 +364,7 @@
}
]
}
],
}
},

mounted () {
// compile & instantiate graphviz wasm
/** @type {Promise<Graphviz>} */
this.graphviz = Graphviz.load()
// allow render to happen before we go configuring svgPanZoom
this.$nextTick(() => {
this.updateTimer()
})
this.mountSVGPanZoom()
},

beforeUnmount () {
clearInterval(this.refreshTimer)
},

computed: {
...mapGetters('workflows', ['getNodes']),
query () {
return new SubscriptionQuery(
QUERY,
this.variables,
'workflow',
[],
/* isDelta */ true,
/* isGlobalCallback */ true
)
},
workflowIDs () {
return [this.workflowId]
},
workflows () {
return this.getNodes('workflow', this.workflowIDs)
]
}
},

Expand Down Expand Up @@ -394,7 +423,9 @@
},
updateTimer () {
// turn the timer on or off depending on the value of autoRefresh
if (this.autoRefresh) {
// if initialLoad is true we want to set a refresh interval
// regardles of autoRefresh state.
if (this.autoRefresh || this.initialLoad) {
this.refreshTimer = setInterval(this.refresh, 2000)
} else {
clearInterval(this.refreshTimer)
Expand Down Expand Up @@ -517,7 +548,7 @@
// generate a hash for this list of nodes and edges
return nonCryptoHash(
nodes.map(n => n.id).reduce((x, y) => { return x + y }) +
edges.map(n => n.id).reduce((x, y) => { return x + y }, 1)
(edges || []).map(n => n.id).reduce((x, y) => { return x + y }, 1)
)
},
reset () {
Expand Down Expand Up @@ -558,10 +589,13 @@
this.updating = true

// extract the graph (non reactive lists of nodes & edges)
const nodes = this.getGraphNodes()
const nodes = await this.waitFor(() => {
const nodes = this.getGraphNodes()
return nodes.length ? nodes : false
MetRonnie marked this conversation as resolved.
Show resolved Hide resolved
})
const edges = this.getGraphEdges()

if (!nodes.length) {
if (!nodes || !nodes.length) {
// we can't graph this, reset and wait for something to draw
this.graphID = null
this.updating = false
Expand Down Expand Up @@ -592,16 +626,24 @@
// obtain the node dimensions to use in the layout
// NOTE: need to wait for the nodes to all be rendered before we can
// measure them
let nodeDimensions
await this.waitFor(() => {
const nodeDimensions = await this.waitFor(() => {
try {
nodeDimensions = this.getNodeDimensions(nodes)
return true // all nodes rendered
return this.getNodeDimensions(nodes) // all nodes rendered
} catch {
return false // one or more nodes awaiting render
}
})

// if autoRefresh is off on page load no graph will be rendered.
// we let the page refresh on initial load
// once nodeDimensions have rendered for the first time
// we prevent further refreshing by setting initialLoad to false
if (nodeDimensions) {
if (this.initialLoad) { this.initialLoad = false }
} else {
return

Check warning on line 644 in src/views/Graph.vue

View check run for this annotation

Codecov / codecov/patch

src/views/Graph.vue#L644

Added line #L644 was not covered by tests
}

// layout the graph
try {
await this.layout(nodes, edges, nodeDimensions)
Expand Down Expand Up @@ -634,9 +676,8 @@
// Will return when the callback returns something truthy.
// OR after the configured number of retries
for (let retry = 0; retry < retries; retry++) {
if (callback()) {
break
}
const ret = callback()
if (ret) return ret
await new Promise(requestAnimationFrame)
await this.$nextTick()
}
Expand Down Expand Up @@ -693,6 +734,13 @@
autoRefresh () {
// toggle the timer when autoRefresh is changed
this.updateTimer()
},
initialLoad () {
// when initialLoad changes from true to false
// do a final refresh
if (!this.autoRefresh) {
this.updateTimer()
}
}
}
}
Expand Down
61 changes: 61 additions & 0 deletions tests/e2e/specs/graph.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,31 @@ function checkGraphLayoutPerformed ($el, depth = 0) {
}
}

function addView (view) {
cy.get('[data-cy=add-view-btn]').click()
cy.get(`#toolbar-add-${view}-view`).click()
// wait for menu to close
.should('not.be.exist')
}

function checkRememberToolbarSettings (selector, stateBefore, stateAfter) {
cy
.get(selector)
.find('.v-btn')
.should(stateBefore, 'text-blue')
.click()
// Navigate away
cy.visit('/#/')
cy.get('.c-dashboard')
// Navigate back
cy.visit('/#/workspace/one')
waitForGraphLayout()
cy
.get(selector)
.find('.v-btn')
.should(stateAfter, 'text-blue')
}

describe('Graph View', () => {
it('should load', () => {
cy.visit('/#/graph/one')
Expand Down Expand Up @@ -69,4 +94,40 @@ describe('Graph View', () => {
.should('have.length', 10)
.should('be.visible')
})

it('loads graph when switching between workflows', () => {
cy.visit('/#/workspace/one')
addView('Graph')
waitForGraphLayout()
cy.visit('/#/workspace/other/multi/run2')
addView('Graph')
waitForGraphLayout()
cy
// there should be 2 graph nodes (all on-screen)
.get('.c-graph:first')
.find('.graph-node-container')
.should('be.visible')
.should('have.length', 2)
cy.visit('/#/workspace/one')
cy
// there should be 7 graph nodes (all on-screen)
.get('.c-graph:first')
.find('.graph-node-container')
.should('be.visible')
.should('have.length', 7)
})

it('remembers autorefresh setting when switching between workflows', () => {
cy.visit('/#/workspace/one')
addView('Graph')
waitForGraphLayout()
checkRememberToolbarSettings('[data-cy=control-autoRefresh]', 'have.class', 'not.have.class')
})

it('remembers transpose setting when switching between workflows', () => {
cy.visit('/#/workspace/one')
addView('Graph')
waitForGraphLayout()
checkRememberToolbarSettings('[data-cy=control-transpose]', 'not.have.class', 'have.class')
})
})
markgrahamdawson marked this conversation as resolved.
Show resolved Hide resolved