@adminforth/background-jobs 1.11.6 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build.log CHANGED
@@ -13,5 +13,5 @@ custom/tsconfig.json
13
13
  custom/useBackgroundJobApi.ts
14
14
  custom/utils.ts
15
15
 
16
- sent 18,507 bytes received 172 bytes 37,358.00 bytes/sec
17
- total size is 17,874 speedup is 0.96
16
+ sent 18,730 bytes received 172 bytes 37,804.00 bytes/sec
17
+ total size is 18,097 speedup is 0.96
@@ -67,6 +67,7 @@ import { getTimeAgoString, callAdminForthApi, getCustomComponent} from '@/utils'
67
67
  import { useI18n } from 'vue-i18n';
68
68
  import StateToIcon from './StateToIcon.vue';
69
69
  import { useAdminforth } from '@/adminforth';
70
+ import { watch } from 'vue';
70
71
 
71
72
 
72
73
  const { t } = useI18n();
@@ -133,6 +134,19 @@ async function getJobTasks(limit: number = 10, offset: number = 0): Promise<{sta
133
134
  }
134
135
  }
135
136
 
137
+ watch(
138
+ () => props.job.state?.error,
139
+ (error) => {
140
+ if (error) {
141
+ adminforth.alert({
142
+ message: error,
143
+ variant: 'danger',
144
+ });
145
+ }
146
+ },
147
+ { immediate: true }
148
+ );
149
+
136
150
 
137
151
 
138
152
  </script>
@@ -67,6 +67,7 @@ import { getTimeAgoString, callAdminForthApi, getCustomComponent} from '@/utils'
67
67
  import { useI18n } from 'vue-i18n';
68
68
  import StateToIcon from './StateToIcon.vue';
69
69
  import { useAdminforth } from '@/adminforth';
70
+ import { watch } from 'vue';
70
71
 
71
72
 
72
73
  const { t } = useI18n();
@@ -133,6 +134,19 @@ async function getJobTasks(limit: number = 10, offset: number = 0): Promise<{sta
133
134
  }
134
135
  }
135
136
 
137
+ watch(
138
+ () => props.job.state?.error,
139
+ (error) => {
140
+ if (error) {
141
+ adminforth.alert({
142
+ message: error,
143
+ variant: 'danger',
144
+ });
145
+ }
146
+ },
147
+ { immediate: true }
148
+ );
149
+
136
150
 
137
151
 
138
152
  </script>
package/dist/index.js CHANGED
@@ -17,6 +17,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
17
17
  constructor(options) {
18
18
  super(options, import.meta.url);
19
19
  this.taskHandlers = {};
20
+ this.onAllTasksDoneHandlers = {};
20
21
  this.jobCustomComponents = {};
21
22
  this.jobParallelLimits = {};
22
23
  this.levelDbInstances = {};
@@ -152,10 +153,48 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
152
153
  return count ? parseInt(count, 10) : 0;
153
154
  });
154
155
  }
