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
19 changes: 19 additions & 0 deletions src/components/Home/RunSection/RunRow.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { useQuery } from "@tanstack/react-query";
import { useNavigate } from "@tanstack/react-router";
import { type MouseEvent } from "react";

import type { PipelineRunResponse } from "@/api/types.gen";
import { StatusBar, StatusIcon } from "@/components/shared/Status";
import { TagList } from "@/components/shared/Tags/TagList";
import { Button } from "@/components/ui/button";
import { InlineStack } from "@/components/ui/layout";
import { TableCell, TableRow } from "@/components/ui/table";
Expand All @@ -13,17 +15,31 @@ import {
} from "@/components/ui/tooltip";
import { Paragraph } from "@/components/ui/typography";
import useToastNotification from "@/hooks/useToastNotification";
import { useBackend } from "@/providers/BackendProvider";
import { APP_ROUTES } from "@/routes/router";
import { fetchRunAnnotations } from "@/services/pipelineRunService";
import { getPipelineTagsFromAnnotations } from "@/utils/annotations";
import { TWENTY_FOUR_HOURS_IN_MS } from "@/utils/constants";
import { formatDate } from "@/utils/date";
import { getOverallExecutionStatusFromStats } from "@/utils/executionStatus";

const RunRow = ({ run }: { run: PipelineRunResponse }) => {
const navigate = useNavigate();
const notify = useToastNotification();
const { backendUrl } = useBackend();

const runId = `${run.id}`;

const { data: annotations } = useQuery({
queryKey: ["pipeline-run-annotations", runId],
queryFn: () => fetchRunAnnotations(runId, backendUrl),
enabled: !!runId,
refetchOnWindowFocus: false,
staleTime: TWENTY_FOUR_HOURS_IN_MS,
});

const name = run.pipeline_name ?? "Unknown pipeline";
const tags = getPipelineTagsFromAnnotations(annotations);

const createdBy = run.created_by ?? "Unknown user";
const truncatedCreatedBy = truncateMiddle(createdBy);
Expand Down Expand Up @@ -101,6 +117,9 @@ const RunRow = ({ run }: { run: PipelineRunResponse }) => {
<TableCell>
{isTruncated ? createdByButtonWithTooltip : createdByButton}
</TableCell>
<TableCell className="max-w-64">
{tags && tags.length > 0 && <TagList tags={tags} />}
</TableCell>
</TableRow>
);
};
Expand Down
50 changes: 28 additions & 22 deletions src/components/Home/RunSection/RunSection.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { useQuery } from "@tanstack/react-query";
import { useLocation, useNavigate, useSearch } from "@tanstack/react-router";
import { ChevronFirst, ChevronLeft, ChevronRight } from "lucide-react";
import { useEffect, useRef, useState } from "react";

