Skip to content

Commit a1631de

Browse files
Merge pull request #840 from zenml-io/staging
2 parents 7b22d04 + 9a180ac commit a1631de

File tree

172 files changed

+5552
-1823
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

172 files changed

+5552
-1823
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { NestedCollapsible } from "@/components/NestedCollapsible";
2+
import { componentQueries } from "@/data/components";
3+
import { Deployment } from "@/types/deployments";
4+
import { useQuery } from "@tanstack/react-query";
5+
import { Skeleton } from "@zenml-io/react-component-library/components/server";
6+
import { DeploymentDetailWrapper } from "./fetch-wrapper";
7+
8+
type Props = {
9+
deployment: Deployment;
10+
};
11+
12+
export function DeployerConfigCollapsible() {
13+
return <DeploymentDetailWrapper Component={DeployerConfigCollapsibleContent} />;
14+
}
15+
16+
function DeployerConfigCollapsibleContent({ deployment }: Props) {
17+
const deployerId = deployment.resources?.deployer?.id;
18+
19+
const deployerQuery = useQuery({
20+
...componentQueries.componentDetail(deployerId!),
21+
enabled: !!deployerId
22+
});
23+
24+
if (!deployerId) return null;
25+
26+
if (deployerQuery.isPending) return <Skeleton className="h-[200px] w-full" />;
27+
if (deployerQuery.isError) return null;
28+
29+
const deployer = deployerQuery.data;
30+
31+
return (
32+
<NestedCollapsible
33+
isInitialOpen
34+
title="Deployer Configuration"
35+
data={deployer.metadata?.configuration}
36+
/>
37+
);
38+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { CollapsibleChevron } from "@/components/collapsible-chevron";
2+
import { CopyButton } from "@/components/CopyButton";
3+
import { Key, KeyValue, Value } from "@/components/KeyValue";
4+
import { NotAvailable } from "@/components/not-available";
5+
import { SnapshotLink } from "@/components/pipeline-snapshots/snapshot-link";
6+
import { PipelineLink } from "@/components/pipelines/pipeline-link";
7+
import { Deployment } from "@/types/deployments";
8+
import {
9+
CollapsibleContent,
10+
CollapsibleHeader,
11+
CollapsiblePanel,
12+
CollapsibleTrigger
13+
} from "@zenml-io/react-component-library";
14+
import { useState } from "react";
15+
import { DeploymentDetailWrapper } from "./fetch-wrapper";
16+
17+
export function DeploymentDetail() {
18+
return <DeploymentDetailWrapper Component={DeploymentDetailContent} />;
19+
}
20+
21+
type Props = {
22+
deployment: Deployment;
23+
};
24+
25+
function DeploymentDetailContent({ deployment }: Props) {
26+
const [open, setOpen] = useState(true);
27+
28+
const deploymentId = deployment.id;
29+
const snapshot = deployment.resources?.snapshot;
30+
const snapshotId = snapshot?.id;
31+
const snapshotName = snapshot?.name;
32+
33+
const pipelineId = deployment.resources?.pipeline?.id;
34+
const pipelineName = deployment.resources?.pipeline?.name;
35+
36+
return (
37+
<CollapsiblePanel open={open} onOpenChange={setOpen}>
38+
<CollapsibleHeader className="flex items-center gap-[10px]">
39+
<CollapsibleTrigger className="flex w-full gap-2">
40+
<CollapsibleChevron open={open} />
41+
Details
42+
</CollapsibleTrigger>
43+
</CollapsibleHeader>
44+
<CollapsibleContent className="border-t border-theme-border-moderate bg-theme-surface-primary px-5 py-3">
45+
<dl className="grid grid-cols-1 gap-x-[10px] gap-y-2 md:grid-cols-3 md:gap-y-4">
46+
<KeyValue
47+
label="Id"
48+
value={
49+
<div className="group/copybutton flex items-center gap-0.5">
50+
{deploymentId}
51+
<CopyButton copyText={deploymentId} />
52+
</div>
53+
}
54+
/>
55+
<KeyValue
56+
label="Snapshot"
57+
value={
58+
snapshotId && snapshotName ? (
59+
<SnapshotLink snapshotId={snapshotId} snapshotName={snapshotName} />
60+
) : (
61+
<NotAvailable />
62+
)
63+
}
64+
/>
65+
<Key className="h-auto">Pipeline</Key>
66+
<Value className="h-auto">
67+
{pipelineId && pipelineName ? (
68+
<PipelineLink pipelineId={pipelineId} pipelineName={pipelineName} />
69+
) : (
70+
<NotAvailable />
71+
)}
72+
</Value>
73+
</dl>
74+
</CollapsibleContent>
75+
</CollapsiblePanel>
76+
);
77+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { CollapsibleChevron } from "@/components/collapsible-chevron";
2+
import { CopyButton } from "@/components/CopyButton";
3+
import { KeyValue } from "@/components/KeyValue";
4+
import { NotAvailable } from "@/components/not-available";
5+
import { Deployment } from "@/types/deployments";
6+
import {
7+
CollapsibleContent,
8+
CollapsibleHeader,
9+
CollapsiblePanel,
10+
CollapsibleTrigger
11+
} from "@zenml-io/react-component-library";
12+
import { useState } from "react";
13+
import { DeploymentDetailWrapper } from "./fetch-wrapper";
14+
15+
export function EndpointCollapsible() {
16+
return <DeploymentDetailWrapper Component={EndpointCollapsibleContent} />;
17+
}
18+
19+
type Props = {
20+
deployment: Deployment;
21+
};
22+
23+
function EndpointCollapsibleContent({ deployment }: Props) {
24+
const [open, setOpen] = useState(true);
25+
26+
const deploymentEndpoint = deployment.body?.url;
27+
28+
return (
29+
<CollapsiblePanel open={open} onOpenChange={setOpen}>
30+
<CollapsibleHeader className="flex items-center gap-[10px]">
31+
<CollapsibleTrigger className="flex w-full gap-2">
32+
<CollapsibleChevron open={open} />
33+
Endpoint
34+
</CollapsibleTrigger>
35+
</CollapsibleHeader>
36+
<CollapsibleContent className="border-t border-theme-border-moderate bg-theme-surface-primary px-5 py-3">
37+
<dl className="grid grid-cols-1 gap-x-[10px] gap-y-2 md:grid-cols-3 md:gap-y-4">
38+
<KeyValue
39+
label="Endpoint"
40+
value={
41+
deploymentEndpoint ? (
42+
<div className="group/copybutton flex items-center gap-0.5">
43+
<a
44+
className="link"
45+
href={deploymentEndpoint}
46+
target="_blank"
47+
rel="noopener noreferrer"
48+
>
49+
{deploymentEndpoint}
50+
</a>
51+
<CopyButton copyText={deploymentEndpoint} />
52+
</div>
53+
) : (
54+
<NotAvailable />
55+
)
56+
}
57+
/>
58+
</dl>
59+
</CollapsibleContent>
60+
</CollapsiblePanel>
61+
);
62+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { deploymentQueries } from "@/data/deployments";
2+
import { Deployment } from "@/types/deployments";
3+
import { useQuery } from "@tanstack/react-query";
4+
import { Skeleton } from "@zenml-io/react-component-library/components/server";
5+
import { ComponentType } from "react";
6+
import { useParams } from "react-router-dom";
7+
8+
type DeploymentDetailWrapperProps = {
9+
Component: ComponentType<{ deployment: Deployment }>;
10+
};
11+
12+
export function DeploymentDetailWrapper({ Component }: DeploymentDetailWrapperProps) {
13+
const { deploymentId } = useParams() as {
14+
deploymentId: string;
15+
};
16+
17+
const deploymentQuery = useQuery({
18+
...deploymentQueries.detail(deploymentId),
19+
throwOnError: true
20+
});
21+
22+
if (deploymentQuery.isPending) return <Skeleton className="h-[200px] w-full" />;
23+
if (deploymentQuery.isError) return null;
24+
25+
const deployment = deploymentQuery.data;
26+
27+
return <Component deployment={deployment} />;
28+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { DeploymentDetailHeaderInfo } from "./info";
2+
import { DeploymentDetailTabs } from "./tabs";
3+
4+
export function DeploymentDetailHeader({ deploymentId }: { deploymentId: string }) {
5+
return (
6+
<section className="overflow-x-hidden border-b border-theme-border-moderate bg-theme-surface-primary">
7+
<div className="space-y-1 px-5 pt-5 lg:px-[80px]">
8+
<DeploymentDetailHeaderInfo deploymentId={deploymentId} />
9+
<DeploymentDetailTabs />
10+
</div>
11+
</section>
12+
);
13+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { SnapshotLink } from "@/components/pipeline-snapshots/snapshot-link";
2+
import { Deployment } from "@/types/deployments";
3+
import { NotAvailableTag } from "./not-available-tag";
4+
import { PipelineTag } from "./pipeline-tag";
5+
6+
type Props = {
7+
deployment: Deployment;
8+
};
9+
10+
export function DeploymentDetailHeaderInfoSubrow({ deployment }: Props) {
11+
const snapshotId = deployment.resources?.snapshot?.id;
12+
const snapshotName = deployment.resources?.snapshot?.name;
13+
const pipelineId = deployment.resources?.pipeline?.id;
14+
const pipelineName = deployment.resources?.pipeline?.name;
15+
16+
return (
17+
<div className="flex items-center gap-5">
18+
<div className="flex items-center gap-[10px]">
19+
<div className="text-text-sm text-theme-text-secondary">Pipeline</div>
20+
{pipelineId && pipelineName ? (
21+
<PipelineTag pipelineId={pipelineId} pipelineName={pipelineName} />
22+
) : (
23+
<NotAvailableTag />
24+
)}
25+
</div>
26+
<div className="flex items-center gap-[10px]">
27+
<div className="text-text-sm text-theme-text-secondary">Snapshot</div>
28+
{snapshotId && snapshotName ? (
29+
<SnapshotLink size="xs" snapshotId={snapshotId} snapshotName={snapshotName} />
30+
) : (
31+
<NotAvailableTag />
32+
)}
33+
</div>
34+
</div>
35+
);
36+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { DisplayDate } from "@/components/DisplayDate";
2+
import { InlineAvatar } from "@/components/InlineAvatar";
3+
import { deploymentQueries } from "@/data/deployments";
4+
import { getUsername } from "@/lib/user";
5+
import { Deployment } from "@/types/deployments";
6+
import { useQuery } from "@tanstack/react-query";
7+
import { Skeleton, Tag } from "@zenml-io/react-component-library/components/server";
8+
import { DeploymentDetailHeaderInfoSubrow } from "./info-subrow";
9+
import { usePipelineDetailRunsBreadcrumbs } from "./use-breadcrumb";
10+
import { capitalize } from "@/lib/strings";
11+
import { useActiveTab } from "./use-active-tab";
12+
import { getDeploymentStatusTagColor } from "@/lib/deployments";
13+
// import { useSnapshotDetailRunsBreadcrumbs } from "./breadcrumbs";
14+
15+
type Props = {
16+
deploymentId: string;
17+
};
18+
19+
export function DeploymentDetailHeaderInfo({ deploymentId }: Props) {
20+
const deploymentQuery = useQuery({
21+
...deploymentQueries.detail(deploymentId),
22+
throwOnError: true
23+
});
24+
25+
const activeTab = useActiveTab();
26+
usePipelineDetailRunsBreadcrumbs(capitalize(activeTab), deploymentQuery.data);
27+
28+
if (deploymentQuery.isPending) return <InfoSkeleton />;
29+
if (deploymentQuery.isError) return null;
30+
31+
const deployment = deploymentQuery.data;
32+
33+
return <SnapshotDetailInfoContent deployment={deployment} />;
34+
}
35+
36+
function SnapshotDetailInfoContent({ deployment }: { deployment: Deployment }) {
37+
const name = deployment.name;
38+
const status = deployment.body?.status;
39+
40+
const user = deployment.resources?.user ?? undefined;
41+
const updated = deployment.body?.updated;
42+
43+
return (
44+
<section className="space-y-2 bg-theme-surface-primary">
45+
<div className="flex items-center gap-1">
46+
<h1 className="text-display-xs font-semibold text-theme-text-primary">{name}</h1>
47+
<Tag
48+
emphasis="subtle"
49+
rounded={false}
50+
className="inline-flex items-center gap-0.5"
51+
color={getDeploymentStatusTagColor(status ?? undefined)}
52+
>
53+
{capitalize(status ?? "")}
54+
</Tag>
55+
</div>
56+
<div className="flex flex-wrap items-center justify-between gap-1">
57+
<DeploymentDetailHeaderInfoSubrow deployment={deployment} />
58+
<div className="flex items-center gap-1 text-text-sm text-theme-text-secondary lg:whitespace-nowrap">
59+
Deployed by{" "}
60+
<InlineAvatar
61+
isServiceAccount={!!user?.body?.is_service_account}
62+
username={getUsername(user!)}
63+
avatarUrl={user?.body?.avatar_url || undefined}
64+
/>
65+
at <DisplayDate dateString={updated || ""} />
66+
</div>
67+
</div>
68+
</section>
69+
);
70+
}
71+
72+
function InfoSkeleton() {
73+
return (
74+
<section className="space-y-2 bg-theme-surface-primary">
75+
{/* Title row with name and status tag */}
76+
<div className="flex items-center gap-1">
77+
<Skeleton className="h-6 w-[180px]" /> {/* Name */}
78+
<Skeleton className="h-5 w-[60px] rounded-sm" /> {/* Status tag */}
79+
</div>
80+
{/* Bottom row with subrow info and deployed by info */}
81+
<div className="flex flex-wrap items-center justify-between gap-1">
82+
<Skeleton className="h-4 w-[120px]" /> {/* Subrow content placeholder */}
83+
<div className="flex items-center gap-1">
84+
<Skeleton className="h-4 w-[80px]" /> {/* "Deployed by" text */}
85+
<Skeleton className="h-5 w-5 rounded-rounded" /> {/* Avatar */}
86+
<Skeleton className="h-4 w-[60px]" /> {/* Username */}
87+
<Skeleton className="h-4 w-[20px]" /> {/* "at" text */}
88+
<Skeleton className="h-4 w-[100px]" /> {/* Date */}
89+
</div>
90+
</div>
91+
</section>
92+
);
93+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Tag } from "@zenml-io/react-component-library";
2+
3+
export function NotAvailableTag() {
4+
return (
5+
<Tag size="xs" rounded={false} emphasis="subtle" color="grey">
6+
Not available
7+
</Tag>
8+
);
9+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import PipelineIcon from "@/assets/icons/pipeline.svg?react";
2+
import { routes } from "@/router/routes";
3+
import { Tag } from "@zenml-io/react-component-library";
4+
import { Link } from "react-router-dom";
5+
type Props = {
6+
pipelineId: string;
7+
pipelineName: string;
8+
};
9+
10+
export function PipelineTag({ pipelineId, pipelineName }: Props) {
11+
return (
12+
<Link to={routes.projects.pipelines.detail.runs(pipelineId)}>
13+
<Tag
14+
size="xs"
15+
color="purple"
16+
className="inline-flex items-center gap-0.5 text-primary-400"
17+
rounded={false}
18+
emphasis="minimal"
19+
>
20+
<PipelineIcon className="size-3 shrink-0 fill-primary-400" />
21+
{pipelineName}
22+
</Tag>
23+
</Link>
24+
);
25+
}

0 commit comments

Comments
 (0)