@adminforth/background-jobs 1.8.1 → 1.11.3

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.
@@ -17,18 +17,18 @@ steps:
17
17
  - infisical export --domain https://vault.devforth.io/api --format=dotenv-export --env="prod" > /woodpecker/deploy.vault.env
18
18
 
19
19
  build:
20
- image: node:20
20
+ image: devforth/node20-pnpm:latest
21
21
  when:
22
22
  - event: push
23
23
  commands:
24
24
  - apt update && apt install -y rsync
25
25
  - . /woodpecker/deploy.vault.env
26
- - npm clean-install
26
+ - pnpm install
27
27
  - /bin/bash ./.woodpecker/buildRelease.sh
28
28
  - npm audit signatures
29
29
 
30
30
  release:
31
- image: node:20
31
+ image: devforth/node20-pnpm:latest
32
32
  when:
33
33
  - event:
34
34
  - push
@@ -36,7 +36,7 @@ steps:
36
36
  - main
37
37
  commands:
38
38
  - . /woodpecker/deploy.vault.env
39
- - npx semantic-release
39
+ - pnpm exec semantic-release
40
40
 
41
41
  slack-on-failure:
42
42
  image: curlimages/curl
package/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # AdminForth Background Jobs Plugin
2
+
3
+ <img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT" /> <img src="https://woodpecker.devforth.io/api/badges/3848/status.svg" alt="Build Status" /> <a href="https://www.npmjs.com/package/@adminforth/background-jobs"><img src="https://img.shields.io/npm/dm/@adminforth/background-jobs" alt="npm downloads" /></a> <a href="https://www.npmjs.com/package/@adminforth/background-jobs"><img src="https://img.shields.io/npm/v/@adminforth/background-jobs" alt="npm version" /></a>
4
+
5
+ [![Ask AI](https://tluma.ai/badge)](https://tluma.ai/ask-ai/devforth/adminforth)
6
+
7
+ Lets you run and manage background jobs from AdminForth resources.
8
+
9
+ ## Features
10
+
11
+ - Run long tasks outside the request-response cycle.
12
+ - Manage background work directly from AdminForth resources.
13
+ - Keep admin operations responsive during heavy processing.
14
+ - Support repeatable job-based back-office workflows.
15
+
16
+ ## Documentation
17
+
18
+ Full setup and configuration guide:
19
+
20
+ [AdminForth Background Jobs Documentation](https://adminforth.dev/docs/tutorial/Plugins/background-jobs/)
21
+
22
+ ## About AdminForth
23
+
24
+ AdminForth is an open-source, agent-first admin framework for building robust admin panels and back-office applications faster.
25
+
26
+ ## Related links
27
+
28
+ - [AdminForth website](https://adminforth.dev)
29
+ - [npm package](https://www.npmjs.com/package/@adminforth/background-jobs)
30
+ - [More AdminForth plugins](https://adminforth.dev/docs/tutorial/ListOfPlugins/)
31
+ - [Built by DevForth](https://devforth.io)
package/build.log CHANGED
@@ -10,7 +10,8 @@ custom/JobsList.vue
10
10
  custom/NavbarJobs.vue
11
11
  custom/StateToIcon.vue
12
12
  custom/tsconfig.json
13
+ custom/useBackgroundJobApi.ts
13
14
  custom/utils.ts
14
15
 
15
- sent 17,404 bytes received 153 bytes 35,114.00 bytes/sec
16
- total size is 16,848 speedup is 0.96
16
+ sent 18,463 bytes received 172 bytes 37,270.00 bytes/sec
17
+ total size is 17,830 speedup is 0.96
@@ -5,11 +5,11 @@
5
5
  ref="dialogRef"
6
6
  removeFromDomOnClose
7
7
  class="p-4"
8
- :beforeCloseFunction="() => { currentJob.value = null; }"
8
+ :beforeCloseFunction="() => { jobStore.clearCurrentJob(); jobStore.setIsOpened(false); }"
9
9
  >
10
10
  <JobInfoPopup
11
- v-if="currentJob"
12
- :job="currentJob"
11
+ v-if="jobStore.currentJob"
12
+ :job="jobStore.currentJob"
13
13
  :meta="meta"
14
14
  :closeModal="closeModal"
15
15
  />
@@ -18,14 +18,15 @@
18
18
  </template>
19
19
 
20
20
  <script setup lang="ts">
21
- import { ref, onMounted, onBeforeUnmount } from 'vue';
22
- import { callAdminForthApi } from '@/utils';
23
- import { useAdminforth } from '@/adminforth';
21
+ import { ref, onMounted, onBeforeUnmount, watch } from 'vue';
24
22
  import { Modal } from '@/afcl';
25
23
  import JobInfoPopup from './JobInfoPopup.vue';
26
24
  import websocket from '@/websocket';
25
+ import { useBackgroundJobApi } from './useBackgroundJobApi';
27
26
 
28
- const adminforth = useAdminforth();
27
+ const jobStore = useBackgroundJobApi();
28
+
29
+ const dialogRef = ref<any>(null);
29
30
 
30
31
  const props = defineProps<{
31
32
  meta: {
@@ -33,45 +34,35 @@ const props = defineProps<{
33
34
  }
34
35
  }>();
35
36
 
36
- const dialogRef = ref<any>(null);
37
- const currentJob = ref<any>(null);
38
37
 
39
38
  function closeModal() {
40
39
  if (!dialogRef.value) return;
41
40
  if (typeof dialogRef.value.close === 'function') {
42
41
  dialogRef.value.close();
42
+ jobStore.clearCurrentJob();
43
+ jobStore.setIsOpened(false);
43
44
  return;
44
45
  }
45
46
  if (typeof dialogRef.value.hide === 'function') {
47
+ jobStore.clearCurrentJob();
48
+ jobStore.setIsOpened(false);
46
49
  dialogRef.value.hide();
47
50
  }
48
51
  }
49
52
 
50
- async function openJobInfo(jobId: string) {
51
- if (!jobId) return;
52
- try {
53
- const res = await callAdminForthApi({
54
- path: `/plugin/${props.meta.pluginInstanceId}/get-job-info`,
55
- method: 'POST',
56
- body: { jobId },
57
- });
58
- if (res && res.ok) {
59
- currentJob.value = res.job;
60
- // open dialog
61
- if (dialogRef.value && typeof dialogRef.value.open === 'function') {
62
- dialogRef.value.open();
63
- } else if (dialogRef.value && typeof dialogRef.value.show === 'function') {
64
- dialogRef.value.show();
65
- }
66
- } else {
67
- adminforth.alert({ variant: 'danger', message: res?.message || 'Failed to load job info' });
68
- }
69
- } catch (e) {
70
- console.error('OpenJobInfoPopup error', e);
71
- adminforth.alert({ variant: 'danger', message: 'Failed to load job info' });
53
+ watch(() => jobStore.isOpened, (newVal) => {
54
+ if (newVal) {
55
+ dialogRef.value?.open?.();
56
+ } else {
57
+ dialogRef.value?.close?.();
72
58
  }
59
+ });
60
+
61
+ async function openJobInfo(jobId: string) {
62
+ jobStore.openJobInfoPopup(jobId);
73
63
  }
74
64
 
65
+
75
66
  onMounted(() => {
76
67
  // expose global function
77
68
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -79,21 +70,18 @@ onMounted(() => {
79
70
  window.OpenJobInfoPopup = openJobInfo;
80
71
 
81
72
  websocket.subscribe('/background-jobs', (data) => {
82
- if (data.jobId === currentJob.value?.id) {
73
+ if (data.jobId === jobStore.currentJob?.id) {
83
74
  if (data.status) {
84
- currentJob.value.status = data.status;
75
+ jobStore.updateCurrentJob({ status: data.status });
85
76
  }
86
77
  if (data.progress !== undefined) {
87
- currentJob.value.progress = data.progress;
78
+ jobStore.updateCurrentJob({ progress: data.progress });
88
79
  }
89
80
  if (data.finishedAt) {
90
- currentJob.value.finishedAt = data.finishedAt;
81
+ jobStore.updateCurrentJob({ finishedAt: data.finishedAt });
91
82
  }
92
83
  if (data.state) {
93
- currentJob.value.state = {
94
- ...currentJob.value.state,
95
- ...data.state,
96
- };
84
+ jobStore.updateCurrentJob({ state: { ...jobStore.currentJob?.state, ...data.state } });
97
85
  }
98
86
  }
99
87
  });
@@ -33,7 +33,7 @@
33
33
  </Tooltip>
34
34
  </div>
35
35
  </div>
36
- <div class="flex items-center gap-4 w-full">
36
+ <div class="flex items-center gap-4 w-full mt-4">
37
37
  <ProgressBar
38
38
  :current-value="job.progress"
39
39
  :max-value="100"
@@ -42,7 +42,7 @@
42
42
  :showLabels="false"
43
43
  :showValues="false"
44
44
  :show-progress="false"
45
- :height="6"
45
+ :height="3"
46
46
  />
47
47
  <Button class="h-8" v-if="job.status === 'IN_PROGRESS'" @click="cancelJob"> {{ t('Cancel') }} </Button>
48
48
  </div>
@@ -4,8 +4,8 @@
4
4
  ref="modalRef"
5
5
  class="p-4"
6
6
  v-for="job in props.jobs" :key="job.id"
7
- :beforeCloseFunction="onBeforeOpen"
8
- :beforeOpenFunction="onBeforeClose"
7
+ :beforeCloseFunction="onBeforeClose"
8
+ :beforeOpenFunction="onBeforeOpen"
9
9
  removeFromDomOnClose
10
10
  >
11
11
  <template #trigger>
@@ -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
- <IconBriefcaseSolid class="w-7 h-7 text-gray-600 hover:text-gray-700" />
6
+ <IconBriefcaseSolid class="w-7 h-7 text-lightNavbarIcons dark:text-darkNavbarIcons" />
7
7
  <template #tooltip>
8
8
  {{ t('All jobs completed') }}
9
9
  </template>
@@ -0,0 +1,55 @@
1
+ import { ref } from 'vue';
2
+ import { callAdminForthApi } from '@/utils';
3
+ import { useAdminforth } from '@/adminforth';
4
+ import { defineStore } from 'pinia'
5
+
6
+ export const useBackgroundJobApi = defineStore('jobInfo', () => {
7
+ const currentJob = ref<any>(null);
8
+ const isOpened = ref(false);
9
+ const adminforth = useAdminforth();
10
+
11
+ async function openJobInfoPopup(jobId: string) {
12
+ if (!jobId) return null;
13
+ try {
14
+ const res = await callAdminForthApi({
15
+ path: `/plugin/get-background-job-info`,
16
+ method: 'POST',
17
+ body: { jobId },
18
+ });
19
+
20
+ if (res && res.ok) {
21
+ currentJob.value = res.job;
22
+ isOpened.value = true;
23
+ return res.job;
24
+ } else {
25
+ adminforth.alert({ variant: 'danger', message: res?.message || 'Failed to load job info' });
26
+ return null;
27
+ }
28
+ } catch (e) {
29
+ console.error('OpenJobInfoPopup error', e);
30
+ adminforth.alert({ variant: 'danger', message: 'Failed to load job info' });
31
+ return null;
32
+ }
33
+ }
34
+
35
+ function setIsOpened(value: boolean) {
36
+ isOpened.value = value;
37
+ }
38
+
39
+ function clearCurrentJob() {
40
+ currentJob.value = null;
41
+ }
42
+
43
+ function updateCurrentJob(jobData: any) {
44
+ currentJob.value = { ...currentJob.value, ...jobData };
45
+ }
46
+
47
+ return {
48
+ currentJob,
49
+ isOpened,
50
+ openJobInfoPopup,
51
+ setIsOpened,
52
+ clearCurrentJob,
53
+ updateCurrentJob
54
+ };
55
+ });
@@ -5,11 +5,11 @@
5
5
  ref="dialogRef"
6
6
  removeFromDomOnClose
7
7
  class="p-4"
8
- :beforeCloseFunction="() => { currentJob.value = null; }"
8
+ :beforeCloseFunction="() => { jobStore.clearCurrentJob(); jobStore.setIsOpened(false); }"
9
9
  >
10
10
  <JobInfoPopup
11
- v-if="currentJob"
12
- :job="currentJob"
11
+ v-if="jobStore.currentJob"
12
+ :job="jobStore.currentJob"
13
13
  :meta="meta"
14
14
  :closeModal="closeModal"
15
15
  />
@@ -18,14 +18,15 @@
18
18
  </template>
19
19
 
20
20
  <script setup lang="ts">
21
- import { ref, onMounted, onBeforeUnmount } from 'vue';
22
- import { callAdminForthApi } from '@/utils';
23
- import { useAdminforth } from '@/adminforth';
21
+ import { ref, onMounted, onBeforeUnmount, watch } from 'vue';
24
22
  import { Modal } from '@/afcl';
25
23
  import JobInfoPopup from './JobInfoPopup.vue';
26
24
  import websocket from '@/websocket';
25
+ import { useBackgroundJobApi } from './useBackgroundJobApi';
27
26
 
28
- const adminforth = useAdminforth();
27
+ const jobStore = useBackgroundJobApi();
28
+
29
+ const dialogRef = ref<any>(null);
29
30
 
30
31
  const props = defineProps<{
31
32
  meta: {
@@ -33,45 +34,35 @@ const props = defineProps<{
33
34
  }
34
35
  }>();
35
36
 
36
- const dialogRef = ref<any>(null);
37
- const currentJob = ref<any>(null);
38
37
 
39
38
  function closeModal() {
40
39
  if (!dialogRef.value) return;
41
40
  if (typeof dialogRef.value.close === 'function') {
42
41
  dialogRef.value.close();
42
+ jobStore.clearCurrentJob();
43
+ jobStore.setIsOpened(false);
43
44
  return;
44
45
  }
45
46
  if (typeof dialogRef.value.hide === 'function') {
47
+ jobStore.clearCurrentJob();
48
+ jobStore.setIsOpened(false);
46
49
  dialogRef.value.hide();
47
50
  }
48
51
  }
49
52
 
50
- async function openJobInfo(jobId: string) {
51
- if (!jobId) return;
52
- try {
53
- const res = await callAdminForthApi({
54
- path: `/plugin/${props.meta.pluginInstanceId}/get-job-info`,
55
- method: 'POST',
56
- body: { jobId },
57
- });
58
- if (res && res.ok) {
59
- currentJob.value = res.job;
60
- // open dialog
61
- if (dialogRef.value && typeof dialogRef.value.open === 'function') {
62
- dialogRef.value.open();
63
- } else if (dialogRef.value && typeof dialogRef.value.show === 'function') {
64
- dialogRef.value.show();
65
- }
66
- } else {
67
- adminforth.alert({ variant: 'danger', message: res?.message || 'Failed to load job info' });
68
- }
69
- } catch (e) {
70
- console.error('OpenJobInfoPopup error', e);
71
- adminforth.alert({ variant: 'danger', message: 'Failed to load job info' });
53
+ watch(() => jobStore.isOpened, (newVal) => {
54
+ if (newVal) {
55
+ dialogRef.value?.open?.();
56
+ } else {
57
+ dialogRef.value?.close?.();
72
58
  }
59
+ });
60
+
61
+ async function openJobInfo(jobId: string) {
62
+ jobStore.openJobInfoPopup(jobId);
73
63
  }
74
64
 
65
+
75
66
  onMounted(() => {
76
67
  // expose global function
77
68
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -79,21 +70,18 @@ onMounted(() => {
79
70
  window.OpenJobInfoPopup = openJobInfo;
80
71
 
81
72
  websocket.subscribe('/background-jobs', (data) => {
82
- if (data.jobId === currentJob.value?.id) {
73
+ if (data.jobId === jobStore.currentJob?.id) {
83
74
  if (data.status) {
84
- currentJob.value.status = data.status;
75
+ jobStore.updateCurrentJob({ status: data.status });
85
76
  }
86
77
  if (data.progress !== undefined) {
87
- currentJob.value.progress = data.progress;
78
+ jobStore.updateCurrentJob({ progress: data.progress });
88
79
  }
89
80
  if (data.finishedAt) {
90
- currentJob.value.finishedAt = data.finishedAt;
81
+ jobStore.updateCurrentJob({ finishedAt: data.finishedAt });
91
82
  }
92
83
  if (data.state) {
93
- currentJob.value.state = {
94
- ...currentJob.value.state,
95
- ...data.state,
96
- };
84
+ jobStore.updateCurrentJob({ state: { ...jobStore.currentJob?.state, ...data.state } });
97
85
  }
98
86
  }
99
87
  });
@@ -33,7 +33,7 @@
33
33
  </Tooltip>
34
34
  </div>
35
35
  </div>
36
- <div class="flex items-center gap-4 w-full">
36
+ <div class="flex items-center gap-4 w-full mt-4">
37
37
  <ProgressBar
38
38
  :current-value="job.progress"
39
39
  :max-value="100"
@@ -42,7 +42,7 @@
42
42
  :showLabels="false"
43
43
  :showValues="false"
44
44
  :show-progress="false"
45
- :height="6"
45
+ :height="3"
46
46
  />
47
47
  <Button class="h-8" v-if="job.status === 'IN_PROGRESS'" @click="cancelJob"> {{ t('Cancel') }} </Button>
48
48
  </div>
@@ -4,8 +4,8 @@
4
4
  ref="modalRef"
5
5
  class="p-4"
6
6
  v-for="job in props.jobs" :key="job.id"
7
- :beforeCloseFunction="onBeforeOpen"
8
- :beforeOpenFunction="onBeforeClose"
7
+ :beforeCloseFunction="onBeforeClose"
8
+ :beforeOpenFunction="onBeforeOpen"
9
9
  removeFromDomOnClose
10
10
  >
11
11
  <template #trigger>
@@ -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
- <IconBriefcaseSolid class="w-7 h-7 text-gray-600 hover:text-gray-700" />
6
+ <IconBriefcaseSolid class="w-7 h-7 text-lightNavbarIcons dark:text-darkNavbarIcons" />
7
7
  <template #tooltip>
8
8
  {{ t('All jobs completed') }}
9
9
  </template>
@@ -0,0 +1,55 @@
1
+ import { ref } from 'vue';
2
+ import { callAdminForthApi } from '@/utils';
3
+ import { useAdminforth } from '@/adminforth';
4
+ import { defineStore } from 'pinia'
5
+
6
+ export const useBackgroundJobApi = defineStore('jobInfo', () => {
7
+ const currentJob = ref<any>(null);
8
+ const isOpened = ref(false);
9
+ const adminforth = useAdminforth();
10
+
11
+ async function openJobInfoPopup(jobId: string) {
12
+ if (!jobId) return null;
13
+ try {
14
+ const res = await callAdminForthApi({
15
+ path: `/plugin/get-background-job-info`,
16
+ method: 'POST',
17
+ body: { jobId },
18
+ });
19
+
20
+ if (res && res.ok) {
21
+ currentJob.value = res.job;
22
+ isOpened.value = true;
23
+ return res.job;
24
+ } else {
25
+ adminforth.alert({ variant: 'danger', message: res?.message || 'Failed to load job info' });
26
+ return null;
27
+ }
28
+ } catch (e) {
29
+ console.error('OpenJobInfoPopup error', e);
30
+ adminforth.alert({ variant: 'danger', message: 'Failed to load job info' });
31
+ return null;
32
+ }
33
+ }
34
+
35
+ function setIsOpened(value: boolean) {
36
+ isOpened.value = value;
37
+ }
38
+
39
+ function clearCurrentJob() {
40
+ currentJob.value = null;
41
+ }
42
+
43
+ function updateCurrentJob(jobData: any) {
44
+ currentJob.value = { ...currentJob.value, ...jobData };
45
+ }
46
+
47
+ return {
48
+ currentJob,
49
+ isOpened,
50
+ openJobInfoPopup,
51
+ setIsOpened,
52
+ clearCurrentJob,
53
+ updateCurrentJob
54
+ };
55
+ });
package/dist/index.js CHANGED
@@ -449,7 +449,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
449
449
  });
450
450
  server.endpoint({
451
451
  method: 'POST',
452
- path: `/plugin/${this.pluginInstanceId}/get-job-info`,
452
+ path: `/plugin/get-background-job-info`,
453
453
  handler: (_a) => __awaiter(this, [_a], void 0, function* ({ adminUser, body }) {
454
454
  const jobId = body.jobId;
455
455
  const job = yield this.adminforth.resource(this.resourceConfig.resourceId).get(Filters.EQ(this.getResourcePk(), jobId));
package/index.ts CHANGED
@@ -15,7 +15,7 @@ type taskType = {
15
15
  skip?: boolean;
16
16
  state: Record<string, any>;
17
17
  }
18
-
18
+
19
19
  export default class BackgroundJobsPlugin extends AdminForthPlugin {
20
20
  options: PluginOptions;
21
21
  private taskHandlers: Record<string, taskHandlerType> = {};
@@ -485,7 +485,7 @@ export default class BackgroundJobsPlugin extends AdminForthPlugin {
485
485
 
486
486
  server.endpoint({
487
487
  method: 'POST',
488
- path: `/plugin/${this.pluginInstanceId}/get-job-info`,
488
+ path: `/plugin/get-background-job-info`,
489
489
  handler: async ({ adminUser, body }) => {
490
490
  const jobId = body.jobId;
491
491
 
package/package.json CHANGED
@@ -1,23 +1,53 @@
1
1
  {
2
2
  "name": "@adminforth/background-jobs",
3
- "version": "1.8.1",
3
+ "version": "1.11.3",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
7
+ "homepage": "https://adminforth.dev/docs/tutorial/Plugins/background-jobs/",
7
8
  "publishConfig": {
8
9
  "access": "public"
9
10
  },
10
11
  "scripts": {
11
12
  "build": "tsc && rsync -av --exclude 'node_modules' custom dist/"
12
13
  },
13
- "keywords": [],
14
- "author": "",
15
- "license": "ISC",
16
- "description": "",
14
+ "keywords": [
15
+ "adminforth",
16
+ "background-jobs",
17
+ "async-processing",
18
+ "task-runner",
19
+ "job-management",
20
+ "progress-tracking",
21
+ "long-running-tasks"
22
+ ],
23
+ "author": "DevForth (https://devforth.io)",
24
+ "license": "MIT",
25
+ "description": "Background jobs plugin for AdminForth to run and manage long tasks from resources",
17
26
  "devDependencies": {
18
27
  "@types/node": "latest",
28
+ "adminforth": "^2.42.0",
29
+ "semantic-release": "^24.2.1",
30
+ "semantic-release-slack-bot": "^4.0.2",
19
31
  "typescript": "^5.7.3"
20
32
  },
33
+ "release": {
34
+ "plugins": [
35
+ "@semantic-release/commit-analyzer",
36
+ "@semantic-release/release-notes-generator",
37
+ "@semantic-release/npm",
38
+ "@semantic-release/github",
39
+ [
40
+ "semantic-release-slack-bot",
41
+ {
42
+ "packageName": "@adminforth/background-jobs",
43
+ "notifyOnSuccess": true,
44
+ "notifyOnFail": true,
45
+ "slackIcon": ":package:",
46
+ "markdownReleaseNotes": true
47
+ }
48
+ ]
49
+ ]
50
+ },
21
51
  "dependencies": {
22
52
  "@vueuse/core": "^14.2.1",
23
53
  "async-mutex": "^0.5.0",
@@ -25,6 +55,6 @@
25
55
  "p-limit": "^7.3.0"
26
56
  },
27
57
  "peerDependencies": {
28
- "adminforth": "next"
58
+ "adminforth": "^2.42.0"
29
59
  }
30
60
  }
package/types.ts CHANGED
@@ -1,5 +1,6 @@
1
+ import {type PluginsCommonOptions } from "adminforth";
1
2
 
2
- export interface PluginOptions {
3
+ export interface PluginOptions extends PluginsCommonOptions {
3
4
  createdAtField: string;
4
5
  finishedAtField: string;
5
6
  startedByField: string;