@adminforth/background-jobs 1.13.0 → 1.14.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 +2 -2
- package/custom/GlobalJobApi.vue +3 -2
- package/custom/JobInfoPopup.vue +63 -12
- package/custom/JobsList.vue +31 -5
- package/custom/NavbarJobs.vue +6 -5
- package/dist/custom/GlobalJobApi.vue +3 -2
- package/dist/custom/JobInfoPopup.vue +63 -12
- package/dist/custom/JobsList.vue +31 -5
- package/dist/custom/NavbarJobs.vue +6 -5
- package/dist/index.js +58 -27
- package/index.ts +84 -28
- package/package.json +1 -1
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
|
|
17
|
-
total size is
|
|
16
|
+
sent 23,563 bytes received 172 bytes 47,470.00 bytes/sec
|
|
17
|
+
total size is 22,930 speedup is 0.97
|
package/custom/GlobalJobApi.vue
CHANGED
|
@@ -27,6 +27,7 @@ import { useBackgroundJobApi } from './useBackgroundJobApi';
|
|
|
27
27
|
const jobStore = useBackgroundJobApi();
|
|
28
28
|
|
|
29
29
|
const dialogRef = ref<any>(null);
|
|
30
|
+
let unsubscribeJobUpdates: (() => void) | undefined;
|
|
30
31
|
|
|
31
32
|
const props = defineProps<{
|
|
32
33
|
meta: {
|
|
@@ -69,7 +70,7 @@ onMounted(() => {
|
|
|
69
70
|
// @ts-ignore
|
|
70
71
|
window.OpenJobInfoPopup = openJobInfo;
|
|
71
72
|
|
|
72
|
-
websocket.subscribe('/background-jobs', (data) => {
|
|
73
|
+
unsubscribeJobUpdates = websocket.subscribe('/background-jobs-job-update', (data) => {
|
|
73
74
|
if (data.jobId === jobStore.currentJob?.id) {
|
|
74
75
|
if (data.status) {
|
|
75
76
|
jobStore.updateCurrentJob({ status: data.status });
|
|
@@ -90,6 +91,6 @@ onBeforeUnmount(() => {
|
|
|
90
91
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
91
92
|
// @ts-ignore
|
|
92
93
|
if (window.OpenJobInfoPopup) delete window.OpenJobInfoPopup;
|
|
93
|
-
|
|
94
|
+
unsubscribeJobUpdates?.();
|
|
94
95
|
});
|
|
95
96
|
</script>
|
package/custom/JobInfoPopup.vue
CHANGED
|
@@ -69,7 +69,7 @@ import { getTimeAgoString, callAdminForthApi, getCustomComponent} from '@/utils'
|
|
|
69
69
|
import { useI18n } from 'vue-i18n';
|
|
70
70
|
import StateToIcon from './StateToIcon.vue';
|
|
71
71
|
import { useAdminforth } from '@/adminforth';
|
|
72
|
-
import { onBeforeUnmount, ref, watch } from 'vue';
|
|
72
|
+
import { onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
|
73
73
|
import websocket from '@/websocket';
|
|
74
74
|
import { useBackgroundJobApi } from './useBackgroundJobApi';
|
|
75
75
|
|
|
@@ -115,13 +115,11 @@ function createStateFieldSubscription(
|
|
|
115
115
|
callback: (data: any) => void,
|
|
116
116
|
) {
|
|
117
117
|
const paths = getUniqueFieldNames(fieldNames).map(pathFactory);
|
|
118
|
-
|
|
119
|
-
websocket.subscribe(path, callback);
|
|
120
|
-
}
|
|
118
|
+
const pathCleanups = paths.map((path) => websocket.subscribe(path, callback));
|
|
121
119
|
|
|
122
120
|
const unsubscribe = () => {
|
|
123
|
-
for (const
|
|
124
|
-
|
|
121
|
+
for (const cleanup of pathCleanups) {
|
|
122
|
+
cleanup();
|
|
125
123
|
}
|
|
126
124
|
subscriptionCleanups.delete(unsubscribe);
|
|
127
125
|
};
|
|
@@ -134,12 +132,14 @@ function handleJobStateFieldUpdate(data: JobStateFieldUpdate) {
|
|
|
134
132
|
return;
|
|
135
133
|
}
|
|
136
134
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
135
|
+
props.job.state[data.fieldName] = data.value;
|
|
136
|
+
if (jobStore.currentJob?.id === props.job.id) {
|
|
137
|
+
jobStore.updateCurrentJob({
|
|
138
|
+
state: {
|
|
139
|
+
...props.job.state,
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
function handleTaskStateFieldUpdate(data: TaskStateFieldUpdate) {
|
|
@@ -153,6 +153,50 @@ function handleTaskStateFieldUpdate(data: TaskStateFieldUpdate) {
|
|
|
153
153
|
};
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
function handleTaskStatusUpdate(data: { taskIndex: number; status: string }) {
|
|
157
|
+
if (!jobTasks.value[data.taskIndex]) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
jobTasks.value[data.taskIndex].status = data.status;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function handleJobUpdate(data: {
|
|
165
|
+
jobId: string;
|
|
166
|
+
status?: IJob['status'];
|
|
167
|
+
progress?: string;
|
|
168
|
+
finishedAt?: Date;
|
|
169
|
+
state?: Record<string, any>;
|
|
170
|
+
}) {
|
|
171
|
+
if (data.jobId !== props.job.id) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (data.status) {
|
|
176
|
+
props.job.status = data.status;
|
|
177
|
+
}
|
|
178
|
+
if (data.progress !== undefined) {
|
|
179
|
+
props.job.progress = data.progress;
|
|
180
|
+
}
|
|
181
|
+
if (data.finishedAt) {
|
|
182
|
+
props.job.finishedAt = data.finishedAt;
|
|
183
|
+
}
|
|
184
|
+
if (data.state) {
|
|
185
|
+
props.job.state = {
|
|
186
|
+
...props.job.state,
|
|
187
|
+
...data.state,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
if (jobStore.currentJob?.id === props.job.id) {
|
|
191
|
+
jobStore.updateCurrentJob({
|
|
192
|
+
status: props.job.status,
|
|
193
|
+
progress: props.job.progress,
|
|
194
|
+
finishedAt: props.job.finishedAt,
|
|
195
|
+
state: props.job.state,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
156
200
|
function subscribeToJobStateFields(fieldNames: string[]) {
|
|
157
201
|
return createStateFieldSubscription(
|
|
158
202
|
fieldNames,
|
|
@@ -239,6 +283,13 @@ watch(
|
|
|
239
283
|
{ immediate: true }
|
|
240
284
|
);
|
|
241
285
|
|
|
286
|
+
onMounted(() => {
|
|
287
|
+
const taskStatusPath = `/background-jobs-task-update/${props.job.id}`;
|
|
288
|
+
subscriptionCleanups.add(websocket.subscribe(taskStatusPath, handleTaskStatusUpdate));
|
|
289
|
+
|
|
290
|
+
subscriptionCleanups.add(websocket.subscribe('/background-jobs-job-update', handleJobUpdate));
|
|
291
|
+
});
|
|
292
|
+
|
|
242
293
|
onBeforeUnmount(() => {
|
|
243
294
|
for (const unsubscribe of Array.from(subscriptionCleanups)) {
|
|
244
295
|
unsubscribe();
|
package/custom/JobsList.vue
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
class="p-4"
|
|
6
6
|
v-for="job in props.jobs" :key="job.id"
|
|
7
7
|
:beforeCloseFunction="onBeforeClose"
|
|
8
|
-
:beforeOpenFunction="onBeforeOpen"
|
|
8
|
+
:beforeOpenFunction="() => onBeforeOpen(job)"
|
|
9
9
|
removeFromDomOnClose
|
|
10
10
|
>
|
|
11
11
|
<template #trigger>
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
</div>
|
|
31
31
|
</template>
|
|
32
32
|
<JobInfoPopup
|
|
33
|
-
|
|
33
|
+
v-if="loadedJobs[job.id]"
|
|
34
|
+
:job="loadedJobs[job.id]"
|
|
34
35
|
:meta="meta"
|
|
35
36
|
:closeModal="closeModal"
|
|
36
37
|
/>
|
|
@@ -42,7 +43,7 @@
|
|
|
42
43
|
|
|
43
44
|
<script setup lang="ts">
|
|
44
45
|
import type { IJob } from './utils';
|
|
45
|
-
import { getTimeAgoString } from '@/utils';
|
|
46
|
+
import { callAdminForthApi, getTimeAgoString } from '@/utils';
|
|
46
47
|
import { ProgressBar, Modal } from '@/afcl';
|
|
47
48
|
import JobInfoPopup from './JobInfoPopup.vue';
|
|
48
49
|
import StateToIcon from './StateToIcon.vue';
|
|
@@ -78,9 +79,34 @@ const props = defineProps<{
|
|
|
78
79
|
|
|
79
80
|
|
|
80
81
|
const isModalOpen = ref(false);
|
|
82
|
+
const loadedJobs = ref<Record<string, IJob>>({});
|
|
81
83
|
|
|
82
|
-
function onBeforeOpen() {
|
|
84
|
+
async function onBeforeOpen(job: IJob) {
|
|
83
85
|
props.closeDropdown();
|
|
86
|
+
try {
|
|
87
|
+
const res = await callAdminForthApi({
|
|
88
|
+
path: `/plugin/get-background-job-info`,
|
|
89
|
+
method: 'POST',
|
|
90
|
+
body: { jobId: job.id },
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
if (res?.ok && res.job) {
|
|
94
|
+
loadedJobs.value[job.id] = res.job;
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
console.log('[background-jobs] failed to load full job info', {
|
|
99
|
+
jobId: job.id,
|
|
100
|
+
response: res,
|
|
101
|
+
});
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.log('[background-jobs] failed to load full job info', {
|
|
104
|
+
error,
|
|
105
|
+
jobId: job.id,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
loadedJobs.value[job.id] = job;
|
|
84
110
|
}
|
|
85
111
|
|
|
86
112
|
function onBeforeClose() {
|
|
@@ -88,4 +114,4 @@ function onBeforeClose() {
|
|
|
88
114
|
}
|
|
89
115
|
|
|
90
116
|
|
|
91
|
-
</script>
|
|
117
|
+
</script>
|
package/custom/NavbarJobs.vue
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<div class="cursor-pointer hover:scale-110 transition-transform" @click="isDropdownOpen = !isDropdownOpen">
|
|
4
4
|
<div class="relative flex items-center justify-center" v-if="jobs.length > 0">
|
|
5
5
|
<Tooltip>
|
|
6
|
-
<
|
|
6
|
+
<IconCheckCircleOutline class="w-7 h-7 text-lightNavbarIcons dark:text-darkNavbarIcons" />
|
|
7
7
|
<template #tooltip>
|
|
8
8
|
{{ t('All jobs completed') }}
|
|
9
9
|
</template>
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
<script setup lang="ts">
|
|
49
49
|
import type { AdminUser } from 'adminforth';
|
|
50
50
|
import { onMounted, onUnmounted, ref, computed } from 'vue';
|
|
51
|
-
import { IconCheckCircleOutline, IconBriefcaseSolid } from '@iconify-prerendered/vue-flowbite';
|
|
52
51
|
import { Tooltip } from '@/afcl';
|
|
52
|
+
import { IconCheckCircleOutline } from '@iconify-prerendered/vue-flowbite';
|
|
53
53
|
import { useI18n } from 'vue-i18n';
|
|
54
54
|
import JobsList from './JobsList.vue';
|
|
55
55
|
import type { IJob } from './utils';
|
|
@@ -69,6 +69,7 @@
|
|
|
69
69
|
const isDropdownOpen = ref(false);
|
|
70
70
|
const jobs = ref<IJob[]>([]);
|
|
71
71
|
const dropdownRef = ref<HTMLElement | null>(null);
|
|
72
|
+
let unsubscribeJobUpdates: (() => void) | undefined;
|
|
72
73
|
|
|
73
74
|
onClickOutside(dropdownRef, () => {
|
|
74
75
|
isDropdownOpen.value = false;
|
|
@@ -85,7 +86,7 @@
|
|
|
85
86
|
|
|
86
87
|
|
|
87
88
|
onMounted(async () => {
|
|
88
|
-
websocket.subscribe('/background-jobs', (data) => {
|
|
89
|
+
unsubscribeJobUpdates = websocket.subscribe('/background-jobs-job-update', (data) => {
|
|
89
90
|
const jobIndex = jobs.value.findIndex(job => job.id === data.jobId);
|
|
90
91
|
if (jobIndex !== -1) {
|
|
91
92
|
if (data.status) {
|
|
@@ -130,7 +131,7 @@
|
|
|
130
131
|
|
|
131
132
|
|
|
132
133
|
onUnmounted(() => {
|
|
133
|
-
|
|
134
|
+
unsubscribeJobUpdates?.();
|
|
134
135
|
});
|
|
135
136
|
|
|
136
137
|
</script>
|
|
@@ -147,4 +148,4 @@
|
|
|
147
148
|
opacity: 0;
|
|
148
149
|
}
|
|
149
150
|
}
|
|
150
|
-
</style>
|
|
151
|
+
</style>
|
|
@@ -27,6 +27,7 @@ import { useBackgroundJobApi } from './useBackgroundJobApi';
|
|
|
27
27
|
const jobStore = useBackgroundJobApi();
|
|
28
28
|
|
|
29
29
|
const dialogRef = ref<any>(null);
|
|
30
|
+
let unsubscribeJobUpdates: (() => void) | undefined;
|
|
30
31
|
|
|
31
32
|
const props = defineProps<{
|
|
32
33
|
meta: {
|
|
@@ -69,7 +70,7 @@ onMounted(() => {
|
|
|
69
70
|
// @ts-ignore
|
|
70
71
|
window.OpenJobInfoPopup = openJobInfo;
|
|
71
72
|
|
|
72
|
-
websocket.subscribe('/background-jobs', (data) => {
|
|
73
|
+
unsubscribeJobUpdates = websocket.subscribe('/background-jobs-job-update', (data) => {
|
|
73
74
|
if (data.jobId === jobStore.currentJob?.id) {
|
|
74
75
|
if (data.status) {
|
|
75
76
|
jobStore.updateCurrentJob({ status: data.status });
|
|
@@ -90,6 +91,6 @@ onBeforeUnmount(() => {
|
|
|
90
91
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
91
92
|
// @ts-ignore
|
|
92
93
|
if (window.OpenJobInfoPopup) delete window.OpenJobInfoPopup;
|
|
93
|
-
|
|
94
|
+
unsubscribeJobUpdates?.();
|
|
94
95
|
});
|
|
95
96
|
</script>
|
|
@@ -69,7 +69,7 @@ import { getTimeAgoString, callAdminForthApi, getCustomComponent} from '@/utils'
|
|
|
69
69
|
import { useI18n } from 'vue-i18n';
|
|
70
70
|
import StateToIcon from './StateToIcon.vue';
|
|
71
71
|
import { useAdminforth } from '@/adminforth';
|
|
72
|
-
import { onBeforeUnmount, ref, watch } from 'vue';
|
|
72
|
+
import { onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
|
73
73
|
import websocket from '@/websocket';
|
|
74
74
|
import { useBackgroundJobApi } from './useBackgroundJobApi';
|
|
75
75
|
|
|
@@ -115,13 +115,11 @@ function createStateFieldSubscription(
|
|
|
115
115
|
callback: (data: any) => void,
|
|
116
116
|
) {
|
|
117
117
|
const paths = getUniqueFieldNames(fieldNames).map(pathFactory);
|
|
118
|
-
|
|
119
|
-
websocket.subscribe(path, callback);
|
|
120
|
-
}
|
|
118
|
+
const pathCleanups = paths.map((path) => websocket.subscribe(path, callback));
|
|
121
119
|
|
|
122
120
|
const unsubscribe = () => {
|
|
123
|
-
for (const
|
|
124
|
-
|
|
121
|
+
for (const cleanup of pathCleanups) {
|
|
122
|
+
cleanup();
|
|
125
123
|
}
|
|
126
124
|
subscriptionCleanups.delete(unsubscribe);
|
|
127
125
|
};
|
|
@@ -134,12 +132,14 @@ function handleJobStateFieldUpdate(data: JobStateFieldUpdate) {
|
|
|
134
132
|
return;
|
|
135
133
|
}
|
|
136
134
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
135
|
+
props.job.state[data.fieldName] = data.value;
|
|
136
|
+
if (jobStore.currentJob?.id === props.job.id) {
|
|
137
|
+
jobStore.updateCurrentJob({
|
|
138
|
+
state: {
|
|
139
|
+
...props.job.state,
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
function handleTaskStateFieldUpdate(data: TaskStateFieldUpdate) {
|
|
@@ -153,6 +153,50 @@ function handleTaskStateFieldUpdate(data: TaskStateFieldUpdate) {
|
|
|
153
153
|
};
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
function handleTaskStatusUpdate(data: { taskIndex: number; status: string }) {
|
|
157
|
+
if (!jobTasks.value[data.taskIndex]) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
jobTasks.value[data.taskIndex].status = data.status;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function handleJobUpdate(data: {
|
|
165
|
+
jobId: string;
|
|
166
|
+
status?: IJob['status'];
|
|
167
|
+
progress?: string;
|
|
168
|
+
finishedAt?: Date;
|
|
169
|
+
state?: Record<string, any>;
|
|
170
|
+
}) {
|
|
171
|
+
if (data.jobId !== props.job.id) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (data.status) {
|
|
176
|
+
props.job.status = data.status;
|
|
177
|
+
}
|
|
178
|
+
if (data.progress !== undefined) {
|
|
179
|
+
props.job.progress = data.progress;
|
|
180
|
+
}
|
|
181
|
+
if (data.finishedAt) {
|
|
182
|
+
props.job.finishedAt = data.finishedAt;
|
|
183
|
+
}
|
|
184
|
+
if (data.state) {
|
|
185
|
+
props.job.state = {
|
|
186
|
+
...props.job.state,
|
|
187
|
+
...data.state,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
if (jobStore.currentJob?.id === props.job.id) {
|
|
191
|
+
jobStore.updateCurrentJob({
|
|
192
|
+
status: props.job.status,
|
|
193
|
+
progress: props.job.progress,
|
|
194
|
+
finishedAt: props.job.finishedAt,
|
|
195
|
+
state: props.job.state,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
156
200
|
function subscribeToJobStateFields(fieldNames: string[]) {
|
|
157
201
|
return createStateFieldSubscription(
|
|
158
202
|
fieldNames,
|
|
@@ -239,6 +283,13 @@ watch(
|
|
|
239
283
|
{ immediate: true }
|
|
240
284
|
);
|
|
241
285
|
|
|
286
|
+
onMounted(() => {
|
|
287
|
+
const taskStatusPath = `/background-jobs-task-update/${props.job.id}`;
|
|
288
|
+
subscriptionCleanups.add(websocket.subscribe(taskStatusPath, handleTaskStatusUpdate));
|
|
289
|
+
|
|
290
|
+
subscriptionCleanups.add(websocket.subscribe('/background-jobs-job-update', handleJobUpdate));
|
|
291
|
+
});
|
|
292
|
+
|
|
242
293
|
onBeforeUnmount(() => {
|
|
243
294
|
for (const unsubscribe of Array.from(subscriptionCleanups)) {
|
|
244
295
|
unsubscribe();
|
package/dist/custom/JobsList.vue
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
class="p-4"
|
|
6
6
|
v-for="job in props.jobs" :key="job.id"
|
|
7
7
|
:beforeCloseFunction="onBeforeClose"
|
|
8
|
-
:beforeOpenFunction="onBeforeOpen"
|
|
8
|
+
:beforeOpenFunction="() => onBeforeOpen(job)"
|
|
9
9
|
removeFromDomOnClose
|
|
10
10
|
>
|
|
11
11
|
<template #trigger>
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
</div>
|
|
31
31
|
</template>
|
|
32
32
|
<JobInfoPopup
|
|
33
|
-
|
|
33
|
+
v-if="loadedJobs[job.id]"
|
|
34
|
+
:job="loadedJobs[job.id]"
|
|
34
35
|
:meta="meta"
|
|
35
36
|
:closeModal="closeModal"
|
|
36
37
|
/>
|
|
@@ -42,7 +43,7 @@
|
|
|
42
43
|
|
|
43
44
|
<script setup lang="ts">
|
|
44
45
|
import type { IJob } from './utils';
|
|
45
|
-
import { getTimeAgoString } from '@/utils';
|
|
46
|
+
import { callAdminForthApi, getTimeAgoString } from '@/utils';
|
|
46
47
|
import { ProgressBar, Modal } from '@/afcl';
|
|
47
48
|
import JobInfoPopup from './JobInfoPopup.vue';
|
|
48
49
|
import StateToIcon from './StateToIcon.vue';
|
|
@@ -78,9 +79,34 @@ const props = defineProps<{
|
|
|
78
79
|
|
|
79
80
|
|
|
80
81
|
const isModalOpen = ref(false);
|
|
82
|
+
const loadedJobs = ref<Record<string, IJob>>({});
|
|
81
83
|
|
|
82
|
-
function onBeforeOpen() {
|
|
84
|
+
async function onBeforeOpen(job: IJob) {
|
|
83
85
|
props.closeDropdown();
|
|
86
|
+
try {
|
|
87
|
+
const res = await callAdminForthApi({
|
|
88
|
+
path: `/plugin/get-background-job-info`,
|
|
89
|
+
method: 'POST',
|
|
90
|
+
body: { jobId: job.id },
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
if (res?.ok && res.job) {
|
|
94
|
+
loadedJobs.value[job.id] = res.job;
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
console.log('[background-jobs] failed to load full job info', {
|
|
99
|
+
jobId: job.id,
|
|
100
|
+
response: res,
|
|
101
|
+
});
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.log('[background-jobs] failed to load full job info', {
|
|
104
|
+
error,
|
|
105
|
+
jobId: job.id,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
loadedJobs.value[job.id] = job;
|
|
84
110
|
}
|
|
85
111
|
|
|
86
112
|
function onBeforeClose() {
|
|
@@ -88,4 +114,4 @@ function onBeforeClose() {
|
|
|
88
114
|
}
|
|
89
115
|
|
|
90
116
|
|
|
91
|
-
</script>
|
|
117
|
+
</script>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<div class="cursor-pointer hover:scale-110 transition-transform" @click="isDropdownOpen = !isDropdownOpen">
|
|
4
4
|
<div class="relative flex items-center justify-center" v-if="jobs.length > 0">
|
|
5
5
|
<Tooltip>
|
|
6
|
-
<
|
|
6
|
+
<IconCheckCircleOutline class="w-7 h-7 text-lightNavbarIcons dark:text-darkNavbarIcons" />
|
|
7
7
|
<template #tooltip>
|
|
8
8
|
{{ t('All jobs completed') }}
|
|
9
9
|
</template>
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
<script setup lang="ts">
|
|
49
49
|
import type { AdminUser } from 'adminforth';
|
|
50
50
|
import { onMounted, onUnmounted, ref, computed } from 'vue';
|
|
51
|
-
import { IconCheckCircleOutline, IconBriefcaseSolid } from '@iconify-prerendered/vue-flowbite';
|
|
52
51
|
import { Tooltip } from '@/afcl';
|
|
52
|
+
import { IconCheckCircleOutline } from '@iconify-prerendered/vue-flowbite';
|
|
53
53
|
import { useI18n } from 'vue-i18n';
|
|
54
54
|
import JobsList from './JobsList.vue';
|
|
55
55
|
import type { IJob } from './utils';
|
|
@@ -69,6 +69,7 @@
|
|
|
69
69
|
const isDropdownOpen = ref(false);
|
|
70
70
|
const jobs = ref<IJob[]>([]);
|
|
71
71
|
const dropdownRef = ref<HTMLElement | null>(null);
|
|
72
|
+
let unsubscribeJobUpdates: (() => void) | undefined;
|
|
72
73
|
|
|
73
74
|
onClickOutside(dropdownRef, () => {
|
|
74
75
|
isDropdownOpen.value = false;
|
|
@@ -85,7 +86,7 @@
|
|
|
85
86
|
|
|
86
87
|
|
|
87
88
|
onMounted(async () => {
|
|
88
|
-
websocket.subscribe('/background-jobs', (data) => {
|
|
89
|
+
unsubscribeJobUpdates = websocket.subscribe('/background-jobs-job-update', (data) => {
|
|
89
90
|
const jobIndex = jobs.value.findIndex(job => job.id === data.jobId);
|
|
90
91
|
if (jobIndex !== -1) {
|
|
91
92
|
if (data.status) {
|
|
@@ -130,7 +131,7 @@
|
|
|
130
131
|
|
|
131
132
|
|
|
132
133
|
onUnmounted(() => {
|
|
133
|
-
|
|
134
|
+
unsubscribeJobUpdates?.();
|
|
134
135
|
});
|
|
135
136
|
|
|
136
137
|
</script>
|
|
@@ -147,4 +148,4 @@
|
|
|
147
148
|
opacity: 0;
|
|
148
149
|
}
|
|
149
150
|
}
|
|
150
|
-
</style>
|
|
151
|
+
</style>
|
package/dist/index.js
CHANGED
|
@@ -25,6 +25,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
|
|
|
25
25
|
this.jobParallelLimits = {};
|
|
26
26
|
this.levelDbInstances = {};
|
|
27
27
|
this.jobStateMutexes = {};
|
|
28
|
+
this.deprecatedWarningsShown = new Set();
|
|
28
29
|
this.options = options;
|
|
29
30
|
this.shouldHaveSingleInstancePerWholeApp = () => true;
|
|
30
31
|
}
|
|
@@ -190,6 +191,13 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
|
|
|
190
191
|
});
|
|
191
192
|
}
|
|
192
193
|
}
|
|
194
|
+
warnDeprecatedOnce(key, message) {
|
|
195
|
+
if (this.deprecatedWarningsShown.has(key)) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
this.deprecatedWarningsShown.add(key);
|
|
199
|
+
afLogger.warn(message);
|
|
200
|
+
}
|
|
193
201
|
triggerOnAllTasksDone(onAllTasksDone, levelDb, jobId) {
|
|
194
202
|
return __awaiter(this, void 0, void 0, function* () {
|
|
195
203
|
if (!onAllTasksDone) {
|
|
@@ -219,8 +227,8 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
|
|
|
219
227
|
registerTaskDetailsComponent({ jobHandlerName, component, }) {
|
|
220
228
|
this.jobCustomComponents[jobHandlerName] = component;
|
|
221
229
|
}
|
|
222
|
-
startNewJob(
|
|
223
|
-
return __awaiter(this,
|
|
230
|
+
startNewJob(jobName_1, adminUser_1, tasks_1, jobHandlerName_1) {
|
|
231
|
+
return __awaiter(this, arguments, void 0, function* (jobName, adminUser, tasks, jobHandlerName, initialState = {}) {
|
|
224
232
|
const handleTask = this.taskHandlers[jobHandlerName];
|
|
225
233
|
const onAllTasksDone = this.onAllTasksDoneHandlers[jobHandlerName];
|
|
226
234
|
if (!handleTask) {
|
|
@@ -235,7 +243,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
|
|
|
235
243
|
[this.options.progressField]: 0,
|
|
236
244
|
[this.options.statusField]: 'IN_PROGRESS',
|
|
237
245
|
[this.options.jobHandlerField]: jobHandlerName,
|
|
238
|
-
[this.options.stateField]:
|
|
246
|
+
[this.options.stateField]: initialState
|
|
239
247
|
};
|
|
240
248
|
const creationResult = yield this.adminforth.resource(this.getResourceId()).create(objectToSave);
|
|
241
249
|
let createdRecord = null;
|
|
@@ -246,7 +254,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
|
|
|
246
254
|
throw new Error(`Failed to create a record for the job. Error: ${creationResult.error}`);
|
|
247
255
|
}
|
|
248
256
|
const jobId = createdRecord[this.getResourcePk()];
|
|
249
|
-
this.adminforth.websocket.publish('/background-jobs', {
|
|
257
|
+
this.adminforth.websocket.publish('/background-jobs-job-update', {
|
|
250
258
|
jobId,
|
|
251
259
|
status: 'IN_PROGRESS',
|
|
252
260
|
name: jobName,
|
|
@@ -288,19 +296,34 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
|
|
|
288
296
|
afLogger.info(`Job ${jobId} was cancelled. Skipping task ${taskIndex}.`);
|
|
289
297
|
return;
|
|
290
298
|
}
|
|
291
|
-
|
|
292
|
-
const setTaskStateField = (state) => __awaiter(this, void 0, void 0, function* () {
|
|
293
|
-
yield this.setLevelDbTaskStateField(jobLevelDb, taskIndex.toString(), state);
|
|
294
|
-
this.publishTaskStateFields(jobId, taskIndex, state);
|
|
295
|
-
});
|
|
296
|
-
const getTaskStateField = () => __awaiter(this, void 0, void 0, function* () {
|
|
299
|
+
const getState = () => __awaiter(this, void 0, void 0, function* () {
|
|
297
300
|
return yield this.getLevelDbTaskStateField(jobLevelDb, taskIndex.toString());
|
|
298
301
|
});
|
|
302
|
+
const setTaskStateField = (fieldNameOrState, value) => __awaiter(this, void 0, void 0, function* () {
|
|
303
|
+
if (typeof fieldNameOrState === 'string') {
|
|
304
|
+
const state = yield getState();
|
|
305
|
+
const updatedState = Object.assign(Object.assign({}, state), { [fieldNameOrState]: value });
|
|
306
|
+
yield this.setLevelDbTaskStateField(jobLevelDb, taskIndex.toString(), updatedState);
|
|
307
|
+
this.publishTaskStateFields(jobId, taskIndex, { [fieldNameOrState]: value });
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
this.warnDeprecatedOnce('setTaskStateField-object', 'BackgroundJobsPlugin: setTaskStateField(stateObject) is deprecated and will be removed soon. Use setTaskStateField(fieldName: string, value: any) instead. Use getState() when you need the full task state.');
|
|
311
|
+
yield this.setLevelDbTaskStateField(jobLevelDb, taskIndex.toString(), fieldNameOrState);
|
|
312
|
+
this.publishTaskStateFields(jobId, taskIndex, fieldNameOrState);
|
|
313
|
+
});
|
|
314
|
+
const getTaskStateField = (fieldName) => __awaiter(this, void 0, void 0, function* () {
|
|
315
|
+
const state = yield getState();
|
|
316
|
+
if (typeof fieldName === 'string') {
|
|
317
|
+
return state[fieldName];
|
|
318
|
+
}
|
|
319
|
+
this.warnDeprecatedOnce('getTaskStateField-no-args', 'BackgroundJobsPlugin: getTaskStateField() without a field name is deprecated and will be removed soon. Use getTaskStateField(fieldName: string) for one field, or getState() for the full task state.');
|
|
320
|
+
return state;
|
|
321
|
+
});
|
|
299
322
|
yield this.setLevelDbTaskStatusField(jobLevelDb, taskIndex.toString(), 'IN_PROGRESS');
|
|
300
323
|
this.adminforth.websocket.publish(`/background-jobs-task-update/${jobId}`, { taskIndex, status: "IN_PROGRESS" });
|
|
301
324
|
//handling the task
|
|
302
325
|
try {
|
|
303
|
-
yield handleTask({ jobId, setTaskStateField, getTaskStateField });
|
|
326
|
+
yield handleTask({ jobId, setTaskStateField, getTaskStateField, getState });
|
|
304
327
|
//Set task status to completed in level db
|
|
305
328
|
yield this.setLevelDbTaskStatusField(jobLevelDb, taskIndex.toString(), 'DONE');
|
|
306
329
|
this.adminforth.websocket.publish(`/background-jobs-task-update/${jobId}`, { taskIndex, status: "DONE" });
|
|
@@ -308,7 +331,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
|
|
|
308
331
|
catch (error) {
|
|
309
332
|
const errorMessage = (error === null || error === void 0 ? void 0 : error.message) || 'Unknown error';
|
|
310
333
|
afLogger.error(`Error in handling task ${taskIndex} of job ${jobId}: ${errorMessage}`);
|
|
311
|
-
yield this.
|
|
334
|
+
yield this.setJobStateField(jobId, 'error', errorMessage);
|
|
312
335
|
yield this.setLevelDbTaskStatusField(jobLevelDb, taskIndex.toString(), 'FAILED');
|
|
313
336
|
this.adminforth.websocket.publish(`/background-jobs-task-update/${jobId}`, { taskIndex, status: "FAILED" });
|
|
314
337
|
failedTasks++;
|
|
@@ -335,7 +358,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
|
|
|
335
358
|
[this.options.statusField]: 'DONE',
|
|
336
359
|
[this.options.finishedAtField]: (new Date()).toISOString(),
|
|
337
360
|
});
|
|
338
|
-
this.adminforth.websocket.publish('/background-jobs', { jobId, status: 'DONE', finishedAt: (new Date()).toISOString() });
|
|
361
|
+
this.adminforth.websocket.publish('/background-jobs-job-update', { jobId, status: 'DONE', finishedAt: (new Date()).toISOString() });
|
|
339
362
|
this.cleanupJobMutexIfTerminalStatus(jobId, 'DONE');
|
|
340
363
|
yield this.triggerOnAllTasksDone(onAllTasksDone, jobLevelDb, jobId);
|
|
341
364
|
}
|
|
@@ -344,7 +367,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
|
|
|
344
367
|
[this.options.statusField]: 'DONE_WITH_ERRORS',
|
|
345
368
|
[this.options.finishedAtField]: (new Date()).toISOString(),
|
|
346
369
|
});
|
|
347
|
-
this.adminforth.websocket.publish('/background-jobs', { jobId, status: 'DONE_WITH_ERRORS' });
|
|
370
|
+
this.adminforth.websocket.publish('/background-jobs-job-update', { jobId, status: 'DONE_WITH_ERRORS' });
|
|
348
371
|
this.cleanupJobMutexIfTerminalStatus(jobId, 'DONE_WITH_ERRORS');
|
|
349
372
|
yield this.triggerOnAllTasksDone(onAllTasksDone, jobLevelDb, jobId);
|
|
350
373
|
}
|
|
@@ -367,7 +390,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
|
|
|
367
390
|
yield this.adminforth.resource(this.getResourceId()).update(jobId, {
|
|
368
391
|
[this.options.progressField]: progress,
|
|
369
392
|
});
|
|
370
|
-
this.adminforth.websocket.publish('/background-jobs', { jobId, progress });
|
|
393
|
+
this.adminforth.websocket.publish('/background-jobs-job-update', { jobId, progress });
|
|
371
394
|
return completedTasks;
|
|
372
395
|
});
|
|
373
396
|
}
|
|
@@ -411,31 +434,40 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
|
|
|
411
434
|
yield this.runProcessingTasks(unfinishedTasks, jobLevelDb, job[this.getResourcePk()], handleTask, parrallelLimit, onAllTasksDone);
|
|
412
435
|
});
|
|
413
436
|
}
|
|
414
|
-
|
|
437
|
+
setJobStateField(jobId, key, value) {
|
|
415
438
|
return __awaiter(this, void 0, void 0, function* () {
|
|
416
439
|
const jobRecord = yield this.adminforth.resource(this.getResourceId()).get(Filters.EQ(this.getResourcePk(), jobId));
|
|
417
440
|
const state = jobRecord[this.options.stateField];
|
|
418
|
-
|
|
419
|
-
parsedState[key] = value;
|
|
441
|
+
state[key] = value;
|
|
420
442
|
yield this.adminforth.resource(this.getResourceId()).update(jobId, {
|
|
421
|
-
[this.options.stateField]:
|
|
443
|
+
[this.options.stateField]: state,
|
|
422
444
|
});
|
|
423
445
|
this.publishJobStateField(jobId, key, value);
|
|
424
446
|
});
|
|
425
447
|
}
|
|
426
|
-
|
|
448
|
+
getJobStateField(jobId, key) {
|
|
427
449
|
return __awaiter(this, void 0, void 0, function* () {
|
|
428
450
|
const jobRecord = yield this.adminforth.resource(this.getResourceId()).get(Filters.EQ(this.getResourcePk(), jobId));
|
|
429
451
|
const state = jobRecord[this.options.stateField];
|
|
430
|
-
|
|
431
|
-
return parsedState[key];
|
|
452
|
+
return state[key];
|
|
432
453
|
});
|
|
433
454
|
}
|
|
434
455
|
getJobState(jobId) {
|
|
435
456
|
return __awaiter(this, void 0, void 0, function* () {
|
|
436
457
|
const jobRecord = yield this.adminforth.resource(this.getResourceId()).get(Filters.EQ(this.getResourcePk(), jobId));
|
|
437
|
-
|
|
438
|
-
|
|
458
|
+
return jobRecord[this.options.stateField];
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
setJobField(jobId, key, value) {
|
|
462
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
463
|
+
this.warnDeprecatedOnce('setJobField', 'BackgroundJobsPlugin: setJobField(jobId, key, value) is deprecated and will be removed soon. Use setJobStateField(jobId, fieldName: string, value: any) instead.');
|
|
464
|
+
return this.setJobStateField(jobId, key, value);
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
getJobField(jobId, key) {
|
|
468
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
469
|
+
this.warnDeprecatedOnce('getJobField', 'BackgroundJobsPlugin: getJobField(jobId, key) is deprecated and will be removed soon. Use getJobStateField(jobId, fieldName: string) instead.');
|
|
470
|
+
return this.getJobStateField(jobId, key);
|
|
439
471
|
});
|
|
440
472
|
}
|
|
441
473
|
updateJobFieldsAtomically(jobId, updateFunction) {
|
|
@@ -504,7 +536,6 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
|
|
|
504
536
|
createdAt: job[this.options.createdAtField],
|
|
505
537
|
finishedAt: job[this.options.finishedAtField] || null,
|
|
506
538
|
status: job[this.options.statusField],
|
|
507
|
-
state: JSON.parse(job[this.options.stateField]),
|
|
508
539
|
progress: job[this.options.progressField],
|
|
509
540
|
customComponent: this.jobCustomComponents[job[this.options.jobHandlerField]],
|
|
510
541
|
};
|
|
@@ -527,7 +558,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
|
|
|
527
558
|
createdAt: job[this.options.createdAtField],
|
|
528
559
|
finishedAt: job[this.options.finishedAtField] || null,
|
|
529
560
|
status: job[this.options.statusField],
|
|
530
|
-
state:
|
|
561
|
+
state: job[this.options.stateField],
|
|
531
562
|
progress: job[this.options.progressField],
|
|
532
563
|
customComponent: this.jobCustomComponents[job[this.options.jobHandlerField]],
|
|
533
564
|
};
|
|
@@ -549,7 +580,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
|
|
|
549
580
|
[this.options.statusField]: 'CANCELLED',
|
|
550
581
|
[this.options.finishedAtField]: (new Date()).toISOString(),
|
|
551
582
|
});
|
|
552
|
-
this.adminforth.websocket.publish('/background-jobs', {
|
|
583
|
+
this.adminforth.websocket.publish('/background-jobs-job-update', {
|
|
553
584
|
jobId,
|
|
554
585
|
status: 'CANCELLED',
|
|
555
586
|
});
|
package/index.ts
CHANGED
|
@@ -8,9 +8,16 @@ import fs from 'fs/promises';
|
|
|
8
8
|
import { Mutex } from 'async-mutex';
|
|
9
9
|
|
|
10
10
|
type TaskStatus = 'SCHEDULED' | 'IN_PROGRESS' | 'DONE' | 'FAILED';
|
|
11
|
-
type setStateFieldParams =
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
type setStateFieldParams = {
|
|
12
|
+
(fieldName: string, value: any): Promise<void>;
|
|
13
|
+
(state: Record<string, any>): Promise<void>;
|
|
14
|
+
};
|
|
15
|
+
type getStateFieldParams = {
|
|
16
|
+
(fieldName: string): Promise<any>;
|
|
17
|
+
(): Promise<Record<string, any>>;
|
|
18
|
+
};
|
|
19
|
+
type getStateParams = () => Promise<Record<string, any>>;
|
|
20
|
+
type taskHandlerType = ( { jobId, setTaskStateField, getTaskStateField, getState }: { jobId: string; setTaskStateField: setStateFieldParams; getTaskStateField: getStateFieldParams; getState: getStateParams } ) => Promise<void>;
|
|
14
21
|
type allTasksDoneStatusType = {
|
|
15
22
|
jobId: string;
|
|
16
23
|
failedTasks: number;
|
|
@@ -34,6 +41,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
|
|
|
34
41
|
private jobParallelLimits: Record<string, number> = {};
|
|
35
42
|
private levelDbInstances: Record<string, Level> = {};
|
|
36
43
|
private jobStateMutexes: Record<string, Mutex> = {};
|
|
44
|
+
private deprecatedWarningsShown = new Set<string>();
|
|
37
45
|
|
|
38
46
|
constructor(options: PluginOptions) {
|
|
39
47
|
super(options, import.meta.url);
|
|
@@ -207,6 +215,15 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
|
|
|
207
215
|
}
|
|
208
216
|
}
|
|
209
217
|
|
|
218
|
+
private warnDeprecatedOnce(key: string, message: string) {
|
|
219
|
+
if (this.deprecatedWarningsShown.has(key)) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
this.deprecatedWarningsShown.add(key);
|
|
224
|
+
afLogger.warn(message);
|
|
225
|
+
}
|
|
226
|
+
|
|
210
227
|
private async triggerOnAllTasksDone(onAllTasksDone: onAllTasksDoneType | undefined, levelDb: Level, jobId: string) {
|
|
211
228
|
if (!onAllTasksDone) {
|
|
212
229
|
return;
|
|
@@ -245,6 +262,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
|
|
|
245
262
|
adminUser: AdminUser,
|
|
246
263
|
tasks: taskType[],
|
|
247
264
|
jobHandlerName: string,
|
|
265
|
+
initialState: Record<string, any> = {},
|
|
248
266
|
): Promise<string> {
|
|
249
267
|
|
|
250
268
|
const handleTask: taskHandlerType = this.taskHandlers[jobHandlerName];
|
|
@@ -261,7 +279,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
|
|
|
261
279
|
[this.options.progressField]: 0,
|
|
262
280
|
[this.options.statusField]: 'IN_PROGRESS',
|
|
263
281
|
[this.options.jobHandlerField]: jobHandlerName,
|
|
264
|
-
[this.options.stateField]:
|
|
282
|
+
[this.options.stateField]: initialState
|
|
265
283
|
}
|
|
266
284
|
|
|
267
285
|
const creationResult = await this.adminforth.resource(this.getResourceId()).create(objectToSave);
|
|
@@ -273,7 +291,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
|
|
|
273
291
|
}
|
|
274
292
|
const jobId = createdRecord[this.getResourcePk()];
|
|
275
293
|
|
|
276
|
-
this.adminforth.websocket.publish('/background-jobs', {
|
|
294
|
+
this.adminforth.websocket.publish('/background-jobs-job-update', {
|
|
277
295
|
jobId,
|
|
278
296
|
status: 'IN_PROGRESS',
|
|
279
297
|
name: jobName,
|
|
@@ -327,21 +345,47 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
|
|
|
327
345
|
return;
|
|
328
346
|
}
|
|
329
347
|
|
|
330
|
-
|
|
331
|
-
const setTaskStateField = async (state: Record<string, any>) => {
|
|
332
|
-
await this.setLevelDbTaskStateField(jobLevelDb, taskIndex.toString(), state);
|
|
333
|
-
this.publishTaskStateFields(jobId, taskIndex, state);
|
|
334
|
-
}
|
|
335
|
-
const getTaskStateField = async () => {
|
|
348
|
+
const getState = async () => {
|
|
336
349
|
return await this.getLevelDbTaskStateField(jobLevelDb, taskIndex.toString());
|
|
337
350
|
}
|
|
351
|
+
const setTaskStateField: setStateFieldParams = async (fieldNameOrState: string | Record<string, any>, value?: any) => {
|
|
352
|
+
if (typeof fieldNameOrState === 'string') {
|
|
353
|
+
const state = await getState();
|
|
354
|
+
const updatedState = {
|
|
355
|
+
...state,
|
|
356
|
+
[fieldNameOrState]: value,
|
|
357
|
+
};
|
|
358
|
+
await this.setLevelDbTaskStateField(jobLevelDb, taskIndex.toString(), updatedState);
|
|
359
|
+
this.publishTaskStateFields(jobId, taskIndex, { [fieldNameOrState]: value });
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
this.warnDeprecatedOnce(
|
|
364
|
+
'setTaskStateField-object',
|
|
365
|
+
'BackgroundJobsPlugin: setTaskStateField(stateObject) is deprecated and will be removed soon. Use setTaskStateField(fieldName: string, value: any) instead. Use getState() when you need the full task state.',
|
|
366
|
+
);
|
|
367
|
+
await this.setLevelDbTaskStateField(jobLevelDb, taskIndex.toString(), fieldNameOrState);
|
|
368
|
+
this.publishTaskStateFields(jobId, taskIndex, fieldNameOrState);
|
|
369
|
+
}
|
|
370
|
+
const getTaskStateField: getStateFieldParams = async (fieldName?: string) => {
|
|
371
|
+
const state = await getState();
|
|
372
|
+
if (typeof fieldName === 'string') {
|
|
373
|
+
return state[fieldName];
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
this.warnDeprecatedOnce(
|
|
377
|
+
'getTaskStateField-no-args',
|
|
378
|
+
'BackgroundJobsPlugin: getTaskStateField() without a field name is deprecated and will be removed soon. Use getTaskStateField(fieldName: string) for one field, or getState() for the full task state.',
|
|
379
|
+
);
|
|
380
|
+
return state;
|
|
381
|
+
}
|
|
338
382
|
|
|
339
383
|
await this.setLevelDbTaskStatusField(jobLevelDb, taskIndex.toString(), 'IN_PROGRESS');
|
|
340
384
|
this.adminforth.websocket.publish(`/background-jobs-task-update/${jobId}`, { taskIndex, status: "IN_PROGRESS" });
|
|
341
385
|
|
|
342
386
|
//handling the task
|
|
343
387
|
try {
|
|
344
|
-
await handleTask({ jobId, setTaskStateField, getTaskStateField });
|
|
388
|
+
await handleTask({ jobId, setTaskStateField, getTaskStateField, getState });
|
|
345
389
|
|
|
346
390
|
//Set task status to completed in level db
|
|
347
391
|
await this.setLevelDbTaskStatusField(jobLevelDb, taskIndex.toString(), 'DONE');
|
|
@@ -349,7 +393,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
|
|
|
349
393
|
} catch (error) {
|
|
350
394
|
const errorMessage = error?.message || 'Unknown error';
|
|
351
395
|
afLogger.error(`Error in handling task ${taskIndex} of job ${jobId}: ${errorMessage}`, );
|
|
352
|
-
await this.
|
|
396
|
+
await this.setJobStateField(jobId, 'error', errorMessage);
|
|
353
397
|
await this.setLevelDbTaskStatusField(jobLevelDb, taskIndex.toString(), 'FAILED');
|
|
354
398
|
this.adminforth.websocket.publish(`/background-jobs-task-update/${jobId}`, { taskIndex, status: "FAILED" });
|
|
355
399
|
failedTasks++;
|
|
@@ -378,7 +422,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
|
|
|
378
422
|
[this.options.statusField]: 'DONE',
|
|
379
423
|
[this.options.finishedAtField]: (new Date()).toISOString(),
|
|
380
424
|
})
|
|
381
|
-
this.adminforth.websocket.publish('/background-jobs', { jobId, status: 'DONE', finishedAt: (new Date()).toISOString() });
|
|
425
|
+
this.adminforth.websocket.publish('/background-jobs-job-update', { jobId, status: 'DONE', finishedAt: (new Date()).toISOString() });
|
|
382
426
|
this.cleanupJobMutexIfTerminalStatus(jobId, 'DONE');
|
|
383
427
|
await this.triggerOnAllTasksDone(onAllTasksDone, jobLevelDb, jobId);
|
|
384
428
|
} else if (failedTasks > 0) {
|
|
@@ -386,7 +430,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
|
|
|
386
430
|
[this.options.statusField]: 'DONE_WITH_ERRORS',
|
|
387
431
|
[this.options.finishedAtField]: (new Date()).toISOString(),
|
|
388
432
|
})
|
|
389
|
-
this.adminforth.websocket.publish('/background-jobs', { jobId, status: 'DONE_WITH_ERRORS' });
|
|
433
|
+
this.adminforth.websocket.publish('/background-jobs-job-update', { jobId, status: 'DONE_WITH_ERRORS' });
|
|
390
434
|
this.cleanupJobMutexIfTerminalStatus(jobId, 'DONE_WITH_ERRORS');
|
|
391
435
|
await this.triggerOnAllTasksDone(onAllTasksDone, jobLevelDb, jobId);
|
|
392
436
|
}
|
|
@@ -407,7 +451,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
|
|
|
407
451
|
await this.adminforth.resource(this.getResourceId()).update(jobId, {
|
|
408
452
|
[this.options.progressField]: progress,
|
|
409
453
|
})
|
|
410
|
-
this.adminforth.websocket.publish('/background-jobs', { jobId, progress });
|
|
454
|
+
this.adminforth.websocket.publish('/background-jobs-job-update', { jobId, progress });
|
|
411
455
|
return completedTasks;
|
|
412
456
|
}
|
|
413
457
|
|
|
@@ -453,28 +497,41 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
|
|
|
453
497
|
|
|
454
498
|
}
|
|
455
499
|
|
|
456
|
-
public async
|
|
500
|
+
public async setJobStateField(jobId: string, key: string, value: any) {
|
|
457
501
|
const jobRecord = await this.adminforth.resource(this.getResourceId()).get(Filters.EQ(this.getResourcePk(), jobId));
|
|
458
502
|
const state = jobRecord[this.options.stateField];
|
|
459
|
-
|
|
460
|
-
parsedState[key] = value;
|
|
503
|
+
state[key] = value;
|
|
461
504
|
await this.adminforth.resource(this.getResourceId()).update(jobId, {
|
|
462
|
-
[this.options.stateField]:
|
|
505
|
+
[this.options.stateField]: state,
|
|
463
506
|
});
|
|
464
507
|
this.publishJobStateField(jobId, key, value);
|
|
465
508
|
}
|
|
466
509
|
|
|
467
|
-
public async
|
|
510
|
+
public async getJobStateField(jobId: string, key: string) {
|
|
468
511
|
const jobRecord = await this.adminforth.resource(this.getResourceId()).get(Filters.EQ(this.getResourcePk(), jobId));
|
|
469
512
|
const state = jobRecord[this.options.stateField];
|
|
470
|
-
|
|
471
|
-
return parsedState[key];
|
|
513
|
+
return state[key];
|
|
472
514
|
}
|
|
473
515
|
|
|
474
516
|
public async getJobState(jobId: string) {
|
|
475
517
|
const jobRecord = await this.adminforth.resource(this.getResourceId()).get(Filters.EQ(this.getResourcePk(), jobId));
|
|
476
|
-
|
|
477
|
-
|
|
518
|
+
return jobRecord[this.options.stateField];
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
public async setJobField(jobId: string, key: string, value: any) {
|
|
522
|
+
this.warnDeprecatedOnce(
|
|
523
|
+
'setJobField',
|
|
524
|
+
'BackgroundJobsPlugin: setJobField(jobId, key, value) is deprecated and will be removed soon. Use setJobStateField(jobId, fieldName: string, value: any) instead.',
|
|
525
|
+
);
|
|
526
|
+
return this.setJobStateField(jobId, key, value);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
public async getJobField(jobId: string, key: string) {
|
|
530
|
+
this.warnDeprecatedOnce(
|
|
531
|
+
'getJobField',
|
|
532
|
+
'BackgroundJobsPlugin: getJobField(jobId, key) is deprecated and will be removed soon. Use getJobStateField(jobId, fieldName: string) instead.',
|
|
533
|
+
);
|
|
534
|
+
return this.getJobStateField(jobId, key);
|
|
478
535
|
}
|
|
479
536
|
|
|
480
537
|
public async updateJobFieldsAtomically(jobId: string, updateFunction: () => Promise<void>) {
|
|
@@ -547,7 +604,6 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
|
|
|
547
604
|
createdAt: job[this.options.createdAtField],
|
|
548
605
|
finishedAt: job[this.options.finishedAtField] || null,
|
|
549
606
|
status: job[this.options.statusField],
|
|
550
|
-
state: JSON.parse(job[this.options.stateField]),
|
|
551
607
|
progress: job[this.options.progressField],
|
|
552
608
|
customComponent: this.jobCustomComponents[job[this.options.jobHandlerField]],
|
|
553
609
|
}
|
|
@@ -572,7 +628,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
|
|
|
572
628
|
createdAt: job[this.options.createdAtField],
|
|
573
629
|
finishedAt: job[this.options.finishedAtField] || null,
|
|
574
630
|
status: job[this.options.statusField],
|
|
575
|
-
state:
|
|
631
|
+
state: job[this.options.stateField],
|
|
576
632
|
progress: job[this.options.progressField],
|
|
577
633
|
customComponent: this.jobCustomComponents[job[this.options.jobHandlerField]],
|
|
578
634
|
};
|
|
@@ -596,7 +652,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
|
|
|
596
652
|
[this.options.statusField]: 'CANCELLED',
|
|
597
653
|
[this.options.finishedAtField]: (new Date()).toISOString(),
|
|
598
654
|
});
|
|
599
|
-
this.adminforth.websocket.publish('/background-jobs', {
|
|
655
|
+
this.adminforth.websocket.publish('/background-jobs-job-update', {
|
|
600
656
|
jobId,
|
|
601
657
|
status: 'CANCELLED',
|
|
602
658
|
});
|