Skip to content

Commit 07681e8

Browse files
committed
Cleanup PipelineDetails
1 parent 04c9d4b commit 07681e8

File tree

7 files changed

+306
-338
lines changed

7 files changed

+306
-338
lines changed
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { useEffect, useState } from "react";
2+
3+
import { PipelineValidationList } from "@/components/Editor/Context/PipelineValidationList";
4+
import { useValidationIssueNavigation } from "@/components/Editor/hooks/useValidationIssueNavigation";
5+
import { ContentBlock } from "@/components/shared/ContextPanel/Blocks/ContentBlock";
6+
import { ListBlock } from "@/components/shared/ContextPanel/Blocks/ListBlock";
7+
import { TextBlock } from "@/components/shared/ContextPanel/Blocks/TextBlock";
8+
import { TaskImplementation } from "@/components/shared/TaskDetails";
9+
import { Icon } from "@/components/ui/icon";
10+
import { BlockStack, InlineStack } from "@/components/ui/layout";
11+
import { Text } from "@/components/ui/typography";
12+
import { useComponentSpec } from "@/providers/ComponentSpecProvider";
13+
import { getComponentFileFromList } from "@/utils/componentStore";
14+
import { USER_PIPELINES_LIST_NAME } from "@/utils/constants";
15+
16+
import PipelineIO from "./PipelineIO";
17+
import RenamePipeline from "./RenamePipeline";
18+
19+
const PipelineDetails = () => {
20+
const {
21+
componentSpec,
22+
digest,
23+
isComponentTreeValid,
24+
globalValidationIssues,
25+
} = useComponentSpec();
26+
27+
const { handleIssueClick, groupedIssues } = useValidationIssueNavigation(
28+
globalValidationIssues,
29+
);
30+
31+
// State for file metadata
32+
const [fileMeta, setFileMeta] = useState<{
33+
creationTime?: Date;
34+
modificationTime?: Date;
35+
createdBy?: string;
36+
}>({});
37+
38+
// Fetch file metadata on mount or when componentSpec.name changes
39+
useEffect(() => {
40+
const fetchMeta = async () => {
41+
if (!componentSpec.name) return;
42+
const file = await getComponentFileFromList(
43+
USER_PIPELINES_LIST_NAME,
44+
componentSpec.name,
45+
);
46+
if (file) {
47+
setFileMeta({
48+
creationTime: file.creationTime,
49+
modificationTime: file.modificationTime,
50+
createdBy: file.componentRef.spec.metadata?.annotations?.author,
51+
});
52+
}
53+
};
54+
fetchMeta();
55+
}, [componentSpec.name]);
56+
57+
const metadata = [
58+
{
59+
name: "Created by",
60+
value: fileMeta.createdBy,
61+
},
62+
{
63+
name: "Created at",
64+
value: fileMeta.creationTime?.toLocaleString(),
65+
},
66+
{
67+
name: "Last updated",
68+
value: fileMeta.modificationTime?.toLocaleString(),
69+
},
70+
];
71+
72+
const annotations = Object.entries(
73+
componentSpec.metadata?.annotations || {},
74+
).map(([key, value]) => ({ name: key, value: String(value) }));
75+
76+
return (
77+
<BlockStack
78+
gap="4"
79+
className="h-full px-2"
80+
data-context-panel="pipeline-details"
81+
>
82+
{/* Header */}
83+
<InlineStack gap="2" wrap="nowrap" blockAlign="center">
84+
<Icon name="Network" size="xl" className="rotate-270 min-w-6 min-h-6" />
85+
<Text size="lg" weight="semibold" data-testid="pipeline-name">
86+
{componentSpec.name ?? "Unnamed Pipeline"}
87+
</Text>
88+
<RenamePipeline />
89+
</InlineStack>
90+
91+
<TaskImplementation
92+
displayName={componentSpec.name ?? "Pipeline"}
93+
componentSpec={componentSpec}
94+
showInlineContent={false}
95+
/>
96+
97+
{/* General Metadata */}
98+
<ListBlock items={metadata} marker="none" />
99+
100+
{/* Description */}
101+
{componentSpec.description && (
102+
<TextBlock title="Description" text={componentSpec.description} />
103+
)}
104+
105+
{/* Component Digest */}
106+
{digest && <TextBlock title="Digest" text={digest} allowCopy />}
107+
108+
{/* Annotations */}
109+
{annotations.length > 0 && (
110+
<ListBlock title="Annotations" items={annotations} marker="none" />
111+
)}
112+
113+
{/* Artifacts (Inputs & Outputs) */}
114+
<PipelineIO />
115+
116+
{/* Validations */}
117+
<ContentBlock title="Validations">
118+
<PipelineValidationList
119+
isComponentTreeValid={isComponentTreeValid}
120+
groupedIssues={groupedIssues}
121+
totalIssueCount={globalValidationIssues.length}
122+
onIssueSelect={handleIssueClick}
123+
/>
124+
</ContentBlock>
125+
</BlockStack>
126+
);
127+
};
128+
129+
export default PipelineDetails;
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import { type ReactNode } from "react";
2+
3+
import { ContentBlock } from "@/components/shared/ContextPanel/Blocks/ContentBlock";
4+
import { ListBlock } from "@/components/shared/ContextPanel/Blocks/ListBlock";
5+
import { typeSpecToString } from "@/components/shared/ReactFlow/FlowCanvas/TaskNode/ArgumentsEditor/utils";
6+
import { Button } from "@/components/ui/button";
7+
import { Icon } from "@/components/ui/icon";
8+
import { BlockStack, InlineStack } from "@/components/ui/layout";
9+
import { Paragraph } from "@/components/ui/typography";
10+
import useToastNotification from "@/hooks/useToastNotification";
11+
import { useComponentSpec } from "@/providers/ComponentSpecProvider";
12+
import { useContextPanel } from "@/providers/ContextPanelProvider";
13+
import { type InputSpec, type OutputSpec } from "@/utils/componentSpec";
14+
15+
import { InputValueEditor } from "../IOEditor/InputValueEditor";
16+
import { OutputNameEditor } from "../IOEditor/OutputNameEditor";
17+
import { getOutputConnectedDetails } from "../utils/getOutputConnectedDetails";
18+
19+
const PipelineIO = () => {
20+
const { setContent } = useContextPanel();
21+
const { componentSpec, graphSpec } = useComponentSpec();
22+
23+
const notify = useToastNotification();
24+
25+
const handleInputEdit = (input: InputSpec) => {
26+
setContent(<InputValueEditor key={input.name} input={input} />);
27+
};
28+
29+
const handleOutputEdit = (output: OutputSpec) => {
30+
const outputConnectedDetails = getOutputConnectedDetails(
31+
graphSpec,
32+
output.name,
33+
);
34+
setContent(
35+
<OutputNameEditor
36+
connectedDetails={outputConnectedDetails}
37+
key={output.name}
38+
output={output}
39+
/>,
40+
);
41+
};
42+
43+
const handleInputCopy = (input: InputSpec) => {
44+
const value = input.value ?? input.default;
45+
46+
if (!value) {
47+
notify("Copy failed: Input has no value", "error");
48+
return;
49+
}
50+
51+
void navigator.clipboard.writeText(value);
52+
notify("Input value copied to clipboard", "success");
53+
};
54+
55+
return (
56+
<BlockStack gap="4">
57+
<ContentBlock title="Inputs">
58+
{componentSpec.inputs && componentSpec.inputs.length > 0 ? (
59+
<BlockStack>
60+
{componentSpec.inputs.map((input) => (
61+
// todo: rework actionblock and linkblock to be more generic
62+
// todo: split into smaller PRs - e.g. add generic blocks, then refactor task details to use them, then refactor pipeline details to use them, then refactor executiond etails etc
63+
<IORow
64+
key={input.name}
65+
name={input.name}
66+
value={input.value || input.default || "No value"}
67+
type={typeSpecToString(input?.type)}
68+
actions={[
69+
{
70+
label: "Copy",
71+
icon: <Icon name="Copy" size="sm" />,
72+
onClick: () => handleInputCopy(input),
73+
},
74+
{
75+
label: "Edit",
76+
onClick: () => handleInputEdit(input),
77+
},
78+
]}
79+
/>
80+
))}
81+
</BlockStack>
82+
) : (
83+
<Paragraph tone="subdued" size="xs">
84+
No inputs
85+
</Paragraph>
86+
)}
87+
</ContentBlock>
88+
<ContentBlock title="Outputs">
89+
{componentSpec.outputs && componentSpec.outputs.length > 0 ? (
90+
<BlockStack>
91+
{componentSpec.outputs.map((output) => {
92+
const connectedOutput = getOutputConnectedDetails(
93+
graphSpec,
94+
output.name,
95+
);
96+
97+
return (
98+
<IORow
99+
key={output.name}
100+
name={output.name}
101+
value={connectedOutput.outputName ?? "No value"}
102+
type={typeSpecToString(connectedOutput.outputType)}
103+
actions={[
104+
{
105+
label: "Edit",
106+
onClick: () => handleOutputEdit(output),
107+
},
108+
]}
109+
/>
110+
);
111+
})}
112+
</BlockStack>
113+
) : (
114+
<Paragraph tone="subdued" size="xs">
115+
No outputs
116+
</Paragraph>
117+
)}
118+
</ContentBlock>
119+
</BlockStack>
120+
);
121+
};
122+
123+
export default PipelineIO;
124+
125+
interface IORowProps {
126+
name: string;
127+
value: string;
128+
type: string;
129+
actions?: { label: string; icon?: ReactNode; onClick: () => void }[];
130+
}
131+
132+
function IORow({ name, value, type, actions }: IORowProps) {
133+
return (
134+
<InlineStack
135+
gap="1"
136+
align="space-between"
137+
blockAlign="center"
138+
className="even:bg-white odd:bg-gray-100 px-2 py-0 rounded-xs w-full"
139+
>
140+
<ListBlock
141+
items={[{ name, value }]}
142+
marker="none"
143+
className="w-2/5 max-w-[200px]"
144+
/>
145+
<ListBlock
146+
items={[
147+
{
148+
name: "Type",
149+
value: type,
150+
},
151+
]}
152+
marker="none"
153+
className="w-fit max-w-[200px]"
154+
/>
155+
156+
<InlineStack className="min-w-24" align="end" blockAlign="center">
157+
{actions?.map((action) => (
158+
<Button
159+
type="button"
160+
variant="ghost"
161+
size="sm"
162+
onClick={action.onClick}
163+
className="text-xs text-muted-foreground hover:text-foreground hover:bg-transparent"
164+
key={action.label}
165+
>
166+
{action.icon ?? action.label}
167+
</Button>
168+
))}
169+
</InlineStack>
170+
</InlineStack>
171+
);
172+
}

