This is an automated email from the ASF dual-hosted git repository.
jscheffl 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 34497e5f3c6 fix the bulk (#65015)
34497e5f3c6 is described below
commit 34497e5f3c68aa432f9c24097766d6b794fec214
Author: Shubham Raj <[email protected]>
AuthorDate: Sat Apr 11 02:44:45 2026 +0530
fix the bulk (#65015)
---
.../www/src/components/BulkWorkerOperations.tsx | 159 +++++++++++++++++++--
.../providers/edge3/plugins/www/src/constants.ts | 12 ++
.../plugins/www/src/hooks/useBulkWorkerActions.ts | 73 ++++++++--
3 files changed, 225 insertions(+), 19 deletions(-)
diff --git
a/providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/BulkWorkerOperations.tsx
b/providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/BulkWorkerOperations.tsx
index a65ad262609..4778d08f827 100644
---
a/providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/BulkWorkerOperations.tsx
+++
b/providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/BulkWorkerOperations.tsx
@@ -16,10 +16,13 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { Button, Dialog, HStack, List, Portal, Text, useDisclosure } from
"@chakra-ui/react";
+import { Button, Dialog, HStack, List, Portal, Text, Textarea, useDisclosure }
from "@chakra-ui/react";
import type { Worker } from "openapi/requests/types.gen";
+import { useState } from "react";
import { FaPowerOff } from "react-icons/fa";
import { FaRegTrashCan } from "react-icons/fa6";
+import { HiOutlineWrenchScrewdriver } from "react-icons/hi2";
+import { IoMdExit } from "react-icons/io";
import { useBulkWorkerActions } from "src/hooks/useBulkWorkerActions";
@@ -44,12 +47,29 @@ export const BulkWorkerOperations = ({
onOpen: onOpenDeleteDialog,
open: isDeleteDialogOpen,
} = useDisclosure();
+ const {
+ onClose: onCloseMaintenanceEnterDialog,
+ onOpen: onOpenMaintenanceEnterDialog,
+ open: isMaintenanceEnterDialogOpen,
+ } = useDisclosure();
+ const {
+ onClose: onCloseMaintenanceExitDialog,
+ onOpen: onOpenMaintenanceExitDialog,
+ open: isMaintenanceExitDialogOpen,
+ } = useDisclosure();
+ const [maintenanceComment, setMaintenanceComment] = useState("");
const {
deleteWorkers,
handleBulkDelete,
+ handleBulkMaintenanceEnter,
+ handleBulkMaintenanceExit,
handleBulkShutdown,
isBulkDeletePending,
+ isBulkMaintenanceEnterPending,
+ isBulkMaintenanceExitPending,
isBulkShutdownPending,
+ maintenanceEnterWorkers,
+ maintenanceExitWorkers,
shutdownWorkers,
} = useBulkWorkerActions({
onClearSelection,
@@ -58,24 +78,53 @@ export const BulkWorkerOperations = ({
});
const onBulkShutdown = async () => {
- try {
- await handleBulkShutdown();
- } finally {
- onCloseShutdownDialog();
- }
+ await handleBulkShutdown();
+ onCloseShutdownDialog();
};
const onBulkDelete = async () => {
- try {
- await handleBulkDelete();
- } finally {
- onCloseDeleteDialog();
- }
+ await handleBulkDelete();
+ onCloseDeleteDialog();
+ };
+
+ const onMaintenanceEnterDialogClose = () => {
+ onCloseMaintenanceEnterDialog();
+ setMaintenanceComment("");
+ };
+
+ const onBulkMaintenanceEnter = async () => {
+ await handleBulkMaintenanceEnter(maintenanceComment);
+ onMaintenanceEnterDialogClose();
+ };
+
+ const onBulkMaintenanceExit = async () => {
+ await handleBulkMaintenanceExit();
+ onCloseMaintenanceExitDialog();
};
return (
<>
<HStack>
+ <Button
+ colorPalette="warning"
+ disabled={maintenanceEnterWorkers.length === 0}
+ onClick={onOpenMaintenanceEnterDialog}
+ size="sm"
+ variant="outline"
+ >
+ <HiOutlineWrenchScrewdriver />
+ Send to Maintenance ({maintenanceEnterWorkers.length})
+ </Button>
+ <Button
+ colorPalette="warning"
+ disabled={maintenanceExitWorkers.length === 0}
+ onClick={onOpenMaintenanceExitDialog}
+ size="sm"
+ variant="outline"
+ >
+ <IoMdExit />
+ Exit Maintenance ({maintenanceExitWorkers.length})
+ </Button>
<Button
colorPalette="danger"
disabled={shutdownWorkers.length === 0}
@@ -98,6 +147,94 @@ export const BulkWorkerOperations = ({
</Button>
</HStack>
+ <Dialog.Root onOpenChange={onMaintenanceEnterDialogClose}
open={isMaintenanceEnterDialogOpen} size="lg">
+ <Portal>
+ <Dialog.Backdrop />
+ <Dialog.Positioner>
+ <Dialog.Content>
+ <Dialog.Header>
+ <Dialog.Title>
+ Send {maintenanceEnterWorkers.length} selected worker(s) to
maintenance
+ </Dialog.Title>
+ </Dialog.Header>
+ <Dialog.Body>
+ <Text mb={3}>
+ Maintenance mode can be requested only for workers in
states: idle or running.
+ </Text>
+ <List.Root ps={5} mb={3}>
+ {maintenanceEnterWorkers.map((worker) => (
+ <List.Item
key={worker.worker_name}>{worker.worker_name}</List.Item>
+ ))}
+ </List.Root>
+ <Textarea
+ placeholder="Enter maintenance comment (required)"
+ value={maintenanceComment}
+ onChange={(e) => setMaintenanceComment(e.target.value)}
+ required
+ maxLength={1024}
+ size="sm"
+ />
+ </Dialog.Body>
+ <Dialog.Footer>
+ <Dialog.ActionTrigger asChild>
+ <Button variant="outline">Cancel</Button>
+ </Dialog.ActionTrigger>
+ <Button
+ colorPalette="warning"
+ disabled={!maintenanceComment.trim()}
+ loading={isBulkMaintenanceEnterPending}
+ loadingText="Sending to maintenance..."
+ onClick={onBulkMaintenanceEnter}
+ >
+ <HiOutlineWrenchScrewdriver />
+ Send to Maintenance
+ </Button>
+ </Dialog.Footer>
+ </Dialog.Content>
+ </Dialog.Positioner>
+ </Portal>
+ </Dialog.Root>
+
+ <Dialog.Root onOpenChange={onCloseMaintenanceExitDialog}
open={isMaintenanceExitDialogOpen} size="lg">
+ <Portal>
+ <Dialog.Backdrop />
+ <Dialog.Positioner>
+ <Dialog.Content>
+ <Dialog.Header>
+ <Dialog.Title>
+ Exit maintenance for {maintenanceExitWorkers.length}
selected worker(s)
+ </Dialog.Title>
+ </Dialog.Header>
+ <Dialog.Body>
+ <Text mb={3}>
+ Exit maintenance is available only for workers in states:
maintenance pending, maintenance
+ mode, maintenance request, or offline maintenance.
+ </Text>
+ <List.Root ps={5}>
+ {maintenanceExitWorkers.map((worker) => (
+ <List.Item
key={worker.worker_name}>{worker.worker_name}</List.Item>
+ ))}
+ </List.Root>
+ </Dialog.Body>
+ <Dialog.Footer>
+ <Dialog.ActionTrigger asChild>
+ <Button variant="outline">Cancel</Button>
+ </Dialog.ActionTrigger>
+ <Button
+ colorPalette="warning"
+ loading={isBulkMaintenanceExitPending}
+ loadingText="Exiting maintenance..."
+ onClick={onBulkMaintenanceExit}
+ >
+ <IoMdExit />
+ Exit Maintenance
+ </Button>
+ </Dialog.Footer>
+ </Dialog.Content>
+ </Dialog.Positioner>
+ </Portal>
+ </Dialog.Root>
+
<Dialog.Root onOpenChange={onCloseShutdownDialog}
open={isShutdownDialogOpen} size="lg">
<Portal>
<Dialog.Backdrop />
diff --git
a/providers/edge3/src/airflow/providers/edge3/plugins/www/src/constants.ts
b/providers/edge3/src/airflow/providers/edge3/plugins/www/src/constants.ts
index 1b23645de86..c6f7d0e9b32 100644
--- a/providers/edge3/src/airflow/providers/edge3/plugins/www/src/constants.ts
+++ b/providers/edge3/src/airflow/providers/edge3/plugins/www/src/constants.ts
@@ -69,4 +69,16 @@ export const bulkWorkerDeleteEligibleStates = new
Set<EdgeWorkerState>([
"offline maintenance",
]);
+export const bulkWorkerMaintenanceEnterEligibleStates = new
Set<EdgeWorkerState>([
+ "idle",
+ "running",
+]);
+
+export const bulkWorkerMaintenanceExitEligibleStates = new
Set<EdgeWorkerState>([
+ "maintenance pending",
+ "maintenance mode",
+ "maintenance request",
+ "offline maintenance",
+]);
+
export const bulkWorkerActionBatchSize = 10;
diff --git
a/providers/edge3/src/airflow/providers/edge3/plugins/www/src/hooks/useBulkWorkerActions.ts
b/providers/edge3/src/airflow/providers/edge3/plugins/www/src/hooks/useBulkWorkerActions.ts
index c9bc1eb8bca..2bb2363239e 100644
---
a/providers/edge3/src/airflow/providers/edge3/plugins/www/src/hooks/useBulkWorkerActions.ts
+++
b/providers/edge3/src/airflow/providers/edge3/plugins/www/src/hooks/useBulkWorkerActions.ts
@@ -16,14 +16,21 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { useUiServiceDeleteWorker, useUiServiceRequestWorkerShutdown } from
"openapi/queries";
+import {
+ useUiServiceDeleteWorker,
+ useUiServiceExitWorkerMaintenance,
+ useUiServiceRequestWorkerMaintenance,
+ useUiServiceRequestWorkerShutdown,
+} from "openapi/queries";
import type { Worker } from "openapi/requests/types.gen";
-import { useMemo, useState } from "react";
+import { useCallback, useMemo, useState } from "react";
import { toaster } from "src/components/ui";
import {
bulkWorkerActionBatchSize,
bulkWorkerDeleteEligibleStates,
+ bulkWorkerMaintenanceEnterEligibleStates,
+ bulkWorkerMaintenanceExitEligibleStates,
bulkWorkerShutdownEligibleStates,
} from "src/constants";
@@ -99,9 +106,13 @@ export const useBulkWorkerActions = ({
}: UseBulkWorkerActionsProps) => {
const [isBulkDeletePending, setIsBulkDeletePending] = useState(false);
const [isBulkShutdownPending, setIsBulkShutdownPending] = useState(false);
+ const [isBulkMaintenanceEnterPending, setIsBulkMaintenanceEnterPending] =
useState(false);
+ const [isBulkMaintenanceExitPending, setIsBulkMaintenanceExitPending] =
useState(false);
const shutdownMutation = useUiServiceRequestWorkerShutdown();
const deleteMutation = useUiServiceDeleteWorker();
+ const maintenanceEnterMutation = useUiServiceRequestWorkerMaintenance();
+ const maintenanceExitMutation = useUiServiceExitWorkerMaintenance();
const shutdownWorkers = useMemo(
() => selectedWorkers.filter((worker) =>
bulkWorkerShutdownEligibleStates.has(worker.state)),
@@ -111,8 +122,16 @@ export const useBulkWorkerActions = ({
() => selectedWorkers.filter((worker) =>
bulkWorkerDeleteEligibleStates.has(worker.state)),
[selectedWorkers],
);
+ const maintenanceEnterWorkers = useMemo(
+ () => selectedWorkers.filter((worker) =>
bulkWorkerMaintenanceEnterEligibleStates.has(worker.state)),
+ [selectedWorkers],
+ );
+ const maintenanceExitWorkers = useMemo(
+ () => selectedWorkers.filter((worker) =>
bulkWorkerMaintenanceExitEligibleStates.has(worker.state)),
+ [selectedWorkers],
+ );
- const handleBulkAction = async ({
+ const handleBulkAction = useCallback(async ({
failureTitle,
setPending,
successToast,
@@ -144,9 +163,9 @@ export const useBulkWorkerActions = ({
} finally {
setPending(false);
}
- };
+ }, [onClearSelection, onOperations]);
- const handleBulkShutdown = async (): Promise<void> => {
+ const handleBulkShutdown = useCallback(async (): Promise<void> => {
await handleBulkAction({
failureTitle: "Bulk Shutdown Partially Failed",
setPending: setIsBulkShutdownPending,
@@ -158,9 +177,9 @@ export const useBulkWorkerActions = ({
workers: shutdownWorkers,
workerMutation: (worker) => shutdownMutation.mutateAsync({ workerName:
worker.worker_name }),
});
- };
+ }, [handleBulkAction, shutdownMutation, shutdownWorkers]);
- const handleBulkDelete = async (): Promise<void> => {
+ const handleBulkDelete = useCallback(async (): Promise<void> => {
await handleBulkAction({
failureTitle: "Bulk Delete Partially Failed",
setPending: setIsBulkDeletePending,
@@ -172,14 +191,52 @@ export const useBulkWorkerActions = ({
workers: deleteWorkers,
workerMutation: (worker) => deleteMutation.mutateAsync({ workerName:
worker.worker_name }),
});
- };
+ }, [deleteWorkers, deleteMutation, handleBulkAction]);
+
+ const handleBulkMaintenanceEnter = useCallback(async (comment: string):
Promise<void> => {
+ await handleBulkAction({
+ failureTitle: "Bulk Maintenance Enter Partially Failed",
+ setPending: setIsBulkMaintenanceEnterPending,
+ successToast: (successCount) => ({
+ description: `Maintenance mode requested for ${successCount}
worker(s).`,
+ title: "Bulk Maintenance Enter Requested",
+ type: "success",
+ }),
+ workers: maintenanceEnterWorkers,
+ workerMutation: (worker) =>
+ maintenanceEnterMutation.mutateAsync({
+ requestBody: { maintenance_comment: comment },
+ workerName: worker.worker_name,
+ }),
+ });
+ }, [handleBulkAction, maintenanceEnterMutation, maintenanceEnterWorkers]);
+
+ const handleBulkMaintenanceExit = useCallback(async (): Promise<void> => {
+ await handleBulkAction({
+ failureTitle: "Bulk Maintenance Exit Partially Failed",
+ setPending: setIsBulkMaintenanceExitPending,
+ successToast: (successCount) => ({
+ description: `${successCount} worker(s) requested to exit maintenance
mode.`,
+ title: "Bulk Maintenance Exit Requested",
+ type: "success",
+ }),
+ workers: maintenanceExitWorkers,
+ workerMutation: (worker) => maintenanceExitMutation.mutateAsync({
workerName: worker.worker_name }),
+ });
+ }, [handleBulkAction, maintenanceExitMutation, maintenanceExitWorkers]);
return {
deleteWorkers,
handleBulkDelete,
+ handleBulkMaintenanceEnter,
+ handleBulkMaintenanceExit,
handleBulkShutdown,
isBulkDeletePending,
+ isBulkMaintenanceEnterPending,
+ isBulkMaintenanceExitPending,
isBulkShutdownPending,
+ maintenanceEnterWorkers,
+ maintenanceExitWorkers,
shutdownWorkers,
};
};