From 391a78231f6c46fddf9f6a5bfd2ee95f1cb18eb9 Mon Sep 17 00:00:00 2001 From: Leonardo Giacone Date: Wed, 16 Oct 2024 10:29:32 +0200 Subject: [PATCH] refactor(api-headless-cms-bulk-action): wait for subtasks to finish before cleanup (#4331) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bruno Zorić --- .../src/tasks/createEmptyTrashBinsTask.ts | 117 +++++++++++++----- .../src/types.ts | 14 +++ packages/tasks/src/crud/service.tasks.ts | 10 +- 3 files changed, 112 insertions(+), 29 deletions(-) diff --git a/packages/api-headless-cms-bulk-actions/src/tasks/createEmptyTrashBinsTask.ts b/packages/api-headless-cms-bulk-actions/src/tasks/createEmptyTrashBinsTask.ts index a7698bd4910..3cdab9b7ca4 100644 --- a/packages/api-headless-cms-bulk-actions/src/tasks/createEmptyTrashBinsTask.ts +++ b/packages/api-headless-cms-bulk-actions/src/tasks/createEmptyTrashBinsTask.ts @@ -1,5 +1,9 @@ -import { createTaskDefinition } from "@webiny/tasks"; -import { HcmsBulkActionsContext, IBulkActionOperationByModelInput } from "~/types"; +import { createPrivateTaskDefinition, TaskDataStatus } from "@webiny/tasks"; +import { + HcmsBulkActionsContext, + IBulkActionOperationByModelInput, + TrashBinCleanUpParams +} from "~/types"; import { ChildTasksCleanup } from "~/useCases/internals"; const calculateDateTimeString = () => { @@ -19,22 +23,86 @@ const calculateDateTimeString = () => { return currentDate.toISOString(); }; +const cleanup = async ({ context, task }: TrashBinCleanUpParams) => { + // We want to clean all child tasks and logs, which have no errors. + const childTasksCleanup = new ChildTasksCleanup(); + try { + await childTasksCleanup.execute({ + context, + task + }); + } catch (ex) { + console.error(`Error while cleaning "EmptyTrashBins" child tasks.`, ex); + } +}; + export const createEmptyTrashBinsTask = () => { - return createTaskDefinition({ - isPrivate: true, + return createPrivateTaskDefinition({ id: "hcmsEntriesEmptyTrashBins", title: "Headless CMS - Empty all trash bins", description: "Delete all entries found in the trash bin, for each model found in the system.", - maxIterations: 1, + maxIterations: 24, + disableDatabaseLogs: true, run: async params => { - const { response, isAborted, context } = params; + const { response, isAborted, isCloseToTimeout, context, trigger, input, store } = + params; + if (isAborted()) { + return response.aborted(); + } else if (isCloseToTimeout()) { + return response.continue( + { + ...input + }, + { + seconds: 30 + } + ); + } - try { - if (isAborted()) { - return response.aborted(); + if (input.triggered) { + const { items } = await context.tasks.listTasks({ + where: { + parentId: store.getTask().id, + taskStatus_in: [TaskDataStatus.RUNNING, TaskDataStatus.PENDING] + }, + limit: 100000 + }); + + if (items.length === 0) { + return response.done( + "Task done: emptying the trash bin for all registered models." + ); + } + + for (const item of items) { + const status = await context.tasks.fetchServiceInfo(item.id); + + if (status?.status === "FAILED" || status?.status === "TIMED_OUT") { + await context.tasks.updateTask(item.id, { + taskStatus: TaskDataStatus.FAILED + }); + continue; + } + + if (status?.status === "ABORTED") { + await context.tasks.updateTask(item.id, { + taskStatus: TaskDataStatus.ABORTED + }); + } } + return response.continue( + { + ...input + }, + { + seconds: 3600 + } + ); + } + + try { const locales = context.i18n.getLocales(); await context.i18n.withEachLocale(locales, async () => { @@ -43,10 +111,9 @@ export const createEmptyTrashBinsTask = () => { }); for (const model of models) { - await context.tasks.trigger({ + await trigger({ name: `Headless CMS - Empty trash bin for "${model.name}" model.`, definition: "hcmsBulkListDeleteEntries", - parent: params.store.getTask(), input: { modelId: model.modelId, where: { @@ -55,29 +122,23 @@ export const createEmptyTrashBinsTask = () => { } }); } - return; }); - return response.done( - `Task done: emptying the trash bin for all registered models.` + return response.continue( + { + triggered: true + }, + { + seconds: 120 + } ); } catch (ex) { return response.error(ex.message ?? "Error while executing EmptyTrashBins task"); } }, - onDone: async ({ context, task }) => { - /** - * We want to clean all child tasks and logs, which have no errors. - */ - const childTasksCleanup = new ChildTasksCleanup(); - try { - await childTasksCleanup.execute({ - context, - task - }); - } catch (ex) { - console.error("Error while cleaning `EmptyTrashBins` child tasks.", ex); - } - } + onMaxIterations: cleanup, + onDone: cleanup, + onError: cleanup, + onAbort: cleanup }); }; diff --git a/packages/api-headless-cms-bulk-actions/src/types.ts b/packages/api-headless-cms-bulk-actions/src/types.ts index e31a64d11ea..913818c8b75 100644 --- a/packages/api-headless-cms-bulk-actions/src/types.ts +++ b/packages/api-headless-cms-bulk-actions/src/types.ts @@ -2,6 +2,10 @@ import { CmsContext } from "@webiny/api-headless-cms/types"; import { Context as BaseContext } from "@webiny/handler/types"; import { Context as TasksContext, + ITaskOnAbortParams, + ITaskOnErrorParams, + ITaskOnMaxIterationsParams, + ITaskOnSuccessParams, ITaskResponseDoneResultOutput, ITaskRunParams } from "@webiny/tasks/types"; @@ -64,3 +68,13 @@ export type IBulkActionOperationByModelTaskParams = ITaskRunParams< IBulkActionOperationByModelInput, IBulkActionOperationByModelOutput >; + +/** + * Trash Bin + */ + +export type TrashBinCleanUpParams = + | ITaskOnSuccessParams + | ITaskOnErrorParams + | ITaskOnAbortParams + | ITaskOnMaxIterationsParams; diff --git a/packages/tasks/src/crud/service.tasks.ts b/packages/tasks/src/crud/service.tasks.ts index 5856cff9455..becc580cfaa 100644 --- a/packages/tasks/src/crud/service.tasks.ts +++ b/packages/tasks/src/crud/service.tasks.ts @@ -75,7 +75,13 @@ export const createServiceCrud = (context: Context): ITasksContextServiceObject delay }); - const task = await context.tasks.createTask(input); + let task: ITask; + try { + task = await context.tasks.createTask(input); + } catch (ex) { + console.log("Could not create the task.", ex); + throw ex; + } let result: Awaited> | null = null; try { @@ -91,6 +97,8 @@ export const createServiceCrud = (context: Context): ITasksContextServiceObject ); } } catch (ex) { + console.log("Could not trigger the step function."); + console.error(ex); /** * In case of failure to create the Event Bridge Event, we need to delete the task that was meant to be created. * TODO maybe we can leave the task and update it as failed - with event bridge error?