Skip to content

Commit 6b33171

Browse files
authored
Abstracts artifacts list (#1481)
## Description Created a reusable `ArtifactsList` component to standardize the display of pipeline inputs and outputs across the application. This component provides a consistent UI for showing artifact details with optional actions like copying values and editing. ## Type of Change - [x] Improvement - [x] Cleanup/Refactor ## Checklist - [x] I have tested this does not break current pipelines / runs functionality - [x] I have tested the changes on staging ## Screenshots (if applicable) ## ![Screenshot 2025-12-05 at 1.52.15 PM.png](https://app.graphite.com/user-attachments/assets/cd37cc42-d69f-4f0b-8132-0a71cef57bc2.png) ![Screenshot 2025-12-05 at 1.52.32 PM.png](https://app.graphite.com/user-attachments/assets/6149a551-74d2-4bb1-8710-509d5042aa3a.png) ## Test Instructions 1. Navigate to the Pipeline Editor and verify that inputs and outputs are displayed correctly in the Pipeline Details panel 2. Check that the edit functionality for inputs and outputs still works as expected 3. View a Pipeline Run and confirm that the artifacts are displayed properly ## Additional Comments The new component improves code maintainability by eliminating duplicate markup and standardizing the artifact display pattern across different parts of the application.
1 parent 0c5937a commit 6b33171

File tree

3 files changed

+158
-173
lines changed

3 files changed

+158
-173
lines changed

src/components/Editor/PipelineDetails.tsx

Lines changed: 41 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useEffect, useState } from "react";
33

44
import { PipelineValidationList } from "@/components/Editor/components/PipelineValidationList/PipelineValidationList";
55
import { useValidationIssueNavigation } from "@/components/Editor/hooks/useValidationIssueNavigation";
6+
import { ArtifactsList } from "@/components/shared/ArtifactsList/ArtifactsList";
67
import { CopyText } from "@/components/shared/CopyText/CopyText";
78
import { Button } from "@/components/ui/button";
89
import { Icon } from "@/components/ui/icon";
@@ -98,18 +99,6 @@ const PipelineDetails = () => {
9899
);
99100
};
100101

101-
const handleInputCopy = (input: InputSpec) => {
102-
const value = input.value ?? input.default;
103-
104-
if (!value) {
105-
notify("Copy failed: Input has no value", "error");
106-
return;
107-
}
108-
109-
void navigator.clipboard.writeText(value);
110-
notify("Input value copied to clipboard", "success");
111-
};
112-
113102
const handleDigestCopy = () => {
114103
navigator.clipboard.writeText(digest);
115104
notify("Digest copied to clipboard", "success");
@@ -232,112 +221,46 @@ const PipelineDetails = () => {
232221
</BlockStack>
233222
)}
234223

