Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const InspectPipelineButton = ({
data-testid="inspect-pipeline-button"
{...rest}
>
<Icon name="Network" className="rotate-270" />
<Icon name="Binoculars" />
{displayLabel ?? (showLabel ? "Inspect" : null)}
</TooltipButton>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { screen } from "@testing-library/dom";
import { act, fireEvent, render } from "@testing-library/react";
import { describe, expect, test, vi } from "vitest";

import { SharePipelineButton } from "./SharePipelineButton";

const mockNotify = vi.fn();

vi.mock("@/hooks/useToastNotification", () => ({
default: () => mockNotify,
}));

describe("<SharePipelineButton/>", () => {
test("copies the current URL to the clipboard on click", () => {
const writeText = vi.fn();
Object.defineProperty(navigator, "clipboard", {
value: { writeText },
configurable: true,
});

render(<SharePipelineButton />);
act(() => fireEvent.click(screen.getByTestId("share-pipeline-button")));

expect(writeText).toHaveBeenCalledWith(window.location.href);
expect(mockNotify).toHaveBeenCalledWith(
"Run URL copied to clipboard",
"success",
);
});
});
42 changes: 42 additions & 0 deletions src/components/PipelineRun/components/SharePipelineButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { type ComponentPropsWithoutRef, useCallback } from "react";

import TooltipButton from "@/components/shared/Buttons/TooltipButton";
import { Icon } from "@/components/ui/icon";
import useToastNotification from "@/hooks/useToastNotification";
import { copyToClipboard } from "@/utils/string";

type SharePipelineButtonProps = {
showLabel?: boolean;
displayLabel?: string;
showTooltip?: boolean;
} & Omit<
ComponentPropsWithoutRef<typeof TooltipButton>,
"onClick" | "tooltip" | "variant" | "children"
>;

export const SharePipelineButton = ({
showLabel,
displayLabel,
showTooltip = true,
...rest
}: SharePipelineButtonProps) => {
const notify = useToastNotification();

const handleShare = useCallback(() => {
copyToClipboard(window.location.href);
notify("Run URL copied to clipboard", "success");
}, [notify]);

return (
<TooltipButton
variant="outline"
onClick={handleShare}
tooltip={showTooltip ? "Share run" : undefined}
data-testid="share-pipeline-button"
{...rest}
>
<Icon name="Share2" />
{displayLabel ?? (showLabel ? "Share" : null)}
</TooltipButton>
);
};
26 changes: 15 additions & 11 deletions src/components/shared/CodeViewer/CodeViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ interface CodeViewerProps {
filename?: string;
fullscreen?: boolean;
scrollToBottom?: boolean;
allowFullscreen?: boolean;
onClose?: () => void;
}

Expand All @@ -24,6 +25,7 @@ const CodeViewer = ({
filename = "",
fullscreen = false,
scrollToBottom = false,
allowFullscreen = true,
onClose,
}: CodeViewerProps) => {
const [isFullscreen, setIsFullscreen] = useState(fullscreen);
Expand Down Expand Up @@ -65,17 +67,19 @@ const CodeViewer = ({
(Read Only)
</Text>
</div>
<Button
type="button"
variant="ghost"
size="icon"
onClick={handleToggleFullscreen}
className="text-muted-foreground hover:text-foreground"
title={isFullscreen ? "Exit fullscreen" : "View fullscreen"}
aria-label={isFullscreen ? "Exit fullscreen" : "View fullscreen"}
>
{isFullscreen ? <Icon name="X" /> : <Icon name="Maximize2" />}
</Button>
{allowFullscreen && (
<Button
type="button"
variant="ghost"
size="icon"
onClick={handleToggleFullscreen}
className="text-muted-foreground hover:text-foreground"
title={isFullscreen ? "Exit fullscreen" : "View fullscreen"}
aria-label={isFullscreen ? "Exit fullscreen" : "View fullscreen"}
>
{isFullscreen ? <Icon name="X" /> : <Icon name="Maximize2" />}
</Button>
)}
</div>
<div className="flex-1 relative">
<div
Expand Down
2 changes: 1 addition & 1 deletion src/components/shared/CopyText/CopyText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export const CopyText = ({

return (
<div
className="group cursor-pointer"
className="group max-w-full min-w-0 cursor-pointer"
onClick={handleCopy}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
Expand Down
10 changes: 7 additions & 3 deletions src/components/shared/InfoBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,12 @@ export const InfoBox = ({
data-testid={`info-box-${variant}`}
className={cn("border rounded-md p-2", styles.container, widthClass)}
>
<InlineStack align="space-between" blockAlign="start">
<InlineStack align="space-between" blockAlign="start" wrap="nowrap">
<Text
as="span"
size="sm"
weight="semibold"
className={cn("mb-1", styles.title)}
className={cn("mb-1 min-w-0 wrap-break-word", styles.title)}
data-testid="info-box-title"
>
{title}
Expand All @@ -87,7 +87,11 @@ export const InfoBox = ({
</Button>
)}
</InlineStack>
<div className={cn("text-sm", className)}>{children}</div>
<div
className={cn("text-sm wrap-break-word whitespace-normal", className)}
>
{children}
</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { ReactNode } from "react";
import { useState } from "react";

import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import { Icon } from "@/components/ui/icon";
import { BlockStack } from "@/components/ui/layout";
import { Text } from "@/components/ui/typography";

interface IOCollapsibleSectionProps {
title: string;
count: number;
children: ReactNode;
}

const IOCollapsibleSection = ({
title,
count,
children,
}: IOCollapsibleSectionProps) => {
const [open, setOpen] = useState(true);

return (
<Collapsible open={open} onOpenChange={setOpen} className="w-full">
<CollapsibleTrigger className="flex w-full cursor-pointer items-center gap-1 rounded-sm py-0.5 hover:bg-muted/50">
<Icon
name={open ? "ChevronDown" : "ChevronRight"}
size="xs"
className="text-muted-foreground"
/>
<Text size="md" weight="semibold">
{title}
</Text>
{count > 0 && (
<Text size="xs" tone="subdued">
{count}
</Text>
)}
</CollapsibleTrigger>

<CollapsibleContent>
<BlockStack gap="1" className="w-full pt-1">
{children}
</BlockStack>
</CollapsibleContent>
</Collapsible>
);
};

export default IOCollapsibleSection;
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import type { GetExecutionArtifactsResponse } from "@/api/types.gen";
import { BlockStack } from "@/components/ui/layout";
import { Heading } from "@/components/ui/typography";
import type { InputSpec, OutputSpec } from "@/utils/componentSpec";

import IOCell from "./IOCell/IOCell";
import IOCollapsibleSection from "./IOCollapsibleSection";

interface IOExtrasProps {
inputs?: InputSpec[];
Expand All @@ -27,21 +26,25 @@ const IOExtras = ({ inputs, outputs, artifacts }: IOExtrasProps) => {
return (
<>
{additionalInputs.length > 0 && (
<BlockStack gap="1" className="w-full">
<Heading level={1}>Additional Input Artifacts</Heading>
<IOCollapsibleSection
title="Additional Input Artifacts"
count={additionalInputs.length}
>
{additionalInputs.map(([key, artifact]) => (
<IOCell key={key} name={key} artifact={artifact} />
))}
</BlockStack>
</IOCollapsibleSection>
)}

{additionalOutputs.length > 0 && (
<BlockStack gap="1" className="w-full">
<Heading level={1}>Additional Output Artifacts</Heading>
<IOCollapsibleSection
title="Additional Output Artifacts"
count={additionalOutputs.length}
>
{additionalOutputs.map(([key, artifact]) => (
<IOCell key={key} name={key} artifact={artifact} />
))}
</BlockStack>
</IOCollapsibleSection>
)}
</>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { GetExecutionArtifactsResponse } from "@/api/types.gen";
import { BlockStack } from "@/components/ui/layout";
import { Heading, Paragraph } from "@/components/ui/typography";
import { Paragraph } from "@/components/ui/typography";
import type { InputSpec } from "@/utils/componentSpec";

import IOCell from "./IOCell/IOCell";
import IOCollapsibleSection from "./IOCollapsibleSection";

interface IOInputsProps {
inputs?: InputSpec[];
Expand All @@ -12,9 +12,7 @@ interface IOInputsProps {

const IOInputs = ({ inputs, artifacts }: IOInputsProps) => {
return (
<BlockStack gap="1" className="w-full">
<Heading level={1}>Inputs</Heading>

<IOCollapsibleSection title="Inputs" count={inputs?.length ?? 0}>
{(!inputs || inputs.length === 0) && (
<Paragraph tone="subdued" size="xs">
No inputs defined
Expand All @@ -33,7 +31,7 @@ const IOInputs = ({ inputs, artifacts }: IOInputsProps) => {
/>
);
})}
</BlockStack>
</IOCollapsibleSection>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { GetExecutionArtifactsResponse } from "@/api/types.gen";
import { BlockStack } from "@/components/ui/layout";
import { Heading, Paragraph } from "@/components/ui/typography";
import { Paragraph } from "@/components/ui/typography";
import type { OutputSpec } from "@/utils/componentSpec";

import IOCell from "./IOCell/IOCell";
import IOCollapsibleSection from "./IOCollapsibleSection";

interface IOOutputsProps {
outputs?: OutputSpec[];
Expand All @@ -12,9 +12,7 @@ interface IOOutputsProps {

const IOOutputs = ({ outputs, artifacts }: IOOutputsProps) => {
return (
<BlockStack gap="1" className="w-full">
<Heading level={1}>Outputs</Heading>

<IOCollapsibleSection title="Outputs" count={outputs?.length ?? 0}>
{(!outputs || outputs.length === 0) && (
<Paragraph tone="subdued" size="xs">
No outputs defined
Expand All @@ -33,7 +31,7 @@ const IOOutputs = ({ outputs, artifacts }: IOOutputsProps) => {
/>
);
})}
</BlockStack>
</IOCollapsibleSection>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ import { shouldStatusHaveLogs } from "@/utils/executionStatus";

const LogDisplay = ({
logs,
allowFullscreen,
}: {
logs: {
log_text?: string;
system_error_exception_full?: string;
};
allowFullscreen?: boolean;
}) => {
if (!logs.log_text && !logs.system_error_exception_full) {
return <div>No logs available</div>;
Expand All @@ -36,6 +38,7 @@ const LogDisplay = ({
language="text"
filename="Execution Logs"
scrollToBottom
allowFullscreen={allowFullscreen}
/>
</div>
)}
Expand All @@ -46,6 +49,7 @@ const LogDisplay = ({
language="text"
filename="System Error Logs"
scrollToBottom
allowFullscreen={allowFullscreen}
/>
</div>
)}
Expand All @@ -56,9 +60,11 @@ const LogDisplay = ({
const Logs = ({
executionId,
status,
allowFullscreen = true,
}: {
executionId?: string | number;
status?: string;
allowFullscreen?: boolean;
}) => {
const { backendUrl, configured, available } = useBackend();

Expand Down Expand Up @@ -132,7 +138,7 @@ const Logs = ({
return (
<div className="space-y-4 h-full">
<div className="font-mono text-sm whitespace-pre-wrap bg-gray-50 dark:bg-muted p-4 rounded-lg h-full min-h-0 flex-1">
{logs && <LogDisplay logs={logs} />}
{logs && <LogDisplay logs={logs} allowFullscreen={allowFullscreen} />}
</div>
</div>
);
Expand Down
Loading
Loading