This is an automated email from the ASF dual-hosted git repository.
bbovenzi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new 2ff19a5438b Implement backfill banner (#47411)
2ff19a5438b is described below
commit 2ff19a5438b652ff7b8bb558531058bf12360bfa
Author: Aritra Basu <[email protected]>
AuthorDate: Thu Mar 13 01:45:23 2025 +0530
Implement backfill banner (#47411)
* WIP: Implement backfill banner
resolves: #43968
* Removed generic Banner in favor of BackfillBanner
* Removed usage of useEffect
* Added pause/unpause and stop backfill in the banner
* Update backfill filter and some names
* Removed visibility button
* Resolved review comments
---
.../ui/src/components/Banner/BackfillBanner.tsx | 111 +++++++++++++++++++++
airflow/ui/src/components/Banner/index.tsx | 20 ++++
airflow/ui/src/layouts/Details/DetailsLayout.tsx | 2 +
airflow/ui/src/queries/useCreateBackfill.ts | 8 +-
4 files changed, 139 insertions(+), 2 deletions(-)
diff --git a/airflow/ui/src/components/Banner/BackfillBanner.tsx
b/airflow/ui/src/components/Banner/BackfillBanner.tsx
new file mode 100644
index 00000000000..4e8b415076f
--- /dev/null
+++ b/airflow/ui/src/components/Banner/BackfillBanner.tsx
@@ -0,0 +1,111 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { Box, HStack, Spacer, Text } from "@chakra-ui/react";
+import { MdPause, MdPlayArrow, MdStop } from "react-icons/md";
+
+import {
+ useBackfillServiceCancelBackfill,
+ useBackfillServiceListBackfills,
+ useBackfillServiceListBackfillsKey,
+ useBackfillServicePauseBackfill,
+ useBackfillServiceUnpauseBackfill,
+} from "openapi/queries";
+import { queryClient } from "src/queryClient";
+
+import Time from "../Time";
+import { Button, ProgressBar } from "../ui";
+
+type Props = {
+ readonly dagId: string;
+};
+
+const onSuccess = async () => {
+ await queryClient.invalidateQueries({
+ queryKey: [useBackfillServiceListBackfillsKey],
+ });
+};
+
+const BackfillBanner = ({ dagId }: Props) => {
+ const { data, isLoading } = useBackfillServiceListBackfills({
+ dagId,
+ });
+ const [backfill] = data?.backfills.filter((bf) => bf.completed_at === null)
?? [];
+
+ const { isPending: isPausePending, mutate: pauseMutate } =
useBackfillServicePauseBackfill({ onSuccess });
+ const { isPending: isUnPausePending, mutate: unpauseMutate } =
useBackfillServiceUnpauseBackfill({
+ onSuccess,
+ });
+
+ const { isPending: isStopPending, mutate: stopPending } =
useBackfillServiceCancelBackfill({ onSuccess });
+
+ const togglePause = () => {
+ if (backfill?.is_paused) {
+ unpauseMutate({ backfillId: backfill.id });
+ } else {
+ pauseMutate({ backfillId: backfill?.id });
+ }
+ };
+
+ const cancel = () => {
+ stopPending({ backfillId: backfill?.id });
+ };
+
+ if (isLoading || backfill === undefined) {
+ return undefined;
+ }
+
+ return (
+ <Box bg="blue.solid" color="white" fontSize="m" mr="0.5" my="1" px="2"
py="1" rounded="lg">
+ <HStack>
+ <Text key="backfill">Backfill in progress:</Text>
+ <>
+ <Time datetime={data?.backfills[0]?.from_date} /> - <Time
datetime={data?.backfills[0]?.to_date} />
+ </>
+ <Spacer flex="max-content" />
+ <ProgressBar size="xs" visibility="visible" />
+ <Button
+ aria-label={backfill.is_paused ? "Unpause backfill" : "Pause
backfill"}
+ loading={isPausePending || isUnPausePending}
+ onClick={() => {
+ togglePause();
+ }}
+ rounded="full"
+ size="xs"
+ variant="outline"
+ >
+ {backfill.is_paused ? <MdPlayArrow color="white" size="1" /> :
<MdPause color="white" size="1" />}
+ </Button>
+ <Button
+ aria-label="Cancel backfill"
+ loading={isStopPending}
+ onClick={() => {
+ cancel();
+ }}
+ rounded="full"
+ size="xs"
+ variant="outline"
+ >
+ <MdStop color="white" size="1" />
+ </Button>
+ </HStack>
+ </Box>
+ );
+};
+
+export default BackfillBanner;
diff --git a/airflow/ui/src/components/Banner/index.tsx
b/airflow/ui/src/components/Banner/index.tsx
new file mode 100644
index 00000000000..02602681f11
--- /dev/null
+++ b/airflow/ui/src/components/Banner/index.tsx
@@ -0,0 +1,20 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export { default as BackfillBanner } from "./BackfillBanner";
diff --git a/airflow/ui/src/layouts/Details/DetailsLayout.tsx
b/airflow/ui/src/layouts/Details/DetailsLayout.tsx
index a2800eaf0d4..1eafd7cf107 100644
--- a/airflow/ui/src/layouts/Details/DetailsLayout.tsx
+++ b/airflow/ui/src/layouts/Details/DetailsLayout.tsx
@@ -25,6 +25,7 @@ import { useLocalStorage } from "usehooks-ts";
import { useDagServiceGetDag } from "openapi/queries";
import type { DAGResponse } from "openapi/requests/types.gen";
+import BackfillBanner from "src/components/Banner/BackfillBanner";
import { ErrorAlert } from "src/components/ErrorAlert";
import { SearchDagsButton } from "src/components/SearchDags";
import TriggerDAGButton from "src/components/TriggerDag/TriggerDAGButton";
@@ -64,6 +65,7 @@ export const DetailsLayout = ({ children, error, isLoading,
tabs }: Props) => {
</Flex>
</HStack>
<Toaster />
+ <BackfillBanner dagId={dagId} />
<Box flex={1} minH={0}>
<PanelGroup autoSaveId={dagId} direction="horizontal">
<Panel defaultSize={dagView === "graph" ? 70 : 20} minSize={6}>
diff --git a/airflow/ui/src/queries/useCreateBackfill.ts
b/airflow/ui/src/queries/useCreateBackfill.ts
index 2a96ecd84d8..9c045f3b327 100644
--- a/airflow/ui/src/queries/useCreateBackfill.ts
+++ b/airflow/ui/src/queries/useCreateBackfill.ts
@@ -18,15 +18,19 @@
*/
import { useState } from "react";
-import { useBackfillServiceCreateBackfill } from "openapi/queries";
+import { useBackfillServiceCreateBackfill, useBackfillServiceListBackfillsKey
} from "openapi/queries";
import type { CreateBackfillData } from "openapi/requests/types.gen";
import { toaster } from "src/components/ui";
+import { queryClient } from "src/queryClient";
export const useCreateBackfill = ({ onSuccessConfirm }: { onSuccessConfirm: ()
=> void }) => {
const [dateValidationError, setDateValidationError] =
useState<unknown>(undefined);
const [error, setError] = useState<unknown>(undefined);
- const onSuccess = () => {
+ const onSuccess = async () => {
+ await queryClient.invalidateQueries({
+ queryKey: [useBackfillServiceListBackfillsKey],
+ });
toaster.create({
description: "Backfill jobs have been successfully triggered.",
title: "Backfill generated",