import type { ListPipelineJobsResponse } from "@/api/types.gen";
import { InfoBox } from "@/components/shared/InfoBox";
import { useFlagValue } from "@/components/shared/Settings/useFlags";
import { Button } from "@/components/ui/button";
import { Icon } from "@/components/ui/icon";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { InlineStack } from "@/components/ui/layout";
import { BlockStack, InlineStack } from "@/components/ui/layout";
import { Spinner } from "@/components/ui/spinner";
import { Switch } from "@/components/ui/switch";
import {
Expand All @@ -19,6 +19,7 @@ import {
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Text } from "@/components/ui/typography";
import { useBackend } from "@/providers/BackendProvider";
import { getBackendStatusString } from "@/utils/backend";
import { fetchWithErrorHandling } from "@/utils/fetchWithErrorHandling";
Expand Down Expand Up @@ -198,9 +199,9 @@ export const RunSection = ({ onEmptyList, hideFilters }: RunSectionProps) => {

if (isLoading || isFetching || !ready) {
return (
<div className="flex gap-2 items-center">
<InlineStack gap="2">
<Spinner /> Loading...
</div>
</InlineStack>
);
}

Expand Down Expand Up @@ -259,29 +260,30 @@ export const RunSection = ({ onEmptyList, hideFilters }: RunSectionProps) => {

if (!data?.pipeline_runs || data?.pipeline_runs?.length === 0) {
return (
<div className="flex flex-col gap-2">
<BlockStack gap="4">
{searchMarkup}
{createdByValue ? (
<div>
<Text>
No runs found for user: <strong>{createdByValue}</strong>.
</div>
</Text>
) : (
<div>No runs found. Run a pipeline to see it here.</div>
<Text>No runs found. Run a pipeline to see it here.</Text>
)}
</div>
</BlockStack>
);
}

return (
<div>
<BlockStack gap="4">
{searchMarkup}
<Table>
<TableHeader>
<TableRow className="text-xs">
<TableHead className="w-1/3">Name</TableHead>
<TableHead className="w-1/3">Status</TableHead>
<TableHead className="w-1/6">Date</TableHead>
<TableHead className="w-1/6">Initiated By</TableHead>
<TableHead className="w-1/4">Name</TableHead>
<TableHead className="w-1/4">Status</TableHead>
<TableHead className="w-3/20">Date</TableHead>
<TableHead className="w-3/20">Initiated By</TableHead>
<TableHead className="w-1/5">Tags</TableHead>
</TableRow>
</TableHeader>
<TableBody>
Expand All @@ -292,34 +294,38 @@ export const RunSection = ({ onEmptyList, hideFilters }: RunSectionProps) => {
</Table>

{(data.next_page_token || previousPageTokens.length > 0) && (
<div className="flex justify-between items-center mt-4">
<div className="flex gap-2">
<InlineStack
align="space-between"
blockAlign="center"
className="w-full"
>
<InlineStack gap="2">
<Button
variant="outline"
onClick={handleFirstPage}
disabled={!pageToken}
>
<ChevronFirst className="h-4 w-4" />
<Icon name="ChevronFirst" />
</Button>
<Button
variant="outline"
onClick={handlePreviousPage}
disabled={previousPageTokens.length === 0}
>
<ChevronLeft className="h-4 w-4 mr-2" />
<Icon name="ChevronLeft" />
Previous
</Button>
</div>
</InlineStack>
<Button
variant="outline"
onClick={handleNextPage}
disabled={!data.next_page_token}
>
Next
<ChevronRight className="h-4 w-4 ml-2" />
<Icon name="ChevronRight" />
</Button>
</div>
</InlineStack>
)}
</div>
</BlockStack>
);
};
14 changes: 13 additions & 1 deletion src/components/PipelineRun/RunDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,24 @@ import { useExecutionData } from "@/providers/ExecutionDataProvider";
import {
FLEX_NODES_ANNOTATION,
getAnnotationValue,
getPipelineTagsFromSpec,
PIPELINE_NOTES_ANNOTATION,
PIPELINE_TAGS_ANNOTATION,
} from "@/utils/annotations";
import {
flattenExecutionStatusStats,
getExecutionStatusLabel,
getOverallExecutionStatusFromStats,
} from "@/utils/executionStatus";

import { TagList } from "../shared/Tags/TagList";
import { RunNotesEditor } from "./RunNotesEditor";

const EXCLUDED_ANNOTATIONS = [PIPELINE_NOTES_ANNOTATION, FLEX_NODES_ANNOTATION];
const EXCLUDED_ANNOTATIONS = [
PIPELINE_NOTES_ANNOTATION,
FLEX_NODES_ANNOTATION,
PIPELINE_TAGS_ANNOTATION,
];

export const RunDetails = () => {
const { configured } = useBackend();
Expand Down Expand Up @@ -76,6 +83,7 @@ export const RunDetails = () => {
pipelineAnnotations,
PIPELINE_NOTES_ANNOTATION,
);
const tags = getPipelineTagsFromSpec(componentSpec);

const displayedAnnotations = Object.entries(pipelineAnnotations)
.filter(([key]) => !EXCLUDED_ANNOTATIONS.includes(key))
Expand Down Expand Up @@ -142,6 +150,10 @@ export const RunDetails = () => {
)}
</BlockStack>
</ContentBlock>

<ContentBlock title="Tags">
<TagList tags={tags} />
</ContentBlock>
</BlockStack>
);
};
8 changes: 6 additions & 2 deletions src/components/PipelineRun/RunNotesEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Paragraph } from "@/components/ui/typography";
import { useBackend } from "@/providers/BackendProvider";
import {
fetchRunAnnotations,
updateRunNotes,
updateRunAnnotation,
} from "@/services/pipelineRunService";
import {
getAnnotationValue,
Expand Down Expand Up @@ -35,7 +35,11 @@ export const RunNotesEditor = ({ runId, readOnly }: RunNotesEditorProps) => {
});

