Skip to content
Open
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ and this project adheres to

### Fixed

- `Cmd/Ctrl+Enter` now runs the workflow directly; `Cmd/Ctrl+Shift+Enter` opens
"run with custom input". When a retryable run is loaded, the primary action
switches to retry. [#4736](https://github.com/OpenFn/lightning/issues/4736)

- `mix lightning.install_runtime` no longer reports success when Rambo's binary
fails to start; both `Rambo.run/2` calls now raise with the underlying reason.
[#4735](https://github.com/OpenFn/lightning/pull/4735)
Expand Down
103 changes: 102 additions & 1 deletion assets/js/collaborative-editor/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { buildClassicalEditorUrl } from '../../utils/editorUrlConversion';
import * as dataclipApi from '../api/dataclips';
import { StoreContext } from '../contexts/StoreProvider';
import { channelRequest } from '../hooks/useChannel';
import { getCsrfToken } from '../lib/csrf';
import { useActiveRun } from '../hooks/useHistory';
import { useSession } from '../hooks/useSession';
import {
Expand Down Expand Up @@ -236,6 +237,12 @@ export function Header({
const [isSubmitting, setIsSubmitting] = useState(false);
const activeRun = useActiveRun();
const runIsProcessing = activeRun ? !isFinalState(activeRun.state) : false;
const followedRunId = params.run ?? null;
const isRetryable =
!!followedRunId &&
!!activeRun &&
isFinalState(activeRun.state) &&
!!activeRun.steps?.length;

// Check GitHub sync limit
const githubSyncLimit = limits.github_sync ?? {
Expand Down Expand Up @@ -296,6 +303,57 @@ export function Header({
updateSearchParams,
]);

const handleRetryClick = useCallback(async () => {
if (!followedRunId || !activeRun?.steps?.length || !projectId) return;

setIsSubmitting(true);
try {
await saveWorkflow({ silent: true });

const firstStep = activeRun.steps[0];
const retryUrl = `/projects/${projectId}/runs/${followedRunId}/retry`;
const csrfToken = getCsrfToken();
const response = await fetch(retryUrl, {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken || '',
},
body: JSON.stringify({ step_id: firstStep.id }),
});

if (!response.ok) {
const error = (await response.json()) as { error?: string };
throw new Error(error.error || 'Failed to retry run');
}

const result = (await response.json()) as { data: { run_id: string } };

notifications.success({
title: 'Retry started',
description: 'Saved latest changes and re-running with previous input',
});

if (getLimits) void getLimits('new_run');
updateSearchParams({ run: result.data.run_id });
} catch (error) {
notifications.alert({
title: 'Retry failed',
description: error instanceof Error ? error.message : 'Unknown error',
});
} finally {
setIsSubmitting(false);
}
}, [
followedRunId,
activeRun,
projectId,
saveWorkflow,
getLimits,
updateSearchParams,
]);

const handleRunWithCustomInputClick = useCallback(() => {
if (firstTriggerId) {
selectNode(firstTriggerId);
Expand Down Expand Up @@ -326,6 +384,46 @@ export function Header({
}
}, [provider, projectId, workflowId, isNewWorkflow]);

useKeyboardShortcut(
'Control+Enter, Meta+Enter',
() => {
if (isRetryable) {
void handleRetryClick();
} else {
void handleRunClick();
}
},
0,
{
enabled:
canRun &&
!isRunPanelOpen &&
!isIDEOpen &&
!isNewWorkflow &&
!!projectId &&
!!workflowId &&
(isRetryable || !!firstTriggerId),
}
);

useKeyboardShortcut(
'Control+Shift+Enter, Meta+Shift+Enter',
() => {
handleRunWithCustomInputClick();
},
0,
{
enabled:
canRun &&
!isRunPanelOpen &&
!isIDEOpen &&
!isNewWorkflow &&
!!projectId &&
!!workflowId &&
!!firstTriggerId,
}
);

useKeyboardShortcut(
'Control+s, Meta+s',
() => {
Expand Down Expand Up @@ -440,10 +538,13 @@ export function Header({
<div className="relative flex gap-2">
{projectId && workflowId && firstTriggerId && !isNewWorkflow && (
<NewRunButton
onClick={handleRunClick}
onClick={() => {
void (isRetryable ? handleRetryClick() : handleRunClick());
}}
onRunWithCustomInputClick={handleRunWithCustomInputClick}
disabled={!canRun || isRunPanelOpen || isIDEOpen}
isRunning={isSubmitting || runIsProcessing}
text={isRetryable ? 'Run (Retry)' : 'Run'}
/>
)}
<SaveButton
Expand Down
33 changes: 24 additions & 9 deletions assets/js/collaborative-editor/components/NewRunButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export function NewRunButton({
const icon = isRunning ? (
<span className="hero-arrow-path h-4 w-4 animate-spin" />
) : (
<span className="hero-play h-4 w-4" />
<span className="hero-play-solid h-4 w-4" />
);

const splitButtonClasses =
Expand Down Expand Up @@ -112,18 +112,33 @@ export function NewRunButton({
transition data-closed:scale-95 data-closed:transform
data-closed:opacity-0 data-enter:duration-200 data-enter:ease-out
data-leave:duration-75 data-leave:ease-in"
onKeyDown={e => {
if (e.key === 'Enter') {
e.preventDefault();
}
}}
>
<MenuItem>
<button
type="button"
onClick={onRunWithCustomInputClick}
className="flex items-center gap-2 w-full text-left px-4 py-2
{({ close }) => (
<Tooltip
content={<ShortcutKeys keys={['mod', 'shift', 'enter']} />}
side="bottom"
>
<button
type="button"
onClick={() => {
onRunWithCustomInputClick();
close();
}}
className="flex items-center gap-2 w-full text-left px-4 py-2
text-sm text-gray-700 data-focus:bg-gray-100
data-focus:outline-hidden"
>
<span className="hero-play h-4 w-4" />
Run with custom input
</button>
>
<span className="hero-play h-4 w-4" />
Run with custom input
</button>
</Tooltip>
)}
</MenuItem>
</MenuItems>
</Menu>
Expand Down
41 changes: 17 additions & 24 deletions assets/js/collaborative-editor/components/RunRetryButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -236,31 +236,24 @@ export function RunRetryButton({

{/* Chevron dropdown button - stays visible during processing for consistency */}
<div className="relative -ml-px">
<Tooltip
content={!chevronDisabled ? dropdownTooltip : null}
side="bottom"
<button
type="button"
onClick={() => !chevronDisabled && setIsDropdownOpen(!isDropdownOpen)}
disabled={chevronDisabled}
className={cn(
'rounded-md text-sm font-semibold shadow-xs px-1 py-2',
'h-full rounded-l-none',
'focus-visible:outline-2 focus-visible:outline-offset-2',
chevronDisabled
? [styles.submitting, 'cursor-not-allowed']
: [styles.chevronBase, styles.focus]
)}
aria-expanded={isDropdownOpen}
aria-haspopup="true"
>
<button
type="button"
onClick={() =>
!chevronDisabled && setIsDropdownOpen(!isDropdownOpen)
}
disabled={chevronDisabled}
className={cn(
'rounded-md text-sm font-semibold shadow-xs px-1 py-2',
'h-full rounded-l-none',
'focus-visible:outline-2 focus-visible:outline-offset-2',
chevronDisabled
? [styles.submitting, 'cursor-not-allowed']
: [styles.chevronBase, styles.focus]
)}
aria-expanded={isDropdownOpen}
aria-haspopup="true"
>
<span className="sr-only">Open options</span>
<span className="hero-chevron-down w-4 h-4"></span>
</button>
</Tooltip>
<span className="sr-only">Open options</span>
<span className="hero-chevron-down w-4 h-4"></span>
</button>

{/* Dropdown menu */}
{isDropdownOpen && (
Expand Down
27 changes: 0 additions & 27 deletions assets/js/collaborative-editor/components/WorkflowEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -431,33 +431,6 @@ export function WorkflowEditor({
collapseCreateWorkflowPanel();
};

useKeyboardShortcut(
'Control+Enter, Meta+Enter',
() => {
if (isRunPanelOpen) {
return;
}

if (currentNode.type === 'job' && currentNode.node) {
openRunPanel({ jobId: currentNode.node.id });
} else if (currentNode.type === 'trigger' && currentNode.node) {
openRunPanel({ triggerId: currentNode.node.id });
} else {
const firstTrigger = workflow.triggers[0];
if (firstTrigger?.id) {
openRunPanel({
triggerId: firstTrigger.id,
entryPoint: 'custom-input',
});
}
}
},
0,
{
enabled: !isIDEOpen && !isRunPanelOpen,
}
);

/**
* Keyboard shortcuts for new workflow creation panels.
*
Expand Down
Loading