Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[UI v2] feat: Adds flow run data table filter components #17148

Merged
merged 1 commit into from
Feb 18, 2025
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 @@ -34,7 +34,6 @@ describe("CronScheduleForm", () => {
await user.click(screen.getByLabelText(/active/i));
await user.clear(screen.getByLabelText(/value/i));
await user.type(screen.getByLabelText(/value/i), "* * * * 1/2");
screen.logTestingPlaygroundURL();

await user.click(screen.getByRole("switch", { name: /day or/i }));
await user.click(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ describe("CronScheduleForm", () => {
await user.click(screen.getByRole("combobox", { name: /interval/i }));
await user.click(screen.getByRole("option", { name: /hours/i }));

screen.logTestingPlaygroundURL();

await user.click(
screen.getByRole("combobox", { name: /select timezone/i }),
);
Expand Down
15 changes: 15 additions & 0 deletions ui-v2/src/components/flow-runs/data-table/run-name-search.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Icon } from "@/components/ui/icons";
import { Input, type InputProps } from "@/components/ui/input";

export const RunNameSearch = (props: InputProps) => {
return (
<div className="relative">
<Input placeholder="Search by run name" className="pl-10" {...props} />
<Icon
id="Search"
className="absolute left-3 top-2.5 text-muted-foreground"
size={18}
/>
</div>
);
};
74 changes: 74 additions & 0 deletions ui-v2/src/components/flow-runs/data-table/sort-filter.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { beforeAll, describe, expect, it, vi } from "vitest";

import { mockPointerEvents } from "@tests/utils/browser";
import { SortFilter } from "./sort-filter";

describe("FlowRunsDataTable -- SortFilter", () => {
beforeAll(mockPointerEvents);

it("returns correct sort filter for Newest to oldest", async () => {
// Setup
const user = userEvent.setup();
const mockOnSelectFn = vi.fn();
render(<SortFilter value={undefined} onSelect={mockOnSelectFn} />);

// Test
await user.click(
screen.getByRole("combobox", { name: /flow run sort order/i }),
);
await user.click(screen.getByRole("option", { name: /newest to oldest/i }));

// Assert
expect(mockOnSelectFn).toBeCalledWith("START_TIME_ASC");
});

it("returns correct sort filter for Oldest to newest", async () => {
// Setup
const user = userEvent.setup();
const mockOnSelectFn = vi.fn();
render(<SortFilter value={undefined} onSelect={mockOnSelectFn} />);

// Test
await user.click(
screen.getByRole("combobox", { name: /flow run sort order/i }),
);
await user.click(screen.getByRole("option", { name: /oldest to newest/i }));

// Assert
expect(mockOnSelectFn).toBeCalledWith("START_TIME_DESC");
});

it("returns correct sort filter for A to Z", async () => {
// Setup
const user = userEvent.setup();
const mockOnSelectFn = vi.fn();
render(<SortFilter value={undefined} onSelect={mockOnSelectFn} />);

// Test
await user.click(
screen.getByRole("combobox", { name: /flow run sort order/i }),
);
await user.click(screen.getByRole("option", { name: /a to z/i }));

// Assert
expect(mockOnSelectFn).toBeCalledWith("NAME_ASC");
});

it("returns correct sort filter for Z to A", async () => {
// Setup
const user = userEvent.setup();
const mockOnSelectFn = vi.fn();
render(<SortFilter value={undefined} onSelect={mockOnSelectFn} />);

// Test
await user.click(
screen.getByRole("combobox", { name: /flow run sort order/i }),
);
await user.click(screen.getByRole("option", { name: /z to a/i }));

// Assert
expect(mockOnSelectFn).toBeCalledWith("NAME_DESC");
});
});
34 changes: 34 additions & 0 deletions ui-v2/src/components/flow-runs/data-table/sort-filter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";

type SortFilters =
| "START_TIME_ASC"
| "START_TIME_DESC"
| "NAME_ASC"
| "NAME_DESC";

type SortFilterProps = {
onSelect: (filter: SortFilters) => void;
value: undefined | SortFilters;
};

export const SortFilter = ({ value, onSelect }: SortFilterProps) => {
return (
<Select value={value} onValueChange={onSelect}>
<SelectTrigger aria-label="Flow run sort order">
<SelectValue placeholder="Sort by" />
</SelectTrigger>
<SelectContent>
<SelectItem value="START_TIME_ASC">Newest to oldest</SelectItem>
<SelectItem value="START_TIME_DESC">Oldest to newest</SelectItem>
<SelectItem value="NAME_ASC">A to Z</SelectItem>
<SelectItem value="NAME_DESC">Z to A</SelectItem>
</SelectContent>
</Select>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { Meta, StoryObj } from "@storybook/react";

import { useState } from "react";
import { type FlowRunState, StateFilter } from "./state-filter";

const meta: Meta<typeof StateFilter> = {
title: "Components/FlowRuns/DataTable/StateFilter",
component: StateFilterStory,
};
export default meta;

function StateFilterStory() {
const [filters, setFilters] = useState<Set<FlowRunState>>();
return <StateFilter selectedFilters={filters} onSelectFilter={setFilters} />;
}

export const story: StoryObj = { name: "StateFilter" };
87 changes: 87 additions & 0 deletions ui-v2/src/components/flow-runs/data-table/state-filter.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { beforeAll, describe, expect, it } from "vitest";

import { mockPointerEvents } from "@tests/utils/browser";
import { useState } from "react";
import { type FlowRunState, StateFilter } from "./state-filter";

describe("FlowRunsDataTable -- StateFilter", () => {
beforeAll(mockPointerEvents);

const TestStateFilter = () => {
const [filters, setFilters] = useState<Set<FlowRunState>>();
return (
<StateFilter selectedFilters={filters} onSelectFilter={setFilters} />
);
};

it("selects All except scheduled option", async () => {
// Setup
const user = userEvent.setup();
render(<TestStateFilter />);
// Test
await user.click(screen.getByRole("button", { name: /all run states/i }));
await user.click(
screen.getByRole("menuitem", { name: /all except scheduled/i }),
);
await user.keyboard("{Escape}");

// Assert
expect(
screen.getByRole("button", { name: /all except scheduled/i }),
).toBeVisible();
});

it("selects All run states option", async () => {
// Setup
const user = userEvent.setup();
render(<TestStateFilter />);
// Test
await user.click(screen.getByRole("button", { name: /all run states/i }));
await user.click(screen.getByRole("menuitem", { name: /all run states/i }));
await user.keyboard("{Escape}");

// Assert
expect(
screen.getByRole("button", { name: /all run states/i }),
).toBeVisible();
});

it("selects a single run state option", async () => {
// Setup
const user = userEvent.setup();
render(<TestStateFilter />);
// Test
await user.click(screen.getByRole("button", { name: /all run states/i }));
await user.click(screen.getByRole("menuitem", { name: /failed/i }));

await user.keyboard("{Escape}");

// Assert
expect(screen.getByRole("button", { name: /failed/i })).toBeVisible();
});

it("selects multiple run state options", async () => {
// Setup
const user = userEvent.setup();
render(<TestStateFilter />);
// Test
await user.click(screen.getByRole("button", { name: /all run states/i }));
await user.click(screen.getByRole("menuitem", { name: /timedout/i }));
await user.click(screen.getByRole("menuitem", { name: /crashed/i }));

await user.click(screen.getByRole("menuitem", { name: /failed/i }));
await user.click(screen.getByRole("menuitem", { name: /running/i }));
await user.click(screen.getByRole("menuitem", { name: /retrying/i }));

await user.keyboard("{Escape}");

// Assert
expect(
screen.getByRole("button", {
name: /timedout crashed failed running \+ 1/i,
}),
).toBeVisible();
});
});
Loading