@acorex-platform/module-background-job-management-client 0.0.30 → 0.0.32
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/fesm2022/{acorex-platform-module-background-job-management-client-background-job-command-error.util-BzDLhWRP.mjs → acorex-platform-module-background-job-management-client-background-job-command-error.util-BHBmSbvw.mjs} +5 -1
- package/fesm2022/acorex-platform-module-background-job-management-client-background-job-command-error.util-BHBmSbvw.mjs.map +1 -0
- package/fesm2022/acorex-platform-module-background-job-management-client-background-job-source-column.component-JzU8l_7q.mjs +83 -0
- package/fesm2022/acorex-platform-module-background-job-management-client-background-job-source-column.component-JzU8l_7q.mjs.map +1 -0
- package/fesm2022/{acorex-platform-module-background-job-management-client-cancel-background-job.command-OZPzjIow.mjs → acorex-platform-module-background-job-management-client-cancel-background-job.command-BHCWX1c1.mjs} +2 -2
- package/fesm2022/{acorex-platform-module-background-job-management-client-cancel-background-job.command-OZPzjIow.mjs.map → acorex-platform-module-background-job-management-client-cancel-background-job.command-BHCWX1c1.mjs.map} +1 -1
- package/fesm2022/acorex-platform-module-background-job-management-client-contracts.mjs +6 -1
- package/fesm2022/acorex-platform-module-background-job-management-client-contracts.mjs.map +1 -1
- package/fesm2022/acorex-platform-module-background-job-management-client-replay-dead-letter.command-BM4NYBxr.mjs +37 -0
- package/fesm2022/acorex-platform-module-background-job-management-client-replay-dead-letter.command-BM4NYBxr.mjs.map +1 -0
- package/fesm2022/{acorex-platform-module-background-job-management-client-retry-background-job.command-BdJ_PC_y.mjs → acorex-platform-module-background-job-management-client-retry-background-job.command-BBEAc_i7.mjs} +2 -2
- package/fesm2022/{acorex-platform-module-background-job-management-client-retry-background-job.command-BdJ_PC_y.mjs.map → acorex-platform-module-background-job-management-client-retry-background-job.command-BBEAc_i7.mjs.map} +1 -1
- package/fesm2022/{acorex-platform-module-background-job-management-client-run-schedule-now.command-orzJ7ZmD.mjs → acorex-platform-module-background-job-management-client-run-schedule-now.command-5RV6hAnH.mjs} +2 -2
- package/fesm2022/{acorex-platform-module-background-job-management-client-run-schedule-now.command-orzJ7ZmD.mjs.map → acorex-platform-module-background-job-management-client-run-schedule-now.command-5RV6hAnH.mjs.map} +1 -1
- package/fesm2022/acorex-platform-module-background-job-management-client-tray-execute-click.command-CEVhvrDQ.mjs +27 -0
- package/fesm2022/acorex-platform-module-background-job-management-client-tray-execute-click.command-CEVhvrDQ.mjs.map +1 -0
- package/fesm2022/acorex-platform-module-background-job-management-client-tray-run-action.command-B9SFMGxf.mjs +38 -0
- package/fesm2022/acorex-platform-module-background-job-management-client-tray-run-action.command-B9SFMGxf.mjs.map +1 -0
- package/fesm2022/acorex-platform-module-background-job-management-client.mjs +556 -17
- package/fesm2022/acorex-platform-module-background-job-management-client.mjs.map +1 -1
- package/package.json +1 -1
- package/types/acorex-platform-module-background-job-management-client-contracts.d.ts +6 -1
- package/types/acorex-platform-module-background-job-management-client.d.ts +9 -2
- package/fesm2022/acorex-platform-module-background-job-management-client-background-job-command-error.util-BzDLhWRP.mjs.map +0 -1
|
@@ -1,15 +1,20 @@
|
|
|
1
|
-
import { AXP_BACKGROUND_JOB_SCHEDULE_STATUS_DEFINITION_KEY, AXP_BACKGROUND_JOB_SCHEDULE_STATUS_ACTIVE, AXP_BACKGROUND_JOB_STATUS_DEFINITION_KEY, AXP_BACKGROUND_JOB_STATUS_CREATED, AXPBackgroundJobCommandKeys } from '@acorex-platform/framework-shared/background-job';
|
|
2
|
-
import { AXPSessionService, AXP_PERMISSION_DEFINITION_PROVIDER } from '@acorex-platform/framework-client/auth';
|
|
3
|
-
import { AXP_STATUS_PROVIDERS, AXP_MENU_PROVIDER, AXP_SETTING_DEFINITION_PROVIDER } from '@acorex-platform/framework-client/common';
|
|
4
|
-
import { AXP_MODULE_MANIFEST_PROVIDER } from '@acorex-platform/framework-client/core';
|
|
5
|
-
import { provideCommandSetups } from '@acorex-platform/framework-client/runtime';
|
|
1
|
+
import { AXP_BACKGROUND_JOB_SCHEDULE_STATUS_DEFINITION_KEY, AXP_BACKGROUND_JOB_SCHEDULE_STATUS_ACTIVE, AXP_BACKGROUND_JOB_STATUS_DEFINITION_KEY, AXP_BACKGROUND_JOB_STATUS_CREATED, AXPBackgroundJobQueryKeys, isBackgroundJobTerminal, AXPBackgroundJobCommandKeys } from '@acorex-platform/framework-shared/background-job';
|
|
2
|
+
import { AXPSessionService, AXPSessionStatus, AXP_PERMISSION_DEFINITION_PROVIDER } from '@acorex-platform/framework-client/auth';
|
|
3
|
+
import { axpCoerceBackgroundOperationDate, axpBackgroundOperationFailurePolicy, axpBackgroundOperationCapabilities, AXPBackgroundOperationProvider, AXP_BACKGROUND_OPERATION_PROVIDERS, AXP_STATUS_PROVIDERS, AXP_MENU_PROVIDER, AXP_SETTING_DEFINITION_PROVIDER } from '@acorex-platform/framework-client/common';
|
|
4
|
+
import { provideAppStartUpTask, AXP_APP_STARTUP_PRIORITY, AXP_MODULE_MANIFEST_PROVIDER } from '@acorex-platform/framework-client/core';
|
|
5
|
+
import { AXPQueryService, provideCommandSetups } from '@acorex-platform/framework-client/runtime';
|
|
6
|
+
import { AXPWidgetCoreModule, AXP_WIDGET_DEFINITION_PROVIDER } from '@acorex-platform/framework-client/layout/widget-core';
|
|
6
7
|
import * as i0 from '@angular/core';
|
|
7
|
-
import { inject,
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { Injectable, inject, DestroyRef, signal, computed, NgModule } from '@angular/core';
|
|
9
|
+
import { BACKGROUND_JOB_SOURCE_COLUMN_WIDGET, RootConfig, AXPAdministrationPermissionKeys, AXPBackgroundJobManagementMenuKeys, AXPBackgroundJobManagementSettings } from '@acorex-platform/module-background-job-management-client/contracts';
|
|
10
|
+
import { AXPWidgetGroupEnum } from '@acorex-platform/framework-shared/widget-core';
|
|
11
|
+
import { AXPStatusProvider, runtimeQueryCollectionData, AXPWidgetsList } from '@acorex-platform/framework-shared/core';
|
|
12
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
13
|
+
import { filter } from 'rxjs/operators';
|
|
14
|
+
import { Subject, distinctUntilChanged, timer, switchMap, EMPTY, firstValueFrom } from 'rxjs';
|
|
15
|
+
import { AXPRealtimeConnectionService, AXPRealtimeDispatcherService, AXP_REALTIME_TOPIC_BACKGROUND_JOB_TRAY_REFRESH } from '@acorex-platform/framework-client/realtime';
|
|
10
16
|
import { AXPCommonMenuKeys, AXPCommonPermissionGroupKeys } from '@acorex-platform/module-common-client/contracts';
|
|
11
17
|
import { AXPEntityService } from '@acorex-platform/framework-client/layout/entity';
|
|
12
|
-
import { firstValueFrom } from 'rxjs';
|
|
13
18
|
|
|
14
19
|
//#region ---- Client port ----
|
|
15
20
|
/**
|
|
@@ -20,6 +25,37 @@ class AXPBackgroundJobManagementClient {
|
|
|
20
25
|
}
|
|
21
26
|
//#endregion
|
|
22
27
|
|
|
28
|
+
//#region ----- Imports -----
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region ----- Widget -----
|
|
31
|
+
const BackgroundJobSourceColumnWidget = {
|
|
32
|
+
name: BACKGROUND_JOB_SOURCE_COLUMN_WIDGET,
|
|
33
|
+
title: '@background-job-management:jobs.entities.background-job.fields.source.title',
|
|
34
|
+
icon: 'fa-light fa-user',
|
|
35
|
+
groups: [AXPWidgetGroupEnum.BaseWidget],
|
|
36
|
+
type: 'editor',
|
|
37
|
+
components: {
|
|
38
|
+
column: {
|
|
39
|
+
component: () => import('./acorex-platform-module-background-job-management-client-background-job-source-column.component-JzU8l_7q.mjs').then((c) => c.BackgroundJobSourceColumnComponent),
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
//#endregion
|
|
44
|
+
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region ----- Widgets provider -----
|
|
47
|
+
const BACKGROUND_JOB_WIDGETS = [BackgroundJobSourceColumnWidget];
|
|
48
|
+
class AXMBackgroundJobManagementWidgetsProvider {
|
|
49
|
+
getWidgets() {
|
|
50
|
+
return BACKGROUND_JOB_WIDGETS;
|
|
51
|
+
}
|
|
52
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMBackgroundJobManagementWidgetsProvider, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
53
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMBackgroundJobManagementWidgetsProvider }); }
|
|
54
|
+
}
|
|
55
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMBackgroundJobManagementWidgetsProvider, decorators: [{
|
|
56
|
+
type: Injectable
|
|
57
|
+
}] });
|
|
58
|
+
|
|
23
59
|
const I18N$1 = '@background-job-management:schedules.entities.background-job-schedule';
|
|
24
60
|
//#region ---- Job schedule status provider ----
|
|
25
61
|
class AXMBackgroundJobScheduleStatusProvider extends AXPStatusProvider {
|
|
@@ -138,6 +174,472 @@ class AXMBackgroundJobStatusProvider extends AXPStatusProvider {
|
|
|
138
174
|
}
|
|
139
175
|
//#endregion
|
|
140
176
|
|
|
177
|
+
//#region ----- Status mapping -----
|
|
178
|
+
const ACTIVE_JOB_STATUSES = new Set([
|
|
179
|
+
'created',
|
|
180
|
+
'scheduled',
|
|
181
|
+
'queued',
|
|
182
|
+
'running',
|
|
183
|
+
'paused',
|
|
184
|
+
]);
|
|
185
|
+
function mapBackgroundJobStatusToOperationStatus(status) {
|
|
186
|
+
switch (status) {
|
|
187
|
+
case 'running':
|
|
188
|
+
return 'running';
|
|
189
|
+
case 'paused':
|
|
190
|
+
return 'paused';
|
|
191
|
+
case 'completed':
|
|
192
|
+
return 'completed';
|
|
193
|
+
case 'failed':
|
|
194
|
+
case 'dead-lettered':
|
|
195
|
+
return 'failed';
|
|
196
|
+
case 'cancelled':
|
|
197
|
+
return 'cancelled';
|
|
198
|
+
case 'created':
|
|
199
|
+
case 'scheduled':
|
|
200
|
+
case 'queued':
|
|
201
|
+
default:
|
|
202
|
+
return 'queued';
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
//#endregion
|
|
206
|
+
//#region ----- Job → tray operation -----
|
|
207
|
+
function mapBackgroundJobToTrayOperation(job) {
|
|
208
|
+
const status = mapBackgroundJobStatusToOperationStatus(job.status);
|
|
209
|
+
const isActive = ACTIVE_JOB_STATUSES.has(job.status);
|
|
210
|
+
const capabilities = job.capabilities ?? {};
|
|
211
|
+
const createdAt = axpCoerceBackgroundOperationDate(job.createdAt) ?? new Date();
|
|
212
|
+
return {
|
|
213
|
+
id: job.id,
|
|
214
|
+
providerKey: job.providerKey,
|
|
215
|
+
title: job.title,
|
|
216
|
+
status,
|
|
217
|
+
progress: job.progress,
|
|
218
|
+
statusText: job.statusText,
|
|
219
|
+
currentStep: job.currentStep,
|
|
220
|
+
createdAt,
|
|
221
|
+
startedAt: axpCoerceBackgroundOperationDate(job.firstStartedAt) ??
|
|
222
|
+
axpCoerceBackgroundOperationDate(job.executedAt) ??
|
|
223
|
+
undefined,
|
|
224
|
+
completedAt: axpCoerceBackgroundOperationDate(job.completedAt) ?? undefined,
|
|
225
|
+
capabilities: axpBackgroundOperationCapabilities({
|
|
226
|
+
cancellable: isActive &&
|
|
227
|
+
(capabilities.supportsCooperativeCancellation === true || capabilities.canCancelQueued === true),
|
|
228
|
+
pausable: isActive && capabilities.supportsCooperativePause === true,
|
|
229
|
+
resumable: status === 'paused' && capabilities.supportsCooperativePause === true,
|
|
230
|
+
retryable: status === 'failed',
|
|
231
|
+
supportsProgress: capabilities.supportsProgress === true || typeof job.progress === 'number',
|
|
232
|
+
supportsEstimate: capabilities.supportsEstimate === true,
|
|
233
|
+
clickable: capabilities.clickable === true,
|
|
234
|
+
keepInTrayAfterComplete: capabilities.keepInTrayAfterComplete === true,
|
|
235
|
+
notifyOnComplete: capabilities.notifyOnComplete !== false,
|
|
236
|
+
notifyOnFailed: capabilities.notifyOnFailed !== false,
|
|
237
|
+
}),
|
|
238
|
+
failurePolicy: axpBackgroundOperationFailurePolicy({
|
|
239
|
+
maxRetries: job.maxAttempts,
|
|
240
|
+
retryCount: Math.max(0, job.currentAttempt - 1),
|
|
241
|
+
}),
|
|
242
|
+
error: job.result?.message
|
|
243
|
+
? {
|
|
244
|
+
message: job.result.message,
|
|
245
|
+
code: typeof job.result.data?.['code'] === 'string' ? job.result.data['code'] : undefined,
|
|
246
|
+
}
|
|
247
|
+
: undefined,
|
|
248
|
+
data: {
|
|
249
|
+
jobId: job.id,
|
|
250
|
+
providerKey: job.providerKey,
|
|
251
|
+
command: job.command,
|
|
252
|
+
payload: job.command?.payload,
|
|
253
|
+
reportRequestId: typeof job.command?.payload?.['reportRequestId'] === 'string'
|
|
254
|
+
? job.command.payload['reportRequestId']
|
|
255
|
+
: undefined,
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
//#endregion
|
|
260
|
+
|
|
261
|
+
//#endregion
|
|
262
|
+
//#region ----- Tray realtime events -----
|
|
263
|
+
class BackgroundJobTrayRealtimeService {
|
|
264
|
+
constructor() {
|
|
265
|
+
this.changedSubject = new Subject();
|
|
266
|
+
this.changed$ = this.changedSubject.asObservable();
|
|
267
|
+
}
|
|
268
|
+
notifyChanged(payload) {
|
|
269
|
+
this.changedSubject.next({ payload });
|
|
270
|
+
}
|
|
271
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: BackgroundJobTrayRealtimeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
272
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: BackgroundJobTrayRealtimeService, providedIn: 'root' }); }
|
|
273
|
+
}
|
|
274
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: BackgroundJobTrayRealtimeService, decorators: [{
|
|
275
|
+
type: Injectable,
|
|
276
|
+
args: [{
|
|
277
|
+
providedIn: 'root',
|
|
278
|
+
}]
|
|
279
|
+
}] });
|
|
280
|
+
|
|
281
|
+
//#region ----- Constants -----
|
|
282
|
+
/** Safety net while a job is active if a realtime tick was missed. */
|
|
283
|
+
const ACTIVE_JOB_WATCHDOG_MS = 30_000;
|
|
284
|
+
/** Coalesce consecutive full-list fetches (e.g. create + early progress before local row exists). */
|
|
285
|
+
const FULL_REFRESH_DEBOUNCE_MS = 250;
|
|
286
|
+
const TERMINAL_STATUSES = new Set(['completed', 'failed', 'cancelled', 'dead-lettered']);
|
|
287
|
+
//#endregion
|
|
288
|
+
//#region ----- Tray sync service -----
|
|
289
|
+
class BackgroundJobTraySyncService {
|
|
290
|
+
constructor() {
|
|
291
|
+
//#region ----- Dependencies -----
|
|
292
|
+
this.queryService = inject(AXPQueryService);
|
|
293
|
+
this.sessionService = inject(AXPSessionService);
|
|
294
|
+
this.client = inject(AXPBackgroundJobManagementClient);
|
|
295
|
+
this.destroyRef = inject(DestroyRef);
|
|
296
|
+
this.trayRealtime = inject(BackgroundJobTrayRealtimeService, { optional: true });
|
|
297
|
+
//#endregion
|
|
298
|
+
//#region ----- State -----
|
|
299
|
+
this.jobs = signal([], /* @ts-ignore */
|
|
300
|
+
...(ngDevMode ? [{ debugName: "jobs" }] : /* istanbul ignore next */ []));
|
|
301
|
+
this.started = false;
|
|
302
|
+
this.refreshGeneration = 0;
|
|
303
|
+
this.refreshInFlight = false;
|
|
304
|
+
this.refreshQueued = false;
|
|
305
|
+
this.watchdogTimer = null;
|
|
306
|
+
this.fullRefreshTimer = null;
|
|
307
|
+
this.operations = computed(() => this.jobs().map((job) => mapBackgroundJobToTrayOperation(job)), /* @ts-ignore */
|
|
308
|
+
...(ngDevMode ? [{ debugName: "operations" }] : /* istanbul ignore next */ []));
|
|
309
|
+
}
|
|
310
|
+
//#endregion
|
|
311
|
+
//#region ----- Lifecycle -----
|
|
312
|
+
start() {
|
|
313
|
+
if (this.started) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
this.started = true;
|
|
317
|
+
this.destroyRef.onDestroy(() => {
|
|
318
|
+
this.clearActiveWatchdog();
|
|
319
|
+
this.clearFullRefreshTimer();
|
|
320
|
+
});
|
|
321
|
+
this.sessionService.status$
|
|
322
|
+
.pipe(filter((status) => status === AXPSessionStatus.Authorized), takeUntilDestroyed(this.destroyRef))
|
|
323
|
+
.subscribe(() => {
|
|
324
|
+
void this.refresh();
|
|
325
|
+
});
|
|
326
|
+
this.sessionService.status$
|
|
327
|
+
.pipe(filter((status) => status !== AXPSessionStatus.Authorized), takeUntilDestroyed(this.destroyRef))
|
|
328
|
+
.subscribe(() => {
|
|
329
|
+
this.clearActiveWatchdog();
|
|
330
|
+
this.clearFullRefreshTimer();
|
|
331
|
+
this.jobs.set([]);
|
|
332
|
+
});
|
|
333
|
+
this.trayRealtime?.changed$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(({ payload }) => {
|
|
334
|
+
this.applyRealtimePayload(payload);
|
|
335
|
+
});
|
|
336
|
+
if (this.sessionService.isAuthorized) {
|
|
337
|
+
void this.refresh();
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
async refresh() {
|
|
341
|
+
this.clearFullRefreshTimer();
|
|
342
|
+
if (this.refreshInFlight) {
|
|
343
|
+
this.refreshQueued = true;
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
this.refreshInFlight = true;
|
|
347
|
+
const generation = ++this.refreshGeneration;
|
|
348
|
+
try {
|
|
349
|
+
if (!this.sessionService.isAuthorized) {
|
|
350
|
+
if (generation === this.refreshGeneration) {
|
|
351
|
+
this.clearActiveWatchdog();
|
|
352
|
+
this.jobs.set([]);
|
|
353
|
+
}
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
try {
|
|
357
|
+
const result = await this.queryService.fetch(AXPBackgroundJobQueryKeys.JobListActiveForTray, { take: 100 });
|
|
358
|
+
if (generation !== this.refreshGeneration) {
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
const items = runtimeQueryCollectionData(result)?.items ?? [];
|
|
362
|
+
this.jobs.set(items);
|
|
363
|
+
this.armActiveWatchdog();
|
|
364
|
+
}
|
|
365
|
+
catch {
|
|
366
|
+
// Keep the last known tray snapshot when sync fails transiently.
|
|
367
|
+
if (generation === this.refreshGeneration) {
|
|
368
|
+
this.armActiveWatchdog();
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
finally {
|
|
373
|
+
this.refreshInFlight = false;
|
|
374
|
+
if (this.refreshQueued) {
|
|
375
|
+
this.refreshQueued = false;
|
|
376
|
+
if (this.sessionService.isAuthorized) {
|
|
377
|
+
void this.refresh();
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
async dismissJobs(jobIds) {
|
|
383
|
+
const ids = [...new Set(jobIds.map((id) => id?.trim()).filter(Boolean))];
|
|
384
|
+
if (ids.length === 0) {
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
// Optimistic hide; realtime `dismissed` keeps other tabs in sync without a list refetch.
|
|
388
|
+
this.removeJobs(ids);
|
|
389
|
+
await this.client.dismissJobs(ids);
|
|
390
|
+
}
|
|
391
|
+
//#endregion
|
|
392
|
+
//#region ----- Private helpers -----
|
|
393
|
+
applyRealtimePayload(payload) {
|
|
394
|
+
if (!this.sessionService.isAuthorized) {
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
const jobId = payload.jobId?.trim();
|
|
398
|
+
if (!jobId) {
|
|
399
|
+
this.scheduleFullRefresh();
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
if (payload.op === 'dismissed' || payload.trayDismissedAt) {
|
|
403
|
+
this.removeJobs([jobId]);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
if (payload.op === 'created') {
|
|
407
|
+
// New jobs need the full row (title, capabilities, command payload).
|
|
408
|
+
this.scheduleFullRefresh();
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
const existing = this.jobs().find((job) => job.id === jobId);
|
|
412
|
+
if (!existing) {
|
|
413
|
+
this.scheduleFullRefresh();
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
const nextStatus = (payload.status?.trim() || existing.status);
|
|
417
|
+
const keepInTray = payload.keepInTrayAfterComplete === true || existing.capabilities?.keepInTrayAfterComplete === true;
|
|
418
|
+
if (isBackgroundJobTerminal(nextStatus) && !keepInTray) {
|
|
419
|
+
this.removeJobs([jobId]);
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
this.jobs.update((current) => current.map((job) => {
|
|
423
|
+
if (job.id !== jobId) {
|
|
424
|
+
return job;
|
|
425
|
+
}
|
|
426
|
+
return {
|
|
427
|
+
...job,
|
|
428
|
+
status: nextStatus,
|
|
429
|
+
progress: typeof payload.progress === 'number' ? payload.progress : job.progress,
|
|
430
|
+
statusText: payload.statusText ?? job.statusText,
|
|
431
|
+
currentStep: payload.currentStep ?? job.currentStep,
|
|
432
|
+
capabilities: {
|
|
433
|
+
...(job.capabilities ?? {}),
|
|
434
|
+
supportsProgress: job.capabilities?.supportsProgress === true ||
|
|
435
|
+
payload.supportsProgress === true ||
|
|
436
|
+
typeof payload.progress === 'number',
|
|
437
|
+
},
|
|
438
|
+
completedAt: payload.completedAt ??
|
|
439
|
+
(TERMINAL_STATUSES.has(nextStatus) ? job.completedAt ?? new Date().toISOString() : job.completedAt),
|
|
440
|
+
};
|
|
441
|
+
}));
|
|
442
|
+
this.armActiveWatchdog();
|
|
443
|
+
}
|
|
444
|
+
scheduleFullRefresh() {
|
|
445
|
+
this.clearFullRefreshTimer();
|
|
446
|
+
this.fullRefreshTimer = setTimeout(() => {
|
|
447
|
+
this.fullRefreshTimer = null;
|
|
448
|
+
void this.refresh();
|
|
449
|
+
}, FULL_REFRESH_DEBOUNCE_MS);
|
|
450
|
+
}
|
|
451
|
+
clearFullRefreshTimer() {
|
|
452
|
+
if (!this.fullRefreshTimer) {
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
clearTimeout(this.fullRefreshTimer);
|
|
456
|
+
this.fullRefreshTimer = null;
|
|
457
|
+
}
|
|
458
|
+
removeJobs(jobIds) {
|
|
459
|
+
const idSet = new Set(jobIds);
|
|
460
|
+
this.jobs.update((current) => current.filter((job) => !idSet.has(job.id)));
|
|
461
|
+
this.armActiveWatchdog();
|
|
462
|
+
}
|
|
463
|
+
hasActiveTrayJob() {
|
|
464
|
+
return this.jobs().some((job) => !isBackgroundJobTerminal(job.status));
|
|
465
|
+
}
|
|
466
|
+
armActiveWatchdog() {
|
|
467
|
+
this.clearActiveWatchdog();
|
|
468
|
+
if (!this.sessionService.isAuthorized || !this.hasActiveTrayJob()) {
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
this.watchdogTimer = setTimeout(() => {
|
|
472
|
+
this.watchdogTimer = null;
|
|
473
|
+
void this.refresh();
|
|
474
|
+
}, ACTIVE_JOB_WATCHDOG_MS);
|
|
475
|
+
}
|
|
476
|
+
clearActiveWatchdog() {
|
|
477
|
+
if (!this.watchdogTimer) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
clearTimeout(this.watchdogTimer);
|
|
481
|
+
this.watchdogTimer = null;
|
|
482
|
+
}
|
|
483
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: BackgroundJobTraySyncService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
484
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: BackgroundJobTraySyncService, providedIn: 'root' }); }
|
|
485
|
+
}
|
|
486
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: BackgroundJobTraySyncService, decorators: [{
|
|
487
|
+
type: Injectable,
|
|
488
|
+
args: [{ providedIn: 'root' }]
|
|
489
|
+
}] });
|
|
490
|
+
|
|
491
|
+
//#region ----- Tray operation provider -----
|
|
492
|
+
class BackgroundJobTrayOperationProvider extends AXPBackgroundOperationProvider {
|
|
493
|
+
constructor() {
|
|
494
|
+
super(...arguments);
|
|
495
|
+
this.key = 'background-job';
|
|
496
|
+
this.sync = inject(BackgroundJobTraySyncService);
|
|
497
|
+
this.client = inject(AXPBackgroundJobManagementClient);
|
|
498
|
+
this.operations = this.sync.operations;
|
|
499
|
+
}
|
|
500
|
+
async create(_request) {
|
|
501
|
+
throw new Error('Background jobs are created on the server.');
|
|
502
|
+
}
|
|
503
|
+
async cancel(operationId) {
|
|
504
|
+
await this.client.cancelJob(operationId);
|
|
505
|
+
await this.sync.refresh();
|
|
506
|
+
}
|
|
507
|
+
async pause(_operationId) {
|
|
508
|
+
throw new Error('Pause is not supported from the tray yet.');
|
|
509
|
+
}
|
|
510
|
+
async resume(_operationId) {
|
|
511
|
+
throw new Error('Resume is not supported from the tray yet.');
|
|
512
|
+
}
|
|
513
|
+
async retry(operationId) {
|
|
514
|
+
await this.client.retryJob(operationId);
|
|
515
|
+
await this.sync.refresh();
|
|
516
|
+
}
|
|
517
|
+
async dismiss(operationId) {
|
|
518
|
+
await this.sync.dismissJobs([operationId]);
|
|
519
|
+
}
|
|
520
|
+
async dismissMany(operationIds) {
|
|
521
|
+
await this.sync.dismissJobs(operationIds);
|
|
522
|
+
}
|
|
523
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: BackgroundJobTrayOperationProvider, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
524
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: BackgroundJobTrayOperationProvider }); }
|
|
525
|
+
}
|
|
526
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: BackgroundJobTrayOperationProvider, decorators: [{
|
|
527
|
+
type: Injectable
|
|
528
|
+
}] });
|
|
529
|
+
|
|
530
|
+
//#region ----- Constants -----
|
|
531
|
+
const POLL_INTERVAL_MS = 60_000;
|
|
532
|
+
const RECONNECT_REFRESH_DELAY_MS = 250;
|
|
533
|
+
//#endregion
|
|
534
|
+
//#region ----- Tray fallback sync -----
|
|
535
|
+
/**
|
|
536
|
+
* Keeps the footer tray fresh when Socket.IO is down: poll while disconnected,
|
|
537
|
+
* refresh on reconnect and when the tab becomes visible again.
|
|
538
|
+
*/
|
|
539
|
+
function provideBackgroundJobTrayFallbackSync() {
|
|
540
|
+
return [
|
|
541
|
+
provideAppStartUpTask(() => {
|
|
542
|
+
const sync = inject(BackgroundJobTraySyncService);
|
|
543
|
+
const sessionService = inject(AXPSessionService);
|
|
544
|
+
const connection = inject(AXPRealtimeConnectionService, { optional: true });
|
|
545
|
+
const destroyRef = inject(DestroyRef);
|
|
546
|
+
const refreshIfAuthorized = () => {
|
|
547
|
+
if (sessionService.isReadyForAppScopedApi) {
|
|
548
|
+
void sync.refresh();
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
return {
|
|
552
|
+
name: 'background-job-tray-fallback-sync',
|
|
553
|
+
priority: AXP_APP_STARTUP_PRIORITY.realtimeSubscriptions,
|
|
554
|
+
run: async () => {
|
|
555
|
+
if (!connection) {
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
let wasConnected = connection.isSocketConnected();
|
|
559
|
+
connection.connectionStatus$
|
|
560
|
+
.pipe(distinctUntilChanged(), takeUntilDestroyed(destroyRef))
|
|
561
|
+
.subscribe((status) => {
|
|
562
|
+
const connected = status === 'connected';
|
|
563
|
+
if (connected && !wasConnected) {
|
|
564
|
+
timer(RECONNECT_REFRESH_DELAY_MS)
|
|
565
|
+
.pipe(takeUntilDestroyed(destroyRef))
|
|
566
|
+
.subscribe(() => {
|
|
567
|
+
refreshIfAuthorized();
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
wasConnected = connected;
|
|
571
|
+
});
|
|
572
|
+
connection.connectionStatus$
|
|
573
|
+
.pipe(distinctUntilChanged(), switchMap((status) => {
|
|
574
|
+
if (status === 'connected') {
|
|
575
|
+
return EMPTY;
|
|
576
|
+
}
|
|
577
|
+
return timer(POLL_INTERVAL_MS, POLL_INTERVAL_MS);
|
|
578
|
+
}), takeUntilDestroyed(destroyRef))
|
|
579
|
+
.subscribe(() => {
|
|
580
|
+
refreshIfAuthorized();
|
|
581
|
+
});
|
|
582
|
+
document.addEventListener('visibilitychange', () => {
|
|
583
|
+
if (document.visibilityState === 'visible') {
|
|
584
|
+
refreshIfAuthorized();
|
|
585
|
+
}
|
|
586
|
+
}, { passive: true });
|
|
587
|
+
},
|
|
588
|
+
};
|
|
589
|
+
}),
|
|
590
|
+
];
|
|
591
|
+
}
|
|
592
|
+
//#endregion
|
|
593
|
+
|
|
594
|
+
//#region ----- Tray realtime topic handler -----
|
|
595
|
+
function provideBackgroundJobTrayRealtimeHandlers() {
|
|
596
|
+
return [
|
|
597
|
+
provideAppStartUpTask(() => {
|
|
598
|
+
const dispatcher = inject(AXPRealtimeDispatcherService, { optional: true });
|
|
599
|
+
const trayRealtime = inject(BackgroundJobTrayRealtimeService);
|
|
600
|
+
return {
|
|
601
|
+
name: 'background-job-tray-realtime-handlers',
|
|
602
|
+
priority: AXP_APP_STARTUP_PRIORITY.realtimeHandlers,
|
|
603
|
+
run: async () => {
|
|
604
|
+
dispatcher?.register({
|
|
605
|
+
topic: AXP_REALTIME_TOPIC_BACKGROUND_JOB_TRAY_REFRESH,
|
|
606
|
+
handle: (envelope) => {
|
|
607
|
+
const payload = (envelope.payload ?? {});
|
|
608
|
+
trayRealtime.notifyChanged(payload);
|
|
609
|
+
},
|
|
610
|
+
});
|
|
611
|
+
},
|
|
612
|
+
};
|
|
613
|
+
}),
|
|
614
|
+
];
|
|
615
|
+
}
|
|
616
|
+
//#endregion
|
|
617
|
+
|
|
618
|
+
//#region ----- Tray providers -----
|
|
619
|
+
function provideBackgroundJobTray() {
|
|
620
|
+
return [
|
|
621
|
+
BackgroundJobTrayOperationProvider,
|
|
622
|
+
{
|
|
623
|
+
provide: AXP_BACKGROUND_OPERATION_PROVIDERS,
|
|
624
|
+
useExisting: BackgroundJobTrayOperationProvider,
|
|
625
|
+
multi: true,
|
|
626
|
+
},
|
|
627
|
+
...provideBackgroundJobTrayRealtimeHandlers(),
|
|
628
|
+
...provideBackgroundJobTrayFallbackSync(),
|
|
629
|
+
provideAppStartUpTask(() => {
|
|
630
|
+
const sync = inject(BackgroundJobTraySyncService);
|
|
631
|
+
return {
|
|
632
|
+
name: 'background-job-tray-sync',
|
|
633
|
+
priority: AXP_APP_STARTUP_PRIORITY.realtimeHandlers,
|
|
634
|
+
run: async () => {
|
|
635
|
+
sync.start();
|
|
636
|
+
},
|
|
637
|
+
};
|
|
638
|
+
}),
|
|
639
|
+
];
|
|
640
|
+
}
|
|
641
|
+
//#endregion
|
|
642
|
+
|
|
141
643
|
/** Background Job Management module manifest. */
|
|
142
644
|
const BackgroundJobManagementManifest = {
|
|
143
645
|
name: RootConfig.module.name,
|
|
@@ -308,20 +810,38 @@ class AXMBackgroundJobManagementSettingProvider {
|
|
|
308
810
|
|
|
309
811
|
class AXMBackgroundJobManagementModule {
|
|
310
812
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMBackgroundJobManagementModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
311
|
-
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.0.8", ngImport: i0, type: AXMBackgroundJobManagementModule }); }
|
|
813
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.0.8", ngImport: i0, type: AXMBackgroundJobManagementModule, imports: [AXPWidgetCoreModule] }); }
|
|
312
814
|
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMBackgroundJobManagementModule, providers: [
|
|
815
|
+
{
|
|
816
|
+
provide: AXP_WIDGET_DEFINITION_PROVIDER,
|
|
817
|
+
useClass: AXMBackgroundJobManagementWidgetsProvider,
|
|
818
|
+
multi: true,
|
|
819
|
+
},
|
|
820
|
+
...provideBackgroundJobTray(),
|
|
313
821
|
provideCommandSetups([
|
|
314
822
|
{
|
|
315
823
|
key: AXPBackgroundJobCommandKeys.JobCancel,
|
|
316
|
-
command: () => import('./acorex-platform-module-background-job-management-client-cancel-background-job.command-
|
|
824
|
+
command: () => import('./acorex-platform-module-background-job-management-client-cancel-background-job.command-BHCWX1c1.mjs').then((c) => c.CancelBackgroundJobCommand),
|
|
317
825
|
},
|
|
318
826
|
{
|
|
319
827
|
key: AXPBackgroundJobCommandKeys.JobRetry,
|
|
320
|
-
command: () => import('./acorex-platform-module-background-job-management-client-retry-background-job.command-
|
|
828
|
+
command: () => import('./acorex-platform-module-background-job-management-client-retry-background-job.command-BBEAc_i7.mjs').then((c) => c.RetryBackgroundJobCommand),
|
|
829
|
+
},
|
|
830
|
+
{
|
|
831
|
+
key: AXPBackgroundJobCommandKeys.JobReplayDeadLetter,
|
|
832
|
+
command: () => import('./acorex-platform-module-background-job-management-client-replay-dead-letter.command-BM4NYBxr.mjs').then((c) => c.ReplayDeadLetterBackgroundJobCommand),
|
|
833
|
+
},
|
|
834
|
+
{
|
|
835
|
+
key: AXPBackgroundJobCommandKeys.TrayExecuteClick,
|
|
836
|
+
command: () => import('./acorex-platform-module-background-job-management-client-tray-execute-click.command-CEVhvrDQ.mjs').then((c) => c.BackgroundJobTrayExecuteClickCommand),
|
|
837
|
+
},
|
|
838
|
+
{
|
|
839
|
+
key: AXPBackgroundJobCommandKeys.TrayRunAction,
|
|
840
|
+
command: () => import('./acorex-platform-module-background-job-management-client-tray-run-action.command-B9SFMGxf.mjs').then((c) => c.BackgroundJobTrayRunActionCommand),
|
|
321
841
|
},
|
|
322
842
|
{
|
|
323
843
|
key: AXPBackgroundJobCommandKeys.ScheduleRunNow,
|
|
324
|
-
command: () => import('./acorex-platform-module-background-job-management-client-run-schedule-now.command-
|
|
844
|
+
command: () => import('./acorex-platform-module-background-job-management-client-run-schedule-now.command-5RV6hAnH.mjs').then((c) => c.RunScheduleNowCommand),
|
|
325
845
|
},
|
|
326
846
|
]),
|
|
327
847
|
{
|
|
@@ -354,24 +874,43 @@ class AXMBackgroundJobManagementModule {
|
|
|
354
874
|
useValue: BackgroundJobManagementManifest,
|
|
355
875
|
multi: true,
|
|
356
876
|
},
|
|
357
|
-
] }); }
|
|
877
|
+
], imports: [AXPWidgetCoreModule] }); }
|
|
358
878
|
}
|
|
359
879
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMBackgroundJobManagementModule, decorators: [{
|
|
360
880
|
type: NgModule,
|
|
361
881
|
args: [{
|
|
882
|
+
imports: [AXPWidgetCoreModule],
|
|
362
883
|
providers: [
|
|
884
|
+
{
|
|
885
|
+
provide: AXP_WIDGET_DEFINITION_PROVIDER,
|
|
886
|
+
useClass: AXMBackgroundJobManagementWidgetsProvider,
|
|
887
|
+
multi: true,
|
|
888
|
+
},
|
|
889
|
+
...provideBackgroundJobTray(),
|
|
363
890
|
provideCommandSetups([
|
|
364
891
|
{
|
|
365
892
|
key: AXPBackgroundJobCommandKeys.JobCancel,
|
|
366
|
-
command: () => import('./acorex-platform-module-background-job-management-client-cancel-background-job.command-
|
|
893
|
+
command: () => import('./acorex-platform-module-background-job-management-client-cancel-background-job.command-BHCWX1c1.mjs').then((c) => c.CancelBackgroundJobCommand),
|
|
367
894
|
},
|
|
368
895
|
{
|
|
369
896
|
key: AXPBackgroundJobCommandKeys.JobRetry,
|
|
370
|
-
command: () => import('./acorex-platform-module-background-job-management-client-retry-background-job.command-
|
|
897
|
+
command: () => import('./acorex-platform-module-background-job-management-client-retry-background-job.command-BBEAc_i7.mjs').then((c) => c.RetryBackgroundJobCommand),
|
|
898
|
+
},
|
|
899
|
+
{
|
|
900
|
+
key: AXPBackgroundJobCommandKeys.JobReplayDeadLetter,
|
|
901
|
+
command: () => import('./acorex-platform-module-background-job-management-client-replay-dead-letter.command-BM4NYBxr.mjs').then((c) => c.ReplayDeadLetterBackgroundJobCommand),
|
|
902
|
+
},
|
|
903
|
+
{
|
|
904
|
+
key: AXPBackgroundJobCommandKeys.TrayExecuteClick,
|
|
905
|
+
command: () => import('./acorex-platform-module-background-job-management-client-tray-execute-click.command-CEVhvrDQ.mjs').then((c) => c.BackgroundJobTrayExecuteClickCommand),
|
|
906
|
+
},
|
|
907
|
+
{
|
|
908
|
+
key: AXPBackgroundJobCommandKeys.TrayRunAction,
|
|
909
|
+
command: () => import('./acorex-platform-module-background-job-management-client-tray-run-action.command-B9SFMGxf.mjs').then((c) => c.BackgroundJobTrayRunActionCommand),
|
|
371
910
|
},
|
|
372
911
|
{
|
|
373
912
|
key: AXPBackgroundJobCommandKeys.ScheduleRunNow,
|
|
374
|
-
command: () => import('./acorex-platform-module-background-job-management-client-run-schedule-now.command-
|
|
913
|
+
command: () => import('./acorex-platform-module-background-job-management-client-run-schedule-now.command-5RV6hAnH.mjs').then((c) => c.RunScheduleNowCommand),
|
|
375
914
|
},
|
|
376
915
|
]),
|
|
377
916
|
{
|