Skip to content

Commit

Permalink
feat: add pause / resume on queue level, closes #214
Browse files Browse the repository at this point in the history
  • Loading branch information
felixmosh committed Aug 3, 2021
1 parent 2663809 commit 2f96cb7
Show file tree
Hide file tree
Showing 26 changed files with 377 additions and 24 deletions.
14 changes: 14 additions & 0 deletions packages/api/src/handlers/pauseQueue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { BullBoardRequest, ControllerHandlerReturnType } from '../../typings/app';
import { queueProvider } from '../providers/queue';
import { BaseAdapter } from '../queueAdapters/base';

async function pauseQueue(
_req: BullBoardRequest,
queue: BaseAdapter
): Promise<ControllerHandlerReturnType> {
await queue.pause();

return { status: 200, body: {} };
}

export const pauseQueueHandler = queueProvider(pauseQueue);
2 changes: 2 additions & 0 deletions packages/api/src/handlers/queues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ async function getAppQueues(

const counts = await queue.getJobCounts(...allStatuses);

const isPaused = await queue.isPaused();
const pagination = getPagination(status, counts, currentPage);
const jobs = await queue.getJobs(status, pagination.range.start, pagination.range.end);

Expand All @@ -112,6 +113,7 @@ async function getAppQueues(
jobs: jobs.filter(Boolean).map((job) => formatJob(job, queue)),
pagination,
readOnlyMode: queue.readOnlyMode,
isPaused,
};
})
);
Expand Down
14 changes: 14 additions & 0 deletions packages/api/src/handlers/resumeQueue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { BullBoardRequest, ControllerHandlerReturnType } from '../../typings/app';
import { queueProvider } from '../providers/queue';
import { BaseAdapter } from '../queueAdapters/base';

async function resumeQueue(
_req: BullBoardRequest,
queue: BaseAdapter
): Promise<ControllerHandlerReturnType> {
await queue.resume();

return { status: 200, body: {} };
}

export const resumeQueueHandler = queueProvider(resumeQueue);
3 changes: 3 additions & 0 deletions packages/api/src/queueAdapters/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,7 @@ export abstract class BaseAdapter {
public abstract getName(): string;

public abstract getRedisInfo(): Promise<string>;
public abstract isPaused(): Promise<boolean>;
public abstract pause(): Promise<void>;
public abstract resume(): Promise<void>;
}
12 changes: 12 additions & 0 deletions packages/api/src/queueAdapters/bull.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,16 @@ export class BullAdapter extends BaseAdapter {
public getJobLogs(id: string): Promise<string[]> {
return this.queue.getJobLogs(id).then(({ logs }) => logs);
}

public isPaused(): Promise<boolean> {
return this.queue.isPaused();
}

public pause(): Promise<void> {
return this.queue.pause();
}

public resume(): Promise<void> {
return this.queue.resume();
}
}
12 changes: 12 additions & 0 deletions packages/api/src/queueAdapters/bullMQ.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,16 @@ export class BullMQAdapter extends BaseAdapter {
public getJobLogs(id: string): Promise<string[]> {
return this.queue.getJobLogs(id).then(({ logs }) => logs);
}

public isPaused(): Promise<boolean> {
return this.queue.isPaused();
}

public pause(): Promise<void> {
return this.queue.pause();
}

public resume(): Promise<void> {
return this.queue.resume();
}
}
30 changes: 21 additions & 9 deletions packages/api/src/routes.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { AppRouteDefs } from '../typings/app';
import { cleanAllHandler } from './handlers/cleanAll';
import { cleanJobHandler } from './handlers/cleanJob';
import { entryPoint } from './handlers/entryPoint';
import { jobLogsHandler } from './handlers/jobLogs';
import { pauseQueueHandler } from './handlers/pauseQueue';
import { promoteJobHandler } from './handlers/promotJob';
import { queuesHandler } from './handlers/queues';
import { resumeQueueHandler } from './handlers/resumeQueue';
import { retryAllHandler } from './handlers/retryAll';
import { cleanAllHandler } from './handlers/cleanAll';
import { retryJobHandler } from './handlers/retryJob';
import { cleanJobHandler } from './handlers/cleanJob';
import { promoteJobHandler } from './handlers/promotJob';
import { jobLogsHandler } from './handlers/jobLogs';

