From 7285da7c5d1e7ff4b735f3d291cd04fceb79610a Mon Sep 17 00:00:00 2001 From: Brandon Reid Date: Mon, 4 Mar 2024 15:54:43 -0400 Subject: [PATCH] make state bars selectable --- demo/data/graph-small.json | 20 ++++++++++++-------- src/components/RunGraph.vue | 5 ++--- src/factories/data.ts | 1 - src/factories/flowRunState.ts | 34 ++++++++++++++++++++++++++++++---- src/factories/flowRunStates.ts | 2 +- src/models/RunGraph.ts | 3 ++- src/models/index.ts | 3 ++- src/models/selection.ts | 13 +++++++++++-- src/models/states.ts | 3 ++- src/objects/config.ts | 1 + src/objects/flowRunStates.ts | 4 ++-- src/objects/selection.ts | 5 ++++- 12 files changed, 69 insertions(+), 25 deletions(-) diff --git a/demo/data/graph-small.json b/demo/data/graph-small.json index 70ab8ba0..db7c7b50 100644 --- a/demo/data/graph-small.json +++ b/demo/data/graph-small.json @@ -18,20 +18,24 @@ "state_events": [ { "id": "9bb2d2fb-6534-4810-a8d0-scheduled", - "occurred": "2023-07-27T15:50:39.425118+00:00", - "type": "SCHEDULED" + "timestamp": "2023-07-27T15:50:39.425118+00:00", + "type": "SCHEDULED", + "name": "Scheduled" }, { "id": "eab2f3de-91f2-4b29-b9de-pending", - "occurred": "2023-09-27T15:50:38.000000+00:00", - "type": "PENDING" + "timestamp": "2023-09-27T15:50:38.000000+00:00", + "type": "PENDING", + "name": "Pending" }, { "id": "003d4c00-c30a-41c9-bb49-running", - "occurred": "2023-09-27T15:50:39.425118+00:00", - "type": "RUNNING" + "timestamp": "2023-09-27T15:50:39.425118+00:00", + "type": "RUNNING", + "name": "Running" }, { "id": "56912bb1-e172-4ea8-9be7-completed", - "occurred": "2023-09-27T15:51:08.999871+00:00", - "type": "COMPLETED" + "timestamp": "2023-09-27T15:51:08.999871+00:00", + "type": "COMPLETED", + "name": "Completed" } ], "artifacts": [ diff --git a/src/components/RunGraph.vue b/src/components/RunGraph.vue index 9bd1cee6..9632bab3 100644 --- a/src/components/RunGraph.vue +++ b/src/components/RunGraph.vue @@ -128,8 +128,7 @@ .run-graph__actions { position: absolute; - bottom: 0; - right: 0; - padding: theme('spacing.2'); + bottom: theme('spacing.2'); + right: theme('spacing.2'); } \ No newline at end of file diff --git a/src/factories/data.ts b/src/factories/data.ts index 599d942a..320236f4 100644 --- a/src/factories/data.ts +++ b/src/factories/data.ts @@ -20,7 +20,6 @@ export async function dataFactory(runId: string, callback: DataCallback) { console.error(error) } - if (data && !data.end_time) { interval = setTimeout(() => start(), getIntervalForDataSize(data)) } diff --git a/src/factories/flowRunState.ts b/src/factories/flowRunState.ts index c13b1093..410068cc 100644 --- a/src/factories/flowRunState.ts +++ b/src/factories/flowRunState.ts @@ -5,6 +5,7 @@ import { waitForApplication, waitForViewport } from '@/objects' import { waitForConfig } from '@/objects/config' import { emitter } from '@/objects/events' import { waitForScale } from '@/objects/scale' +import { selectItem } from '@/objects/selection' import { layout } from '@/objects/settings' export type FlowRunStateFactory = Awaited> @@ -31,6 +32,7 @@ export async function flowRunStateFactory(state: RunGraphStateEvent, options?: F const area = await rectangleFactory() let end: Date | null = options?.end ?? null + let isHovered = false element.addChild(area) element.addChild(bar) @@ -41,6 +43,28 @@ export async function flowRunStateFactory(state: RunGraphStateEvent, options?: F render() }) + bar.eventMode = 'static' + bar.cursor = 'pointer' + bar.on('mouseover', () => { + isHovered = true + render() + }) + bar.on('mouseleave', () => { + isHovered = false + render() + }) + bar.on('click', clickEvent => { + clickEvent.stopPropagation() + const position = { + x: bar.position.x, + y: bar.position.y, + width: bar.width, + height: bar.height, + } + + selectItem({ ...state, kind: 'state', position }) + }) + function render(newOptions?: FlowRunStateFactoryOptions): void { if (newOptions) { const { end: newEnd } = newOptions @@ -61,7 +85,7 @@ export async function flowRunStateFactory(state: RunGraphStateEvent, options?: F function getRenderStyles(): StateRectangleRenderProps { const { background = '#fff' } = config.styles.state(state) - const x = Math.max(scale(state.occurred) * viewport.scale._x + viewport.worldTransform.tx, 0) + const x = Math.max(scale(state.timestamp) * viewport.scale._x + viewport.worldTransform.tx, 0) const width = end ? scale(end) * viewport.scale._x + viewport.worldTransform.tx - x @@ -75,12 +99,14 @@ export async function flowRunStateFactory(state: RunGraphStateEvent, options?: F } function renderBar({ x, width, background }: StateRectangleRenderProps): void { - const { flowStateBarHeight } = config.styles + const { flowStateBarHeight, flowStateSelectedBarHeight } = config.styles + + const height = isHovered ? flowStateSelectedBarHeight : flowStateBarHeight bar.x = x - bar.y = application.screen.height - flowStateBarHeight + bar.y = application.screen.height - height bar.width = width - bar.height = flowStateBarHeight + bar.height = height bar.tint = background } diff --git a/src/factories/flowRunStates.ts b/src/factories/flowRunStates.ts index 0cfbbb7d..86878183 100644 --- a/src/factories/flowRunStates.ts +++ b/src/factories/flowRunStates.ts @@ -32,7 +32,7 @@ export function flowRunStatesFactory() { async function createState(state: RunGraphStateEvent, currIndex: number): Promise { const nextState = internalData && internalData.length >= currIndex + 1 && internalData[currIndex + 1] - const options = nextState ? { end: nextState.occurred } : undefined + const options = nextState ? { end: nextState.timestamp } : undefined if (states.has(state.id)) { return states.get(state.id)!.render(options) diff --git a/src/models/RunGraph.ts b/src/models/RunGraph.ts index 93e49f7d..e14a725d 100644 --- a/src/models/RunGraph.ts +++ b/src/models/RunGraph.ts @@ -17,7 +17,7 @@ export type RunGraphData = { end_time: Date | null, nodes: RunGraphNodes, artifacts?: RunGraphArtifact[], - state_events?: RunGraphStateEvent[], + states?: RunGraphStateEvent[], } export type RunGraphNodes = Map @@ -88,6 +88,7 @@ export type RunGraphStyles = { artifactIconSize?: number, artifactIconColor?: ColorSource, flowStateBarHeight?: number, + flowStateSelectedBarHeight?: number, flowStateAreaAlpha?: number, guideLineWidth?: number, guideLineColor?: ColorSource, diff --git a/src/models/index.ts b/src/models/index.ts index 65e8f155..04660d4d 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -1,4 +1,5 @@ export * from './artifact' export * from './RunGraph' export * from './viewport' -export * from './selection' \ No newline at end of file +export * from './selection' +export * from './states' \ No newline at end of file diff --git a/src/models/selection.ts b/src/models/selection.ts index dbdbeed7..c7b00569 100644 --- a/src/models/selection.ts +++ b/src/models/selection.ts @@ -1,4 +1,4 @@ -import { RunGraphNode, RunGraphNodeKind, runGraphNodeKinds } from '@/models' +import { RunGraphNode, RunGraphNodeKind, runGraphNodeKinds, RunGraphStateEvent } from '@/models' export type GraphSelectionPosition = { x: number, @@ -32,7 +32,16 @@ export function isArtifactsSelection(selection: GraphItemSelection): selection i return selection.kind === 'artifacts' } +export interface StateSelection extends RunGraphStateEvent { + kind: 'state', + position?: GraphSelectionPosition, +} +export function isStateSelection(selection: GraphItemSelection): selection is StateSelection { + return selection.kind === 'state' +} + export type GraphItemSelection = | NodeSelection | ArtifactSelection - | ArtifactsSelection \ No newline at end of file + | ArtifactsSelection + | StateSelection \ No newline at end of file diff --git a/src/models/states.ts b/src/models/states.ts index 50a4e90b..ac79d85f 100644 --- a/src/models/states.ts +++ b/src/models/states.ts @@ -14,6 +14,7 @@ export type StateType = typeof stateType[number] export type RunGraphStateEvent = { id: string, - occurred: Date, + timestamp: Date, type: StateType, + name: string, } \ No newline at end of file diff --git a/src/objects/config.ts b/src/objects/config.ts index e26c827c..136c02aa 100644 --- a/src/objects/config.ts +++ b/src/objects/config.ts @@ -42,6 +42,7 @@ const defaults: Omit = { artifactIconSize: 16, artifactIconColor: '#ffffff', flowStateBarHeight: 6, + flowStateSelectedBarHeight: 8, flowStateAreaAlpha: 0.1, edgeColor: '#51525C', guideLineWidth: 1, diff --git a/src/objects/flowRunStates.ts b/src/objects/flowRunStates.ts index 490f64c3..d15a1d54 100644 --- a/src/objects/flowRunStates.ts +++ b/src/objects/flowRunStates.ts @@ -12,10 +12,10 @@ export async function startFlowRunStates(): Promise { application.stage.addChild(element) function render(newData?: RunGraphData): void { - renderStates(newData?.state_events) + renderStates(newData?.states) } - if (data.state_events) { + if (data.states) { render(data) } diff --git a/src/objects/selection.ts b/src/objects/selection.ts index 77e742bd..bd9f1959 100644 --- a/src/objects/selection.ts +++ b/src/objects/selection.ts @@ -4,7 +4,8 @@ import { NodeSelection, isArtifactSelection, isArtifactsSelection, - isNodeSelection + isNodeSelection, + isStateSelection } from '@/models/selection' import { emitter } from '@/objects/events' import { waitForViewport } from '@/objects/viewport' @@ -73,6 +74,8 @@ export function isSelected(item: GraphItemSelection): boolean { return isArtifactsSelection(selected) && selected.ids.length === item.ids.length && selected.ids.every(id => item.ids.includes(id)) + case 'state': + return isStateSelection(selected) && selected.id === item.id default: const exhaustive: never = kind throw new Error(`switch does not have case for value: ${exhaustive}`)