Skip to content
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ and this project adheres to
[#4581](https://github.com/OpenFn/lightning/issues/4581)
[#4582](https://github.com/OpenFn/lightning/issues/4582)
[#4583](https://github.com/OpenFn/lightning/issues/4583)
- Remove the redirect to "History" when running a run from the canvas. (Stay
there to see the run.)
[#4198](https://github.com/OpenFn/lightning/issues/4198)

### Fixed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
useHistoryError,
useHistoryLoading,
useRunSteps,
useSelectedRunId,
} from '../../hooks/useHistory';
import {
useIsNewWorkflow,
Expand Down Expand Up @@ -57,9 +58,11 @@ export function CollaborativeWorkflowDiagram({
const historyCollapsed = useHistoryPanelCollapsed();
const { setHistoryPanelCollapsed } = useEditorPreferencesCommands();

// Read selected run ID from URL - single source of truth
// useURLState is reactive, so component re-renders when URL changes
const selectedRunId = params['run'] ?? null;
// Read selected run ID from URL, falling back to the history store's active run.
// The fallback prevents losing the run when LiveView push_patch strips
// client-only URL params.
const activeRunId = useSelectedRunId();
const selectedRunId = params['run'] ?? activeRunId;

const handleToggleHistory = useCallback(() => {
setHistoryPanelCollapsed(!historyCollapsed);
Expand All @@ -76,9 +79,25 @@ export function CollaborativeWorkflowDiagram({
{ enabled: !isNewWorkflow }
);

// Restore the URL run param if it was stripped (e.g. by LiveView push_patch)
// but the history store still has an active run.
// The ref ensures we only attempt one restore per activeRunId to avoid loops
// if LiveView repeatedly strips the param.
const runParam = params['run'] ?? null;
const restoredRunRef = useRef<string | null>(null);
useEffect(() => {
if (!runParam && activeRunId && restoredRunRef.current !== activeRunId) {
restoredRunRef.current = activeRunId;
updateSearchParams({ run: activeRunId });
}
if (runParam) {
restoredRunRef.current = null;
}
}, [runParam, activeRunId, updateSearchParams]);

// Follow the run to receive real-time step updates via run:${runId} channel
// This is essential for highlighting steps as they execute in real-time
useFollowRun(selectedRunId);
const { clearRun } = useFollowRun(selectedRunId);

// Use hook to get run steps with automatic subscription management
const currentRunSteps = useRunSteps(selectedRunId);
Expand Down Expand Up @@ -109,9 +128,12 @@ export function CollaborativeWorkflowDiagram({
);

// Clear URL parameter when deselecting run
// Also close the run viewer in the history store so the restore effect
// (which watches activeRunId) does not immediately re-add the URL param.
const handleDeselectRun = useCallback(() => {
clearRun();
updateSearchParams({ run: null });
}, [updateSearchParams]);
}, [clearRun, updateSearchParams]);

// Request history when panel is first expanded OR when there's a run ID selected
// Wait for channel to be connected before making request
Expand Down
15 changes: 8 additions & 7 deletions assets/js/collaborative-editor/hooks/useRunRetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export function useRunRetry({
// Retry state tracking via HistoryStore (WebSocket updates)
// Note: Connection management is handled by the parent component (FullScreenIDE or ManualRunPanel)
// This hook only reads the current run state from HistoryStore
const { params } = useURLState();
const { params, updateSearchParams } = useURLState();
const followedRunId = params.run ?? null; // 'run' param is run ID
const currentRun = useActiveRun(); // Real-time from WebSocket

Expand Down Expand Up @@ -293,9 +293,9 @@ export function useRunRetry({
onRunSubmitted(response.data.run_id, response.data.dataclip);
// Don't reset isSubmitting here - the effect will do it when WebSocket connects
} else {
// Fallback: navigate away if no callback (for standalone mode)
// Don't reset isSubmitting - the page is redirecting and resetting would cause a flash
window.location.href = `/projects/${projectId}/runs/${response.data.run_id}`;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the change - don't redirect away from the canvas!

// No callback - stay on the current page and track the run in the URL
updateSearchParams({ run: response.data.run_id });
setIsSubmitting(false);
}
} catch (error) {
logger.error('Failed to submit run:', error);
Expand Down Expand Up @@ -391,9 +391,10 @@ export function useRunRetry({
onRunSubmitted(result.data.run_id);
// Don't reset isSubmitting here - the effect will do it when WebSocket connects
} else {
// Fallback: navigate to new run (component will unmount)
// Don't reset isSubmitting - the page is redirecting and resetting would cause a flash
window.location.href = `/projects/${projectId}/runs/${result.data.run_id}`;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here - don't redirect!

// No callback - stay on the current page and track the run in the URL
updateSearchParams({ run: result.data.run_id });
setIsSubmitting(false);
isRetryingRef.current = false;
}
} catch (error) {
logger.error('Failed to retry run:', error);
Expand Down
2 changes: 2 additions & 0 deletions assets/js/workflow-diagram/components/RunIcons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { cn } from '../../utils/cn';

const STATE_ICONS = {
pending: 'hero-ellipsis-horizontal-circle-solid',
running: 'hero-arrow-path-solid animate-spin',
success: 'hero-check-circle-solid',
fail: 'hero-x-circle-solid',
crash: 'hero-x-circle-solid',
Expand All @@ -15,6 +16,7 @@ const STATE_ICONS = {

const STATE_COLORS = {
pending: 'text-gray-400',
running: 'text-blue-500',
success: 'text-green-500',
fail: 'text-red-500',
crash: 'text-orange-800',
Expand Down
12 changes: 9 additions & 3 deletions assets/js/workflow-diagram/nodes/Node.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,12 @@ const Node = ({
? !!runData || (!!data?.startInfo && isTriggerNode)
: true;

const runState = runData ? (runData.exit_reason ?? 'running') : null;

const { width, height, anchorx, strokeWidth, style } = nodeIconStyles(
selected,
hasErrors(errors),
runData?.exit_reason
runState
);

const nodeOpacity = data.dropTargetError ? 0.4 : 1;
Expand Down Expand Up @@ -173,8 +175,12 @@ const Node = ({
)}
{runData && !isTriggerNode ? (
<div className="absolute -left-2 -top-2 pointer-events-auto z-10">
{renderIcon(runData.exit_reason ?? 'pending', {
tooltip: runData?.error_type ?? 'Step completed successfully',
{renderIcon(runState ?? 'pending', {
tooltip:
runData?.error_type ??
(runState === 'running'
? 'Step is running'
: 'Step completed successfully'),
})}
</div>
) : null}
Expand Down
12 changes: 9 additions & 3 deletions assets/js/workflow-diagram/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,24 +166,30 @@ export const styleEdge = (edge: Flow.Edge) => {
const BG_GREEN_100 = '#dcfce7';
const BG_RED_100 = '#ffe2e2';
const BG_ORANGE_100 = '#ffedd4';
const BG_BLUE_100 = '#dbeafe';
const BORDER_GREEN_600 = '#00a63e';
const BORDER_RED_600 = '#e7000b';
const BORDER_ORANGE_600 = '#f54a00';
const BORDER_BLUE_500 = '#3b82f6';

export type NodeRunState = RunStep['exit_reason'] | 'running';

export const nodeIconStyles = (
selected?: boolean,
hasErrors?: boolean,
runReason: RunStep['exit_reason'] = null
runReason: NodeRunState = null
) => {
const getNodeBorderColor = (reasons: RunStep['exit_reason']) => {
const getNodeBorderColor = (reasons: NodeRunState) => {
if (reasons === 'success') return BORDER_GREEN_600;
else if (reasons === 'fail') return BORDER_RED_600;
else if (reasons === 'crash') return BORDER_ORANGE_600;
else if (reasons === 'running') return BORDER_BLUE_500;
};
const getNodeColor = (reasons: RunStep['exit_reason']) => {
const getNodeColor = (reasons: NodeRunState) => {
if (reasons === 'success') return BG_GREEN_100;
else if (reasons === 'fail') return BG_RED_100;
else if (reasons === 'crash') return BG_ORANGE_100;
else if (reasons === 'running') return BG_BLUE_100;
};
const size = 100;
const primaryColor = selected
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ function createWrapper(
requestRunSteps: vi.fn(() => Promise.resolve(null)),
subscribeToRunSteps: vi.fn(),
unsubscribeFromRunSteps: vi.fn(),
_viewRun: vi.fn(),
_closeRunViewer: vi.fn(),
} as any,
uiStore: {} as any,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ describe('CollaborativeWorkflowDiagram - Real-time Run Updates', () => {
}
),
_viewRun: vi.fn(),
_closeRunViewer: vi.fn(),
} as any,
uiStore: {} as any,
};
Expand Down
Loading
Loading