src/components/Editor/components/PipelineValidationList/PipelineValidationList.tsx renamed to src/components/Editor/Context/PipelineValidationList.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { cn } from "@/lib/utils";
2020
import { pluralize } from "@/utils/string";
2121
import type { ComponentValidationIssue } from "@/utils/validations";
2222

23-
import type { ValidationIssueGroup } from "../../hooks/useValidationIssueNavigation";
23+
import type { ValidationIssueGroup } from "../hooks/useValidationIssueNavigation";
2424

2525
interface PipelineValidationListProps {
2626
isComponentTreeValid: boolean;
@@ -75,13 +75,13 @@ export const PipelineValidationList = ({
7575
title={`${totalIssueCount} ${pluralize(totalIssueCount, "issue")} detected`}
7676
>
7777
<Paragraph size="sm" className="mb-4">
78-
{" "}
7978
Select an item to jump to its location in the pipeline.
8079
</Paragraph>
8180

8281
<BlockStack gap="2">
8382
{groupedIssues.map((group) => {
8483
const isOpen = openGroups.has(group.pathKey);
84+
8585
return (
8686
<Collapsible
8787
key={group.pathKey}

src/components/Editor/RenamePipeline.tsx renamed to src/components/Editor/Context/RenamePipeline.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
import { useLocation, useNavigate } from "@tanstack/react-router";
22
import { Edit3 } from "lucide-react";
33

4+
import { PipelineNameDialog } from "@/components/shared/Dialogs";
45
import { Button } from "@/components/ui/button";
56
import useToastNotification from "@/hooks/useToastNotification";
67
import { useComponentSpec } from "@/providers/ComponentSpecProvider";
78
import { APP_ROUTES } from "@/routes/router";
89
import { renameComponentFileInList } from "@/utils/componentStore";
910
import { USER_PIPELINES_LIST_NAME } from "@/utils/constants";
1011

11-
import { PipelineNameDialog } from "../shared/Dialogs";
12-
1312
const RenamePipeline = () => {
1413
const { componentSpec, saveComponentSpec } = useComponentSpec();
1514
const notify = useToastNotification();

0 commit comments

Comments
 (0)