diff --git a/src/components/shared/PipelineRunFiltersBar/PipelineRunFiltersBar.tsx b/src/components/shared/PipelineRunFiltersBar/PipelineRunFiltersBar.tsx
index 726813ea6..b221a6d59 100644
--- a/src/components/shared/PipelineRunFiltersBar/PipelineRunFiltersBar.tsx
+++ b/src/components/shared/PipelineRunFiltersBar/PipelineRunFiltersBar.tsx
@@ -2,6 +2,7 @@ import { useState } from "react";
import type { DateRange } from "react-day-picker";
import { AnnotationFilterInput } from "@/components/shared/AnnotationFilterInput/AnnotationFilterInput";
+import { StatusFilterSelect } from "@/components/shared/StatusFilterSelect/StatusFilterSelect";
import { Button } from "@/components/ui/button";
import { DatePickerWithRange } from "@/components/ui/date-picker";
import { Icon } from "@/components/ui/icon";
@@ -72,6 +73,13 @@ export function PipelineRunFiltersBar() {
)}
+
+ setFilter("status", value)}
+ />
+
+
{
+ describe("rendering", () => {
+ it("should render with default placeholder when no value", () => {
+ render();
+
+ expect(screen.getByRole("combobox")).toBeInTheDocument();
+ expect(screen.getByText("All statuses")).toBeInTheDocument();
+ });
+
+ it("should render with Running status", () => {
+ render();
+
+ expect(screen.getByText("Running")).toBeInTheDocument();
+ });
+
+ it("should render with Succeeded status", () => {
+ render();
+
+ expect(screen.getByText("Succeeded")).toBeInTheDocument();
+ });
+
+ it("should render with Failed status", () => {
+ render();
+
+ expect(screen.getByText("Failed")).toBeInTheDocument();
+ });
+
+ it("should render with Pending status", () => {
+ render();
+
+ expect(screen.getByText("Pending")).toBeInTheDocument();
+ });
+
+ it("should render with Cancelled status", () => {
+ render();
+
+ expect(screen.getByText("Cancelled")).toBeInTheDocument();
+ });
+
+ it("should render with System error status", () => {
+ render();
+
+ expect(screen.getByText("System error")).toBeInTheDocument();
+ });
+ });
+
+ describe("visual indicator", () => {
+ it("should show visual indicator when filter is active", () => {
+ const { container } = render(
+ ,
+ );
+
+ const trigger = container.querySelector("button");
+ expect(trigger).toHaveClass("ring-2");
+ });
+
+ it("should not show visual indicator when no filter", () => {
+ const { container } = render(
+ ,
+ );
+
+ const trigger = container.querySelector("button");
+ expect(trigger).not.toHaveClass("ring-2");
+ });
+ });
+
+ describe("custom className", () => {
+ it("should apply custom className to trigger", () => {
+ const { container } = render(
+ ,
+ );
+
+ const trigger = container.querySelector("button");
+ expect(trigger).toHaveClass("custom-class");
+ });
+ });
+});
diff --git a/src/components/shared/StatusFilterSelect/StatusFilterSelect.tsx b/src/components/shared/StatusFilterSelect/StatusFilterSelect.tsx
new file mode 100644
index 000000000..d038eb5d1
--- /dev/null
+++ b/src/components/shared/StatusFilterSelect/StatusFilterSelect.tsx
@@ -0,0 +1,65 @@
+import type { ContainerExecutionStatus } from "@/api/types.gen";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import { cn } from "@/lib/utils";
+import {
+ EXECUTION_STATUS_LABELS,
+ getExecutionStatusLabel,
+} from "@/utils/executionStatus";
+
+function isValidStatus(value: string): value is ContainerExecutionStatus {
+ return value in EXECUTION_STATUS_LABELS;
+}
+
+const STATUS_OPTIONS = Object.keys(EXECUTION_STATUS_LABELS).filter(
+ isValidStatus,
+);
+
+interface StatusFilterSelectProps {
+ value: ContainerExecutionStatus | undefined;
+ onChange: (value: ContainerExecutionStatus | undefined) => void;
+ className?: string;
+}
+
+export function StatusFilterSelect({
+ value,
+ onChange,
+ className,
+}: StatusFilterSelectProps) {
+ const handleValueChange = (newValue: string) => {
+ if (newValue === "all") {
+ onChange(undefined);
+ } else if (isValidStatus(newValue)) {
+ onChange(newValue);
+ }
+ };
+
+ const hasActiveFilter = value !== undefined;
+
+ return (
+
+ );
+}