155
- registerTaskHandler({ jobHandlerName, handler, parallelLimit = 3, }) {
156
+ getAllTasksDoneStatus(levelDb) {
157
+ return __awaiter(this, void 0, void 0, function* () {
158
+ const totalTasks = yield this.getTotalTasksInLevelDb(levelDb);
159
+ let failedTasks = 0;
160
+ let succeededTasks = 0;
161
+ for (let taskIndex = 0; taskIndex < totalTasks; taskIndex++) {
162
+ const status = yield this.getLevelDbTaskStatusField(levelDb, taskIndex.toString());
163
+ if (status === 'FAILED') {
164
+ failedTasks++;
165
+ }
166
+ else if (status === 'DONE') {
167
+ succeededTasks++;
168
+ }
169
+ }
170
+ return { failedTasks, succeededTasks };
171
+ });
172
+ }
173
+ triggerOnAllTasksDone(onAllTasksDone, levelDb, jobId) {
174
+ return __awaiter(this, void 0, void 0, function* () {
175
+ if (!onAllTasksDone) {
176
+ return;
177
+ }
178
+ try {
179
+ const status = yield this.getAllTasksDoneStatus(levelDb);
180
+ yield onAllTasksDone(Object.assign({ jobId }, status));
181
+ }
182
+ catch (error) {
183
+ const errorMessage = error instanceof Error ? error.message : String(error);
184
+ afLogger.error(`Error in onAllTasksDone callback for job ${jobId}: ${errorMessage}`);
185
+ }
186
+ });
187
+ }
188
+ registerTaskHandler({ jobHandlerName, handler, parallelLimit = 3, onAllTasksDone, }) {
156
189
  //register the handler in a map with jobHandlerName as key and handler as value
157
190
  this.taskHandlers[jobHandlerName] = handler;
158
191
  this.jobParallelLimits[jobHandlerName] = parallelLimit;
192
+ if (onAllTasksDone) {
193
+ this.onAllTasksDoneHandlers[jobHandlerName] = onAllTasksDone;
194
+ }
195
+ else {
196
+ delete this.onAllTasksDoneHandlers[jobHandlerName];
197
+ }
159
198
  }
160
199
  registerTaskDetailsComponent({ jobHandlerName, component, }) {
161
200
  this.jobCustomComponents[jobHandlerName] = component;
@@ -163,6 +202,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
163
202
  startNewJob(jobName, adminUser, tasks, jobHandlerName) {
164
203
  return __awaiter(this, void 0, void 0, function* () {
165
204
  const handleTask = this.taskHandlers[jobHandlerName];
205
+ const onAllTasksDone = this.onAllTasksDoneHandlers[jobHandlerName];
166
206
  if (!handleTask) {
167
207
  throw new Error(`No handler registered for jobHandler ${jobHandlerName}. Please register a handler using the registerTaskHandler method before starting a job with this jobHandler.`);
168
208
  }
@@ -203,11 +243,11 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
203
243
  return limit2(() => this.createLevelDbTaskRecord(jobLevelDb, index.toString(), task.state));
204
244
  });
205
245
  yield Promise.all(createTaskRecordsPromises);
206
- this.runProcessingTasks(tasks, jobLevelDb, jobId, handleTask, parrallelLimit);
246
+ this.runProcessingTasks(tasks, jobLevelDb, jobId, handleTask, parrallelLimit, onAllTasksDone);
207
247
  return jobId;
208
248
  });
209
249
  }
210
- runProcessingTasks(tasks, jobLevelDb, jobId, handleTask, parrallelLimit) {
250
+ runProcessingTasks(tasks, jobLevelDb, jobId, handleTask, parrallelLimit, onAllTasksDone) {
211
251
  return __awaiter(this, void 0, void 0, function* () {
212
252
  const totalTasks = tasks.length;
213
253
  let completedTasks = 0;
@@ -246,7 +286,9 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
246
286
  this.adminforth.websocket.publish(`/background-jobs-task-update/${jobId}`, { taskIndex, status: "DONE" });
247
287
  }
248
288
  catch (error) {
249
- afLogger.error(`Error in handling task ${taskIndex} of job ${jobId}: ${error}`);
289
+ const errorMessage = (error === null || error === void 0 ? void 0 : error.message) || 'Unknown error';
290
+ afLogger.error(`Error in handling task ${taskIndex} of job ${jobId}: ${errorMessage}`);
291
+ yield this.setJobField(jobId, 'error', errorMessage);
250
292
  yield this.setLevelDbTaskStatusField(jobLevelDb, taskIndex.toString(), 'FAILED');
251
293
  this.adminforth.websocket.publish(`/background-jobs-task-update/${jobId}`, { taskIndex, status: "FAILED" });
252
294
  failedTasks++;
@@ -275,6 +317,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
275
317
  });
276
318
  this.adminforth.websocket.publish('/background-jobs', { jobId, status: 'DONE', finishedAt: (new Date()).toISOString() });
277
319
  this.cleanupJobMutexIfTerminalStatus(jobId, 'DONE');
320
+ yield this.triggerOnAllTasksDone(onAllTasksDone, jobLevelDb, jobId);
278
321
  }
279
322
  else if (failedTasks > 0) {
280
323
  yield this.adminforth.resource(this.getResourceId()).update(jobId, {
@@ -283,6 +326,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
283
326
  });
284
327
  this.adminforth.websocket.publish('/background-jobs', { jobId, status: 'DONE_WITH_ERRORS' });
285
328
  this.cleanupJobMutexIfTerminalStatus(jobId, 'DONE_WITH_ERRORS');
329
+ yield this.triggerOnAllTasksDone(onAllTasksDone, jobLevelDb, jobId);
286
330
  }
287
331
  });
288
332
  }
@@ -319,6 +363,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
319
363
  return;
320
364
  }
321
365
  const parrallelLimit = this.jobParallelLimits[jobHandlerName] || 3;
366
+ const onAllTasksDone = this.onAllTasksDoneHandlers[jobHandlerName];
322
367
  const unfinishedTasks = [];