235-
{/* Artifacts (Inputs & Outputs) */}
236-
<BlockStack>
237-
<Text as="h3" size="md" weight="semibold" className="mb-1">
238-
Artifacts
239-
</Text>
240-
<BlockStack gap="4">
241-
<BlockStack>
242-
<Text as="h4" size="sm" weight="semibold" className="mb-1">
243-
Inputs
244-
</Text>
245-
{componentSpec.inputs && componentSpec.inputs.length > 0 ? (
246-
<div className="flex flex-col">
247-
{componentSpec.inputs.map((input) => {
248-
return (
249-
<div
250-
className="flex flex-row justify-between even:bg-white odd:bg-gray-100 gap-1 px-2 py-1 rounded-xs items-center"
251-
key={input.name}
252-
>
253-
<div className="text-xs flex-1 truncate max-w-[200px]">
254-
<span className="font-semibold">{input.name}:</span>{" "}
255-
{input.value || input.default || "No value"}
256-
</div>
257-
258-
<div className="text-xs flex-1 font-mono truncate max-w-[100px]">
259-
<span className="font-semibold">Type:</span>{" "}
260-
{typeSpecToString(input?.type)}
261-
</div>
262-
<InlineStack
263-
className="min-w-24"
264-
align="end"
265-
blockAlign="center"
266-
>
267-
<Button
268-
type="button"
269-
variant="ghost"
270-
size="min"
271-
onClick={() => handleInputCopy(input)}
272-
className="text-muted-foreground hover:text-foreground"
273-
>
274-
<Icon name="Copy" className="size-3" />
275-
</Button>
276-
277-
<Button
278-
type="button"
279-
variant="ghost"
280-
className="text-xs text-muted-foreground cursor-pointer hover:bg-transparent"
281-
size="sm"
282-
onClick={() => handleInputEdit(input)}
283-
>
284-
Edit
285-
</Button>
286-
</InlineStack>
287-
</div>
288-
);
289-
})}
290-
</div>
291-
) : (
292-
<div className="text-xs text-muted-foreground">No inputs</div>
293-
)}
294-
</BlockStack>
295-
<BlockStack>
296-
<Text as="h4" size="sm" weight="semibold" className="mb-1">
297-
Outputs
298-
</Text>
299-
{componentSpec.outputs && componentSpec.outputs.length > 0 ? (
300-
<div className="flex flex-col">
301-
{componentSpec.outputs.map((output) => (
302-
<div
303-
className="flex flex-row justify-between even:bg-white odd:bg-gray-100 gap-1 px-2 py-1 rounded-xs items-center"
304-
key={output.name}
305-
>
306-
<div className="text-xs flex-1 truncate max-w-[200px]">
307-
<span className="font-semibold">{output.name}:</span>{" "}
308-
{
309-
getOutputConnectedDetails(graphSpec, output.name)
310-
.outputName
311-
}
312-
</div>
313-
<div className="text-xs">
314-
<span className="font-semibold">Type:</span>{" "}
315-
{typeSpecToString(
316-
getOutputConnectedDetails(graphSpec, output.name)
317-
.outputType,
318-
)}
319-
</div>
320-
321-
<InlineStack className="min-w-24" align={"end"}>
322-
<Button
323-
type="button"
324-
variant="ghost"
325-
className="text-xs text-muted-foreground cursor-pointer hover:bg-transparent"
326-
size="sm"
327-
onClick={() => handleOutputEdit(output)}
328-
>
329-
Edit
330-
</Button>
331-
</InlineStack>
332-
</div>
333-
))}
334-
</div>
335-
) : (
336-
<div className="text-xs text-muted-foreground">No outputs</div>
337-
)}
338-
</BlockStack>
339-
</BlockStack>
340-
</BlockStack>
224+
<ArtifactsList
225+
inputs={(componentSpec.inputs ?? []).map((input) => ({
226+
name: input.name,
227+
type: typeSpecToString(input?.type),
228+
value: input.value || input.default,
229+
actions: (
230+
<Button
231+
type="button"
232+
variant="ghost"
233+
size="min"
234+
onClick={() => handleInputEdit(input)}
235+
className="text-muted-foreground hover:text-foreground h-4 w-4"
236+
>
237+
<Icon name="Pencil" className="size-2.5" />
238+
</Button>
239+
),
240+
}))}
241+
outputs={(componentSpec.outputs ?? []).map((output) => {
242+
const connectedDetails = getOutputConnectedDetails(
243+
graphSpec,
244+
output.name,
245+
);
246+
return {
247+
name: output.name,
248+
type: typeSpecToString(connectedDetails.outputType),
249+
value: connectedDetails.outputName,
250+
actions: (
251+
<Button
252+
type="button"
253+
variant="ghost"
254+
size="min"
255+
onClick={() => handleOutputEdit(output)}
256+
className="text-muted-foreground hover:text-foreground h-4 w-4"
257+
>
258+
<Icon name="Pencil" className="size-2.5" />
259+
</Button>
260+
),
261+
};
262+
})}
263+
/>
341264

342265
{/* Validations */}
343266
<BlockStack>

src/components/PipelineRun/RunDetails.tsx

Lines changed: 12 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Frown } from "lucide-react";
22

3+
import { ArtifactsList } from "@/components/shared/ArtifactsList/ArtifactsList";
34
import { CopyText } from "@/components/shared/CopyText/CopyText";
45
import { BlockStack, InlineStack } from "@/components/ui/layout";
56
import { Spinner } from "@/components/ui/spinner";
@@ -201,61 +202,17 @@ export const RunDetails = () => {
201202
</BlockStack>
202203
)}
203204

