diff --git a/src/components/Home/RunSection/RunRow.tsx b/src/components/Home/RunSection/RunRow.tsx
index 291479ccd..ff6fd1297 100644
--- a/src/components/Home/RunSection/RunRow.tsx
+++ b/src/components/Home/RunSection/RunRow.tsx
@@ -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";
@@ -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);
@@ -101,6 +117,9 @@ const RunRow = ({ run }: { run: PipelineRunResponse }) => {
{isTruncated ? createdByButtonWithTooltip : createdByButton}
+
+ {tags && tags.length > 0 && }
+
);
};
diff --git a/src/components/Home/RunSection/RunSection.tsx b/src/components/Home/RunSection/RunSection.tsx
index 7ac3891e7..72b96efcf 100644
--- a/src/components/Home/RunSection/RunSection.tsx
+++ b/src/components/Home/RunSection/RunSection.tsx
@@ -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 {
@@ -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";
@@ -198,9 +199,9 @@ export const RunSection = ({ onEmptyList, hideFilters }: RunSectionProps) => {
if (isLoading || isFetching || !ready) {
return (
-
+
Loading...
-
+
);
}
@@ -259,29 +260,30 @@ export const RunSection = ({ onEmptyList, hideFilters }: RunSectionProps) => {
if (!data?.pipeline_runs || data?.pipeline_runs?.length === 0) {
return (
-
+
{searchMarkup}
{createdByValue ? (
-
+
No runs found for user: {createdByValue} .
-
+
) : (
- No runs found. Run a pipeline to see it here.
+ No runs found. Run a pipeline to see it here.
)}
-
+
);
}
return (
-
+
{searchMarkup}
- Name
- Status
- Date
- Initiated By
+ Name
+ Status
+ Date
+ Initiated By
+ Tags
@@ -292,34 +294,38 @@ export const RunSection = ({ onEmptyList, hideFilters }: RunSectionProps) => {
{(data.next_page_token || previousPageTokens.length > 0) && (
-
-
+
+
-
+
-
+
Previous
-
+
Next
-
+
-
+
)}
-
+
);
};
diff --git a/src/components/PipelineRun/RunDetails.tsx b/src/components/PipelineRun/RunDetails.tsx
index faf33f624..2dccf4556 100644
--- a/src/components/PipelineRun/RunDetails.tsx
+++ b/src/components/PipelineRun/RunDetails.tsx
@@ -15,7 +15,9 @@ import { useExecutionData } from "@/providers/ExecutionDataProvider";
import {
FLEX_NODES_ANNOTATION,
getAnnotationValue,
+ getPipelineTagsFromSpec,
PIPELINE_NOTES_ANNOTATION,
+ PIPELINE_TAGS_ANNOTATION,
} from "@/utils/annotations";
import {
flattenExecutionStatusStats,
@@ -23,9 +25,14 @@ import {
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();
@@ -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))
@@ -142,6 +150,10 @@ export const RunDetails = () => {
)}
+
+
+
+
);
};
diff --git a/src/components/PipelineRun/RunNotesEditor.tsx b/src/components/PipelineRun/RunNotesEditor.tsx
index 17b2de026..11107553b 100644
--- a/src/components/PipelineRun/RunNotesEditor.tsx
+++ b/src/components/PipelineRun/RunNotesEditor.tsx
@@ -6,7 +6,7 @@ import { Paragraph } from "@/components/ui/typography";
import { useBackend } from "@/providers/BackendProvider";
import {
fetchRunAnnotations,
- updateRunNotes,
+ updateRunAnnotation,
} from "@/services/pipelineRunService";
import {
getAnnotationValue,
@@ -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();
},
diff --git a/src/components/shared/AnnouncementBanners.tsx b/src/components/shared/AnnouncementBanners.tsx
index 529d1e11e..152f24a63 100644
--- a/src/components/shared/AnnouncementBanners.tsx
+++ b/src/components/shared/AnnouncementBanners.tsx
@@ -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 {
diff --git a/src/components/shared/Submitters/Oasis/OasisSubmitter.tsx b/src/components/shared/Submitters/Oasis/OasisSubmitter.tsx
index f7d47228b..72e37879e 100644
--- a/src/components/shared/Submitters/Oasis/OasisSubmitter.tsx
+++ b/src/components/shared/Submitters/Oasis/OasisSubmitter.tsx
@@ -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,
@@ -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) => {
@@ -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?.();
diff --git a/src/components/shared/Tags/TagList.tsx b/src/components/shared/Tags/TagList.tsx
index 11e7dfc17..8ff99a876 100644
--- a/src/components/shared/Tags/TagList.tsx
+++ b/src/components/shared/Tags/TagList.tsx
@@ -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[];
@@ -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 (
+
+ None
+
+ );
+ }
+
return (
{visibleTags.map((tag) => (
diff --git a/src/services/pipelineRunService.ts b/src/services/pipelineRunService.ts
index f6d97c9ee..adc9e7abd 100644
--- a/src/services/pipelineRunService.ts
+++ b/src/services/pipelineRunService.ts
@@ -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,
@@ -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",
+ });
};
diff --git a/src/utils/annotations.ts b/src/utils/annotations.ts
index cfb146946..b28ccc758 100644
--- a/src/utils/annotations.ts
+++ b/src/utils/annotations.ts
@@ -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) ?? "";