Skip to content

Commit

Permalink
Add Job Modal (#744)
Browse files Browse the repository at this point in the history
* feat(api): add job route

* feat(adapter): pass body

* feat(ui): add job modal

* feat(ui): prevent submit when json invalid
  • Loading branch information
Diluka authored May 22, 2024
1 parent 12cd4bc commit bad7942
Show file tree
Hide file tree
Showing 23 changed files with 332 additions and 9 deletions.
23 changes: 23 additions & 0 deletions packages/api/src/handlers/addJob.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { BaseAdapter } from '../queueAdapters/base';
import { BullBoardRequest, ControllerHandlerReturnType } from '../../typings/app';
import { queueProvider } from '../providers/queue';
import { formatJob } from './queues';

async function addJob(
req: BullBoardRequest,
queue: BaseAdapter
): Promise<ControllerHandlerReturnType> {
const { name, data, options } = req.body;

const job = await queue.addJob(name, data, options);

return {
status: 200,
body: {
job: formatJob(job, queue),
status: job.getState(),
},
};
}

export const addJobHandler = queueProvider(addJob);
3 changes: 3 additions & 0 deletions packages/api/src/queueAdapters/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
JobStatus,
QueueAdapterOptions,
QueueJob,
QueueJobOptions,
Status,
} from '../../typings/app';

Expand Down Expand Up @@ -45,6 +46,8 @@ export abstract class BaseAdapter {

public abstract clean(queueStatus: JobCleanStatus, graceTimeMs: number): Promise<void>;

public abstract addJob(name: string, data: any, options: QueueJobOptions): Promise<QueueJob>;

public abstract getJob(id: string): Promise<QueueJob | undefined | null>;

public abstract getJobCounts(): Promise<JobCounts>;
Expand Down
5 changes: 5 additions & 0 deletions packages/api/src/queueAdapters/bull.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
JobCounts,
JobStatus,
QueueAdapterOptions,
QueueJobOptions,
Status,
} from '../../typings/app';
import { STATUSES } from '../constants/statuses';
Expand All @@ -26,6 +27,10 @@ export class BullAdapter extends BaseAdapter {
return this.queue.clean(graceTimeMs, jobStatus as any);
}

public addJob(name: string, data: any, options: QueueJobOptions) {
return this.queue.add(name, data, options);
}

public getJob(id: string): Promise<Job | undefined | null> {
return this.queue.getJob(id).then((job) => job && this.alignJobData(job));
}
Expand Down
5 changes: 5 additions & 0 deletions packages/api/src/queueAdapters/bullMQ.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
JobCounts,
JobStatus,
QueueAdapterOptions,
QueueJobOptions,
Status,
} from '../../typings/app';
import { STATUSES } from '../constants/statuses';
Expand All @@ -27,6 +28,10 @@ export class BullMQAdapter extends BaseAdapter {
await this.queue.clean(graceTimeMs, 1000, jobStatus);
}

public addJob(name: string, data: any, options: QueueJobOptions) {
return this.queue.add(name, data, options);
}

