fix progress state on Vue nodes in subgraphs (#5842)

## Summary

Fixes two errors with subgraph progress states:

1. Nodes inside subgraphs were not having progress state shown
2. Subgraph nodes (outer representation) themselves did not have a
visible progress state

1 is fixed by using locator IDs instead of local node IDs.

2 is fixed by ensuring the subgraph title button does not wrap to a
newline and thus block the progress bar under the node header.

## Changes

- **What**: Updated `useNodeExecutionState` composable to use
`nodeLocatorId` for tracking execution state across subgraph boundaries
- **What**: Modified NodeHeader layout to fix subgraph enter button
positioning with proper flexbox gap

## Review Focus

Execution state tracking accuracy for nested subgraph nodes and
NodeHeader layout consistency across different node types.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5842-fix-progress-state-on-Vue-nodes-in-subgraphs-27c6d73d365081cb8335c8bb5dbd74f7)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Christian Byrne
2025-09-30 15:01:18 -07:00
committed by GitHub
parent ac72999e26
commit 3f291672d4
20 changed files with 28 additions and 29 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View File

@@ -211,7 +211,8 @@ const isSelected = computed(() => {
})
// Use execution state composable
const { executing, progress } = useNodeExecutionState(() => nodeData.id)
const nodeLocatorId = computed(() => getLocatorIdFromNodeData(nodeData))
const { executing, progress } = useNodeExecutionState(nodeLocatorId)
// Direct access to execution store for error state
const executionStore = useExecutionStore()

View File

@@ -9,7 +9,7 @@
:data-testid="`node-header-${nodeData?.id || ''}`"
@dblclick="handleDoubleClick"
>
<div class="flex items-center justify-between relative">
<div class="flex items-center justify-between gap-2.5 relative">
<!-- Collapse/Expand Button -->
<button
v-show="!readonly"
@@ -43,24 +43,22 @@
data-testid="node-pin-indicator"
/>
</div>
<div v-if="!readonly" class="flex items-center lod-toggle shrink-0">
<IconButton
v-if="isSubgraphNode"
size="sm"
type="transparent"
class="text-stone-200 dark-theme:text-slate-300"
data-testid="subgraph-enter-button"
title="Enter Subgraph"
@click.stop="handleEnterSubgraph"
@dblclick.stop
>
<i class="pi pi-external-link"></i>
</IconButton>
</div>
<LODFallback />
</div>
<!-- Title Buttons -->
<div v-if="!readonly" class="flex items-center lod-toggle">
<IconButton
v-if="isSubgraphNode"
size="sm"
type="transparent"
class="text-stone-200 dark-theme:text-slate-300"
data-testid="subgraph-enter-button"
title="Enter Subgraph"
@click.stop="handleEnterSubgraph"
@dblclick.stop
>
<i class="pi pi-external-link"></i>
</IconButton>
</div>
</div>
</template>

View File

@@ -9,27 +9,27 @@ import { useExecutionStore } from '@/stores/executionStore'
* Provides reactive access to execution state and progress for a specific node
* by injecting execution data from the parent GraphCanvas provider.
*
* @param nodeIdMaybe - The ID of the node to track execution state for
* @param nodeLocatorIdMaybe - Locator ID (root or subgraph scoped) of the node to track
* @returns Object containing reactive execution state and progress
*/
export const useNodeExecutionState = (
nodeIdMaybe: MaybeRefOrGetter<string>
nodeLocatorIdMaybe: MaybeRefOrGetter<string | undefined>
) => {
const nodeId = toValue(nodeIdMaybe)
const { uniqueExecutingNodeIdStrings, nodeProgressStates } =
storeToRefs(useExecutionStore())
const locatorId = computed(() => toValue(nodeLocatorIdMaybe) ?? '')
const { nodeLocationProgressStates } = storeToRefs(useExecutionStore())
const executing = computed(() => {
return uniqueExecutingNodeIdStrings.value.has(nodeId)
const progressState = computed(() => {
const id = locatorId.value
return id ? nodeLocationProgressStates.value[id] : undefined
})
const executing = computed(() => progressState.value?.state === 'running')
const progress = computed(() => {
const state = nodeProgressStates.value[nodeId]
return state?.max > 0 ? state.value / state.max : undefined
const state = progressState.value
return state && state.max > 0 ? state.value / state.max : undefined
})
const progressState = computed(() => nodeProgressStates.value[nodeId])
const progressPercentage = computed(() => {
const prog = progress.value
return prog !== undefined ? Math.round(prog * 100) : undefined