diff --git a/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml b/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml index 8961ba7888091..1e7f2f690656c 100644 --- a/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml +++ b/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml @@ -2931,8 +2931,6 @@ paths: type: array items: type: string - default: - - dag_id title: Order By - name: is_favorite in: query diff --git a/airflow-core/src/airflow/ui/src/components/DataTable/DataTable.tsx b/airflow-core/src/airflow/ui/src/components/DataTable/DataTable.tsx index ff625191d1385..c9d369b62fa0a 100644 --- a/airflow-core/src/airflow/ui/src/components/DataTable/DataTable.tsx +++ b/airflow-core/src/airflow/ui/src/components/DataTable/DataTable.tsx @@ -106,6 +106,7 @@ export const DataTable = ({ columns, data, enableHiding: true, + enableMultiSort: true, getCoreRowModel: getCoreRowModel(), getExpandedRowModel: getExpandedRowModel(), getPaginationRowModel: getPaginationRowModel(), diff --git a/airflow-core/src/airflow/ui/src/components/DataTable/searchParams.ts b/airflow-core/src/airflow/ui/src/components/DataTable/searchParams.ts index f1923e90e2244..4e25b84dab629 100644 --- a/airflow-core/src/airflow/ui/src/components/DataTable/searchParams.ts +++ b/airflow-core/src/airflow/ui/src/components/DataTable/searchParams.ts @@ -40,12 +40,9 @@ export const stateToSearchParams = (state: TableState, defaultTableState?: Table } if (state.sorting.length) { + queryParams.delete(SORT_PARAM); state.sorting.forEach(({ desc, id }) => { - if (defaultTableState?.sorting.find((sort) => sort.id === id && sort.desc === desc)) { - queryParams.delete(SORT_PARAM, `${desc ? "-" : ""}${id}`); - } else { - queryParams.set(SORT_PARAM, `${desc ? "-" : ""}${id}`); - } + queryParams.append(SORT_PARAM, `${desc ? "-" : ""}${id}`); }); } else { queryParams.delete(SORT_PARAM); diff --git a/airflow-core/src/airflow/ui/src/pages/Asset/AssetLayout.tsx b/airflow-core/src/airflow/ui/src/pages/Asset/AssetLayout.tsx index 51a1442fcc90b..b398b429ee4b6 100644 --- a/airflow-core/src/airflow/ui/src/pages/Asset/AssetLayout.tsx +++ b/airflow-core/src/airflow/ui/src/pages/Asset/AssetLayout.tsx @@ -32,6 +32,7 @@ import { ProgressBar } from "src/components/ui"; import { AssetGraph } from "./AssetGraph"; import { CreateAssetEvent } from "./CreateAssetEvent"; import { Header } from "./Header"; +import { getOrderBy } from "src/utils"; export const AssetLayout = () => { const { i18n, t: translate } = useTranslation(["assets", "common"]); @@ -40,8 +41,8 @@ export const AssetLayout = () => { const { setTableURLState, tableURLState } = useTableURLState(); const { pagination, sorting } = tableURLState; - const [sort] = sorting; - const orderBy = sort ? [`${sort.desc ? "-" : ""}${sort.id}`] : ["-timestamp"]; + + const orderBy = getOrderBy("-timestamp"); const { data: asset, isLoading } = useAssetServiceGetAsset( { assetId: assetId === undefined ? 0 : parseInt(assetId, 10) }, diff --git a/airflow-core/src/airflow/ui/src/pages/AssetsList/AssetsList.tsx b/airflow-core/src/airflow/ui/src/pages/AssetsList/AssetsList.tsx index 960a959af65ba..1632eb854a20c 100644 --- a/airflow-core/src/airflow/ui/src/pages/AssetsList/AssetsList.tsx +++ b/airflow-core/src/airflow/ui/src/pages/AssetsList/AssetsList.tsx @@ -32,6 +32,7 @@ import { SearchBar } from "src/components/SearchBar"; import Time from "src/components/Time"; import { SearchParamsKeys } from "src/constants/searchParams"; import { CreateAssetEvent } from "src/pages/Asset/CreateAssetEvent"; +import { getOrderBy } from "src/utils"; import { DependencyPopover } from "./DependencyPopover"; @@ -104,8 +105,8 @@ export const AssetsList = () => { const { setTableURLState, tableURLState } = useTableURLState(); const { pagination, sorting } = tableURLState; - const [sort] = sorting; - const orderBy = sort ? [`${sort.desc ? "-" : ""}${sort.id}`] : undefined; + + const orderBy = getOrderBy(); const { data, error, isLoading } = useAssetServiceGetAssets({ limit: pagination.pageSize, diff --git a/airflow-core/src/airflow/ui/src/pages/Connections/Connections.tsx b/airflow-core/src/airflow/ui/src/pages/Connections/Connections.tsx index 4ff052ec3a9a3..a8d7fd0978384 100644 --- a/airflow-core/src/airflow/ui/src/pages/Connections/Connections.tsx +++ b/airflow-core/src/airflow/ui/src/pages/Connections/Connections.tsx @@ -35,6 +35,7 @@ import { ActionBar } from "src/components/ui/ActionBar"; import { Checkbox } from "src/components/ui/Checkbox"; import { SearchParamsKeys, type SearchParamsKeysType } from "src/constants/searchParams"; import { useConnectionTypeMeta } from "src/queries/useConnectionTypeMeta"; +import { getOrderBy } from "src/utils"; import AddConnectionButton from "./AddConnectionButton"; import DeleteConnectionButton from "./DeleteConnectionButton"; @@ -134,8 +135,8 @@ export const Connections = () => { useConnectionTypeMeta(); // Pre-fetch connection type metadata const { pagination, sorting } = tableURLState; - const [sort] = sorting; - const orderBy = sort ? [`${sort.desc ? "-" : ""}${sort.id}`] : ["connection_id"]; + const orderBy = getOrderBy("-connection_id"); + const { data, error, isFetching, isLoading } = useConnectionServiceGetConnections({ connectionIdPattern: connectionIdPattern ?? undefined, limit: pagination.pageSize, diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Overview/Overview.tsx b/airflow-core/src/airflow/ui/src/pages/Dag/Overview/Overview.tsx index 0a0c13c4ef251..67d02c8d6ecf0 100644 --- a/airflow-core/src/airflow/ui/src/pages/Dag/Overview/Overview.tsx +++ b/airflow-core/src/airflow/ui/src/pages/Dag/Overview/Overview.tsx @@ -66,7 +66,7 @@ export const Overview = () => { }); const { data: gridRuns, isLoading: isLoadingRuns } = useGridRuns({ limit }); const { data: assetEventsData, isLoading: isLoadingAssetEvents } = useAssetServiceGetAssetEvents({ - limit, + limit: 6, orderBy: [assetSortBy], sourceDagId: dagId, timestampGte: startDate, diff --git a/airflow-core/src/airflow/ui/src/pages/DagRuns.tsx b/airflow-core/src/airflow/ui/src/pages/DagRuns.tsx index c0376ce4b77fd..5194e8e583434 100644 --- a/airflow-core/src/airflow/ui/src/pages/DagRuns.tsx +++ b/airflow-core/src/airflow/ui/src/pages/DagRuns.tsx @@ -44,7 +44,7 @@ import { Select } from "src/components/ui"; import { SearchParamsKeys, type SearchParamsKeysType } from "src/constants/searchParams"; import { dagRunTypeOptions, dagRunStateOptions as stateOptions } from "src/constants/stateOptions"; import DeleteRunButton from "src/pages/DeleteRunButton"; -import { renderDuration, useAutoRefresh, isStatePending } from "src/utils"; +import { renderDuration, useAutoRefresh, isStatePending, getOrderBy } from "src/utils"; type DagRunRow = { row: { original: DAGRunResponse } }; const { @@ -174,8 +174,7 @@ export const DagRuns = () => { const { setTableURLState, tableURLState } = useTableURLState(); const { pagination, sorting } = tableURLState; - const [sort] = sorting; - const orderBy = sort ? [`${sort.desc ? "-" : ""}${sort.id}`] : ["-run_after"]; + const orderBy = getOrderBy("-run_after"); const { pageIndex, pageSize } = pagination; const filteredState = searchParams.get(STATE_PARAM); diff --git a/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.tsx b/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.tsx index f3f785218a579..4153e47397d15 100644 --- a/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.tsx +++ b/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.tsx @@ -49,6 +49,7 @@ import { SearchParamsKeys } from "src/constants/searchParams"; import { DagsLayout } from "src/layouts/DagsLayout"; import { useConfig } from "src/queries/useConfig"; import { useDags } from "src/queries/useDags"; +import { getOrderBy } from "src/utils"; import { DAGImportErrors } from "../Dashboard/Stats/DAGImportErrors"; import { DagCard } from "./DagCard"; @@ -208,8 +209,7 @@ export const DagsList = () => { searchParams.get(NAME_PATTERN) ?? savedSearchPattern, ); - const [sort] = sorting; - const orderBy = sort ? `${sort.desc ? "-" : ""}${sort.id}` : "dag_display_name"; + const orderBy = getOrderBy("-last_run_start_date"); const columns = useMemo(() => createColumns(translate), [translate]); @@ -252,7 +252,7 @@ export const DagsList = () => { lastDagRunState, limit: pagination.pageSize, offset: pagination.pageIndex * pagination.pageSize, - orderBy: [orderBy], + orderBy: orderBy, owners, paused, tags: selectedTags, diff --git a/airflow-core/src/airflow/ui/src/pages/DagsList/SortSelect.tsx b/airflow-core/src/airflow/ui/src/pages/DagsList/SortSelect.tsx index e0e66b9033f96..f5a7d68dab424 100644 --- a/airflow-core/src/airflow/ui/src/pages/DagsList/SortSelect.tsx +++ b/airflow-core/src/airflow/ui/src/pages/DagsList/SortSelect.tsx @@ -24,7 +24,7 @@ import { createDagSortOptions } from "src/constants/sortParams"; type Props = { readonly handleSortChange: ({ value }: SelectValueChangeDetails>) => void; - readonly orderBy?: string; + readonly orderBy?: Array; }; export const SortSelect = ({ handleSortChange, orderBy }: Props) => { @@ -36,7 +36,7 @@ export const SortSelect = ({ handleSortChange, orderBy }: Props) => { collection={dagSortOptions} data-testid="sort-by-select" onValueChange={handleSortChange} - value={orderBy === undefined ? undefined : [orderBy]} + value={orderBy === undefined ? undefined : orderBy} width="310px" > diff --git a/airflow-core/src/airflow/ui/src/pages/Events/Events.tsx b/airflow-core/src/airflow/ui/src/pages/Events/Events.tsx index d804ee30686e5..6053065af08ae 100644 --- a/airflow-core/src/airflow/ui/src/pages/Events/Events.tsx +++ b/airflow-core/src/airflow/ui/src/pages/Events/Events.tsx @@ -29,6 +29,7 @@ import { useTableURLState } from "src/components/DataTable/useTableUrlState"; import { ErrorAlert } from "src/components/ErrorAlert"; import RenderedJsonField from "src/components/RenderedJsonField"; import Time from "src/components/Time"; +import { getOrderBy } from "src/utils"; type EventsColumn = { dagId?: string; @@ -146,10 +147,9 @@ export const Events = () => { const { dagId, runId, taskId } = useParams(); const { setTableURLState, tableURLState } = useTableURLState(); const { pagination, sorting } = tableURLState; - const [sort] = sorting; const { onClose, onOpen, open } = useDisclosure(); - const orderBy = sort ? [`${sort.desc ? "-" : ""}${sort.id}`] : ["-when"]; + const orderBy = getOrderBy("-when"); const { data, error, isFetching, isLoading } = useEventLogServiceGetEventLogs( { diff --git a/airflow-core/src/airflow/ui/src/pages/Pools/Pools.tsx b/airflow-core/src/airflow/ui/src/pages/Pools/Pools.tsx index 8ab1cbfb493c1..99493da28b36f 100644 --- a/airflow-core/src/airflow/ui/src/pages/Pools/Pools.tsx +++ b/airflow-core/src/airflow/ui/src/pages/Pools/Pools.tsx @@ -32,6 +32,7 @@ import { SearchBar } from "src/components/SearchBar"; import { Select } from "src/components/ui"; import type { SearchParamsKeysType } from "src/constants/searchParams"; import { SearchParamsKeys } from "src/constants/searchParams"; +import { getOrderBy } from "src/utils"; import AddPoolButton from "./AddPoolButton"; import PoolBarCard from "./PoolBarCard"; @@ -58,8 +59,7 @@ export const Pools = () => { const { setTableURLState, tableURLState } = useTableURLState(); const { pagination, sorting } = tableURLState; - const [sort] = sorting; - const orderBy = sort ? [`${sort.desc ? "-" : ""}${sort.id}`] : ["name"]; + const orderBy = getOrderBy("name"); const { data, error, isLoading } = usePoolServiceGetPools({ limit: pagination.pageSize, diff --git a/airflow-core/src/airflow/ui/src/pages/TaskInstances/TaskInstances.tsx b/airflow-core/src/airflow/ui/src/pages/TaskInstances/TaskInstances.tsx index 01b1e0b5d57fc..5e6bb1b1827ad 100644 --- a/airflow-core/src/airflow/ui/src/pages/TaskInstances/TaskInstances.tsx +++ b/airflow-core/src/airflow/ui/src/pages/TaskInstances/TaskInstances.tsx @@ -35,7 +35,7 @@ import { StateBadge } from "src/components/StateBadge"; import Time from "src/components/Time"; import { TruncatedText } from "src/components/TruncatedText"; import { SearchParamsKeys, type SearchParamsKeysType } from "src/constants/searchParams"; -import { getDuration, useAutoRefresh, isStatePending } from "src/utils"; +import { getDuration, useAutoRefresh, isStatePending, getOrderBy } from "src/utils"; import { getTaskInstanceLink } from "src/utils/links"; import DeleteTaskInstanceButton from "./DeleteTaskInstanceButton"; @@ -191,8 +191,7 @@ export const TaskInstances = () => { const [searchParams] = useSearchParams(); const { setTableURLState, tableURLState } = useTableURLState(); const { pagination, sorting } = tableURLState; - const [sort] = sorting; - const orderBy = sort ? [`${sort.desc ? "-" : ""}${sort.id}`] : ["-start_date", "-run_after"]; + const orderBy = getOrderBy("-start_date"); const filteredState = searchParams.getAll(STATE_PARAM); const startDate = searchParams.get(START_DATE_PARAM); diff --git a/airflow-core/src/airflow/ui/src/pages/Variables/Variables.tsx b/airflow-core/src/airflow/ui/src/pages/Variables/Variables.tsx index 3e4ccc2927a5c..06ce4bce2770d 100644 --- a/airflow-core/src/airflow/ui/src/pages/Variables/Variables.tsx +++ b/airflow-core/src/airflow/ui/src/pages/Variables/Variables.tsx @@ -35,6 +35,7 @@ import { Button, Tooltip } from "src/components/ui"; import { ActionBar } from "src/components/ui/ActionBar"; import { Checkbox } from "src/components/ui/Checkbox"; import { SearchParamsKeys, type SearchParamsKeysType } from "src/constants/searchParams"; +import { getOrderBy } from "src/utils"; import { TrimText } from "src/utils/TrimText"; import { downloadJson } from "src/utils/downloadJson"; @@ -123,8 +124,7 @@ export const Variables = () => { ); const [selectedVariables, setSelectedVariables] = useState>({}); const { pagination, sorting } = tableURLState; - const [sort] = sorting; - const orderBy = sort ? [`${sort.desc ? "-" : ""}${sort.id === "value" ? "_val" : sort.id}`] : ["-key"]; + const orderBy = getOrderBy("-key").map((el) => el.replace("value", "_val")); const { data, error, isFetching, isLoading } = useVariableServiceGetVariables({ limit: pagination.pageSize, diff --git a/airflow-core/src/airflow/ui/src/utils/query.ts b/airflow-core/src/airflow/ui/src/utils/query.ts index 5becaa0ac29fd..edb6ce4ea0d9a 100644 --- a/airflow-core/src/airflow/ui/src/utils/query.ts +++ b/airflow-core/src/airflow/ui/src/utils/query.ts @@ -20,6 +20,12 @@ import { useDagServiceGetDagDetails } from "openapi/queries"; import type { TaskInstanceState } from "openapi/requests/types.gen"; import { useConfig } from "src/queries/useConfig"; +export const getOrderBy = (defaultOrder?: string): Array => { + const sortParam = new URLSearchParams(globalThis.location.search).getAll("sort"); + + return sortParam.length === 0 && defaultOrder !== undefined ? [defaultOrder] : sortParam; +}; + export const isStatePending = (state?: TaskInstanceState | null) => state === "deferred" || state === "scheduled" ||