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
32 changes: 0 additions & 32 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-collapsible": "^1.1.3",
"@radix-ui/react-dialog": "^1.1.14",
"@radix-ui/react-hover-card": "^1.1.15",
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-radio-group": "^1.3.8",
Expand Down
34 changes: 8 additions & 26 deletions src/components/Home/RunSection/RunRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,8 @@ import { useNavigate } from "@tanstack/react-router";
import { type MouseEvent } from "react";

import type { PipelineRunResponse } from "@/api/types.gen";
import { StatusBar, StatusIcon, StatusText } from "@/components/shared/Status";
import { StatusBar, StatusIcon } from "@/components/shared/Status";
import { Button } from "@/components/ui/button";
import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from "@/components/ui/hover-card";
import { TableCell, TableRow } from "@/components/ui/table";
import {
Tooltip,
Expand All @@ -18,11 +13,8 @@ import {
import { Paragraph } from "@/components/ui/typography";
import useToastNotification from "@/hooks/useToastNotification";
import { APP_ROUTES } from "@/routes/router";
import {
convertExecutionStatsToStatusCounts,
getRunStatus,
} from "@/services/executionService";
import { convertUTCToLocalTime, formatDate } from "@/utils/date";
import { getOverallExecutionStatusFromStats } from "@/utils/executionStatus";

const RunRow = ({ run }: { run: PipelineRunResponse }) => {
const navigate = useNavigate();
Expand All @@ -42,8 +34,8 @@ const RunRow = ({ run }: { run: PipelineRunResponse }) => {
notify(`"${createdBy}" copied to clipboard`, "success");
};

const statusCounts = convertExecutionStatsToStatusCounts(
run.execution_status_stats,
const overallStatus = getOverallExecutionStatusFromStats(
run.execution_status_stats ?? undefined,
);

const clickThroughUrl = `${APP_ROUTES.RUNS}/${runId}`;
Expand Down Expand Up @@ -77,26 +69,16 @@ const RunRow = ({ run }: { run: PipelineRunResponse }) => {
className="cursor-pointer text-gray-500 text-xs"
>
<TableCell className="text-sm flex items-center gap-2">
<StatusIcon status={getRunStatus(statusCounts)} />
<StatusIcon status={overallStatus} />
<Paragraph className="truncate max-w-[400px]" title={name}>
{name}
</Paragraph>
<span>{`#${runId}`}</span>
</TableCell>
<TableCell>
<HoverCard openDelay={100}>
<HoverCardTrigger>
<div className="w-2/3">
<StatusBar statusCounts={statusCounts} />
</div>
</HoverCardTrigger>
<HoverCardContent>
<div className="flex flex-col gap-2">
<div className="text-sm font-bold">Status</div>
<StatusText statusCounts={statusCounts} />
</div>
</HoverCardContent>
</HoverCard>
<div className="w-2/3">
<StatusBar executionStatusStats={run.execution_status_stats} />
</div>
</TableCell>
<TableCell>
{run.created_at
Expand Down
75 changes: 28 additions & 47 deletions src/components/PipelineRun/RunDetails.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { useBackend } from "@/providers/BackendProvider";
import { ComponentSpecProvider } from "@/providers/ComponentSpecProvider";
import { ContextPanelProvider } from "@/providers/ContextPanelProvider";
import { ExecutionDataProvider } from "@/providers/ExecutionDataProvider";
import * as executionService from "@/services/executionService";
import type { ComponentSpec } from "@/utils/componentSpec";

import { RunDetails } from "./RunDetails";
Expand All @@ -41,10 +40,6 @@ vi.mock("@/services/executionService", async (importOriginal) => {
await importOriginal<typeof import("@/services/executionService")>();
return {
...actual,
countTaskStatuses: vi.fn(),
getRunStatus: vi.fn(),
isStatusInProgress: vi.fn(),
isStatusComplete: vi.fn(),
};
});
vi.mock("@/providers/BackendProvider");
Expand Down Expand Up @@ -140,22 +135,6 @@ describe("<RunDetails/>", () => {

queryClient.setQueryData(["pipeline-run-metadata", "123"], mockPipelineRun);

vi.mocked(executionService.countTaskStatuses).mockReturnValue({
total: 2,
succeeded: 1,
failed: 0,
running: 1,
waiting: 0,
skipped: 0,
cancelled: 0,
});

vi.mocked(executionService.getRunStatus).mockReturnValue("RUNNING");

vi.mocked(executionService.isStatusInProgress).mockReturnValue(true);

vi.mocked(executionService.isStatusComplete).mockReturnValue(false);

vi.mocked(useBackend).mockReturnValue({
configured: true,
available: true,
Expand Down Expand Up @@ -249,21 +228,22 @@ describe("<RunDetails/>", () => {
});

test("should NOT render cancel button when status is not RUNNING", async () => {
// arrange
vi.mocked(executionService.countTaskStatuses).mockReturnValue({
total: 2,
succeeded: 1,
failed: 0,
running: 0,
waiting: 0,
skipped: 0,
cancelled: 1,
// arrange - mock a cancelled execution state (no in-progress statuses)
vi.mocked(usePipelineRunData).mockReturnValue({
executionData: {
details: mockExecutionDetails,
state: {
child_execution_status_stats: {
execution1: { SUCCEEDED: 1 },
execution2: { CANCELLED: 1 },
},
},
},
rootExecutionId: "456",
isLoading: false,
error: null,
});

vi.mocked(executionService.getRunStatus).mockReturnValue("CANCELLED");
vi.mocked(executionService.isStatusInProgress).mockReturnValue(false);
vi.mocked(executionService.isStatusComplete).mockReturnValue(true);

// act
renderWithProviders(<RunDetails />);

Expand Down Expand Up @@ -299,21 +279,22 @@ describe("<RunDetails/>", () => {

describe("Rerun Pipeline Run Button", () => {
test("should render rerun button when status is CANCELLED", async () => {
// arrange
vi.mocked(executionService.countTaskStatuses).mockReturnValue({
total: 2,
succeeded: 1,
failed: 0,
running: 0,
waiting: 0,
skipped: 0,
cancelled: 1,
// arrange - mock a completed execution state (no in-progress statuses)
vi.mocked(usePipelineRunData).mockReturnValue({
executionData: {
details: mockExecutionDetails,
state: {
child_execution_status_stats: {
execution1: { SUCCEEDED: 1 },
execution2: { CANCELLED: 1 },
},
},
},
rootExecutionId: "456",
isLoading: false,
error: null,
});

vi.mocked(executionService.getRunStatus).mockReturnValue("CANCELLED");
vi.mocked(executionService.isStatusInProgress).mockReturnValue(false);
vi.mocked(executionService.isStatusComplete).mockReturnValue(true);

// act
renderWithProviders(<RunDetails />);

Expand Down
55 changes: 30 additions & 25 deletions src/components/PipelineRun/RunDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
import {
ActionBlock,
type ActionOrReactNode,
} from "@/components/shared/ContextPanel/Blocks/ActionBlock";
import { ContentBlock } from "@/components/shared/ContextPanel/Blocks/ContentBlock";
import { ListBlock } from "@/components/shared/ContextPanel/Blocks/ListBlock";
import { TextBlock } from "@/components/shared/ContextPanel/Blocks/TextBlock";
import { CopyText } from "@/components/shared/CopyText/CopyText";
import PipelineIO from "@/components/shared/Execution/PipelineIO";
import { InfoBox } from "@/components/shared/InfoBox";
import { LoadingScreen } from "@/components/shared/LoadingScreen";
import { StatusBar } from "@/components/shared/Status";
import { TaskImplementation } from "@/components/shared/TaskDetails";
import { BlockStack, InlineStack } from "@/components/ui/layout";
import { Text } from "@/components/ui/typography";
import { useCheckComponentSpecFromPath } from "@/hooks/useCheckComponentSpecFromPath";
Expand All @@ -7,24 +19,13 @@ import { useBackend } from "@/providers/BackendProvider";
import { useComponentSpec } from "@/providers/ComponentSpecProvider";
import { useExecutionData } from "@/providers/ExecutionDataProvider";
import {
countTaskStatuses,
getRunStatus,
isStatusComplete,
isStatusInProgress,
} from "@/services/executionService";
countInProgressFromStats,
flattenExecutionStatusStats,
getExecutionStatusLabel,
getOverallExecutionStatusFromStats,
isExecutionComplete,
} from "@/utils/executionStatus";

import {
ActionBlock,
type ActionOrReactNode,
} from "../shared/ContextPanel/Blocks/ActionBlock";
import { ContentBlock } from "../shared/ContextPanel/Blocks/ContentBlock";
import { ListBlock } from "../shared/ContextPanel/Blocks/ListBlock";
import { TextBlock } from "../shared/ContextPanel/Blocks/TextBlock";
import PipelineIO from "../shared/Execution/PipelineIO";
import { InfoBox } from "../shared/InfoBox";
import { LoadingScreen } from "../shared/LoadingScreen";
import { StatusBar, StatusText } from "../shared/Status";
import { TaskImplementation } from "../shared/TaskDetails";
import { CancelPipelineRunButton } from "./components/CancelPipelineRunButton";
import { ClonePipelineButton } from "./components/ClonePipelineButton";
import { InspectPipelineButton } from "./components/InspectPipelineButton";
Expand Down Expand Up @@ -79,11 +80,16 @@ export const RunDetails = () => {
);
}

const statusCounts = countTaskStatuses(details, state);
const runStatus = getRunStatus(statusCounts);
const hasRunningTasks = statusCounts.running > 0;
const isInProgress = isStatusInProgress(runStatus) || hasRunningTasks;
const isComplete = isStatusComplete(runStatus);
const executionStatusStats =
metadata?.execution_status_stats ??
flattenExecutionStatusStats(state.child_execution_status_stats);

const overallStatus =
getOverallExecutionStatusFromStats(executionStatusStats);
const statusLabel = getExecutionStatusLabel(overallStatus);

const isInProgress = countInProgressFromStats(executionStatusStats) > 0;
const isComplete = isExecutionComplete(executionStatusStats);

const annotations = componentSpec.metadata?.annotations || {};

Expand Down Expand Up @@ -154,11 +160,10 @@ export const RunDetails = () => {
<ContentBlock title="Status">
<InlineStack gap="2" blockAlign="center" className="mb-1">
<Text size="sm" weight="semibold">
{runStatus}
{statusLabel}
</Text>
<StatusText statusCounts={statusCounts} />
</InlineStack>
<StatusBar statusCounts={statusCounts} />
<StatusBar executionStatusStats={executionStatusStats} />
</ContentBlock>

{Object.keys(annotations).length > 0 && (
Expand Down
27 changes: 25 additions & 2 deletions src/components/shared/PipelineRunDisplay/RunOverview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,32 @@ import { useNavigate } from "@tanstack/react-router";
import { StatusBar, StatusText } from "@/components/shared/Status/";
import { cn } from "@/lib/utils";
import { APP_ROUTES } from "@/routes/router";
import type { PipelineRun } from "@/types/pipelineRun";
import type { PipelineRun, TaskStatusCounts } from "@/types/pipelineRun";
import { formatDate } from "@/utils/date";
import type { ExecutionStatusStats } from "@/utils/executionStatus";

import { PipelineRunStatus } from "./components/PipelineRunStatus";

/**
* Convert TaskStatusCounts (lowercase keys) to ExecutionStatusStats (uppercase keys)
* for use with the simplified StatusBar component.
*/
const statusCountsToExecutionStats = (
counts: TaskStatusCounts | undefined,
): ExecutionStatusStats | undefined => {
if (!counts) return undefined;

const stats: ExecutionStatusStats = {};
if (counts.succeeded > 0) stats.SUCCEEDED = counts.succeeded;
if (counts.failed > 0) stats.FAILED = counts.failed;
if (counts.running > 0) stats.RUNNING = counts.running;
if (counts.pending > 0) stats.PENDING = counts.pending;
if (counts.waiting > 0) stats.WAITING_FOR_UPSTREAM = counts.waiting;
if (counts.skipped > 0) stats.SKIPPED = counts.skipped;
if (counts.cancelled > 0) stats.CANCELLED = counts.cancelled;
return stats;
};

interface RunOverviewProps {
run: PipelineRun;
config?: {
Expand Down Expand Up @@ -90,7 +111,9 @@ const RunOverview = ({ run, config, className = "" }: RunOverviewProps) => {
</div>

{combinedConfig?.showTaskStatusBar && (
<StatusBar statusCounts={run.statusCounts} />
<StatusBar
executionStatusStats={statusCountsToExecutionStats(run.statusCounts)}
/>
)}
</div>
);
Expand Down
Loading