From c0952b0f6728c2306fda7944a218d514c53ea303 Mon Sep 17 00:00:00 2001 From: pierrejeambrun Date: Thu, 21 May 2026 13:46:03 +0200 Subject: [PATCH] UI: Use react-query native error state for bulk action hooks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both ``useBulkTaskInstances`` and ``useBulkDeleteDagRuns`` mirrored the same anti-pattern: a ``useState`` field, an ``onError`` callback that just forwarded to ``setError``, and a tiny helper that grabbed ``errors[0]`` from the response body and re-shaped it into ``{body:{detail:...}}`` so ``ErrorAlert`` could render the first per-entity failure. That hid every error past the first and duplicated mutation state into React state for no reason. Both hooks now return ``{ bulkAction, data, error, isPending, reset }`` straight from ``useMutation``: - ``error`` covers HTTP-level failures (4xx/5xx, network). - ``data.delete.errors`` / ``data.update.errors`` carries the per-entity failures the backend returns on a 200 response (partial success). Consumers render every entry, not just the first. - ``reset`` replaces the consumer-side ``setError(undefined)`` calls. ``onSuccess`` still invalidates queries unconditionally, fires the toaster + clears selection when ``success.length > 0``, and only closes the dialog when ``errors.length === 0`` — partial-success keeps the dialog open so the user can read what failed. The three consumers (``BulkMarkTaskInstancesAsButton``, ``BulkDeleteTaskInstancesButton``, ``BulkDeleteDagRunsButton``) now render network errors via ``ErrorAlert`` and per-entity errors as a ``Stack`` of ``Alert`` rows below it. --- .../pages/DagRuns/BulkDeleteDagRunsButton.tsx | 16 +++- .../BulkDeleteTaskInstancesButton.tsx | 16 +++- .../BulkMarkTaskInstancesAsButton.tsx | 18 ++++- .../ui/src/queries/useBulkDeleteDagRuns.ts | 74 ++++++++---------- .../ui/src/queries/useBulkTaskInstances.ts | 75 +++++++------------ 5 files changed, 99 insertions(+), 100 deletions(-) diff --git a/airflow-core/src/airflow/ui/src/pages/DagRuns/BulkDeleteDagRunsButton.tsx b/airflow-core/src/airflow/ui/src/pages/DagRuns/BulkDeleteDagRunsButton.tsx index e1a4b9c5c35ff..66cbb06f4dd45 100644 --- a/airflow-core/src/airflow/ui/src/pages/DagRuns/BulkDeleteDagRunsButton.tsx +++ b/airflow-core/src/airflow/ui/src/pages/DagRuns/BulkDeleteDagRunsButton.tsx @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { Box, Button, Flex, Heading, Text, useDisclosure, VStack } from "@chakra-ui/react"; +import { Box, Button, Flex, Heading, Stack, Text, useDisclosure, VStack } from "@chakra-ui/react"; import type { ColumnDef } from "@tanstack/react-table"; import type { TFunction } from "i18next"; import { useTranslation } from "react-i18next"; @@ -27,7 +27,7 @@ import { DataTable } from "src/components/DataTable"; import { ErrorAlert } from "src/components/ErrorAlert"; import { StateBadge } from "src/components/StateBadge"; import Time from "src/components/Time"; -import { Accordion, Dialog } from "src/components/ui"; +import { Accordion, Alert, Dialog } from "src/components/ui"; import { useBulkDeleteDagRuns } from "src/queries/useBulkDeleteDagRuns"; type Props = { @@ -61,11 +61,13 @@ const getColumns = (translate: TFunction): Array> => [ const BulkDeleteDagRunsButton = ({ clearSelections, selectedDagRuns }: Props) => { const { t: translate } = useTranslation(["common", "dags"]); const { onClose, onOpen, open } = useDisclosure(); - const { bulkAction, error, isPending } = useBulkDeleteDagRuns({ + const { bulkAction, data, error, isPending } = useBulkDeleteDagRuns({ clearSelections, onSuccessConfirm: onClose, }); + const actionErrors = (data?.delete?.errors ?? []) as Array<{ error: string; status_code?: number }>; + const columns = getColumns(translate); const byDagId = new Map>(); @@ -143,6 +145,14 @@ const BulkDeleteDagRunsButton = ({ clearSelections, selectedDagRuns }: Props) => + {actionErrors.length > 0 ? ( + + {actionErrors.map((actionError, index) => ( + // eslint-disable-next-line react/no-array-index-key -- per-entity errors have no stable id + + ))} + + ) : undefined}