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",

Reply via email to