Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,12 @@ def change_maintenance_comment(
"""Write maintenance comment in the db."""
query = select(EdgeWorkerModel).where(EdgeWorkerModel.worker_name == worker_name)
worker: EdgeWorkerModel = session.scalar(query)
if worker.state in (EdgeWorkerState.MAINTENANCE_MODE, EdgeWorkerState.OFFLINE_MAINTENANCE):
if worker.state in (
EdgeWorkerState.MAINTENANCE_MODE,
EdgeWorkerState.MAINTENANCE_PENDING,
EdgeWorkerState.MAINTENANCE_REQUEST,
EdgeWorkerState.OFFLINE_MAINTENANCE,
):
worker.maintenance_comment = maintenance_comment
else:
error_message = f"Cannot change maintenance comment as {worker_name} is not in maintenance!"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,42 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
patch:
tags:
- UI
summary: Update Worker Maintenance
description: Update maintenance comments for a worker.
operationId: update_worker_maintenance
security:
- OAuth2PasswordBearer: []
- HTTPBearer: []
parameters:
- name: worker_name
in: path
required: true
schema:
type: string
title: Worker Name
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/MaintenanceRequest'
responses:
'200':
description: Successful Response
content:
application/json:
schema:
type: 'null'
title: Response Update Worker Maintenance
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
delete:
tags:
- UI
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,6 @@ export type UiServiceRequestWorkerShutdownMutationResult = Awaited<ReturnType<ty
export type JobsServiceStateMutationResult = Awaited<ReturnType<typeof JobsService.state>>;
export type WorkerServiceSetStateMutationResult = Awaited<ReturnType<typeof WorkerService.setState>>;
export type WorkerServiceUpdateQueuesMutationResult = Awaited<ReturnType<typeof WorkerService.updateQueues>>;
export type UiServiceUpdateWorkerMaintenanceMutationResult = Awaited<ReturnType<typeof UiService.updateWorkerMaintenance>>;
export type UiServiceExitWorkerMaintenanceMutationResult = Awaited<ReturnType<typeof UiService.exitWorkerMaintenance>>;
export type UiServiceDeleteWorkerMutationResult = Awaited<ReturnType<typeof UiService.deleteWorker>>;
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ export const useWorkerServiceUpdateQueues = <TData = Common.WorkerServiceUpdateQ
requestBody: WorkerQueueUpdateBody;
workerName: string;
}, TContext>({ mutationFn: ({ authorization, requestBody, workerName }) => WorkerService.updateQueues({ authorization, requestBody, workerName }) as unknown as Promise<TData>, ...options });
export const useUiServiceUpdateWorkerMaintenance = <TData = Common.UiServiceUpdateWorkerMaintenanceMutationResult, TError = unknown, TContext = unknown>(options?: Omit<UseMutationOptions<TData, TError, {
requestBody: MaintenanceRequest;
workerName: string;
}, TContext>, "mutationFn">) => useMutation<TData, TError, {
requestBody: MaintenanceRequest;
workerName: string;
}, TContext>({ mutationFn: ({ requestBody, workerName }) => UiService.updateWorkerMaintenance({ requestBody, workerName }) as unknown as Promise<TData>, ...options });
export const useUiServiceExitWorkerMaintenance = <TData = Common.UiServiceExitWorkerMaintenanceMutationResult, TError = unknown, TContext = unknown>(options?: Omit<UseMutationOptions<TData, TError, {
workerName: string;
}, TContext>, "mutationFn">) => useMutation<TData, TError, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import type { CancelablePromise } from './core/CancelablePromise';
import { OpenAPI } from './core/OpenAPI';
import { request as __request } from './core/request';
import type { FetchData, FetchResponse, StateData, StateResponse, LogfilePathData, LogfilePathResponse, PushLogsData, PushLogsResponse, RegisterData, RegisterResponse, SetStateData, SetStateResponse, UpdateQueuesData, UpdateQueuesResponse, HealthResponse, WorkerResponse, JobsResponse, RequestWorkerMaintenanceData, RequestWorkerMaintenanceResponse, ExitWorkerMaintenanceData, ExitWorkerMaintenanceResponse, RequestWorkerShutdownData, RequestWorkerShutdownResponse, DeleteWorkerData, DeleteWorkerResponse } from './types.gen';
import type { FetchData, FetchResponse, StateData, StateResponse, LogfilePathData, LogfilePathResponse, PushLogsData, PushLogsResponse, RegisterData, RegisterResponse, SetStateData, SetStateResponse, UpdateQueuesData, UpdateQueuesResponse, HealthResponse, WorkerResponse, JobsResponse, RequestWorkerMaintenanceData, RequestWorkerMaintenanceResponse, UpdateWorkerMaintenanceData, UpdateWorkerMaintenanceResponse, ExitWorkerMaintenanceData, ExitWorkerMaintenanceResponse, RequestWorkerShutdownData, RequestWorkerShutdownResponse, DeleteWorkerData, DeleteWorkerResponse } from './types.gen';