public getJob(id: string): Promise<Job | undefined> {
return this.queue.getJob(id);
}
Expand Down
6 changes: 6 additions & 0 deletions packages/api/src/routes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AppRouteDefs } from '../typings/app';
import { addJobHandler } from './handlers/addJob';
import { cleanAllHandler } from './handlers/cleanAll';
import { cleanJobHandler } from './handlers/cleanJob';
import { emptyQueueHandler } from './handlers/emptyQueue';
Expand Down Expand Up @@ -33,6 +34,11 @@ export const appRoutes: AppRouteDefs = {
route: '/api/queues/:queueName/:jobId',
handler: jobHandler,
},
{
method: 'post',
route: '/api/queues/:queueName/add',
handler: addJobHandler,
},
{
method: 'put',
route: '/api/queues/:queueName/retry/:queueStatus',
Expand Down
6 changes: 6 additions & 0 deletions packages/api/typings/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ export interface QueueJobJson {
parentKey?: string;
}

export interface QueueJobOptions {
delay?: number;
attempts?: number;
}

export interface RedisStats {
version: string;
mode: RedisInfo['redis_mode'];
Expand Down Expand Up @@ -127,6 +132,7 @@ export interface BullBoardRequest {
queues: BullBoardQueues;
query: Record<string, any>;
params: Record<string, any>;
body: Record<string, any>;
}

export type ControllerHandlerReturnType = {
Expand Down
2 changes: 2 additions & 0 deletions packages/express/src/ExpressAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export class ExpressAdapter implements IServerAdapter {
throw new Error(`Please call 'setQueues' before using 'registerPlugin'`);
}
const router = Router();
router.use(express.json());

routes.forEach((route) =>
(Array.isArray(route.method) ? route.method : [route.method]).forEach(
Expand All @@ -63,6 +64,7 @@ export class ExpressAdapter implements IServerAdapter {
queues: this.bullBoardQueues as BullBoardQueues,
query: req.query,
params: req.params,
body: req.body,
});

res.status(response.status || 200).json(response.body);
Expand Down
1 change: 1 addition & 0 deletions packages/fastify/src/FastifyAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ export class FastifyAdapter implements IServerAdapter {
queues: this.bullBoardQueues as any,
params: request.params as any,
query: request.query as any,
body: request.body as any,
});

return reply.status(response.status || 200).send(response.body);
Expand Down
12 changes: 7 additions & 5 deletions packages/h3/src/H3Adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,23 @@ import {
AppViewRoute,
BullBoardQueues,
ControllerHandlerReturnType,
HTTPMethod,
IServerAdapter,
UIConfig,
} from '@bull-board/api/dist/typings/app';
import ejs from 'ejs';
import { readFileSync, statSync } from 'fs';
import { resolve, normalize } from 'node:path';
import {
createError,
createRouter,
eventHandler,
getRouterParams,
getQuery,
getRouterParams,
readBody,
serveStatic,
createError,
} from 'h3';
import ejs from 'ejs';
import { normalize, resolve } from 'node:path';
import { getContentType } from './utils/getContentType';
import { HTTPMethod } from '@bull-board/api/dist/typings/app';

export class H3Adapter implements IServerAdapter {
private uiHandler = createRouter();
Expand Down Expand Up @@ -183,6 +184,7 @@ export class H3Adapter implements IServerAdapter {
queues: this.bullBoardQueues as BullBoardQueues,
params: getRouterParams(event),
query: getQuery(event),
body: readBody(event),
});

return body;
Expand Down
1 change: 1 addition & 0 deletions packages/hapi/src/HapiAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ export class HapiAdapter implements IServerAdapter {
queues: this.bullBoardQueues as any,
params: request.params as any,
query: request.query as any,
body: request.payload as any,
});

return h.response(response.body).code(response.status || 200);
Expand Down
1 change: 1 addition & 0 deletions packages/hono/src/HonoAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export class HonoAdapter implements IServerAdapter {
queues: bullBoardQueues,
params: c.req.param(),
query: c.req.query(),
body: c.req.json(),
});
if (response.status == 204) return c.body(null, 204);
return c.json(response.body, response.status || 200);
Expand Down
1 change: 1 addition & 0 deletions packages/koa/src/KoaAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ export class KoaAdapter implements IServerAdapter {
queues: this.bullBoardQueues as any,
params: ctx.params,
query: ctx.query,
body: ctx.body,
});

ctx.status = response.status || 200;
Expand Down
2 changes: 2 additions & 0 deletions packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@
"i18next-hmr": "^3.0.3",
"i18next-http-backend": "^2.4.2",
"i18next-locales-sync": "^2.0.1",
"jsoneditor": "^10.0.3",
"jsoneditor-react": "^3.1.2",
"mini-css-extract-plugin": "^2.6.0",
"nanoid": "^4.0.1",
"postcss": "^8.4.12",
Expand Down
81 changes: 81 additions & 0 deletions packages/ui/src/components/AddJobModal/AddJobModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { AppQueue } from '@bull-board/api/dist/typings/app';
import * as Dialog from '@radix-ui/react-dialog';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { QueueActions } from '../../../typings/app';
import { Button } from '../Button/Button';
import { InputField } from '../Form/InputField/InputField';
import { JsonField } from '../Form/JsonField/JsonField';
import { Modal } from '../Modal/Modal';