204-
<BlockStack className="w-full">
205-
<Text as="h3" size="md" weight="semibold" className="mb-1">
206-
Artifacts
207-
</Text>
208-
<BlockStack gap="4" className="w-full">
209-
<BlockStack className="w-full">
210-
<Text as="h4" size="sm" weight="semibold" className="mb-1">
211-
Inputs
212-
</Text>
213-
{componentSpec.inputs && componentSpec.inputs.length > 0 ? (
214-
<div className="flex flex-col w-full">
215-
{componentSpec.inputs.map((input) => (
216-
<div
217-
className="flex flex-row justify-between even:bg-white odd:bg-gray-100 gap-1 px-2 py-1 rounded-xs items-center w-full"
218-
key={input.name}
219-
>
220-
<div className="text-xs flex-1 truncate">
221-
<span className="font-semibold">{input.name}</span>
222-
</div>
223-
<div className="text-xs text-muted-foreground">
224-
{typeof input.type === "string" ? input.type : "object"}
225-
</div>
226-
</div>
227-
))}
228-
</div>
229-
) : (
230-
<div className="text-xs text-muted-foreground">No inputs</div>
231-
)}
232-
</BlockStack>
233-
<BlockStack className="w-full">
234-
<Text as="h4" size="sm" weight="semibold" className="mb-1">
235-
Outputs
236-
</Text>
237-
{componentSpec.outputs && componentSpec.outputs.length > 0 ? (
238-
<div className="flex flex-col w-full">
239-
{componentSpec.outputs.map((output) => (
240-
<div
241-
className="flex flex-row justify-between even:bg-white odd:bg-gray-100 gap-1 px-2 py-1 rounded-xs items-center w-full"
242-
key={output.name}
243-
>
244-
<div className="text-xs flex-1 truncate">
245-
<span className="font-semibold">{output.name}</span>
246-
</div>
247-
<div className="text-xs text-muted-foreground">
248-
{typeof output.type === "string" ? output.type : "object"}
249-
</div>
250-
</div>
251-
))}
252-
</div>
253-
) : (
254-
<div className="text-xs text-muted-foreground">No outputs</div>
255-
)}
256-
</BlockStack>
257-
</BlockStack>
258-
</BlockStack>
205+
<ArtifactsList
206+
inputs={(componentSpec.inputs ?? []).map((input) => ({
207+
name: input.name,
208+
type: typeof input.type === "string" ? input.type : "object",
209+
value: input.value ?? input.default,
210+
}))}
211+
outputs={(componentSpec.outputs ?? []).map((output) => ({
212+
name: output.name,
213+
type: typeof output.type === "string" ? output.type : "object",
214+
}))}
215+
/>
259216
</BlockStack>
260217
);
261218
};
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { type ReactNode } from "react";
2+
3+
import { CopyText } from "@/components/shared/CopyText/CopyText";
4+
import { BlockStack, InlineStack } from "@/components/ui/layout";
5+
import { Text } from "@/components/ui/typography";
6+
7+
interface ArtifactItem {
8+
name: string;
9+
type: string;
10+
value?: string;
11+
actions?: ReactNode;
12+
}
13+
14+
interface ArtifactsSectionProps {
15+
title: string;
16+
items: ArtifactItem[];
17+
emptyMessage?: string;
18+
disableCopy?: boolean;
19+
}
20+
21+
const ArtifactsSection = ({
22+
title,
23+
items,
24+
emptyMessage = "None",
25+
disableCopy = false,
26+
}: ArtifactsSectionProps) => (
27+
<BlockStack className="w-full">
28+
<Text as="h4" size="sm" weight="semibold" className="mb-1">
29+
{title}
30+
</Text>
31+
{items.length > 0 ? (
32+
<BlockStack className="w-full">
33+
{items.map((item) => (
34+
<InlineStack
35+
key={item.name}
36+
gap="2"
37+
align="space-between"
38+
blockAlign="center"
39+
className="even:bg-white odd:bg-gray-100 p-2 rounded-xs w-full"
40+
>
41+
<InlineStack
42+
gap="1"
43+
blockAlign="center"
44+
wrap="nowrap"
45+
className="flex-1 min-w-0 text-xs"
46+
>
47+
<Text as="span" weight="semibold" className="shrink-0">
48+
{item.name}:
49+
</Text>
50+
{item.value !== undefined ? (
51+
!disableCopy ? (
52+
<CopyText className="truncate">
53+
{item.value || "No value"}
54+
</CopyText>
55+
) : (
56+
<Text as="span" className="truncate">
57+
{item.value || "No value"}
58+
</Text>
59+
)
60+
) : (
61+
<Text as="span" tone="subdued">
62+
63+
</Text>
64+
)}
65+
</InlineStack>
66+
<Text size="xs" tone="subdued" className="shrink-0">
67+
{item.type}
68+
</Text>
69+
{item.actions}
70+
</InlineStack>
71+
))}
72+
</BlockStack>
73+
) : (
74+
<Text size="xs" tone="subdued">
75+
{emptyMessage}
76+
</Text>
77+
)}
78+
</BlockStack>
79+
);
80+
81+
interface ArtifactsListProps {
82+
inputs: ArtifactItem[];
83+
outputs: ArtifactItem[];
84+
}
85+
86+
export const ArtifactsList = ({ inputs, outputs }: ArtifactsListProps) => (
87+
<BlockStack className="w-full">
88+
<Text as="h3" size="md" weight="semibold" className="mb-1">
89+
Artifacts
90+
</Text>
91+
<BlockStack gap="4" className="w-full">
92+
<ArtifactsSection
93+
title="Inputs"
94+
items={inputs}
95+
emptyMessage="No inputs"
96+
/>
97+
<ArtifactsSection
98+
title="Outputs"
99+
items={outputs}
100+
emptyMessage="No outputs"
101+
disableCopy
102+
/>
103+
</BlockStack>
104+
</BlockStack>
105+
);

0 commit comments

Comments
 (0)