-
Notifications
You must be signed in to change notification settings - Fork 5
Add task status list with node focus navigation in pipeline run view #1605
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
base: master
Are you sure you want to change the base?
Conversation
This stack of pull requests is managed by Graphite. Learn more about stacking. |
a8d0d34 to
5f33bd2
Compare
2c94e0d to
0a1f283
Compare
26c3d2c to
8c37884
Compare
src/hooks/useNodeFocus.ts
Outdated
| const handleClick = () => { | ||
| const { focus: _, ...rest } = search; | ||
| navigate({ | ||
| to: pathname, | ||
| search: rest, | ||
| replace: true, | ||
| }); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical race condition: The global click handler fires during the capture phase before the task navigation click handlers. When a user clicks on a task in the status list:
- This handler fires first (capture phase) and removes the
focusparameter - Then the task item's click handler fires and sets a new
focusparameter - Both handlers call
navigate()withreplace: true, causing a navigation race
This will cause the focus highlighting to flicker or fail entirely when clicking on tasks from the status list.
Fix: Add a check to ignore clicks on task navigation elements:
const handleClick = (event: MouseEvent) => {
// Don't clear focus if clicking on task navigation elements
const target = event.target as HTMLElement;
if (target.closest('[data-task-navigation]')) {
return;
}
const { focus: _, ...rest } = search;
navigate({
to: pathname,
search: rest,
replace: true,
});
};And add data-task-navigation attribute to the task buttons in TaskStatusList.tsx.
| const handleClick = () => { | |
| const { focus: _, ...rest } = search; | |
| navigate({ | |
| to: pathname, | |
| search: rest, | |
| replace: true, | |
| }); | |
| }; | |
| const handleClick = (event: MouseEvent) => { | |
| // Don't clear focus if clicking on task navigation elements | |
| const target = event.target as HTMLElement; | |
| if (target.closest('[data-task-navigation]')) { | |
| return; | |
| } | |
| const { focus: _, ...rest } = search; | |
| navigate({ | |
| to: pathname, | |
| search: rest, | |
| replace: true, | |
| }); | |
| }; | |
Spotted by Graphite Agent
Is this helpful? React 👍 or 👎 to let us know.
4564ec1 to
84fe6c1
Compare
84fe6c1 to
9cace25
Compare
| if (!componentSpec || !state || !details || !backendUrl) { | ||
| return []; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NIT: due to enabled condition, this extra check may not be required, unless it narrows down the typing.
| }, | ||
| enabled: | ||
| !!componentSpec && !!state && !!details && !!backendUrl && configured, | ||
| staleTime: 30000, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NIT: use 30 * SECONDS for better readability
| "rounded-2xl border-gray-200 border-2 wrap-break-word p-0 drop-shadow-none gap-2", | ||
| selected ? "border-gray-500" : "hover:border-slate-200", | ||
| (highlighted || highlightedState) && "border-orange-500!", | ||
| (highlighted || highlightedState || isFocusedByUrl) && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this condition becomes pretty complex - it makes sense to extract it into a dedicated variable to improve readability.
| rootExecutionId?: string; | ||
| } | ||
|
|
||
| const TaskStatusBar = ({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not related to the PR, but it bothers me: we have TWO aliases for the same component: TaskStatusBar and just StatusBar. We should fix it future
| {showViewAll && ( | ||
| <CollapsibleTrigger asChild> | ||
| <InlineStack align="end"> | ||
| <Button | ||
| variant="link" | ||
| size="inline-xs" | ||
| className="text-muted-foreground hover:text-foreground" | ||
| > | ||
| <InlineStack gap="1" blockAlign="center"> | ||
| {isExpanded ? "Hide" : viewAllLabel} | ||
| <Icon | ||
| name="ChevronDown" | ||
| size="xs" | ||
| className={cn( | ||
| "transition-transform duration-200", | ||
| isExpanded && "rotate-180", | ||
| )} | ||
| /> | ||
| </InlineStack> | ||
| </Button> | ||
| </InlineStack> | ||
| </CollapsibleTrigger> | ||
| )} | ||
| </BlockStack> | ||
| {showViewAll && ( | ||
| <CollapsibleContent> | ||
| <BlockStack className="mt-1 max-h-64 overflow-y-auto"> | ||
| <TaskStatusList | ||
| tasks={taskStatuses} | ||
| rootExecutionId={rootExecutionId} | ||
| /> | ||
| </BlockStack> | ||
| </CollapsibleContent> | ||
| )} | ||
| </BlockStack> | ||
| </Collapsible> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this must be a separate component for simplified maintenance. StatusBar is used a lot across the app, and only in one place with taskStatuses (which changes behavior drastically). So my suggestion is to extract this into a separate component and use it next to the StatusBar in RunDetails.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After reviewing, I think it definitely need to be a separate component. Also helpers from collectAllTaskStatuses need to be ingested by that new component in form of lazy useSuspenseQuery. useTaskNavigation must be also co-located with the new component.
| return undefined; | ||
| }; | ||
|
|
||
| export const useNodeFocus = () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The purpose of the hook is to "remove selection" when clicked outside of the selected node (click-away-to-dismiss behavior), if I read it correctly.
I would advice to change the name of the hook to be reflect the purpose.
| const target = event.target; | ||
| if ( | ||
| target instanceof HTMLElement && | ||
| target.closest("[data-task-status-list]") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Magic string. Risk of divergence in future, if someone changes it in a different place.
| * Hook for navigating to a task from the status list. | ||
| * Updates the URL with `?focus=nodeId` to trigger node highlighting. | ||
| */ | ||
| export const useTaskNavigation = ({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hook is used in one place only. I suggest to move it closer to the usage, so it follows "co-location" principle.
| filter?: string; | ||
| }; | ||
|
|
||
| const validateHomeSearch = ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how is it related to the PR?
| * Recursively collects all LEAF tasks (non-subgraph tasks) with their execution statuses. | ||
| * This traverses into subgraphs to find the actual tasks. | ||
| */ | ||
| export const collectAllTaskStatuses = async ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this been used only in one place. I suggest to move this helper closer to the usage to satisfy our co-location principle.
| if (subgraphExecutionId && context.backendUrl) { | ||
| try { | ||
| [nestedDetails, nestedState] = await Promise.all([ | ||
| fetchExecutionDetails(subgraphExecutionId, context.backendUrl), | ||
| fetchExecutionState(subgraphExecutionId, context.backendUrl), | ||
| ]); | ||
| } catch { | ||
| // If fetching fails, continue without nested execution data | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's something we need to think about. We were fighting hard to reduce number of requests coming from the Pipeline Run page and managed to do that.
Now we introducing those queries back. I would suggest to rethink and try to find way to avoid this or at least make it "lazy" - component is "hidden" by default, so we can fetch data only when we "expand" it.
maxy-shpfy
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UI and purpose seems to be pretty nice! I liked it.
But
We were fighting hard to reduce number of requests coming from the Pipeline Run page and managed to do that.
Now we introducing those queries back. I would suggest to rethink and try to find way to avoid this or at least make it "lazy" - component is "hidden" by default, so we can fetch data only when we "expand" it.
Also "state" wont be re-fetched leading to "stale" data displayed in the expanded form.
Screen Recording 2026-01-15 at 10.46.29 AM.mov (uploaded via Graphite)


Description
Added a task status list feature that allows users to view and navigate to individual tasks in a pipeline run. The implementation includes:
Type of Change
Checklist
Test Instructions
status_click.mov (uploaded via Graphite)
Additional Comments
This feature improves pipeline run debugging by making it easier to find and navigate to specific tasks, especially in complex pipelines with many nested components.