Skip to content
Draft
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
117 changes: 69 additions & 48 deletions src/components/shared/Execution/PipelineIO.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,15 @@ import { getOutputConnectedDetails } from "../../Editor/utils/getOutputConnected

const PipelineIO = ({
taskArguments,
section,
}: {
taskArguments?: TaskSpecOutput["arguments"] | null;
/**
* When set, renders only one side (without the wrapping ContentBlock) so the
* caller can supply its own section header (e.g. a collapsible section).
* When omitted, renders both Inputs/Arguments and Outputs blocks.
*/
section?: "inputs" | "outputs";
}) => {
const { setContent } = useContextPanel();
const { componentSpec, graphSpec } = useComponentSpec();
Expand Down Expand Up @@ -71,58 +78,72 @@ const PipelineIO = ({
},
];

const hasInputs = !!componentSpec.inputs && componentSpec.inputs.length > 0;
const hasOutputs =
!!componentSpec.outputs && componentSpec.outputs.length > 0;

const inputsContent = hasInputs ? (
<BlockStack>
{componentSpec.inputs?.map((input) => (
<IORow
key={input.name}
value={
getArgumentValue(taskArguments, input.name) ||
input.value ||
input.default ||
"—"
}
type={typeSpecToString(input?.type)}
spec={input}
actions={inputActions}
/>
))}
</BlockStack>
) : (
<Paragraph tone="subdued" size="xs">
No inputs
</Paragraph>
);

const outputsContent = hasOutputs ? (
<BlockStack>
{componentSpec.outputs?.map((output) => {
const connectedOutput = getOutputConnectedDetails(
graphSpec,
output.name,
);

return (
<IORow
key={output.name}
value={connectedOutput.outputName ?? "No value"}
type={typeSpecToString(connectedOutput.outputType)}
spec={output}
actions={outputActions}
/>
);
})}
</BlockStack>
) : (
<Paragraph tone="subdued" size="xs">
No outputs
</Paragraph>
);

if (section === "inputs") {
return inputsContent;
}

if (section === "outputs") {
return outputsContent;
}

return (
<BlockStack gap="4">
<ContentBlock title={taskArguments ? "Arguments" : "Inputs"}>
{componentSpec.inputs && componentSpec.inputs.length > 0 ? (
<BlockStack>
{componentSpec.inputs.map((input) => (
<IORow
key={input.name}
value={
getArgumentValue(taskArguments, input.name) ||
input.value ||
input.default ||
"—"
}
type={typeSpecToString(input?.type)}
spec={input}
actions={inputActions}
/>
))}
</BlockStack>
) : (
<Paragraph tone="subdued" size="xs">
No inputs
</Paragraph>
)}
</ContentBlock>
<ContentBlock title="Outputs">
{componentSpec.outputs && componentSpec.outputs.length > 0 ? (
<BlockStack>
{componentSpec.outputs.map((output) => {
const connectedOutput = getOutputConnectedDetails(
graphSpec,
output.name,
);

return (
<IORow
key={output.name}
value={connectedOutput.outputName ?? "No value"}
type={typeSpecToString(connectedOutput.outputType)}
spec={output}
actions={outputActions}
/>
);
})}
</BlockStack>
) : (
<Paragraph tone="subdued" size="xs">
No outputs
</Paragraph>
)}
{inputsContent}
</ContentBlock>
<ContentBlock title="Outputs">{outputsContent}</ContentBlock>
</BlockStack>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { useAnalytics } from "@/providers/AnalyticsProvider";
import { AnnotationsBlock } from "@/routes/v2/pages/Editor/components/AnnotationsBlock/AnnotationsBlock";
import { ValidationSummary } from "@/routes/v2/pages/Editor/components/ValidationSummary";
import { usePipelineActions } from "@/routes/v2/pages/Editor/store/actions/usePipelineActions";
import { PipelineDetailsCollapsibleSection } from "@/routes/v2/shared/components/PipelineDetailsCollapsibleSection";
import { useSpec } from "@/routes/v2/shared/providers/SpecContext";
import { useSharedStores } from "@/routes/v2/shared/store/SharedStoreContext";
import {
Expand All @@ -20,7 +21,6 @@ import {
import { InputsBlock } from "./components/InputsBlock";
import { MetadataBlock } from "./components/MetadataBlock";
import { OutputsBlock } from "./components/OutputsBlock";
import { PipelineDetailsCollapsibleSection } from "./components/PipelineDetailsCollapsibleSection";
import { PipelineDetailsHeader } from "./components/PipelineDetailsHeader";
import { PipelineDetailsTextField } from "./components/PipelineDetailsTextField";
import { TagsBlock } from "./components/TagsBlock";
Expand Down
183 changes: 183 additions & 0 deletions src/routes/v2/pages/RunView/components/RunActionsBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import TooltipButton from "@/components/shared/Buttons/TooltipButton";
import ConfirmationDialog from "@/components/shared/Dialogs/ConfirmationDialog";
import { StatusBar } from "@/components/shared/Status";
import TaskImplementation from "@/components/shared/TaskDetails/Implementation";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Icon } from "@/components/ui/icon";
import { BlockStack, InlineStack } from "@/components/ui/layout";
import { Text } from "@/components/ui/typography";
import { useCancelPipelineRun } from "@/routes/v2/pages/RunView/hooks/useCancelPipelineRun";
import { useClonePipelineRun } from "@/routes/v2/pages/RunView/hooks/useClonePipelineRun";
import { useExportPipelineYaml } from "@/routes/v2/pages/RunView/hooks/useExportPipelineYaml";
import { useInspectPipeline } from "@/routes/v2/pages/RunView/hooks/useInspectPipeline";
import { useRerunPipelineRun } from "@/routes/v2/pages/RunView/hooks/useRerunPipelineRun";
import { useRunViewActions } from "@/routes/v2/pages/RunView/hooks/useRunViewActions";
import { useYamlViewer } from "@/routes/v2/pages/RunView/hooks/useYamlViewer";
import type { ExecutionStatusStats } from "@/utils/executionStatus";
import { tracking } from "@/utils/tracking";

interface RunActionsBarProps {
executionStatusStats: ExecutionStatusStats | null | undefined;
statusLabel: string;
}

export function RunActionsBar({
executionStatusStats,
statusLabel,
}: RunActionsBarProps) {
const actions = useRunViewActions();
const componentSpec = actions.ready ? actions.componentSpec : undefined;
const runId = actions.ready ? actions.runId : undefined;
const pipelineName = actions.ready ? actions.pipelineName : undefined;

const { yamlViewerOpen, openYamlViewer, closeYamlViewer } = useYamlViewer();
const { clone, isCloning } = useClonePipelineRun(componentSpec, runId);
const { rerun, isRerunning } = useRerunPipelineRun(componentSpec);
const {
cancelDialogOpen,
isCancelling,
requestCancel,
confirmCancel,
dismissCancel,
} = useCancelPipelineRun(runId);
const { inspect } = useInspectPipeline(pipelineName);
const { exportYaml } = useExportPipelineYaml(componentSpec, pipelineName);

const status = (
<BlockStack gap="1" className="min-w-0 flex-1">
<Text size="xs" weight="semibold" className="truncate">
{statusLabel}
</Text>
<StatusBar executionStatusStats={executionStatusStats} />
</BlockStack>
);

if (!actions.ready) {
return (
<InlineStack
gap="2"
blockAlign="center"
wrap="nowrap"
className="w-full rounded-md border px-2 py-1"
>
{status}
</InlineStack>
);
}

const { canAccessEditorSpec, isRunCreator, isInProgress, isComplete } =
actions;
const showCancel = isInProgress && isRunCreator;
const showSeparator = showCancel || isComplete;

return (
<>
<InlineStack
gap="2"
blockAlign="center"
wrap="nowrap"
className="w-full rounded-md border px-2 py-1"
>
{status}

<InlineStack blockAlign="center" className="shrink-0">
<TooltipButton
variant="ghost"
size="min"
onClick={openYamlViewer}
tooltip="View YAML"
{...tracking("v2.run_view.menu_bar.view_yaml")}
>
<Icon name="FileCode" size="sm" />
</TooltipButton>

<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="min">
<Icon name="EllipsisVertical" size="sm" />
</Button>
</DropdownMenuTrigger>

<DropdownMenuContent align="end">
{canAccessEditorSpec && pipelineName && (
<DropdownMenuItem
onSelect={inspect}
{...tracking("v2.run_view.menu_bar.inspect_pipeline")}
>
<Icon name="ExternalLink" size="sm" />
Inspect Pipeline
</DropdownMenuItem>
)}

<DropdownMenuItem
onSelect={clone}
disabled={isCloning}
{...tracking("v2.run_view.menu_bar.clone_pipeline")}
>
<Icon name="CopyPlus" size="sm" />
Clone Pipeline
</DropdownMenuItem>

<DropdownMenuItem
onSelect={exportYaml}
{...tracking("v2.run_view.menu_bar.export_yaml")}
>
<Icon name="FileDown" size="sm" />
Export YAML
</DropdownMenuItem>

{showSeparator && <DropdownMenuSeparator />}

{showCancel && (
<DropdownMenuItem
onSelect={requestCancel}
disabled={isCancelling}
className="text-destructive focus:text-destructive"
{...tracking("v2.run_view.menu_bar.cancel_run")}
>
<Icon name="CircleX" size="sm" />
Cancel Run
</DropdownMenuItem>
)}

{isComplete && (
<DropdownMenuItem
onSelect={rerun}
disabled={isRerunning}
{...tracking("v2.run_view.menu_bar.rerun_pipeline")}
>
<Icon name="RefreshCcw" size="sm" />
Rerun Pipeline
</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>
</InlineStack>
</InlineStack>

<ConfirmationDialog
isOpen={cancelDialogOpen}
title="Cancel run"
description="The run will be scheduled for cancellation. This action cannot be undone."
onConfirm={confirmCancel}
onCancel={dismissCancel}
/>

{yamlViewerOpen && (
<TaskImplementation
componentSpec={componentSpec}
displayName={pipelineName ?? componentSpec?.name ?? "Pipeline"}
fullscreen
onClose={closeYamlViewer}
/>
)}
</>
);
}
Loading
Loading