Skip to content

Commit 8d60661

Browse files
committed
Cleanup PipelineDetails
1 parent b61217a commit 8d60661

File tree

12 files changed

+393
-368
lines changed

12 files changed

+393
-368
lines changed
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
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 { CopyText } from "@/components/shared/CopyText/CopyText";
9+
import { TaskImplementation } from "@/components/shared/TaskDetails";
10+
import { BlockStack, InlineStack } from "@/components/ui/layout";
11+
import { useComponentSpec } from "@/providers/ComponentSpecProvider";
12+
import { getComponentFileFromList } from "@/utils/componentStore";
13+
import { USER_PIPELINES_LIST_NAME } from "@/utils/constants";
14+
15+
import PipelineIO from "./PipelineIO";
16+
import RenamePipeline from "./RenamePipeline";
17+
18+
const PipelineDetails = () => {
19+
const {
20+
componentSpec,
21+
digest,
22+
isComponentTreeValid,
23+
globalValidationIssues,
24+
} = useComponentSpec();
25+
26+
const { handleIssueClick, groupedIssues } = useValidationIssueNavigation(
27+
globalValidationIssues,
28+
);
29+
30+
// State for file metadata
31+
const [fileMeta, setFileMeta] = useState<{
32+
creationTime?: Date;
33+
modificationTime?: Date;
34+
createdBy?: string;
35+
}>({});
36+
37+
// Fetch file metadata on mount or when componentSpec.name changes
38+
useEffect(() => {
39+
const fetchMeta = async () => {
40+
if (!componentSpec.name) return;
41+
const file = await getComponentFileFromList(
42+
USER_PIPELINES_LIST_NAME,
43+
componentSpec.name,
44+
);
45+
if (file) {
46+
setFileMeta({
47+
creationTime: file.creationTime,
48+
modificationTime: file.modificationTime,
49+
createdBy: file.componentRef.spec.metadata?.annotations?.author,
50+
});
51+
}
52+
};
53+
fetchMeta();
54+
}, [componentSpec.name]);
55+
56+
const metadata = [
57+
{
58+
label: "Created by",
59+
value: fileMeta.createdBy,
60+
},
61+
{
62+
label: "Created at",
63+
value: fileMeta.creationTime?.toLocaleString(),
64+
},
65+
{
66+
label: "Last updated",
67+
value: fileMeta.modificationTime?.toLocaleString(),
68+
},
69+
];
70+
71+
const annotations = Object.entries(
72+
componentSpec.metadata?.annotations || {},
73+
).map(([key, value]) => ({ label: key, value: String(value) }));
74+
75+
return (
76+
<BlockStack
77+
gap="4"
78+
className="h-full px-2"
79+
data-context-panel="pipeline-details"
80+
>
81+
{/* Header */}
82+
<CopyText className="text-lg font-semibold">
83+
{componentSpec.name ?? "Unnamed Pipeline"}
84+
</CopyText>
85+
86+
<InlineStack gap="2">
87+
<RenamePipeline />
88+
<TaskImplementation
89+
displayName={componentSpec.name ?? "Pipeline"}
90+
componentSpec={componentSpec}
91+
showInlineContent={false}
92+
/>
93+
</InlineStack>
94+
95+
{/* General Metadata */}
96+
<ListBlock items={metadata} marker="none" />
97+
98+
{/* Description */}
99+
{componentSpec.description && (
100+
<TextBlock title="Description" text={componentSpec.description} />
101+
)}
102+
103+
{/* Component Digest */}
104+
{digest && (
105+
<TextBlock
106+
title="Digest"
107+
text={digest}
108+
copyable
109+
className="bg-secondary p-2 rounded-md border"
110+
mono
111+
/>
112+
)}
113+
114+
{/* Annotations */}
115+
{annotations.length > 0 && (
116+
<ListBlock title="Annotations" items={annotations} marker="none" />
117+
)}
118+
119+
{/* Artifacts (Inputs & Outputs) */}
120+
<PipelineIO />
121+
122+
{/* Validations */}
123+
<ContentBlock title="Validations">
124+
<PipelineValidationList
125+
isComponentTreeValid={isComponentTreeValid}
126+
groupedIssues={groupedIssues}
127+
totalIssueCount={globalValidationIssues.length}
128+
onIssueSelect={handleIssueClick}
129+
/>
130+
</ContentBlock>
131+
</BlockStack>
132+
);
133+
};
134+
135+
export default PipelineDetails;
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { type ReactNode } from "react";
2+
3+
import { ContentBlock } from "@/components/shared/ContextPanel/Blocks/ContentBlock";
4+
import { KeyValuePair } from "@/components/shared/ContextPanel/Blocks/KeyValuePair";
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 { useComponentSpec } from "@/providers/ComponentSpecProvider";
11+
import { useContextPanel } from "@/providers/ContextPanelProvider";
12+
import { type InputSpec, type OutputSpec } from "@/utils/componentSpec";
13+
14+
import { InputValueEditor } from "../IOEditor/InputValueEditor";
15+
import { OutputNameEditor } from "../IOEditor/OutputNameEditor";
16+
import { getOutputConnectedDetails } from "../utils/getOutputConnectedDetails";
17+
18+
const PipelineIO = () => {
19+
const { setContent } = useContextPanel();
20+
const { componentSpec, graphSpec } = useComponentSpec();
21+
22+
const handleInputEdit = (input: InputSpec) => {
23+
setContent(<InputValueEditor key={input.name} input={input} />);
24+
};
25+
26+
const handleOutputEdit = (output: OutputSpec) => {
27+
const outputConnectedDetails = getOutputConnectedDetails(
28+
graphSpec,
29+
output.name,
30+
);
31+
setContent(
32+
<OutputNameEditor
33+
connectedDetails={outputConnectedDetails}
34+
key={output.name}
35+
output={output}
36+
/>,
37+
);
38+
};
39+
40+
return (
41+
<BlockStack gap="4">
42+
<ContentBlock title="Inputs">
43+
{componentSpec.inputs && componentSpec.inputs.length > 0 ? (
44+
<BlockStack>
45+
{componentSpec.inputs.map((input) => (
46+
<IORow
47+
key={input.name}
48+
name={input.name}
49+
value={input.value || input.default || "No value"}
50+
type={typeSpecToString(input?.type)}
51+
actions={[
52+
{
53+
label: "Edit",
54+
icon: <Icon name="Pencil" size="sm" />,
55+
onClick: () => handleInputEdit(input),
56+
},
57+
]}
58+
/>
59+
))}
60+
</BlockStack>
61+
) : (
62+
<Paragraph tone="subdued" size="xs">
63+
No inputs
64+
</Paragraph>
65+
)}
66+
</ContentBlock>
67+
<ContentBlock title="Outputs">
68+
{componentSpec.outputs && componentSpec.outputs.length > 0 ? (
69+
<BlockStack>
70+
{componentSpec.outputs.map((output) => {
71+
const connectedOutput = getOutputConnectedDetails(
72+
graphSpec,
73+
output.name,
74+
);
75+
76+
return (
77+
<IORow
78+
key={output.name}
79+
name={output.name}
80+
value={connectedOutput.outputName ?? "No value"}
81+
type={typeSpecToString(connectedOutput.outputType)}
82+
actions={[
83+
{
84+
label: "Edit",
85+
onClick: () => handleOutputEdit(output),
86+
},
87+
]}
88+
/>
89+
);
90+
})}
91+
</BlockStack>
92+
) : (
93+
<Paragraph tone="subdued" size="xs">
94+
No outputs
95+
</Paragraph>
96+
)}
97+
</ContentBlock>
98+
</BlockStack>
99+
);
100+
};
101+
102+
export default PipelineIO;
103+
104+
interface IORowProps {
105+
name: string;
106+
value: string;
107+
type: string;
108+
actions?: { label: string; icon?: ReactNode; onClick: () => void }[];
109+
}
110+
111+
function IORow({ name, value, type, actions }: IORowProps) {
112+
return (
113+
<InlineStack
114+
gap="1"
115+
align="space-between"
116+
blockAlign="center"
117+
className="even:bg-white odd:bg-secondary px-2 py-0 rounded-xs w-full"
118+
wrap="nowrap"
119+
>
120+
<div className="flex-1 min-w-0">
121+
<KeyValuePair label={name} value={value} copyable />
122+
</div>
123+
124+
<InlineStack
125+
className="min-w-24 shrink-0"
126+
align="end"
127+
blockAlign="center"
128+
>
129+
<Paragraph size="xs" tone="subdued">
130+
({type})
131+
</Paragraph>
132+
{actions?.map((action) => (
133+
<Button
134+
type="button"
135+
variant="ghost"
136+
size="sm"
137+
onClick={action.onClick}
138+
className="text-xs text-muted-foreground hover:text-foreground hover:bg-transparent"
139+
key={action.label}
140+
>
141+
{action.icon ?? action.label}
142+
</Button>
143+
))}
144+
</InlineStack>
145+
</InlineStack>
146+
);
147+
}

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
@@ -2,14 +2,13 @@ import { useLocation, useNavigate } from "@tanstack/react-router";
22
import { Edit3 } from "lucide-react";
33

44
import TooltipButton from "@/components/shared/Buttons/TooltipButton";
5+
import { PipelineNameDialog } from "@/components/shared/Dialogs";
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)