const { mutate: saveRunNotes, isPending } = useMutation({
mutationFn: (runId: string) => updateRunNotes(runId, backendUrl, value),
mutationFn: (runId: string) =>
updateRunAnnotation(runId, backendUrl, {
key: PIPELINE_RUN_NOTES_ANNOTATION,
value: value,
}),
onSuccess: () => {
refetch();
},
Expand Down
3 changes: 2 additions & 1 deletion src/components/shared/AnnouncementBanners.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import "@/config/announcements";

import { useState } from "react";

import { InfoBox } from "@/components/shared/InfoBox";
import { BlockStack } from "@/components/ui/layout";
import "@/config/announcements";
import { getStorage } from "@/utils/typedStorage";

interface DismissedAnnouncementsStorage {
Expand Down
26 changes: 24 additions & 2 deletions src/components/shared/Submitters/Oasis/OasisSubmitter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@ import useToastNotification from "@/hooks/useToastNotification";
import { cn } from "@/lib/utils";
import { useBackend } from "@/providers/BackendProvider";
import { APP_ROUTES } from "@/routes/router";
import { updateRunNotes } from "@/services/pipelineRunService";
import { updateRunAnnotation } from "@/services/pipelineRunService";
import type { PipelineRun } from "@/types/pipelineRun";
import {
getPipelineTagsFromSpec,
PIPELINE_RUN_NOTES_ANNOTATION,
PIPELINE_TAGS_ANNOTATION,
} from "@/utils/annotations";
import {
type ArgumentType,
type ComponentSpec,
Expand Down Expand Up @@ -110,7 +115,18 @@ const OasisSubmitter = ({

const { mutate: saveNotes } = useMutation({
mutationFn: (runId: string) =>
updateRunNotes(runId, backendUrl, runNotes.current),
updateRunAnnotation(runId, backendUrl, {
key: PIPELINE_RUN_NOTES_ANNOTATION,
value: runNotes.current,
}),
});

const { mutate: saveTags } = useMutation({
mutationFn: (runId: string) =>
updateRunAnnotation(runId, backendUrl, {
key: PIPELINE_TAGS_ANNOTATION,
value: getPipelineTagsFromSpec(componentSpec).join(","),
}),
});

const handleError = (message: string) => {
Expand Down Expand Up @@ -147,6 +163,12 @@ const OasisSubmitter = ({
if (runNotes.current.trim() !== "") {
saveNotes(response.id.toString());
}

const tags = getPipelineTagsFromSpec(componentSpec);
if (tags.length > 0) {
saveTags(response.id.toString());
}

setSubmitSuccess(true);
setCooldownTime(3);
onSubmitComplete?.();
Expand Down
9 changes: 9 additions & 0 deletions src/components/shared/Tags/TagList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useState } from "react";

import { Badge } from "@/components/ui/badge";
import { InlineStack } from "@/components/ui/layout";
import { Paragraph } from "@/components/ui/typography";

interface TagListProps {
tags: string[];
Expand All @@ -15,6 +16,14 @@ export const TagList = ({ tags }: TagListProps) => {
const hasMoreTags = tags.length > MAX_VISIBLE_TAGS;
const visibleTags = showAllTags ? tags : tags.slice(0, MAX_VISIBLE_TAGS);

if (visibleTags.length === 0) {
return (
<Paragraph size="xs" tone="subdued">
None
</Paragraph>
);
}

return (
<InlineStack gap="2" wrap="wrap">
{visibleTags.map((tag) => (
Expand Down
22 changes: 12 additions & 10 deletions src/services/pipelineRunService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import type {
} from "@/api/types.gen";
import { APP_ROUTES } from "@/routes/router";
import type { PipelineRun } from "@/types/pipelineRun";
import { PIPELINE_RUN_NOTES_ANNOTATION } from "@/utils/annotations";
import { removeCachingStrategyFromSpec } from "@/utils/cache";
import {
type ComponentSpec,
Expand Down Expand Up @@ -229,17 +228,20 @@ export const fetchRunAnnotations = async (
return fetchWithErrorHandling(url);
};

export const updateRunNotes = async (
export const updateRunAnnotation = async (
runId: string,
backendUrl: string,
notes: string,
annotation: {
key: string;
value: string;
},
) => {
await fetchWithErrorHandling(
`${backendUrl}/api/pipeline_runs/${runId}/annotations/${PIPELINE_RUN_NOTES_ANNOTATION}?value=${encodeURIComponent(
notes,
)}`,
{
method: "PUT",
},
const url = new URL(
`${backendUrl}/api/pipeline_runs/${runId}/annotations/${annotation.key}`,
);
url.searchParams.append("value", annotation.value);

await fetchWithErrorHandling(url.toString(), {
method: "PUT",
});
};
16 changes: 16 additions & 0 deletions src/utils/annotations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,22 @@ export function getPipelineTagsFromSpec(
}

const annotations = componentSpec.metadata?.annotations;

return getPipelineTagsFromAnnotations(annotations);
}

/**
* Extract the pipeline tags from annotations.
* @param annotations - The annotations object
* @returns The pipeline tags as an array of strings
*/
export function getPipelineTagsFromAnnotations(
annotations?: Annotations,
): string[] {
if (!annotations) {
return [];
}

const tagsString =
getAnnotationValue(annotations, PIPELINE_TAGS_ANNOTATION) ?? "";

Expand Down
Loading