323
368
  let taskIndex = 0;
324
369
  while (true) {
@@ -343,7 +388,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
343
388
  }
344
389
  taskIndex++;
345
390
  }
346
- yield this.runProcessingTasks(unfinishedTasks, jobLevelDb, job[this.getResourcePk()], handleTask, parrallelLimit);
391
+ yield this.runProcessingTasks(unfinishedTasks, jobLevelDb, job[this.getResourcePk()], handleTask, parrallelLimit, onAllTasksDone);
347
392
  });
348
393
  }
349
394
  setJobField(jobId, key, value) {
package/index.ts CHANGED
@@ -11,6 +11,12 @@ type TaskStatus = 'SCHEDULED' | 'IN_PROGRESS' | 'DONE' | 'FAILED';
11
11
  type setStateFieldParams = (state: Record<string, any>) => void;
12
12
  type getStateFieldParams = () => any;
13
13
  type taskHandlerType = ( { jobId, setTaskStateField, getTaskStateField }: { jobId: string; setTaskStateField: setStateFieldParams; getTaskStateField: getStateFieldParams } ) => Promise<void>;
14
+ type allTasksDoneStatusType = {
15
+ jobId: string;
16
+ failedTasks: number;
17
+ succeededTasks: number;
18
+ };
19
+ type onAllTasksDoneType = (status: allTasksDoneStatusType) => Promise<void> | void;
14
20
  type taskType = {
15
21
  skip?: boolean;
16
22
  state: Record<string, any>;
@@ -19,6 +25,7 @@ type taskType = {
19
25
  export default class BackgroundJobsPlugin extends AdminForthPlugin {
20
26
  options: PluginOptions;
21
27
  private taskHandlers: Record<string, taskHandlerType> = {};
28
+ private onAllTasksDoneHandlers: Partial<Record<string, onAllTasksDoneType>> = {};
22
29
  private jobCustomComponents: Record<string, AdminForthComponentDeclarationFull> = {};
23
30
  private jobParallelLimits: Record<string, number> = {};
24
31
  private levelDbInstances: Record<string, Level> = {};
@@ -159,12 +166,48 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
159
166
  const count = await levelDb.get('_meta:count');
160
167
  return count ? parseInt(count, 10) : 0;
161
168
  }
169
+
170
+ private async getAllTasksDoneStatus(levelDb: Level): Promise<Omit<allTasksDoneStatusType, 'jobId'>> {
171
+ const totalTasks = await this.getTotalTasksInLevelDb(levelDb);
172
+ let failedTasks = 0;
173
+ let succeededTasks = 0;
174
+
175
+ for (let taskIndex = 0; taskIndex < totalTasks; taskIndex++) {
176
+ const status = await this.getLevelDbTaskStatusField(levelDb, taskIndex.toString());
177
+ if (status === 'FAILED') {
178
+ failedTasks++;
179
+ } else if (status === 'DONE') {
180
+ succeededTasks++;
181
+ }
182
+ }
183
+
184
+ return { failedTasks, succeededTasks };
185
+ }
186
+
187
+ private async triggerOnAllTasksDone(onAllTasksDone: onAllTasksDoneType | undefined, levelDb: Level, jobId: string) {
188
+ if (!onAllTasksDone) {
189
+ return;
190
+ }
191
+
192
+ try {
193
+ const status = await this.getAllTasksDoneStatus(levelDb);
194
+ await onAllTasksDone({ jobId, ...status });
195
+ } catch (error) {
196
+ const errorMessage = error instanceof Error ? error.message : String(error);
197
+ afLogger.error(`Error in onAllTasksDone callback for job ${jobId}: ${errorMessage}`);
198
+ }
199
+ }
162
200
 
163
- public registerTaskHandler({ jobHandlerName, handler, parallelLimit = 3,
164
- }:{jobHandlerName: string, handler: taskHandlerType, parallelLimit?: number}) {
201
+ public registerTaskHandler({ jobHandlerName, handler, parallelLimit = 3, onAllTasksDone,
202
+ }:{jobHandlerName: string, handler: taskHandlerType, parallelLimit?: number, onAllTasksDone?: onAllTasksDoneType}) {
165
203
  //register the handler in a map with jobHandlerName as key and handler as value
166
204
  this.taskHandlers[jobHandlerName] = handler;
167
205
  this.jobParallelLimits[jobHandlerName] = parallelLimit;
206
+ if (onAllTasksDone) {
207
+ this.onAllTasksDoneHandlers[jobHandlerName] = onAllTasksDone;
208
+ } else {
209
+ delete this.onAllTasksDoneHandlers[jobHandlerName];
210
+ }
168
211
  }
169
212
 
170
213
  public registerTaskDetailsComponent({
@@ -182,6 +225,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
182
225
  ): Promise<string> {
183
226
 
184
227
  const handleTask: taskHandlerType = this.taskHandlers[jobHandlerName];
228
+ const onAllTasksDone = this.onAllTasksDoneHandlers[jobHandlerName];
185
229
  if (!handleTask) {
186
230
  throw new Error(`No handler registered for jobHandler ${jobHandlerName}. Please register a handler using the registerTaskHandler method before starting a job with this jobHandler.`);
187
231
  }
@@ -226,7 +270,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
226
270
 
227
271
  await Promise.all(createTaskRecordsPromises);
228
272
 
229
- this.runProcessingTasks(tasks, jobLevelDb, jobId, handleTask, parrallelLimit);
273
+ this.runProcessingTasks(tasks, jobLevelDb, jobId, handleTask, parrallelLimit, onAllTasksDone);
230
274
  return jobId;
231
275
  }
232
276
 
@@ -236,6 +280,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
236
280
  jobId: string,
237
281
  handleTask: taskHandlerType,
238
282
  parrallelLimit: number,
283
+ onAllTasksDone?: onAllTasksDoneType,
239
284
  ) {
240
285
  const totalTasks = tasks.length;
241
286
  let completedTasks = 0;
@@ -279,7 +324,9 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
279
324
  await this.setLevelDbTaskStatusField(jobLevelDb, taskIndex.toString(), 'DONE');
280
325
  this.adminforth.websocket.publish(`/background-jobs-task-update/${jobId}`, { taskIndex, status: "DONE" });
281
326
  } catch (error) {
282
- afLogger.error(`Error in handling task ${taskIndex} of job ${jobId}: ${error}`, );
327
+ const errorMessage = error?.message || 'Unknown error';
328
+ afLogger.error(`Error in handling task ${taskIndex} of job ${jobId}: ${errorMessage}`, );
329
+ await this.setJobField(jobId, 'error', errorMessage);
283
330
  await this.setLevelDbTaskStatusField(jobLevelDb, taskIndex.toString(), 'FAILED');
284
331
  this.adminforth.websocket.publish(`/background-jobs-task-update/${jobId}`, { taskIndex, status: "FAILED" });
285
332
  failedTasks++;
@@ -310,6 +357,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
310
357
  })
311
358
  this.adminforth.websocket.publish('/background-jobs', { jobId, status: 'DONE', finishedAt: (new Date()).toISOString() });
312
359
  this.cleanupJobMutexIfTerminalStatus(jobId, 'DONE');
360
+ await this.triggerOnAllTasksDone(onAllTasksDone, jobLevelDb, jobId);
313
361
  } else if (failedTasks > 0) {
314
362
  await this.adminforth.resource(this.getResourceId()).update(jobId, {
315
363
  [this.options.statusField]: 'DONE_WITH_ERRORS',
@@ -317,6 +365,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
317
365
  })
318
366
  this.adminforth.websocket.publish('/background-jobs', { jobId, status: 'DONE_WITH_ERRORS' });
319
367
  this.cleanupJobMutexIfTerminalStatus(jobId, 'DONE_WITH_ERRORS');
368
+ await this.triggerOnAllTasksDone(onAllTasksDone, jobLevelDb, jobId);
320
369
  }
321
370
  }
322
371
 
@@ -353,6 +402,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
353
402
  return;
354
403
  }
355
404
  const parrallelLimit = this.jobParallelLimits[jobHandlerName] || 3;
405
+ const onAllTasksDone = this.onAllTasksDoneHandlers[jobHandlerName];
356
406
 
357
407
  const unfinishedTasks: taskType[] = [];
358
408
  let taskIndex = 0;
@@ -376,7 +426,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
376
426
  }
377
427
  taskIndex++;
378
428
  }
379
- await this.runProcessingTasks(unfinishedTasks, jobLevelDb, job[this.getResourcePk()], handleTask, parrallelLimit);
429
+ await this.runProcessingTasks(unfinishedTasks, jobLevelDb, job[this.getResourcePk()], handleTask, parrallelLimit, onAllTasksDone);
380
430
 
381
431
  }
382
432
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/background-jobs",
3
- "version": "1.11.6",
3
+ "version": "1.12.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",