diff --git a/src/app/deployments/[deploymentId]/_components/code-collapsible.tsx b/src/app/deployments/[deploymentId]/_components/code-collapsible.tsx index e1915d365..82e98251f 100644 --- a/src/app/deployments/[deploymentId]/_components/code-collapsible.tsx +++ b/src/app/deployments/[deploymentId]/_components/code-collapsible.tsx @@ -60,7 +60,7 @@ function DeploymentCodeContent({ deployment }: Props) { return ( - + Invocation diff --git a/src/app/deployments/[deploymentId]/_components/deployment-detail.tsx b/src/app/deployments/[deploymentId]/_components/deployment-detail.tsx index 9d80a2162..da186fb57 100644 --- a/src/app/deployments/[deploymentId]/_components/deployment-detail.tsx +++ b/src/app/deployments/[deploymentId]/_components/deployment-detail.tsx @@ -35,7 +35,7 @@ function DeploymentDetailContent({ deployment }: Props) { return ( - + Details diff --git a/src/app/deployments/[deploymentId]/_components/endpoint.tsx b/src/app/deployments/[deploymentId]/_components/endpoint.tsx index af6a990e0..104519e78 100644 --- a/src/app/deployments/[deploymentId]/_components/endpoint.tsx +++ b/src/app/deployments/[deploymentId]/_components/endpoint.tsx @@ -27,7 +27,7 @@ function EndpointCollapsibleContent({ deployment }: Props) { return ( - + Endpoint diff --git a/src/app/deployments/[deploymentId]/_components/stack-collapsible.tsx b/src/app/deployments/[deploymentId]/_components/stack-collapsible.tsx index d2d49e775..b5c3e3cc7 100644 --- a/src/app/deployments/[deploymentId]/_components/stack-collapsible.tsx +++ b/src/app/deployments/[deploymentId]/_components/stack-collapsible.tsx @@ -1,12 +1,13 @@ +import AlertCircle from "@/assets/icons/alert-circle.svg?react"; import { CollapsibleCard } from "@/components/CollapsibleCard"; -import { StackInfo } from "@/components/stacks/info"; +import { EmptyState } from "@/components/EmptyState"; +import { StackInfoFull } from "@/components/stacks/info/stack-info-full"; import { pipelineSnapshotQueries } from "@/data/pipeline-snapshots"; import { useStack } from "@/data/stacks/stack-detail-query"; import { Deployment } from "@/types/deployments"; import { PipelineSnapshot } from "@/types/pipeline-snapshots"; import { useQuery } from "@tanstack/react-query"; import { Skeleton } from "@zenml-io/react-component-library"; -import { AlertEmptyState } from "./common"; import { DeploymentDetailWrapper } from "./fetch-wrapper"; type DeploymentStackCollapsibleContentProps = { @@ -14,15 +15,7 @@ type DeploymentStackCollapsibleContentProps = { }; export function DeploymentStackCollapsible() { - return ; -} - -function DeploymentStackCollapsibleWrapper({ deployment }: DeploymentStackCollapsibleContentProps) { - return ( - - - - ); + return ; } function DeploymentStackCollapsibleContent({ deployment }: DeploymentStackCollapsibleContentProps) { @@ -46,7 +39,7 @@ function DeploymentStackCollapsibleWithSnapshot({ snapshotId }: { snapshotId: st if (snapshotQuery.isError) { return ( - @@ -77,8 +70,8 @@ function DeploymentStackCollapsibleStackSection({ if (stackQuery.isError) { return ( - ); @@ -88,12 +81,28 @@ function DeploymentStackCollapsibleStackSection({ const snapshotConfig = snapshot.metadata?.pipeline_configuration.settings || {}; - return ; + return ; +} + +function StackCollapsibleEmptyState({ title, subtitle }: { title: string; subtitle?: string }) { + return ( + + } + > +
+

{title}

+ {subtitle &&

{subtitle}

} +
+
+
+ ); } function NoStackEmptyState() { return ( - diff --git a/src/app/snapshots/[snapshotId]/_components/snapshot-detail.tsx b/src/app/snapshots/[snapshotId]/_components/snapshot-detail.tsx index 2f7c50e53..0e9ff37fa 100644 --- a/src/app/snapshots/[snapshotId]/_components/snapshot-detail.tsx +++ b/src/app/snapshots/[snapshotId]/_components/snapshot-detail.tsx @@ -41,7 +41,7 @@ function DetailsContent({ snapshot }: Props) { return ( - + Details diff --git a/src/app/snapshots/[snapshotId]/_components/snapshot-stack-content.tsx b/src/app/snapshots/[snapshotId]/_components/snapshot-stack-content.tsx deleted file mode 100644 index fb84119da..000000000 --- a/src/app/snapshots/[snapshotId]/_components/snapshot-stack-content.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import StackIcon from "@/assets/icons/stack.svg?react"; -import { CollapsibleChevron } from "@/components/collapsible-chevron"; -import { ComponentCollapsible } from "@/components/stacks/info/ComponentCollapsible"; -import { Stack, StackComponentsList } from "@/types/stack"; -import { - CollapsibleContent, - CollapsibleHeader, - CollapsiblePanel, - CollapsibleTrigger, - Tag -} from "@zenml-io/react-component-library"; -import { useState } from "react"; - -type Props = { - stack: Stack; -}; - -export function StackContent({ stack }: Props) { - const [open, setOpen] = useState(true); - - const stackName = stack.name; - - const components = Object.values( - (stack.metadata?.components as StackComponentsList) ?? {} - ).flat(); - - return ( - - - - - Stack - -
- - - {stackName} - -
-
- -
    - {components.map((c) => ( -
  • - -
  • - ))} -
-
-
- ); -} diff --git a/src/app/snapshots/[snapshotId]/_components/snapshot-stack.tsx b/src/app/snapshots/[snapshotId]/_components/snapshot-stack.tsx index a47636546..135afb788 100644 --- a/src/app/snapshots/[snapshotId]/_components/snapshot-stack.tsx +++ b/src/app/snapshots/[snapshotId]/_components/snapshot-stack.tsx @@ -1,9 +1,9 @@ +import { StackInfoFull } from "@/components/stacks/info/stack-info-full"; import { pipelineSnapshotQueries } from "@/data/pipeline-snapshots"; import { stackQueries } from "@/data/stacks"; import { useQuery } from "@tanstack/react-query"; import { Skeleton } from "@zenml-io/react-component-library/components/server"; import { useParams } from "react-router-dom"; -import { StackContent } from "./snapshot-stack-content"; export function SnapshotStack() { const { snapshotId } = useParams() as { @@ -39,5 +39,5 @@ export function SnapshotStack() { return
Failed to load stack.
; } - return ; + return ; } diff --git a/src/components/CollapsibleCard.tsx b/src/components/CollapsibleCard.tsx index f57eeca64..374f4ee01 100644 --- a/src/components/CollapsibleCard.tsx +++ b/src/components/CollapsibleCard.tsx @@ -1,4 +1,3 @@ -import ChevronDown from "@/assets/icons/chevron-down.svg?react"; import { CollapsibleContent, CollapsibleHeader, @@ -8,6 +7,7 @@ import { } from "@zenml-io/react-component-library"; import { cn } from "@zenml-io/react-component-library/utilities"; import { useState } from "react"; +import { CollapsibleChevron } from "./collapsible-chevron"; import { CopyMetadataButton } from "./copy-metadata-button"; type CollapsibleCardProps = { @@ -35,12 +35,8 @@ export function CollapsibleCard({ return ( - - + + {title} @@ -59,7 +55,7 @@ export function CollapsibleCard({ ); } -type Props = Omit & { +type Props = CollapsibleCardProps & { copyText: string; displayCopyButton?: boolean; }; @@ -69,6 +65,7 @@ export function CollapsibleCardWithCopy({ displayCopyButton = true, headerClassName, className, + headerChildren, ...props }: Props) { return ( @@ -80,7 +77,9 @@ export function CollapsibleCardWithCopy({ headerClassName )} headerChildren={ - displayCopyButton ? ( + headerChildren ? ( + headerChildren + ) : displayCopyButton ? (
diff --git a/src/components/NestedCollapsible.tsx b/src/components/NestedCollapsible.tsx index a9767db64..03d4e031c 100644 --- a/src/components/NestedCollapsible.tsx +++ b/src/components/NestedCollapsible.tsx @@ -23,6 +23,7 @@ type Props = { sortKeysAlphabetically?: boolean; className?: string; schema?: JSONSchemaDefinition; + headerChildren?: ReactNode; }; const regex = /^$/; @@ -36,7 +37,8 @@ export function NestedCollapsible({ className, children, isInitialOpen = false, - sortKeysAlphabetically = true + sortKeysAlphabetically = true, + headerChildren }: PropsWithChildren) { const objects: { [key: string]: Record } = {}; const nonObjects: { [key: string]: unknown } = {}; @@ -62,6 +64,7 @@ export function NestedCollapsible({ return ( - + Failed to fetch Stack

; } - const config = (run.metadata?.config.settings as { [key: string]: any } | undefined) || {}; + const config = run.metadata?.config.settings || {}; - return ; + return ; } diff --git a/src/components/stacks/info/ComponentCollapsible.tsx b/src/components/stacks/info/ComponentCollapsible.tsx deleted file mode 100644 index 4f88b7e24..000000000 --- a/src/components/stacks/info/ComponentCollapsible.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { snakeCaseToTitleCase } from "@/lib/strings"; -import { sanitizeUrl } from "@/lib/url"; -import { routes } from "@/router/routes"; -import { StackComponent } from "@/types/components"; -import { Box, Button } from "@zenml-io/react-component-library/components/server"; -import { Link } from "react-router-dom"; -import { NestedCollapsible } from "../../NestedCollapsible"; -import { ComponentBadge } from "../../stack-components/ComponentBadge"; - -type Props = { - component: StackComponent; - objectConfig: Record; -}; -export function ComponentCollapsible({ component, objectConfig }: Props) { - const keyName = `${component.body?.type}.${component.body?.flavor_name}`; - const settings = objectConfig?.[keyName] ?? undefined; - - if (!settings || Object.keys(settings).length === 0) { - return ( - - - - - - ); - } - - return ( - - - - } - data={settings} - > - - - ); -} - -export function ComponentCollapsibleItem({ component }: { component: StackComponent }) { - return ( - <> -
- {`${component.body?.flavor_name} -
-

{component.name}

-

{component.id.split("-")[0]}

-
-
- - {snakeCaseToTitleCase(component.body?.type || "")} - - - ); -} diff --git a/src/components/stacks/info/StackHeader.tsx b/src/components/stacks/info/StackHeader.tsx deleted file mode 100644 index b9157162d..000000000 --- a/src/components/stacks/info/StackHeader.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { Stack } from "@/types/stack"; -import StackIcon from "@/assets/icons/stack.svg?react"; -import { Avatar, AvatarFallback } from "@zenml-io/react-component-library/components/client"; -import { Box, Tag } from "@zenml-io/react-component-library/components/server"; - -type Props = { - stack: Stack; -}; - -export function StackHeader({ stack }: Props) { - return ( - -
- - {stack.name[0]} - -
-

{stack.name}

-

{stack.id.split("-")[0]}

-
-
- - -
Stack
-
-
- ); -} diff --git a/src/components/stacks/info/index.tsx b/src/components/stacks/info/index.tsx deleted file mode 100644 index ac272147b..000000000 --- a/src/components/stacks/info/index.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Stack, StackComponentsList } from "@/types/stack"; -import { InfoBox } from "../../Infobox"; -import { ComponentCollapsible } from "./ComponentCollapsible"; -import { StackHeader } from "./StackHeader"; - -type Props = { - stack: Stack; - displayInfoBox?: boolean; - objectConfig: Record; -}; -export function StackInfo({ stack, objectConfig, displayInfoBox = true }: Props) { - const allComponents = Object.values( - (stack.metadata?.components as StackComponentsList) || {} - ).flat(); - return ( -
- {displayInfoBox && } - -
    - {allComponents.map((component) => ( -
  • - -
  • - ))} -
-
- ); -} - -function StackInfobox() { - return ( - -

- Current run-specific stack settings. Click on a component for full default settings. -

-
- ); -} diff --git a/src/components/stacks/info/sort-component.ts b/src/components/stacks/info/sort-component.ts new file mode 100644 index 000000000..60acd0240 --- /dev/null +++ b/src/components/stacks/info/sort-component.ts @@ -0,0 +1,16 @@ +import { StackComponent } from "@/types/components"; +import { StackComponentType } from "@/types/components"; +import { stackComponentTypes } from "@/lib/constants"; + +export function sortComponents(components: StackComponent[]) { + return components.sort((a, b) => { + const typeA = a.body?.type ?? ""; + const typeB = b.body?.type ?? ""; + const indexA = stackComponentTypes.indexOf(typeA as StackComponentType); + const indexB = stackComponentTypes.indexOf(typeB as StackComponentType); + // If type not found in array, put it at the end + const orderA = indexA === -1 ? stackComponentTypes.length : indexA; + const orderB = indexB === -1 ? stackComponentTypes.length : indexB; + return orderA - orderB; + }); +} diff --git a/src/components/stacks/info/stack-info-collapsible.tsx b/src/components/stacks/info/stack-info-collapsible.tsx new file mode 100644 index 000000000..b22b34386 --- /dev/null +++ b/src/components/stacks/info/stack-info-collapsible.tsx @@ -0,0 +1,45 @@ +import { + Avatar, + AvatarFallback, + CollapsibleContent, + CollapsibleHeader, + CollapsiblePanel, + CollapsibleTrigger +} from "@zenml-io/react-component-library"; +import { cn } from "@zenml-io/react-component-library/utilities"; +import { PropsWithChildren, useState } from "react"; +import { CollapsibleChevron } from "../../collapsible-chevron"; + +type Props = { + stackName: string; +}; + +export function StackInfoCollapsible({ children, stackName }: PropsWithChildren) { + const [open, setOpen] = useState(true); + return ( + + + + +
+ + {stackName[0] || "S"} + +
+

Stack

+

{stackName}

+
+
+
+
+ + + {children} + +
+ ); +} diff --git a/src/components/stacks/info/stack-info-component-list-item.tsx b/src/components/stacks/info/stack-info-component-list-item.tsx new file mode 100644 index 000000000..d3d5f5477 --- /dev/null +++ b/src/components/stacks/info/stack-info-component-list-item.tsx @@ -0,0 +1,100 @@ +import { ComponentIcon } from "@/components/ComponentIcon"; +import { snakeCaseToTitleCase } from "@/lib/strings"; +import { isObject } from "@/lib/type-guards"; +import { sanitizeUrl } from "@/lib/url"; +import { routes } from "@/router/routes"; +import { StackComponent, StackComponentType } from "@/types/components"; +import { Box } from "@zenml-io/react-component-library/components/server"; +import { Link } from "react-router-dom"; +import { NestedCollapsible } from "../../NestedCollapsible"; +import { CopyMetadataButton } from "@/components/copy-metadata-button"; +import { PropsWithChildren } from "react"; + +type Props = { + component: StackComponent; + objectConfig: Record; +}; +export function StackInfoComponentListItem({ component, objectConfig }: Props) { + const keyName = `${component.body?.type}.${component.body?.flavor_name}`; + const settings = objectConfig?.[keyName] ?? undefined; + + const isValidObject = isObject(settings); + + if (!isValidObject || Object.keys(settings).length === 0) { + return ( + + +
+ + + + ); + } + + return ( + + +
+ +
+
+ + +
+ } + data={settings as Record} + > + ); +} + +type ComponentCollapsibleHeaderProps = { + componentName: string; + componentLogoUrl: string | undefined; + componentType: StackComponentType; +}; + +function ComponentCollapsibleHeader({ + componentLogoUrl, + componentName, + componentType, + children +}: PropsWithChildren) { + return ( +
+
+ {snakeCaseToTitleCase(componentType)} +
+
+
+ {componentLogoUrl ? ( + {`${componentName} + ) : ( + + )} +

{componentName}

+
+ {children} +
+
+ ); +} diff --git a/src/components/stacks/info/stack-info-component-list.tsx b/src/components/stacks/info/stack-info-component-list.tsx new file mode 100644 index 000000000..6ee0ff4b8 --- /dev/null +++ b/src/components/stacks/info/stack-info-component-list.tsx @@ -0,0 +1,23 @@ +import { Stack, StackComponentsList } from "@/types/stack"; +import { StackInfoComponentListItem } from "./stack-info-component-list-item"; +import { sortComponents } from "./sort-component"; + +type Props = { + stack: Stack; + objectConfig: Record; +}; +export function StackInfoComponentList({ stack, objectConfig }: Props) { + const allComponents = sortComponents( + Object.values((stack.metadata?.components as StackComponentsList) || {}).flat() + ); + + return ( +
    + {allComponents.map((component) => ( +
  • + +
  • + ))} +
+ ); +} diff --git a/src/components/stacks/info/stack-info-full.tsx b/src/components/stacks/info/stack-info-full.tsx new file mode 100644 index 000000000..e9465157a --- /dev/null +++ b/src/components/stacks/info/stack-info-full.tsx @@ -0,0 +1,16 @@ +import { Stack } from "@/types/stack"; +import { StackInfoCollapsible } from "./stack-info-collapsible"; +import { StackInfoComponentList } from "./stack-info-component-list"; + +type Props = { + stack: Stack; + objectConfig: Record; +}; + +export function StackInfoFull({ stack, objectConfig }: Props) { + return ( + + + + ); +} diff --git a/src/components/steps/step-sheet/StacksTab.tsx b/src/components/steps/step-sheet/StacksTab.tsx index 2d9fb73b7..3bb5122b2 100644 --- a/src/components/steps/step-sheet/StacksTab.tsx +++ b/src/components/steps/step-sheet/StacksTab.tsx @@ -1,5 +1,5 @@ import AlertCircle from "@/assets/icons/alert-circle.svg?react"; -import { StackInfo } from "@/components/stacks/info"; +import { StackInfoFull } from "@/components/stacks/info/stack-info-full"; import { usePipelineRun } from "@/data/pipeline-runs/pipeline-run-detail-query"; import { useStack } from "@/data/stacks/stack-detail-query"; import { useStepDetail } from "@/data/steps/step-detail-query"; @@ -20,7 +20,7 @@ export function StackTab({ stepId }: Props) { if (run.isError || step.isError) return

Something went wrong fetching the run

; const stackId = run.data?.resources?.stack?.id; - const config = (step.data.metadata?.config.settings as { [key: string]: any } | undefined) || {}; + const config = step.data.metadata?.config.settings || {}; if (!stackId) return ( @@ -39,7 +39,7 @@ export function StackTab({ stepId }: Props) { type StackTabContentProps = { stackId: string; - objectConfig: Record; + objectConfig: Record; }; function StackTabContent({ stackId, objectConfig }: StackTabContentProps) { const { data, isError, isPending } = useStack({ stackId: stackId }); @@ -50,5 +50,5 @@ function StackTabContent({ stackId, objectConfig }: StackTabContentProps) { return

Failed to fetch Stack

; } - return ; + return ; }