export class JobsService {
/**
Expand Down Expand Up @@ -310,6 +310,30 @@ export class UiService {
});
}

/**
* Update Worker Maintenance
* Update maintenance comments for a worker.
* @param data The data for the request.
* @param data.workerName
* @param data.requestBody
* @returns null Successful Response
* @throws ApiError
*/
public static updateWorkerMaintenance(data: UpdateWorkerMaintenanceData): CancelablePromise<UpdateWorkerMaintenanceResponse> {
return __request(OpenAPI, {
method: 'PATCH',
url: '/edge_worker/ui/worker/{worker_name}/maintenance',
path: {
worker_name: data.workerName
},
body: data.requestBody,
mediaType: 'application/json',
errors: {
422: 'Validation Error'
}
});
}

/**
* Exit Worker Maintenance
* Exit a worker from maintenance mode.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,13 @@ export type RequestWorkerMaintenanceData = {

export type RequestWorkerMaintenanceResponse = null;

export type UpdateWorkerMaintenanceData = {
requestBody: MaintenanceRequest;
workerName: string;
};

export type UpdateWorkerMaintenanceResponse = null;

export type ExitWorkerMaintenanceData = {
workerName: string;
};
Expand Down Expand Up @@ -701,6 +708,19 @@ export type $OpenApiTs = {
422: HTTPValidationError;
};
};
patch: {
req: UpdateWorkerMaintenanceData;
res: {
/**
* Successful Response
*/
200: null;
/**
* Validation Error
*/
422: HTTPValidationError;
};
};
delete: {
req: ExitWorkerMaintenanceData;
res: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*!
* 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 { Button, CloseButton, Dialog, IconButton, Portal, Textarea, useDisclosure } from "@chakra-ui/react";
import { useUiServiceUpdateWorkerMaintenance } from "openapi/queries";
import { useState } from "react";
import { FiEdit } from "react-icons/fi";

interface MaintenanceEditCommentButtonProps {
onEditComment: (toast: Record<string, string>) => void;
workerName: string;
}

export const MaintenanceEditCommentButton = ({
onEditComment,
workerName,
}: MaintenanceEditCommentButtonProps) => {
const { onClose, onOpen, open } = useDisclosure();
const [comment, setComment] = useState("");

const editCommentMutation = useUiServiceUpdateWorkerMaintenance({
onError: (error) => {
onEditComment({
description: `Unable to update comments for worker ${workerName}: ${error}`,
title: "Updating Comments failed",
type: "error",
});
},
onSuccess: () => {
onEditComment({
description: `Worker maintenance comments for ${workerName} were updated.`,
title: "Maintenance Comments updated",
type: "success",
});
onClose();
},
});

const editComment = () => {
editCommentMutation.mutate({ requestBody: { maintenance_comment: comment }, workerName });
};

return (
<>
<IconButton size="sm" variant="ghost" aria-label="Edit Comments" title="Edit Comments" onClick={onOpen}>
<FiEdit />
</IconButton>

<Dialog.Root onOpenChange={onClose} open={open} size="md">
<Portal>
<Dialog.Backdrop />
<Dialog.Positioner>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>Edit maintenance comments for worker {workerName}</Dialog.Title>
</Dialog.Header>
<Dialog.Body>
<Textarea
placeholder="Change maintenance comment (required)"
value={comment}
onChange={(e) => setComment(e.target.value)}
required
maxLength={1024}
size="sm"
/>
</Dialog.Body>
<Dialog.Footer>
<Dialog.ActionTrigger asChild>
<Button variant="outline">Cancel</Button>
</Dialog.ActionTrigger>
<Button onClick={editComment} disabled={!comment.trim()}>
Update Comments
</Button>
</Dialog.Footer>
<Dialog.CloseTrigger asChild>
<CloseButton size="sm" />
</Dialog.CloseTrigger>
</Dialog.Content>
</Dialog.Positioner>
</Portal>
</Dialog.Root>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import type { Worker } from "openapi/requests/types.gen";

import { toaster } from "src/components/ui";

import { MaintenanceEditCommentButton } from "./MaintenanceEditCommentButton";
import { MaintenanceEnterButton } from "./MaintenanceEnterButton";
import { MaintenanceExitButton } from "./MaintenanceExitButton";
import { WorkerDeleteButton } from "./WorkerDeleteButton";
Expand Down Expand Up @@ -59,11 +60,13 @@ export const WorkerOperations = ({ onOperations, worker }: WorkerOperationsProps
{worker.maintenance_comments || "No comment"}
</Box>
<Flex justifyContent="end" gap={2}>
<MaintenanceEditCommentButton onEditComment={onWorkerChange} workerName={workerName} />
<MaintenanceExitButton onExitMaintenance={onWorkerChange} workerName={workerName} />
{state === "offline maintenance" && (
{state === "offline maintenance" ? (
<WorkerDeleteButton onDelete={onWorkerChange} workerName={workerName} />
) : (
<WorkerShutdownButton onShutdown={onWorkerChange} workerName={workerName} />
)}
<WorkerShutdownButton onShutdown={onWorkerChange} workerName={workerName} />
</Flex>
</VStack>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from airflow.providers.edge3.models.edge_job import EdgeJobModel
from airflow.providers.edge3.models.edge_worker import (
EdgeWorkerModel,
change_maintenance_comment,
exit_maintenance,
remove_worker,
request_maintenance,
Expand Down Expand Up @@ -141,6 +142,37 @@ def request_worker_maintenance(
raise HTTPException(status.HTTP_400_BAD_REQUEST, detail=str(e))


@ui_router.patch(
"/worker/{worker_name}/maintenance",
dependencies=[
Depends(requires_access_view(access_view=AccessView.JOBS)),
],
)
def update_worker_maintenance(
worker_name: str,
maintenance_request: MaintenanceRequest,
session: SessionDep,
user: GetUserDep,
) -> None:
"""Update maintenance comments for a worker."""
# Check if worker exists first
worker_query = select(EdgeWorkerModel).where(EdgeWorkerModel.worker_name == worker_name)
worker = session.scalar(worker_query)
if not worker:
raise HTTPException(status.HTTP_404_NOT_FOUND, detail=f"Worker {worker_name} not found")
if not maintenance_request.maintenance_comment:
raise HTTPException(status.HTTP_400_BAD_REQUEST, detail="Maintenance comment is required")

# Format the comment with timestamp and username (username will be added by plugin layer)
first_line = worker.maintenance_comment.split("\n", 1)[0] if worker.maintenance_comment else ""
formatted_comment = f"{first_line}\n[{datetime.now().strftime('%Y-%m-%d %H:%M')}] - {user.get_name()} updated comment:\n{maintenance_request.maintenance_comment}"

try:
change_maintenance_comment(worker_name, formatted_comment, session=session)
except Exception as e:
raise HTTPException(status.HTTP_400_BAD_REQUEST, detail=str(e))


@ui_router.delete(
"/worker/{worker_name}/maintenance",
dependencies=[
Expand Down
2 changes: 1 addition & 1 deletion providers/edge3/www-hash.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
7bd9c9e39161fd873c231f70eb1a3552215aed06820c48a97da1dea0eef1a872
9ade83e065f7115a0426e3c234a6071e671de25f9c78782bd2a2d087fbe30270