Skip to content

Commit

Permalink
Merge pull request #433 from PrefectHQ/selectable-state-changes
Browse files Browse the repository at this point in the history
make state bars selectable
  • Loading branch information
brandonreid authored Mar 4, 2024
2 parents 8ddcd58 + c05fcce commit c9a96f5
Show file tree
Hide file tree
Showing 12 changed files with 77 additions and 25 deletions.
20 changes: 12 additions & 8 deletions demo/data/graph-small.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
5 changes: 2 additions & 3 deletions src/components/RunGraph.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
</style>
1 change: 0 additions & 1 deletion src/factories/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Expand Down
42 changes: 38 additions & 4 deletions src/factories/flowRunState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 { isSelected, selectItem } from '@/objects/selection'
import { layout } from '@/objects/settings'

export type FlowRunStateFactory = Awaited<ReturnType<typeof flowRunStateFactory>>
Expand All @@ -31,15 +32,46 @@ export async function flowRunStateFactory(state: RunGraphStateEvent, options?: F
const area = await rectangleFactory()

let end: Date | null = options?.end ?? null
let hovered = false
let selected = false

element.addChild(area)
element.addChild(bar)

bar.eventMode = 'static'
bar.cursor = 'pointer'
bar.on('mouseover', () => {
hovered = true
render()
})
bar.on('mouseleave', () => {
hovered = false
render()
})
bar.on('click', () => {
const position = {
x: bar.position.x,
y: bar.position.y,
width: bar.width,
height: bar.height,
}

selectItem({ ...state, kind: 'state', position })
})

emitter.on('viewportMoved', () => render())
emitter.on('scaleUpdated', updated => {
scale = updated
render()
})
emitter.on('itemSelected', item => {
const isCurrentlySelected = isSelected({ kind: 'state', ...state })

if (isCurrentlySelected !== selected) {
selected = isCurrentlySelected
render()
}
})

function render(newOptions?: FlowRunStateFactoryOptions): void {
if (newOptions) {
Expand All @@ -61,7 +93,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
Expand All @@ -75,12 +107,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 = hovered || selected ? 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
}

Expand Down
2 changes: 1 addition & 1 deletion src/factories/flowRunStates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export function flowRunStatesFactory() {

async function createState(state: RunGraphStateEvent, currIndex: number): Promise<void> {
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)
Expand Down
3 changes: 2 additions & 1 deletion src/models/RunGraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export type RunGraphData = {
end_time: Date | null,
nodes: RunGraphNodes,
artifacts?: RunGraphArtifact[],
state_events?: RunGraphStateEvent[],
states?: RunGraphStateEvent[],
}

export type RunGraphNodes = Map<string, RunGraphNode>
Expand Down Expand Up @@ -88,6 +88,7 @@ export type RunGraphStyles = {
artifactIconSize?: number,
artifactIconColor?: ColorSource,
flowStateBarHeight?: number,
flowStateSelectedBarHeight?: number,
flowStateAreaAlpha?: number,
guideLineWidth?: number,
guideLineColor?: ColorSource,
Expand Down
3 changes: 2 additions & 1 deletion src/models/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './artifact'
export * from './RunGraph'
export * from './viewport'
export * from './selection'
export * from './selection'
export * from './states'
13 changes: 11 additions & 2 deletions src/models/selection.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { RunGraphNode, RunGraphNodeKind, runGraphNodeKinds } from '@/models'
import { RunGraphNode, RunGraphNodeKind, runGraphNodeKinds, RunGraphStateEvent } from '@/models'

export type GraphSelectionPosition = {
x: number,
Expand Down Expand Up @@ -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
| ArtifactsSelection
| StateSelection
3 changes: 2 additions & 1 deletion src/models/states.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export type StateType = typeof stateType[number]

export type RunGraphStateEvent = {
id: string,
occurred: Date,
timestamp: Date,
type: StateType,
name: string,
}
1 change: 1 addition & 0 deletions src/objects/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const defaults: Omit<RequiredGraphConfig, 'runId' | 'fetch'> = {
artifactIconSize: 16,
artifactIconColor: '#ffffff',
flowStateBarHeight: 6,
flowStateSelectedBarHeight: 8,
flowStateAreaAlpha: 0.1,
edgeColor: '#51525C',
guideLineWidth: 1,
Expand Down
4 changes: 2 additions & 2 deletions src/objects/flowRunStates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ export async function startFlowRunStates(): Promise<void> {
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)
}

Expand Down
5 changes: 4 additions & 1 deletion src/objects/selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import {
NodeSelection,
isArtifactSelection,
isArtifactsSelection,
isNodeSelection
isNodeSelection,
isStateSelection
} from '@/models/selection'
import { emitter } from '@/objects/events'
import { waitForViewport } from '@/objects/viewport'
Expand Down Expand Up @@ -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}`)
Expand Down

0 comments on commit c9a96f5

Please sign in to comment.