export interface AddJobModalProps {
open: boolean;

onClose(): void;

actions: QueueActions;
queue: AppQueue;
}

export const AddJobModal = ({ open, onClose, actions, queue }: AddJobModalProps) => {
const [isValid, setValid] = useState(true);
const [jobName, setJobName] = useState('');
const [jobData, setJobData] = useState<any>({});
const [jobDelay, setJobDelay] = useState('');
const [jobAttempts, setJobAttempts] = useState('');
const { t } = useTranslation();

const addJob = () => {
actions.addJob(queue.name, jobName || '__default__', jobData, {
delay: jobDelay ? +jobDelay : undefined,
attempts: jobAttempts ? +jobAttempts : undefined,
})();
};

return (
<Modal
width="small"
open={open}
onClose={onClose}
title={t('ADD_JOB.TITLE')}
actionButton={
<Dialog.Close asChild>
<Button theme="primary" onClick={addJob} disabled={!isValid}>
{t('ADD_JOB.ADD')}
</Button>
</Dialog.Close>
}
>
<InputField
label={t('ADD_JOB.JOB_NAME')}
id="job-name"
value={jobName}
placeholder="__default__"
onChange={(event) => setJobName(event.target.value)}
/>
<JsonField
label={t('ADD_JOB.JOB_DATA')}
id="job-data"
value={jobData}
onChange={(v) => setJobData(v)}
onValidationError={(errors) => setValid(!errors.length)}
/>
<InputField
label={t('ADD_JOB.JOB_DELAY')}
id="job-delay"
type="number"
value={jobDelay}
min={0}
onChange={(event) => setJobDelay(event.target.value)}
/>
<InputField
label={t('ADD_JOB.JOB_ATTEMPTS')}
id="job-attempts"
type="number"
value={jobAttempts}
min={1}
onChange={(event) => setJobAttempts(event.target.value)}
/>
</Modal>
);
};
17 changes: 17 additions & 0 deletions packages/ui/src/components/Form/JsonField/JsonField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { JsonEditor as Editor } from 'jsoneditor-react';
import 'jsoneditor-react/es/editor.min.css';
import React, { HTMLProps } from 'react';
import { Field } from '../Field/Field';

interface JsonFieldProps extends HTMLProps<any> {
label?: string;
value?: any;
onChange?: (v: any) => void;
onValidationError?: (errors: Error[]) => void;
}

export const JsonField = ({ label, id, ...props }: JsonFieldProps) => (
<Field label={label} id={id}>
<Editor mode="code" {...props} />
</Field>
);
28 changes: 28 additions & 0 deletions packages/ui/src/components/Icons/Add.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react';

export const AddIcon = () => (
<svg
role="img"
width="256px"
height="256px"
viewBox="0 0 24.00 24.00"
fill="none"
xmlns="http://www.w3.org/2000/svg"
stroke="#748094"
>
<g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
<g id="SVGRepo_tracerCarrier" strokeLinecap="round" strokeLinejoin="round"></g>
<g id="SVGRepo_iconCarrier">
<path
d="M12 6C12.5523 6 13 6.44772 13 7V11H17C17.5523 11 18 11.4477 18 12C18 12.5523 17.5523 13 17 13H13V17C13 17.5523 12.5523 18 12 18C11.4477 18 11 17.5523 11 17V13H7C6.44772 13 6 12.5523 6 12C6 11.4477 6.44772 11 7 11H11V7C11 6.44772 11.4477 6 12 6Z"
fill="#748094"
></path>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M2 4.5C2 3.11929 3.11929 2 4.5 2H19.5C20.8807 2 22 3.11929 22 4.5V19.5C22 20.8807 20.8807 22 19.5 22H4.5C3.11929 22 2 20.8807 2 19.5V4.5ZM4.5 4C4.22386 4 4 4.22386 4 4.5V19.5C4 19.7761 4.22386 20 4.5 20H19.5C19.7761 20 20 19.7761 20 19.5V4.5C20 4.22386 19.7761 4 19.5 4H4.5Z"
fill="#748094"
></path>
</g>
</svg>
);
Loading

0 comments on commit bad7942

Please sign in to comment.