Repository: apache/airflow
Pull request #47411: Implement backfill banner
Author: login: aritra24
Labels: name: area:UI
description: Related to UI/UX. For Frontend Developers.
Created: 2025-03-05T19:29:03Z | Merged: 2025-03-12T20:15:23Z

resolves: #43968

<!--
 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.
 -->

<!--
Thank you for contributing! Please make sure that your code changes
are covered with tests. And in case of new features or big changes
remember to adjust the documentation.

Feel free to ping committers for the review!

In case of an existing issue, reference it using one of the following:

closes: #ISSUE
related: #ISSUE

How to write a good git commit message:
http://chris.beams.io/posts/git-commit/
-->



<!-- Please keep an empty line above the dashes. -->
---
**^ Add meaningful description above**
Read the **[Pull Request Guidelines](https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#pull-request-guidelines)** for more information.
In case of fundamental code changes, an Airflow Improvement Proposal ([AIP](https://cwiki.apache.org/confluence/display/AIRFLOW/Airflow+Improvement+Proposals)) is needed.
In case of a new dependency, check compliance with the [ASF 3rd Party License Policy](https://www.apache.org/legal/resolved.html#category-x).
In case of backwards incompatible changes please leave a note in a newsfragment file, named `{pr_number}.significant.rst` or `{issue_number}.significant.rst`, in [newsfragments](https://github.com/apache/airflow/tree/main/newsfragments).



Diff:
diff --git a/airflow/ui/src/components/Banner/BackfillBanner.tsx b/airflow/ui/src/components/Banner/BackfillBanner.tsx
new file mode 100644
index 0000000000000..4e8b415076f69
--- /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 0000000000000..02602681f11b2
--- /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 a2800eaf0d47e..1eafd7cf10734 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 2a96ecd84d873..9c045f3b32735 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",


Reviews:
user: login: aritra24
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T04:54:53Z
user: login: bbovenzi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T20:13:37Z
user: login: bbovenzi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T20:18:10Z
user: login: bbovenzi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T20:19:06Z
user: login: aritra24
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-10T06:42:55Z
user: login: aritra24
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-10T06:45:43Z
user: login: bbovenzi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T16:07:25Z
user: login: bbovenzi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T16:12:40Z
user: login: aritra24
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-10T17:46:51Z
user: login: bbovenzi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T14:24:55Z
user: login: aritra24
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-12T14:51:51Z
user: login: bbovenzi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T15:05:09Z
user: login: bbovenzi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T15:09:08Z
user: login: aritra24
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-12T16:38:52Z
user: login: aritra24
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-12T16:40:13Z
user: login: bbovenzi
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T20:15:03Z

Review comments:
diff_hunk: @@ -0,0 +1,88 @@
+/*!
+ * 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 { useEffect, useState } from "react";
+import { MdClose, MdPause, MdStop } from "react-icons/md";
+
+import { useBackfillServiceListBackfills } from "openapi/queries";
+
+import Time from "../Time";
+import { ProgressBar } from "../ui";
+import ActionButton from "../ui/ActionButton";
+
+type Props = {
+  readonly dagId: string;
+};
+
+const BackfillBanner = ({ dagId }: Props) => {
+  const { data } = useBackfillServiceListBackfills({
+    dagId,
+  });
+
+  const [visible, setVisible] = useState<boolean>(false);
+
+  useEffect(() => {
+    setVisible(data?.total_entries === undefined ? false : data.total_entries > 0);
+  }, [data]);
+
+  return visible ? (
+    <Box bg="blue.solid" color="white" fontSize="m" 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} />
path: airflow/ui/src/components/Banner/BackfillBanner.tsx
user: login: aritra24
body: Probably need to update this to show a different time range since backfills returns all active backfill runs?
created_at: 2025-03-07T04:54:09Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,88 @@
+/*!
+ * 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 { useEffect, useState } from "react";
+import { MdClose, MdPause, MdStop } from "react-icons/md";
+
+import { useBackfillServiceListBackfills } from "openapi/queries";
+
+import Time from "../Time";
+import { ProgressBar } from "../ui";
+import ActionButton from "../ui/ActionButton";
+
+type Props = {
+  readonly dagId: string;
+};
+
+const BackfillBanner = ({ dagId }: Props) => {
+  const { data } = useBackfillServiceListBackfills({
+    dagId,
+  });
+
+  const [visible, setVisible] = useState<boolean>(false);
+
+  useEffect(() => {
+    setVisible(data?.total_entries === undefined ? false : data.total_entries > 0);
+  }, [data]);
+
+  return visible ? (
+    <Box bg="blue.solid" color="white" fontSize="m" 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 key="progressbar" size="xs" visibility="visible" />
+        <ActionButton
+          actionName=""
+          icon={<MdPause color="white" size="xs" />}
+          key="pause"
+          rounded="full"
+          size="xs"
+          text=""
+          variant="outline"
+        />
+        <ActionButton
+          actionName=""
+          icon={<MdStop color="white" size="xs" />}
+          key="stop"
+          rounded="full"
+          size="xs"
+          text=""
+          variant="outline"
+        />
path: airflow/ui/src/components/Banner/BackfillBanner.tsx
user: login: aritra24
body: Need to implement their actions still
created_at: 2025-03-07T04:54:46Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,88 @@
+/*!
+ * 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 { useEffect, useState } from "react";
+import { MdClose, MdPause, MdStop } from "react-icons/md";
+
+import { useBackfillServiceListBackfills } from "openapi/queries";
+
+import Time from "../Time";
+import { ProgressBar } from "../ui";
+import ActionButton from "../ui/ActionButton";
+
+type Props = {
+  readonly dagId: string;
+};
+
+const BackfillBanner = ({ dagId }: Props) => {
+  const { data } = useBackfillServiceListBackfills({
+    dagId,
+  });
+
+  const [visible, setVisible] = useState<boolean>(false);
+
+  useEffect(() => {
+    setVisible(data?.total_entries === undefined ? false : data.total_entries > 0);
+  }, [data]);
+
+  return visible ? (
+    <Box bg="blue.solid" color="white" fontSize="m" 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 key="progressbar" size="xs" visibility="visible" />
+        <ActionButton
+          actionName=""
+          icon={<MdPause color="white" size="xs" />}
+          key="pause"
+          rounded="full"
+          size="xs"
+          text=""
+          variant="outline"
+        />
+        <ActionButton
+          actionName=""
+          icon={<MdStop color="white" size="xs" />}
+          key="stop"
+          rounded="full"
+          size="xs"
+          text=""
+          variant="outline"
+        />
path: airflow/ui/src/components/Banner/BackfillBanner.tsx
user: login: bbovenzi
body: Let's leave them disabled then
created_at: 2025-03-07T20:13:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,88 @@
+/*!
+ * 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 { useEffect, useState } from "react";
+import { MdClose, MdPause, MdStop } from "react-icons/md";
+
+import { useBackfillServiceListBackfills } from "openapi/queries";
+
+import Time from "../Time";
+import { ProgressBar } from "../ui";
+import ActionButton from "../ui/ActionButton";
+
+type Props = {
+  readonly dagId: string;
+};
+
+const BackfillBanner = ({ dagId }: Props) => {
+  const { data } = useBackfillServiceListBackfills({
+    dagId,
+  });
+
+  const [visible, setVisible] = useState<boolean>(false);
+
+  useEffect(() => {
+    setVisible(data?.total_entries === undefined ? false : data.total_entries > 0);
+  }, [data]);
path: airflow/ui/src/components/Banner/BackfillBanner.tsx
user: login: bbovenzi
body: It's best to not have `useEffect` update `useState`. 

Instead we should just have an if statement for the entire component to `return undefined` 
created_at: 2025-03-07T20:18:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,88 @@
+/*!
+ * 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 { useEffect, useState } from "react";
+import { MdClose, MdPause, MdStop } from "react-icons/md";
+
+import { useBackfillServiceListBackfills } from "openapi/queries";
+
+import Time from "../Time";
+import { ProgressBar } from "../ui";
+import ActionButton from "../ui/ActionButton";
+
+type Props = {
+  readonly dagId: string;
+};
+
+const BackfillBanner = ({ dagId }: Props) => {
+  const { data } = useBackfillServiceListBackfills({
+    dagId,
+  });
+
+  const [visible, setVisible] = useState<boolean>(false);
+
+  useEffect(() => {
+    setVisible(data?.total_entries === undefined ? false : data.total_entries > 0);
+  }, [data]);
+
+  return visible ? (
+    <Box bg="blue.solid" color="white" fontSize="m" 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} />
path: airflow/ui/src/components/Banner/BackfillBanner.tsx
user: login: bbovenzi
body: You should only ever have one active backfill so I think this is ok
created_at: 2025-03-07T20:19:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,88 @@
+/*!
+ * 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 { useEffect, useState } from "react";
+import { MdClose, MdPause, MdStop } from "react-icons/md";
+
+import { useBackfillServiceListBackfills } from "openapi/queries";
+
+import Time from "../Time";
+import { ProgressBar } from "../ui";
+import ActionButton from "../ui/ActionButton";
+
+type Props = {
+  readonly dagId: string;
+};
+
+const BackfillBanner = ({ dagId }: Props) => {
+  const { data } = useBackfillServiceListBackfills({
+    dagId,
+  });
+
+  const [visible, setVisible] = useState<boolean>(false);
+
+  useEffect(() => {
+    setVisible(data?.total_entries === undefined ? false : data.total_entries > 0);
+  }, [data]);
+
+  return visible ? (
+    <Box bg="blue.solid" color="white" fontSize="m" 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} />
path: airflow/ui/src/components/Banner/BackfillBanner.tsx
user: login: aritra24
body: Ack, that sounds good in that case. 
created_at: 2025-03-10T06:42:55Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,88 @@
+/*!
+ * 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 { useEffect, useState } from "react";
+import { MdClose, MdPause, MdStop } from "react-icons/md";
+
+import { useBackfillServiceListBackfills } from "openapi/queries";
+
+import Time from "../Time";
+import { ProgressBar } from "../ui";
+import ActionButton from "../ui/ActionButton";
+
+type Props = {
+  readonly dagId: string;
+};
+
+const BackfillBanner = ({ dagId }: Props) => {
+  const { data } = useBackfillServiceListBackfills({
+    dagId,
+  });
+
+  const [visible, setVisible] = useState<boolean>(false);
+
+  useEffect(() => {
+    setVisible(data?.total_entries === undefined ? false : data.total_entries > 0);
+  }, [data]);
path: airflow/ui/src/components/Banner/BackfillBanner.tsx
user: login: aritra24
body: Hmm, I was getting some weird effect of the UI not rendering if I just used just an if, perhaps I was missing something so I'll try that again and update 🤔
created_at: 2025-03-10T06:45:43Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,123 @@
+/*!
+ * 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 { useState } from "react";
+import { MdClose, MdPause, MdPlayArrow, MdStop } from "react-icons/md";
+
+import {
+  useBackfillServiceCancelBackfill,
+  useBackfillServiceListBackfills,
+  useBackfillServicePauseBackfill,
+  useBackfillServiceUnpauseBackfill,
+} from "openapi/queries";
+
+import Time from "../Time";
+import { ProgressBar } from "../ui";
+import ActionButton from "../ui/ActionButton";
+
+type Props = {
+  readonly dagId: string;
+};
+
+const BackfillBanner = ({ dagId }: Props) => {
+  const { data, isLoading } = useBackfillServiceListBackfills({
+    dagId,
+  });
+
+  const backfill = data?.backfills[0];
+
+  const { isPending: isPausePending, mutate: pauseMutate } = useBackfillServicePauseBackfill();
+  const { isPending: isUnPausePending, mutate: unpauseMutate } = useBackfillServiceUnpauseBackfill();
+
+  const { isPending: isStopPending, mutate: stopPending } = useBackfillServiceCancelBackfill();
+
+  let initialVisibility = false;
+
+  if (data?.total_entries !== undefined && data.total_entries > 0) {
+    initialVisibility = true;
+  }
+  const [isVisible, setIsVisible] = useState<boolean>(initialVisibility);
+  const [isActive, setIsActive] = useState(false);
+  const [isDisabled, setIsDisabled] = useState<boolean>(false);
path: airflow/ui/src/components/Banner/BackfillBanner.tsx
user: login: bbovenzi
body: Usually `isDisabled` isn't a state but just a variable like:

`const isDisabled = isPausePending || isUnPausePending || isStopPending || isLoading;`
created_at: 2025-03-10T16:07:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,123 @@
+/*!
+ * 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 { useState } from "react";
+import { MdClose, MdPause, MdPlayArrow, MdStop } from "react-icons/md";
+
+import {
+  useBackfillServiceCancelBackfill,
+  useBackfillServiceListBackfills,
+  useBackfillServicePauseBackfill,
+  useBackfillServiceUnpauseBackfill,
+} from "openapi/queries";
+
+import Time from "../Time";
+import { ProgressBar } from "../ui";
+import ActionButton from "../ui/ActionButton";
+
+type Props = {
+  readonly dagId: string;
+};
+
+const BackfillBanner = ({ dagId }: Props) => {
+  const { data, isLoading } = useBackfillServiceListBackfills({
path: airflow/ui/src/components/Banner/BackfillBanner.tsx
user: login: bbovenzi
body: @dstandish Do you remember with this endpoint. Will cancelled backfills be returned?
created_at: 2025-03-10T16:12:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,123 @@
+/*!
+ * 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 { useState } from "react";
+import { MdClose, MdPause, MdPlayArrow, MdStop } from "react-icons/md";
+
+import {
+  useBackfillServiceCancelBackfill,
+  useBackfillServiceListBackfills,
+  useBackfillServicePauseBackfill,
+  useBackfillServiceUnpauseBackfill,
+} from "openapi/queries";
+
+import Time from "../Time";
+import { ProgressBar } from "../ui";
+import ActionButton from "../ui/ActionButton";
+
+type Props = {
+  readonly dagId: string;
+};
+
+const BackfillBanner = ({ dagId }: Props) => {
+  const { data, isLoading } = useBackfillServiceListBackfills({
+    dagId,
+  });
+
+  const backfill = data?.backfills[0];
+
+  const { isPending: isPausePending, mutate: pauseMutate } = useBackfillServicePauseBackfill();
+  const { isPending: isUnPausePending, mutate: unpauseMutate } = useBackfillServiceUnpauseBackfill();
+
+  const { isPending: isStopPending, mutate: stopPending } = useBackfillServiceCancelBackfill();
+
+  let initialVisibility = false;
+
+  if (data?.total_entries !== undefined && data.total_entries > 0) {
+    initialVisibility = true;
+  }
+  const [isVisible, setIsVisible] = useState<boolean>(initialVisibility);
+  const [isActive, setIsActive] = useState(false);
+  const [isDisabled, setIsDisabled] = useState<boolean>(false);
path: airflow/ui/src/components/Banner/BackfillBanner.tsx
user: login: aritra24
body: I wanted to be able to disable the buttons once the stop button is clicked, which if I use a let/const doesn't work, so I used a state. If there's a better conventional way to do it do let me know and I can try it out 🤔 
created_at: 2025-03-10T17:46:51Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,122 @@
+/*!
+ * 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 { useState } from "react";
+import { MdClose, MdPause, MdPlayArrow, MdStop } from "react-icons/md";
+
+import {
+  useBackfillServiceCancelBackfill,
+  useBackfillServiceListBackfills,
+  useBackfillServicePauseBackfill,
+  useBackfillServiceUnpauseBackfill,
+} from "openapi/queries";
+
+import Time from "../Time";
+import { ProgressBar } from "../ui";
+import ActionButton from "../ui/ActionButton";
+
+type Props = {
+  readonly dagId: string;
+};
+
+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();
+  const { isPending: isUnPausePending, mutate: unpauseMutate } = useBackfillServiceUnpauseBackfill();
+
+  const { isPending: isStopPending, mutate: stopPending } = useBackfillServiceCancelBackfill();
+
+  let initialVisibility = false;
+
+  if (backfill !== undefined) {
+    initialVisibility = true;
+  }
+  const [isVisible, setIsVisible] = useState<boolean>(initialVisibility);
+  const [isPaused, setIsPaused] = useState(backfill?.is_paused ?? false);
+  const [isDisabled, setIsDisabled] = useState<boolean>(isPausePending || isUnPausePending || isStopPending);
+  const togglePause = () => {
+    if (isPaused) {
+      pauseMutate({ backfillId: backfill?.id });
+    } else {
+      unpauseMutate({ backfillId: backfill?.id });
+    }
+    setIsPaused(!isPaused);
+  };
+
+  const cancel = () => {
+    stopPending({ backfillId: backfill?.id });
+    setIsDisabled(true);
+  };
+
+  return isVisible && !isLoading ? (
+    <Box bg="blue.solid" color="white" fontSize="m" mr="1.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" />
+        <ActionButton
+          actionName=""
+          disabled={isDisabled}
+          icon={isPaused ? <MdPlayArrow color="white" size="1" /> : <MdPause color="white" size="1" />}
+          loading={isPausePending || isUnPausePending}
+          onClick={() => {
+            togglePause();
+          }}
+          rounded="full"
+          size="xs"
+          text=""
+          variant="outline"
+        />
+        <ActionButton
+          actionName=""
+          disabled={isDisabled}
+          icon={<MdStop color="white" size="1" />}
+          loading={isStopPending}
+          onClick={() => {
+            cancel();
+          }}
+          rounded="full"
+          size="xs"
+          text=""
+          variant="outline"
+        />
+        <ActionButton
+          actionName=""
+          icon={<MdClose color="white" size="1" />}
+          onClick={() => setIsVisible(false)}
+          rounded="full"
+          size="xs"
+          text=""
+          variant="outline"
+        />
path: airflow/ui/src/components/Banner/BackfillBanner.tsx
user: login: bbovenzi
body: Let's remove the visibility functionality. Always show a banner if a backfill is active, never show a banner when it isnt
created_at: 2025-03-12T14:24:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,122 @@
+/*!
+ * 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 { useState } from "react";
+import { MdClose, MdPause, MdPlayArrow, MdStop } from "react-icons/md";
+
+import {
+  useBackfillServiceCancelBackfill,
+  useBackfillServiceListBackfills,
+  useBackfillServicePauseBackfill,
+  useBackfillServiceUnpauseBackfill,
+} from "openapi/queries";
+
+import Time from "../Time";
+import { ProgressBar } from "../ui";
+import ActionButton from "../ui/ActionButton";
+
+type Props = {
+  readonly dagId: string;
+};
+
+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();
+  const { isPending: isUnPausePending, mutate: unpauseMutate } = useBackfillServiceUnpauseBackfill();
+
+  const { isPending: isStopPending, mutate: stopPending } = useBackfillServiceCancelBackfill();
+
+  let initialVisibility = false;
+
+  if (backfill !== undefined) {
+    initialVisibility = true;
+  }
+  const [isVisible, setIsVisible] = useState<boolean>(initialVisibility);
+  const [isPaused, setIsPaused] = useState(backfill?.is_paused ?? false);
+  const [isDisabled, setIsDisabled] = useState<boolean>(isPausePending || isUnPausePending || isStopPending);
+  const togglePause = () => {
+    if (isPaused) {
+      pauseMutate({ backfillId: backfill?.id });
+    } else {
+      unpauseMutate({ backfillId: backfill?.id });
+    }
+    setIsPaused(!isPaused);
+  };
+
+  const cancel = () => {
+    stopPending({ backfillId: backfill?.id });
+    setIsDisabled(true);
+  };
+
+  return isVisible && !isLoading ? (
+    <Box bg="blue.solid" color="white" fontSize="m" mr="1.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" />
+        <ActionButton
+          actionName=""
+          disabled={isDisabled}
+          icon={isPaused ? <MdPlayArrow color="white" size="1" /> : <MdPause color="white" size="1" />}
+          loading={isPausePending || isUnPausePending}
+          onClick={() => {
+            togglePause();
+          }}
+          rounded="full"
+          size="xs"
+          text=""
+          variant="outline"
+        />
+        <ActionButton
+          actionName=""
+          disabled={isDisabled}
+          icon={<MdStop color="white" size="1" />}
+          loading={isStopPending}
+          onClick={() => {
+            cancel();
+          }}
+          rounded="full"
+          size="xs"
+          text=""
+          variant="outline"
+        />
+        <ActionButton
+          actionName=""
+          icon={<MdClose color="white" size="1" />}
+          onClick={() => setIsVisible(false)}
+          rounded="full"
+          size="xs"
+          text=""
+          variant="outline"
+        />
path: airflow/ui/src/components/Banner/BackfillBanner.tsx
user: login: aritra24
body: Done
created_at: 2025-03-12T14:51:51Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,109 @@
+/*!
+ * 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 { useState } from "react";
+import { MdPause, MdPlayArrow, MdStop } from "react-icons/md";
+
+import {
+  useBackfillServiceCancelBackfill,
+  useBackfillServiceListBackfills,
+  useBackfillServicePauseBackfill,
+  useBackfillServiceUnpauseBackfill,
+} from "openapi/queries";
+
+import Time from "../Time";
+import { ProgressBar } from "../ui";
+import ActionButton from "../ui/ActionButton";
+
+type Props = {
+  readonly dagId: string;
+};
+
+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();
+  const { isPending: isUnPausePending, mutate: unpauseMutate } = useBackfillServiceUnpauseBackfill();
+
+  const { isPending: isStopPending, mutate: stopPending } = useBackfillServiceCancelBackfill();
+
+  const [isPaused, setIsPaused] = useState(backfill?.is_paused ?? false);
+  const [isDisabled, setIsDisabled] = useState<boolean>(isPausePending || isUnPausePending || isStopPending);
path: airflow/ui/src/components/Banner/BackfillBanner.tsx
user: login: bbovenzi
body: We don't need either of these states.

Paused: We should just always use the backfill's value. And on an action make sure we refetch the backill. Ctrl+f the codebase for `await queryClient.invalidateQueries({`. We can make it a single `onSuccess` function we pass to all three of our mutation queries

Disabled: Actually, you can delete it entirely. When `loading` is true on the button, it will already be disabled.
created_at: 2025-03-12T15:05:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,109 @@
+/*!
+ * 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 { useState } from "react";
+import { MdPause, MdPlayArrow, MdStop } from "react-icons/md";
+
+import {
+  useBackfillServiceCancelBackfill,
+  useBackfillServiceListBackfills,
+  useBackfillServicePauseBackfill,
+  useBackfillServiceUnpauseBackfill,
+} from "openapi/queries";
+
+import Time from "../Time";
+import { ProgressBar } from "../ui";
+import ActionButton from "../ui/ActionButton";
+
+type Props = {
+  readonly dagId: string;
+};
+
+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();
+  const { isPending: isUnPausePending, mutate: unpauseMutate } = useBackfillServiceUnpauseBackfill();
+
+  const { isPending: isStopPending, mutate: stopPending } = useBackfillServiceCancelBackfill();
+
+  const [isPaused, setIsPaused] = useState(backfill?.is_paused ?? false);
+  const [isDisabled, setIsDisabled] = useState<boolean>(isPausePending || isUnPausePending || isStopPending);
+  const togglePause = () => {
+    if (isPaused) {
+      pauseMutate({ backfillId: backfill?.id });
+    } else {
+      unpauseMutate({ backfillId: backfill?.id });
+    }
+    setIsPaused(!isPaused);
+  };
+
+  const cancel = () => {
+    stopPending({ backfillId: backfill?.id });
+    setIsDisabled(true);
+  };
+
+  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" />
+        <ActionButton
path: airflow/ui/src/components/Banner/BackfillBanner.tsx
user: login: bbovenzi
body: ```suggestion
        <Button
```

Let's just the plain Button from `components/ui`. This component isn't taking advantage of the ActionButton at all
created_at: 2025-03-12T15:09:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,109 @@
+/*!
+ * 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 { useState } from "react";
+import { MdPause, MdPlayArrow, MdStop } from "react-icons/md";
+
+import {
+  useBackfillServiceCancelBackfill,
+  useBackfillServiceListBackfills,
+  useBackfillServicePauseBackfill,
+  useBackfillServiceUnpauseBackfill,
+} from "openapi/queries";
+
+import Time from "../Time";
+import { ProgressBar } from "../ui";
+import ActionButton from "../ui/ActionButton";
+
+type Props = {
+  readonly dagId: string;
+};
+
+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();
+  const { isPending: isUnPausePending, mutate: unpauseMutate } = useBackfillServiceUnpauseBackfill();
+
+  const { isPending: isStopPending, mutate: stopPending } = useBackfillServiceCancelBackfill();
+
+  const [isPaused, setIsPaused] = useState(backfill?.is_paused ?? false);
+  const [isDisabled, setIsDisabled] = useState<boolean>(isPausePending || isUnPausePending || isStopPending);
+  const togglePause = () => {
+    if (isPaused) {
+      pauseMutate({ backfillId: backfill?.id });
+    } else {
+      unpauseMutate({ backfillId: backfill?.id });
+    }
+    setIsPaused(!isPaused);
+  };
+
+  const cancel = () => {
+    stopPending({ backfillId: backfill?.id });
+    setIsDisabled(true);
+  };
+
+  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" />
+        <ActionButton
path: airflow/ui/src/components/Banner/BackfillBanner.tsx
user: login: aritra24
body: Done
created_at: 2025-03-12T16:38:52Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,109 @@
+/*!
+ * 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 { useState } from "react";
+import { MdPause, MdPlayArrow, MdStop } from "react-icons/md";
+
+import {
+  useBackfillServiceCancelBackfill,
+  useBackfillServiceListBackfills,
+  useBackfillServicePauseBackfill,
+  useBackfillServiceUnpauseBackfill,
+} from "openapi/queries";
+
+import Time from "../Time";
+import { ProgressBar } from "../ui";
+import ActionButton from "../ui/ActionButton";
+
+type Props = {
+  readonly dagId: string;
+};
+
+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();
+  const { isPending: isUnPausePending, mutate: unpauseMutate } = useBackfillServiceUnpauseBackfill();
+
+  const { isPending: isStopPending, mutate: stopPending } = useBackfillServiceCancelBackfill();
+
+  const [isPaused, setIsPaused] = useState(backfill?.is_paused ?? false);
+  const [isDisabled, setIsDisabled] = useState<boolean>(isPausePending || isUnPausePending || isStopPending);
path: airflow/ui/src/components/Banner/BackfillBanner.tsx
user: login: aritra24
body: Done
created_at: 2025-03-12T16:39:20Z
author_association: COLLABORATOR

PR comments:
user: login: bbovenzi
created_at: 2025-03-05T19:32:07Z
body: We only use this banner once. I think you can combined the two components, Banner and BackfillBanner, and not worry about arrays and keys.
author_association: CONTRIBUTOR
user: login: aritra24
created_at: 2025-03-06T03:27:35Z
body: Hmm, do you foresee there being a use for it in any other place in the future? If so I'd leave it in. Else I'll go ahead with merging the two 
author_association: COLLABORATOR
user: login: bbovenzi
created_at: 2025-03-06T14:48:04Z
body: > Hmm, do you foresee there being a use for it in any other place in the future? If so I'd leave it in. Else I'll go ahead with merging the two

Let merge it for now and we can always split it apart in the future.
author_association: CONTRIBUTOR
user: login: aritra24
created_at: 2025-03-10T14:13:02Z
body: Removed the usage of useEffect, it seems to work now. 
author_association: COLLABORATOR
user: login: aritra24
created_at: 2025-03-10T17:44:58Z
body: I think there's still some buggy behaviour so I'll leave it as wip, will check it out some more tomorrow 🤔 
author_association: COLLABORATOR
user: login: bbovenzi
created_at: 2025-03-11T23:08:39Z
body: > I think there's still some buggy behaviour so I'll leave it as wip, will check it out some more tomorrow 🤔

I see it's marked as ready for review. Did you figure out the buggy behavior?
author_association: CONTRIBUTOR
user: login: aritra24
created_at: 2025-03-12T03:26:37Z
body: Hey @bbovenzi, this is still a wip. Might've been a miss from my side for it to be ready for review. There's still a bug i think where sometimes on reload the banner doesn't show up. I'm trying to nail down why that is. Might take me a couple days to get to it. 
author_association: COLLABORATOR
user: login: bbovenzi
created_at: 2025-03-12T20:15:19Z
body: Nice work. Thanks for being so patient through my reviews!
author_association: CONTRIBUTOR
user: login: aritra24
created_at: 2025-03-12T20:17:18Z
body: Thanks for the detailed and insightful reviews, it's been a learning experience! 
author_association: COLLABORATOR