2
0
mirror of https://github.com/inventree/InvenTree.git synced 2026-04-15 15:58:48 +00:00

[API] Monitor task (#11527)

* Enhance docstring

* Return the ID of an offloaded task

* Add API endpoint for background task detail

* Add UI hook for monitoring background task  progress

* Handle queued tasks (not yet started)

* Improve UX

* Update frontend lib version

* Bump API version

* Fix notification

* Simplify UI interface

* Implement internal hook

* Fix API path sequence

* Add unit tests for task detail endpoint

* Refactor code into reusable model

* Explicit operation_id for API endpoints

* Further refactoring

* Use 200 response code

- axios does not like 202, simplify it

* Return task response for validation of part BOM

* Fix schema

* Cleanup

* Run background worker during playwright tests

- For full e2e integration testing

* Improve hooks and unit testing

* Rename custom hooks to meet react naming requirements
This commit is contained in:
Oliver
2026-03-15 14:11:22 +11:00
committed by GitHub
parent 133d254ba7
commit 6830ba5efe
20 changed files with 482 additions and 54 deletions

View File

@@ -0,0 +1,119 @@
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { apiUrl } from '@lib/functions/Api';
import { useDocumentVisibility } from '@mantine/hooks';
import { notifications, showNotification } from '@mantine/notifications';
import {
IconCircleCheck,
IconCircleX,
IconExclamationCircle
} from '@tabler/icons-react';
import { type QueryClient, useQuery } from '@tanstack/react-query';
import type { AxiosInstance } from 'axios';
import { useEffect, useState } from 'react';
import { queryClient } from '../../src/App';
export type MonitorBackgroundTaskProps = {
api: AxiosInstance;
queryClient?: QueryClient;
title?: string;
message: string;
errorMessage?: string;
successMessage?: string;
failureMessage?: string;
taskId?: string;
onSuccess?: () => void;
onFailure?: () => void;
onComplete?: () => void;
onError?: (error: Error) => void;
};
/**
* Hook for monitoring a background task running on the server
*/
export default function useMonitorBackgroundTask(
props: MonitorBackgroundTaskProps
) {
const visibility = useDocumentVisibility();
const [tracking, setTracking] = useState<boolean>(false);
useEffect(() => {
if (!!props.taskId) {
setTracking(true);
showNotification({
id: `background-task-${props.taskId}`,
title: props.title,
message: props.message,
loading: true,
autoClose: false,
withCloseButton: false
});
} else {
setTracking(false);
}
}, [props.taskId]);
useQuery(
{
enabled: !!props.taskId && tracking && visibility === 'visible',
refetchInterval: 500,
queryKey: ['background-task', props.taskId],
queryFn: () =>
props.api
.get(apiUrl(ApiEndpoints.task_overview, props.taskId))
.then((response) => {
const data = response?.data ?? {};
if (data.complete) {
setTracking(false);
props.onComplete?.();
notifications.update({
id: `background-task-${props.taskId}`,
title: props.title,
loading: false,
color: data.success ? 'green' : 'red',
message: response.data?.success
? (props.successMessage ?? props.message)
: (props.failureMessage ?? props.message),
icon: response.data?.success ? (
<IconCircleCheck />
) : (
<IconCircleX />
),
autoClose: 1000,
withCloseButton: true
});
if (data.success) {
props.onSuccess?.();
} else {
props.onFailure?.();
}
}
return response;
})
.catch((error) => {
console.error(
`Error fetching background task status for task ${props.taskId}:`,
error
);
setTracking(false);
props.onError?.(error);
notifications.update({
id: `background-task-${props.taskId}`,
title: props.title,
loading: false,
color: 'red',
message: props.errorMessage ?? props.message,
icon: <IconExclamationCircle color='red' />,
autoClose: 5000,
withCloseButton: true
});
})
},
queryClient
);
}

View File

@@ -9,48 +9,44 @@ import { ProgressBar } from '../components/ProgressBar';
import { ApiEndpoints } from '../enums/ApiEndpoints';
import { apiUrl } from '../functions/Api';
/**
* Hook for monitoring a data output process running on the server
*/
export default function monitorDataOutput({
api,
queryClient,
title,
hostname,
id
}: {
export type MonitorDataOutputProps = {
api: AxiosInstance;
queryClient?: QueryClient;
title: string;
hostname?: string;
id?: number;
}) {
};
/**
* Hook for monitoring a data output process running on the server
*/
export default function useMonitorDataOutput(props: MonitorDataOutputProps) {
const visibility = useDocumentVisibility();
const [loading, setLoading] = useState<boolean>(false);
useEffect(() => {
if (!!id) {
if (!!props.id) {
setLoading(true);
showNotification({
id: `data-output-${id}`,
title: title,
id: `data-output-${props.id}`,
title: props.title,
loading: true,
autoClose: false,
withCloseButton: false,
message: <ProgressBar size='lg' value={0} progressLabel />
});
} else setLoading(false);
}, [id, title]);
}, [props.id, props.title]);
useQuery(
{
enabled: !!id && loading && visibility === 'visible',
enabled: !!props.id && loading && visibility === 'visible',
refetchInterval: 500,
queryKey: ['data-output', id, title],
queryKey: ['data-output', props.id, props.title],
queryFn: () =>
api
.get(apiUrl(ApiEndpoints.data_output, id))
props.api
.get(apiUrl(ApiEndpoints.data_output, props.id))
.then((response) => {
const data = response?.data ?? {};
@@ -61,21 +57,21 @@ export default function monitorDataOutput({
data?.error ?? data?.errors?.error ?? t`Process failed`;
notifications.update({
id: `data-output-${id}`,
id: `data-output-${props.id}`,
loading: false,
icon: <IconExclamationCircle />,
autoClose: 2500,
title: title,
title: props.title,
message: error,
color: 'red'
});
} else if (data.complete) {
setLoading(false);
notifications.update({
id: `data-output-${id}`,
id: `data-output-${props.id}`,
loading: false,
autoClose: 2500,
title: title,
title: props.title,
message: t`Process completed successfully`,
color: 'green',
icon: <IconCircleCheck />
@@ -83,7 +79,7 @@ export default function monitorDataOutput({
if (data.output) {
const url = data.output;
const base = hostname ?? window.location.origin;
const base = props.hostname ?? window.location.origin;
const downloadUrl = new URL(url, base);
@@ -91,7 +87,7 @@ export default function monitorDataOutput({
}
} else {
notifications.update({
id: `data-output-${id}`,
id: `data-output-${props.id}`,
loading: true,
autoClose: false,
withCloseButton: false,
@@ -110,19 +106,19 @@ export default function monitorDataOutput({
return data;
})
.catch((error: Error) => {
console.error('Error in monitorDataOutput:', error);
console.error('Error in useMonitorDataOutput:', error);
setLoading(false);
notifications.update({
id: `data-output-${id}`,
id: `data-output-${props.id}`,
loading: false,
autoClose: 2500,
title: title,
title: props.title,
message: error.message || t`Process failed`,
color: 'red'
});
return {};
})
},
queryClient
props.queryClient
);
}

View File

@@ -73,4 +73,11 @@ export {
} from './components/RowActions';
// Shared hooks
export { default as monitorDataOutput } from './hooks/MonitorDataOutput';
export {
default as useMonitorDataOutput,
type MonitorDataOutputProps
} from './hooks/MonitorDataOutput';
export {
default as useMonitorBackgroundTask,
type MonitorBackgroundTaskProps
} from './hooks/MonitorBackgroundTask';