Skip to content

Commit

Permalink
feat(ui): add job modal
Browse files Browse the repository at this point in the history
  • Loading branch information
Diluka committed May 17, 2024
1 parent 98c5c79 commit 2f8fff5
Show file tree
Hide file tree
Showing 11 changed files with 263 additions and 3 deletions.
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
92 changes: 92 additions & 0 deletions packages/ui/src/components/AddJobModal/AddJobModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import * as Dialog from '@radix-ui/react-dialog';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQueues } from '../../hooks/useQueues';
import { Button } from '../Button/Button';
import { InputField } from '../Form/InputField/InputField';
import { JsonField } from '../Form/JsonField/JsonField';
import { SelectField } from '../Form/SelectField/InputField';
import { Modal } from '../Modal/Modal';

export interface AddJobModalProps {
open: boolean;

onClose(): void;
}

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

useEffect(() => {
if (queues && queues.length) {
setQueueName(queues[0].name);
}
}, [queues]);

const addJob = () => {
actions.addJob(queueName, 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}>
{t('ADD_JOB.ADD')}
</Button>
</Dialog.Close>
}
>
<SelectField
label={t('ADD_JOB.QUEUE_NAME')}
id="queue-name"
options={(queues || []).map((queue) => ({
text: queue.name,
value: queue.name,
}))}
value={queueName}
onChange={(event) => setQueueName(event.target.value)}
/>
<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)}
/>
<InputField
label={t('ADD_JOB.JOB_DELAY')}
id="job-delay"
type="number"
value={jobDelay}
onChange={(event) => setJobDelay(event.target.value)}
/>
<InputField
label={t('ADD_JOB.JOB_ATTEMPTS')}
id="job-attempts"
type="number"
value={jobAttempts}
onChange={(event) => setJobAttempts(event.target.value)}
/>
</Modal>
);
};
16 changes: 16 additions & 0 deletions packages/ui/src/components/Form/JsonField/JsonField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
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;
}

export const JsonField = ({ label, id, ...props }: JsonFieldProps) => (
<Field label={label} id={id}>
<Editor mode="code" {...props} />
</Field>
);
20 changes: 19 additions & 1 deletion packages/ui/src/components/HeaderActions/HeaderActions.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import React, { useState, Suspense } from 'react';
import { useUIConfig } from '../../hooks/useUIConfig';
import { CustomLinksDropdown } from '../CustomLinksDropdown/CustomLinksDropdown';
import { AddIcon } from '../Icons/Add';
import { FullscreenIcon } from '../Icons/Fullscreen';
import { RedisIcon } from '../Icons/Redis';
import { Settings } from '../Icons/Settings';
import { Button } from '../Button/Button';
import s from './HeaderActions.module.css';

type ModalTypes = 'redis' | 'settings';
type ModalTypes = 'redis' | 'settings' | 'addJobs';
type AllModalTypes = ModalTypes | `${ModalTypes}Closing` | null;

function waitForClosingAnimation(
Expand Down Expand Up @@ -38,6 +39,12 @@ const onClickFullScreen = async () => {
return document.exitFullscreen();
};

const AddJobModalLazy = React.lazy(() =>
import('../AddJobModal/AddJobModal').then(({ AddJobModal }) => ({
default: AddJobModal,
}))
);

export const HeaderActions = () => {
const [openedModal, setModalOpen] = useState<AllModalTypes>(null);
const { miscLinks = [] } = useUIConfig();
Expand All @@ -55,6 +62,11 @@ export const HeaderActions = () => {
<FullscreenIcon />
</Button>
</li>
<li>
<Button onClick={() => setModalOpen('addJobs')} className={s.button}>
<AddIcon />
</Button>
</li>
<li>
<Button onClick={() => setModalOpen('settings')} className={s.button}>
<Settings />
Expand All @@ -79,6 +91,12 @@ export const HeaderActions = () => {
onClose={waitForClosingAnimation('settings', setModalOpen)}
/>
)}
{(openedModal === 'addJobs' || openedModal === 'addJobsClosing') && (
<AddJobModalLazy
open={openedModal === 'addJobs'}
onClose={waitForClosingAnimation('addJobs', setModalOpen)}
/>
)}
</Suspense>
</>
);
Expand Down
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>
);
8 changes: 8 additions & 0 deletions packages/ui/src/hooks/useQueues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,13 @@ export function useQueues(): Omit<QueuesState, 'updateQueues'> & { actions: Queu
confirmQueueActions
);

const addJob = (queueName: string, jobName:string, jobData: any, jobOptions: any) =>
withConfirmAndUpdate(
() => api.addJob(queueName, jobName, jobData, jobOptions),
t('QUEUE.ACTIONS.ADD_JOB_CONFIRM_MSG'),
false
);

return {
queues,
loading,
Expand All @@ -120,6 +127,7 @@ export function useQueues(): Omit<QueuesState, 'updateQueues'> & { actions: Queu
pauseQueue,
resumeQueue,
emptyQueue,
addJob,
},
};
}
13 changes: 13 additions & 0 deletions packages/ui/src/services/Api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,19 @@ export class Api {
);
}

public addJob(
queueName: string,
jobName: string,
jobData: any,
jobOptions: any
): Promise<GetJobResponse> {
return this.axios.post(`/queues/${encodeURIComponent(queueName)}/add`, {
name: jobName,
data: jobData,
options: jobOptions,
});
}

public pauseQueue(queueName: string) {
return this.axios.put(`/queues/${encodeURIComponent(queueName)}/pause`);
}
Expand Down
9 changes: 9 additions & 0 deletions packages/ui/src/static/locales/en-US/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,5 +115,14 @@
"COLLAPSE_JOB_DATA": "Collapse job data",
"COLLAPSE_JOB_OPTIONS": "Collapse job options",
"COLLAPSE_JOB_ERROR": "Collapse job error"
},
"ADD_JOB": {
"TITLE": "Add job",
"QUEUE_NAME": "Queue name",
"JOB_NAME": "Job name",
"JOB_DATA": "Job data",
"JOB_DELAY": "delay (ms)",
"JOB_ATTEMPTS": "attempts",
"ADD": "Add"
}
}
1 change: 1 addition & 0 deletions packages/ui/typings/app.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export interface QueueActions {
emptyQueue: (queueName: string) => () => Promise<void>;
updateQueues: () => Promise<void>;
pollQueues: () => void;
addJob: (queueName: string, jobName: string, jobData: any, jobOptions: any) => () => Promise<void>;
}

export interface JobActions {
Expand Down
4 changes: 4 additions & 0 deletions packages/ui/typings/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@ declare module '*.css' {
const resource: Record<string, string>;
export = resource;
}

declare module 'jsoneditor-react' {
export const JsonEditor: (options: any) => JSX.Element;
}
Loading

0 comments on commit 2f8fff5

Please sign in to comment.