diff --git a/airflow/ui/src/components/SearchBar.tsx b/airflow/ui/src/components/SearchBar.tsx
index 1e3124fa415ae..349b66ce9e2e6 100644
--- a/airflow/ui/src/components/SearchBar.tsx
+++ b/airflow/ui/src/components/SearchBar.tsx
@@ -29,11 +29,19 @@ type Props = {
readonly buttonProps?: ButtonProps;
readonly defaultValue: string;
readonly groupProps?: InputGroupProps;
+ readonly hideAdvanced?: boolean;
readonly onChange: (value: string) => void;
readonly placeHolder: string;
};
-export const SearchBar = ({ buttonProps, defaultValue, groupProps, onChange, placeHolder }: Props) => {
+export const SearchBar = ({
+ buttonProps,
+ defaultValue,
+ groupProps,
+ hideAdvanced = false,
+ onChange,
+ placeHolder,
+}: Props) => {
const handleSearchChange = useDebouncedCallback((val: string) => onChange(val), debounceDelay);
const [value, setValue] = useState(defaultValue);
@@ -61,9 +69,11 @@ export const SearchBar = ({ buttonProps, defaultValue, groupProps, onChange, pla
size="xs"
/>
) : undefined}
-
+ {Boolean(hideAdvanced) ? undefined : (
+
+ )}
>
}
startElement={}
diff --git a/airflow/ui/src/pages/Run/TaskInstances.tsx b/airflow/ui/src/pages/Run/TaskInstances.tsx
index bbfaa35babef4..48c322a7a12a0 100644
--- a/airflow/ui/src/pages/Run/TaskInstances.tsx
+++ b/airflow/ui/src/pages/Run/TaskInstances.tsx
@@ -16,18 +16,21 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { Box, Link } from "@chakra-ui/react";
+import { Box, Link, createListCollection, HStack, type SelectValueChangeDetails } from "@chakra-ui/react";
import type { ColumnDef } from "@tanstack/react-table";
-import { Link as RouterLink, useParams } from "react-router-dom";
+import { useCallback, useState } from "react";
+import { Link as RouterLink, useParams, useSearchParams } from "react-router-dom";
import { useTaskInstanceServiceGetTaskInstances } from "openapi/queries";
-import type { TaskInstanceResponse } from "openapi/requests/types.gen";
+import type { TaskInstanceResponse, TaskInstanceState } from "openapi/requests/types.gen";
import { DataTable } from "src/components/DataTable";
import { useTableURLState } from "src/components/DataTable/useTableUrlState";
import { ErrorAlert } from "src/components/ErrorAlert";
+import { SearchBar } from "src/components/SearchBar";
import Time from "src/components/Time";
-import { Status } from "src/components/ui";
-import { getDuration } from "src/utils";
+import { Select, Status } from "src/components/ui";
+import { SearchParamsKeys, type SearchParamsKeysType } from "src/constants/searchParams";
+import { capitalize, getDuration } from "src/utils";
import { getTaskInstanceLink } from "src/utils/links";
const columns: Array> = [
@@ -82,12 +85,74 @@ const columns: Array> = [
},
];
+const stateOptions = createListCollection<{ label: string; value: TaskInstanceState | "all" | "none" }>({
+ items: [
+ { label: "All States", value: "all" },
+ { label: "Scheduled", value: "scheduled" },
+ { label: "Queued", value: "queued" },
+ { label: "Running", value: "running" },
+ { label: "Success", value: "success" },
+ { label: "Restarting", value: "restarting" },
+ { label: "Failed", value: "failed" },
+ { label: "Up For Retry", value: "up_for_retry" },
+ { label: "Up For Reschedule", value: "up_for_reschedule" },
+ { label: "Upstream failed", value: "upstream_failed" },
+ { label: "Skipped", value: "skipped" },
+ { label: "Deferred", value: "deferred" },
+ { label: "Removed", value: "removed" },
+ { label: "No Status", value: "none" },
+ ],
+});
+
+const STATE_PARAM = "state";
+
export const TaskInstances = () => {
const { dagId = "", runId = "" } = useParams();
+ const [searchParams, setSearchParams] = useSearchParams();
const { setTableURLState, tableURLState } = useTableURLState();
const { pagination, sorting } = tableURLState;
const [sort] = sorting;
const orderBy = sort ? `${sort.desc ? "-" : ""}${sort.id}` : "-start_date";
+ const filteredState = searchParams.getAll(STATE_PARAM);
+ const hasFilteredState = filteredState.length > 0;
+ const { NAME_PATTERN: NAME_PATTERN_PARAM }: SearchParamsKeysType = SearchParamsKeys;
+
+ const [taskDisplayNamePattern, setTaskDisplayNamePattern] = useState(
+ searchParams.get(NAME_PATTERN_PARAM) ?? undefined,
+ );
+
+ const handleStateChange = useCallback(
+ ({ value }: SelectValueChangeDetails) => {
+ const [val, ...rest] = value;
+
+ if ((val === undefined || val === "all") && rest.length === 0) {
+ searchParams.delete(STATE_PARAM);
+ } else {
+ searchParams.delete(STATE_PARAM);
+ value.filter((state) => state !== "all").map((state) => searchParams.append(STATE_PARAM, state));
+ }
+ setTableURLState({
+ pagination: { ...pagination, pageIndex: 0 },
+ sorting,
+ });
+ setSearchParams(searchParams);
+ },
+ [pagination, searchParams, setSearchParams, setTableURLState, sorting],
+ );
+
+ const handleSearchChange = (value: string) => {
+ if (value) {
+ searchParams.set(NAME_PATTERN_PARAM, value);
+ } else {
+ searchParams.delete(NAME_PATTERN_PARAM);
+ }
+ setTableURLState({
+ pagination: { ...pagination, pageIndex: 0 },
+ sorting,
+ });
+ setTaskDisplayNamePattern(value);
+ setSearchParams(searchParams);
+ };
const { data, error, isFetching, isLoading } = useTaskInstanceServiceGetTaskInstances(
{
@@ -96,13 +161,64 @@ export const TaskInstances = () => {
limit: pagination.pageSize,
offset: pagination.pageIndex * pagination.pageSize,
orderBy,
+ state: hasFilteredState ? filteredState : undefined,
+ taskDisplayNamePattern: Boolean(taskDisplayNamePattern) ? taskDisplayNamePattern : undefined,
},
undefined,
{ enabled: !isNaN(pagination.pageSize) },
);
return (
-
+
+
+
+
+
+ {() =>
+ hasFilteredState ? (
+
+ {filteredState.map((state) => (
+
+ {state === "none" ? "No Status" : capitalize(state)}
+
+ ))}
+
+ ) : (
+ "All States"
+ )
+ }
+
+
+
+ {stateOptions.items.map((option) => (
+
+ {option.value === "all" ? (
+ option.label
+ ) : (
+ {option.label}
+ )}
+
+ ))}
+
+
+
+