Skip to content

Commit 85c5c2a

Browse files
committed
chore: add background jobs plugin to the dev demo
1 parent 7a83d21 commit 85c5c2a

File tree

8 files changed

+342
-3
lines changed

8 files changed

+342
-3
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ node_modules
22
plugins/*
33
!plugins/install-plugins.sh
44
adapters/*
5-
!adapters/install-adapters.sh
5+
!adapters/install-adapters.sh
6+
background-jobs-dbs

dev-demo/custom/AfComponents.vue

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,17 +161,21 @@
161161
:currentValue="2600"
162162
:minValue="0"
163163
:maxValue="5000"
164+
:height="6"
165+
:leftLabel="'Level 2'"
166+
:rightLabel="'Level 3'"
167+
showAnimation
164168
/>
165169

166-
<ProgressBar
170+
<!-- <ProgressBar
167171
:currentValue="1070"
168172
:minValue="0"
169173
:maxValue="5000"
170174
:leftLabel="'Level 2'"
171175
:rightLabel="'Level 3'"
172176
:formatter="(value: number) => `${value} points`"
173177
:progressFormatter="(value: number, percentage: number) => `${value} done`"
174-
/>
178+
/> -->
175179

176180
<div class="flex flex-col gap-2">
177181
<Skeleton class="w-full h-4" />
@@ -287,6 +291,22 @@
287291
label="Pick start"
288292
/>
289293

294+
295+
<Modal class="w-96" clickToCloseOutside>
296+
<template #trigger>
297+
<Button>Modal Toggle</Button>
298+
</template>
299+
300+
<div class="space-y-4">
301+
<p>This is the first paragraph of dialog content.</p>
302+
<p>And this is the second paragraph.</p>
303+
</div>
304+
</Modal>
305+
306+
<Button class="mt-48 ml-48" @click="createJob"> Create Job</Button>
307+
308+
309+
290310
</div>
291311

292312

@@ -313,11 +333,13 @@ import { ProgressBar } from '@/afcl';
313333
import { Skeleton } from '@/afcl';
314334
import { Spinner } from '@/afcl';
315335
import { Toggle } from '@/afcl';
336+
import { Modal } from '@/afcl';
316337
import { IconSearchOutline } from '@iconify-prerendered/vue-flowbite'
317338
import { DatePicker } from '@/afcl';
318339
import CustomRangePicker from "@/components/CustomRangePicker.vue";
319340
import Toast from '@/components/Toast.vue';
320341
import { useAdminforth } from '@/adminforth';
342+
import { callApi } from '@/utils';
321343
322344
const { alert } = useAdminforth();
323345
import adminforth from '@/adminforth';
@@ -346,4 +368,13 @@ function doSmth(){
346368
adminforth.alert({message: 'You clicked the button!', variant: 'success' })
347369
}
348370
371+
async function createJob() {
372+
try {
373+
const res = await callApi({path: '/api/create-job/', method: 'POST'});
374+
console.log('Job created successfully:', res);
375+
} catch (error) {
376+
console.error('Error creating job:', error);
377+
}
378+
}
379+
349380
</script>
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<template>
2+
<div class="w-[1000px] h-[500px] bg-gray-100 rounded-lg p-4 flex flex-col items-center justify-center ">
3+
<Button class="h-10" @click="loadTasks">
4+
Get Job Tasks
5+
</Button>
6+
{{ tasks }}
7+
</div>
8+
</template>
9+
10+
11+
<script setup lang="ts">
12+
import { Button, JsonViewer } from '@/afcl';
13+
import { onMounted, onUnmounted, ref } from 'vue';
14+
import websocket from '@/websocket';
15+
import type { AdminForthComponentDeclarationFull } from 'adminforth';
16+
17+
18+
const tasks = ref<{state: Record<string, any>, status: string}[]>([]);
19+
20+
21+
const props = defineProps<{
22+
meta: any;
23+
getJobTasks: (limit?: number, offset?: number) => Promise<{state: Record<string, any>, status: string}[]>;
24+
job: {
25+
id: string;
26+
name: string;
27+
status: 'IN_PROGRESS' | 'DONE' | 'DONE_WITH_ERRORS' | 'CANCELLED';
28+
progress: number; // 0 to 100
29+
createdAt: Date;
30+
customComponent?: AdminForthComponentDeclarationFull;
31+
};
32+
}>();
33+
34+
const loadTasks = async () => {
35+
tasks.value = await props.getJobTasks(10, 0);
36+
console.log('Loaded tasks for job:', tasks.value);
37+
}
38+
39+
40+
onMounted(async () => {
41+
loadTasks();
42+
websocket.subscribe(`/background-jobs-task-update/${props.job.id}`, (data: { taskIndex: number, status?: string, state?: Record<string, any> }) => {
43+
console.log('Received WebSocket message for job:', data.status);
44+
45+
if (data.state) {
46+
tasks.value[data.taskIndex].state = data.state;
47+
}
48+
if (data.status) {
49+
tasks.value[data.taskIndex].status = data.status;
50+
}
51+
52+
});
53+
});
54+
55+
onUnmounted(() => {
56+
console.log('Unsubscribing from WebSocket for job:', props.job.id);
57+
websocket.unsubscribe(`/background-jobs-task-update/${props.job.id}`);
58+
});
59+
60+
61+
</script>

dev-demo/index.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import cars_MyS_resource from './resources/cars_MyS.js';
1010
import cars_PG_resource from './resources/cars_PG.js';
1111
import cars_Mongo_resource from './resources/cars_mongo.js';
1212
import cars_Ch_resource from './resources/cars_Ch.js';
13+
import background_jobs_resource from './resources/background_jobs.js';
14+
import BackgroundJobsPlugin from '../plugins/adminforth-background-jobs/index.js';
1315

1416
import auditLogsResource from "./resources/auditLogs.js"
1517
import { FICTIONAL_CAR_BRANDS, FICTIONAL_CAR_MODELS_BY_BRAND, ENGINE_TYPES, BODY_TYPES } from './custom/cars_data.js';
@@ -79,6 +81,14 @@ export const admin = new AdminForth({
7981
}
8082
}
8183
},
84+
componentsToExplicitRegister: [
85+
{
86+
file: '@@/JobCustomComponent.vue',
87+
meta: {
88+
label: 'Job Custom Component',
89+
}
90+
}
91+
],
8292
dataSources: [
8393
{
8494
id: 'sqlite',
@@ -112,6 +122,7 @@ export const admin = new AdminForth({
112122
passkeysResource,
113123
carsDescriptionImage,
114124
translations,
125+
background_jobs_resource
115126
],
116127
menu: [
117128
{ type: 'heading', label: 'SYSTEM' },
@@ -180,6 +191,32 @@ if (fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) {
180191
const app = express();
181192
app.use(express.json());
182193

194+
app.post(`${ADMIN_BASE_URL}/api/create-job/`,
195+
admin.express.authorize(
196+
async (req: any, res: any) => {
197+
const backgroundJobsPlugin = admin.getPluginByClassName<BackgroundJobsPlugin>('BackgroundJobsPlugin');
198+
if (!backgroundJobsPlugin) {
199+
res.status(404).json({ error: 'BackgroundJobsPlugin not found' });
200+
return;
201+
}
202+
backgroundJobsPlugin.startNewJob(
203+
'Example Job', //job name
204+
req.adminUser, // adminuser
205+
[
206+
{ state: { step: 1 } },
207+
{ state: { step: 2 } },
208+
{ state: { step: 3 } },
209+
{ state: { step: 4 } },
210+
{ state: { step: 5 } },
211+
{ state: { step: 6 } },
212+
], //initial tasks
213+
'example_job_handler', //job handler name
214+
)
215+
res.json({ok: true, message: 'Job started' });
216+
}
217+
),
218+
);
219+
183220
initApi(app, admin);
184221

185222
const port = 3000;
@@ -190,6 +227,30 @@ if (fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) {
190227

191228
admin.express.serve(app);
192229

230+
const backgroundJobsPlugin = admin.getPluginByClassName<BackgroundJobsPlugin>('BackgroundJobsPlugin');
231+
232+
backgroundJobsPlugin.registerTaskHandler({
233+
// job handler name
234+
jobHandlerName: 'example_job_handler',
235+
//handler function
236+
handler: async ({ setTaskStateField, getTaskStateField }) => {
237+
const state = await getTaskStateField();
238+
console.log('State of the task at the beginning of the job handler:', state);
239+
await new Promise(resolve => setTimeout(resolve, 3000));
240+
await setTaskStateField({[state.step]: `Step ${state.step} completed`});
241+
const updatedState = await getTaskStateField();
242+
console.log('State of the task after setting the new state in the job handler:', updatedState);
243+
},
244+
//limit of tasks, that are running in parallel
245+
parallelLimit: 1
246+
})
247+
248+
backgroundJobsPlugin.registerTaskDetailsComponent({
249+
jobHandlerName: 'example_job_handler', // Handler name
250+
component: {
251+
file: '@@/JobCustomComponent.vue' //custom component for the job details
252+
},
253+
})
193254
admin.discoverDatabases().then(async () => {
194255
if (await admin.resource('adminuser').count() === 0) {
195256
await admin.resource('adminuser').create({
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
Warnings:
3+
4+
- You are about to alter the column `price` on the `cars` table. The data in that column could be lost. The data in that column will be cast from `Decimal` to `Float`.
5+
6+
*/
7+
-- CreateTable
8+
CREATE TABLE "jobs" (
9+
"id" TEXT NOT NULL PRIMARY KEY,
10+
"created_at" DATETIME NOT NULL,
11+
"finished_at" DATETIME,
12+
"started_by" TEXT NOT NULL,
13+
"name" TEXT NOT NULL,
14+
"state" TEXT NOT NULL,
15+
"progress" TEXT NOT NULL,
16+
"status" TEXT NOT NULL,
17+
"job_handler_name" TEXT NOT NULL
18+
);
19+
20+
-- RedefineTables
21+
PRAGMA defer_foreign_keys=ON;
22+
PRAGMA foreign_keys=OFF;
23+
CREATE TABLE "new_cars" (
24+
"id" TEXT NOT NULL PRIMARY KEY,
25+
"model" TEXT NOT NULL,
26+
"price" REAL NOT NULL,
27+
"created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
28+
"engine_type" TEXT,
29+
"engine_power" INTEGER,
30+
"production_year" INTEGER,
31+
"description" TEXT,
32+
"listed" BOOLEAN NOT NULL DEFAULT false,
33+
"mileage" REAL,
34+
"color" TEXT,
35+
"body_type" TEXT,
36+
"photos" TEXT,
37+
"seller_id" TEXT,
38+
"seller" TEXT,
39+
"promo_picture" TEXT,
40+
"generated_promo_picture" TEXT
41+
);
42+
INSERT INTO "new_cars" ("body_type", "color", "created_at", "description", "engine_power", "engine_type", "generated_promo_picture", "id", "listed", "mileage", "model", "photos", "price", "production_year", "promo_picture", "seller", "seller_id") SELECT "body_type", "color", "created_at", "description", "engine_power", "engine_type", "generated_promo_picture", "id", "listed", "mileage", "model", "photos", "price", "production_year", "promo_picture", "seller", "seller_id" FROM "cars";
43+
DROP TABLE "cars";
44+
ALTER TABLE "new_cars" RENAME TO "cars";
45+
PRAGMA foreign_keys=ON;
46+
PRAGMA defer_foreign_keys=OFF;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
-- RedefineTables
2+
PRAGMA defer_foreign_keys=ON;
3+
PRAGMA foreign_keys=OFF;
4+
CREATE TABLE "new_jobs" (
5+
"id" TEXT NOT NULL PRIMARY KEY,
6+
"created_at" DATETIME NOT NULL,
7+
"finished_at" DATETIME,
8+
"started_by" TEXT NOT NULL,
9+
"name" TEXT NOT NULL,
10+
"state" TEXT,
11+
"progress" TEXT NOT NULL,
12+
"status" TEXT NOT NULL,
13+
"job_handler_name" TEXT NOT NULL
14+
);
15+
INSERT INTO "new_jobs" ("created_at", "finished_at", "id", "job_handler_name", "name", "progress", "started_by", "state", "status") SELECT "created_at", "finished_at", "id", "job_handler_name", "name", "progress", "started_by", "state", "status" FROM "jobs";
16+
DROP TABLE "jobs";
17+
ALTER TABLE "new_jobs" RENAME TO "jobs";
18+
PRAGMA foreign_keys=ON;
19+
PRAGMA defer_foreign_keys=OFF;

dev-demo/migrations/prisma/sqlite/schema.prisma

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,16 @@ model audit_logs {
8989
@@index([en_string, category])
9090
@@index([category])
9191
@@index([completedLangs])
92+
}
93+
94+
model jobs {
95+
id String @id
96+
created_at DateTime
97+
finished_at DateTime?
98+
started_by String
99+
name String
100+
state String?
101+
progress String
102+
status String
103+
job_handler_name String
92104
}

0 commit comments

Comments
 (0)