export const appRoutes: AppRouteDefs = {
entryPoint: {
Expand All @@ -16,6 +18,11 @@ export const appRoutes: AppRouteDefs = {
},
api: [
{ method: 'get', route: '/api/queues', handler: queuesHandler },
{
method: 'get',
route: '/api/queues/:queueName/:jobId/logs',
handler: jobLogsHandler,
},
{
method: 'put',
route: '/api/queues/:queueName/retry',
Expand All @@ -26,6 +33,16 @@ export const appRoutes: AppRouteDefs = {
route: '/api/queues/:queueName/clean/:queueStatus',
handler: cleanAllHandler,
},
{
method: 'put',
route: '/api/queues/:queueName/pause',
handler: pauseQueueHandler,
},
{
method: 'put',
route: '/api/queues/:queueName/resume',
handler: resumeQueueHandler,
},
{
method: 'put',
route: '/api/queues/:queueName/:jobId/retry',
Expand All @@ -41,10 +58,5 @@ export const appRoutes: AppRouteDefs = {
route: '/api/queues/:queueName/:jobId/promote',
handler: promoteJobHandler,
},
{
method: 'get',
route: '/api/queues/:queueName/:jobId/logs',
handler: jobLogsHandler,
},
],
};
1 change: 1 addition & 0 deletions packages/api/typings/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export interface AppQueue {
jobs: AppJob[];
pagination: Pagination;
readOnlyMode: boolean;
isPaused: boolean;
}

export type HTTPMethod = 'get' | 'post' | 'put';
Expand Down
3 changes: 2 additions & 1 deletion packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
},
"dependencies": {
"@bull-board/api": "3.3.3",
"@radix-ui/react-alert-dialog": "^0.0.19"
"@radix-ui/react-alert-dialog": "^0.0.19",
"@radix-ui/react-dropdown-menu": "^0.0.22"
},
"devDependencies": {
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export const App = ({ api }: { api: Api }) => {
)}
</div>
</main>
<Menu queues={state.data?.queues.map((q) => q.name)} selectedStatuses={selectedStatuses} />
<Menu queues={state.data?.queues} selectedStatuses={selectedStatuses} />
<ToastContainer />
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@

.overlay[data-state='open'],
.contentWrapper[data-state='open'] {
animation: fadeIn 250ms ease-out;
animation: fadeIn 150ms ease-out;
}

.overlay[data-state='closed'],
.contentWrapper[data-state='closed'] {
animation: fadeOut 250ms ease-in;
animation: fadeOut 150ms ease-in;
}

.overlay {
Expand Down
11 changes: 11 additions & 0 deletions packages/ui/src/components/Icons/EllipsisVertical.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';

export const EllipsisVerticalIcon = () => (
<svg aria-hidden="true" role="img"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 192 512"
>
<path
d="M96 184c39.8 0 72 32.2 72 72s-32.2 72-72 72-72-32.2-72-72 32.2-72 72-72zM24 80c0 39.8 32.2 72 72 72s72-32.2 72-72S135.8 8 96 8 24 40.2 24 80zm0 352c0 39.8 32.2 72 72 72s72-32.2 72-72-32.2-72-72-72-72 32.2-72 72z"
></path>
</svg>
);
10 changes: 10 additions & 0 deletions packages/ui/src/components/Icons/Pause.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react';

export const PauseIcon = () => (
<svg aria-hidden="true" focusable="false" role="img"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<path
d="M144 479H48c-26.5 0-48-21.5-48-48V79c0-26.5 21.5-48 48-48h96c26.5 0 48 21.5 48 48v352c0 26.5-21.5 48-48 48zm304-48V79c0-26.5-21.5-48-48-48h-96c-26.5 0-48 21.5-48 48v352c0 26.5 21.5 48 48 48h96c26.5 0 48-21.5 48-48z"
></path>
</svg>
);
10 changes: 10 additions & 0 deletions packages/ui/src/components/Icons/Play.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react';

export const PlayIcon = () => (
<svg aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512">
<path
d="M424.4 214.7L72.4 6.6C43.8-10.3 0 6.1 0 47.9V464c0 37.5 40.7 60.1 72.4 41.3l352-208c31.4-18.5 31.5-64.1 0-82.6z"
></path>
</svg>
);
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

.button.default > svg {
width: 1.25em;
height: 1.25em;
vertical-align: middle;
display: inline-block;
fill: #718096;
Expand Down
7 changes: 7 additions & 0 deletions packages/ui/src/components/Menu/Menu.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,10 @@
.appVersion {
text-align: center;
}

.isPaused {
color: #828e97;
font-size: 0.833em;
display: block;
margin-bottom: -0.75em;
}
9 changes: 5 additions & 4 deletions packages/ui/src/components/Menu/Menu.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
import { AppQueue } from '@bull-board/api/typings/app';
import React from 'react';
import { NavLink } from 'react-router-dom';
import { STATUS_LIST } from '../../constants/status-list';
import { Store } from '../../hooks/useStore';
import s from './Menu.module.css';
import { STATUS_LIST } from '../../constants/status-list';

export const Menu = ({
queues,
selectedStatuses,
}: {
queues: string[] | undefined;
queues: AppQueue[] | undefined;
selectedStatuses: Store['selectedStatuses'];
}) => (
<aside className={s.aside}>
<div>QUEUES</div>
<nav>
{!!queues && (
<ul className={s.menu}>
{queues.map((queueName) => (
{queues.map(({ name: queueName, isPaused }) => (
<li key={queueName}>
<NavLink
to={`/queue/${encodeURIComponent(queueName)}${
Expand All @@ -27,7 +28,7 @@ export const Menu = ({
activeClassName={s.active}
title={queueName}
>
{queueName}
{queueName} {isPaused && <span className={s.isPaused}>[ Paused ]</span>}
</NavLink>
</li>
))}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
.trigger {
padding: .5em .65em;
margin-bottom: 0.25em;
justify-self: flex-end;
}

.content {
background: #fff;
box-shadow: 0 2px 3px 0 rgba(34,36,38 ,0.15);
border: 1px solid rgba(34,36,38,.15);
border-radius: .28571429rem;
padding-top: .25rem;
padding-bottom: .25rem;
font-size: .875rem;
line-height: 1.25rem;
font-weight: 400;
min-width: 150px;
}


.item {
display: block;
color: rgba(0,0,0,.87);
padding: .5rem 1rem;
font-weight: 400;
white-space: nowrap;
cursor: pointer;
}

.item > svg {
float: none;
margin-right: .5rem;
width: 1.18em;
height: 1em;
vertical-align: middle;
fill: #718096;
}

.item:hover {
color: rgba(0,0,0,.95);
background: #e2e8f0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { AppQueue } from '@bull-board/api/typings/app';
import { Content, Item, Root, Trigger } from '@radix-ui/react-dropdown-menu';
import React from 'react';
import { Store } from '../../hooks/useStore';
import { EllipsisVerticalIcon } from '../Icons/EllipsisVertical';
import { PauseIcon } from '../Icons/Pause';
import { PlayIcon } from '../Icons/Play';
import { Button } from '../JobCard/Button/Button';
import s from './QueueDropdownActions.module.css';

export const QueueDropdownActions = ({
queue,
actions,
}: {
queue: AppQueue;
actions: Store['actions'];
}) => (
<Root>
<Trigger as={Button} className={s.trigger}>
<EllipsisVerticalIcon />
</Trigger>

<Content className={s.content} align="end">
<Item
className={s.item}
onSelect={queue.isPaused ? actions.resumeQueue(queue.name) : actions.pauseQueue(queue.name)}
>
{queue.isPaused ? (
<>
<PlayIcon />
Resume
</>
) : (
<>
<PauseIcon />
Pause
</>
)}
</Item>
</Content>
</Root>
);
2 changes: 1 addition & 1 deletion packages/ui/src/components/QueuePage/QueuePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const QueuePage = ({
return (
<section>
<div className={s.stickyHeader}>
<StatusMenu queue={queue} />
<StatusMenu queue={queue} actions={actions} />
<div className={s.actionContainer}>
<div>
{queue.jobs.length > 0 && !queue.readOnlyMode && (
Expand Down
7 changes: 5 additions & 2 deletions packages/ui/src/components/StatusMenu/StatusMenu.module.css
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
.statusMenu {
display: flex;
margin-bottom: 2rem;
min-height: 2.85714286em;
border-bottom: 2px solid #e0e7eb;
align-items: flex-end;
}

.statusMenu > a {
Expand Down Expand Up @@ -41,6 +39,11 @@
font-weight: 500;
}

.statusMenu > div {
flex: 1;
text-align: right;
}

.badge {
display: inline-block;
background-color: #a0aec0;
Expand Down
Loading

0 comments on commit 2f96cb7

Please sign in to comment.