Skip to content

Commit

Permalink
feat: add queue description option and show the queue name on the board.
Browse files Browse the repository at this point in the history
  • Loading branch information
felixmosh committed Jul 28, 2022
1 parent 393b99e commit 91f03fb
Show file tree
Hide file tree
Showing 11 changed files with 135 additions and 65 deletions.
3 changes: 3 additions & 0 deletions packages/api/src/handlers/queues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,11 @@ async function getAppQueues(
? await queue.getJobs(status, pagination.range.start, pagination.range.end)
: [];

const description = queue.getDescription() || undefined;

return {
name: queueName,
description,
counts: counts as Record<Status, number>,
jobs: jobs.filter(Boolean).map((job) => formatJob(job, queue)),
pagination,
Expand Down
6 changes: 6 additions & 0 deletions packages/api/src/queueAdapters/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,18 @@ export abstract class BaseAdapter {
public readonly readOnlyMode: boolean;
public readonly allowRetries: boolean;
public readonly prefix: string;
public readonly description: string;
private formatters = new Map<FormatterField, (data: any) => any>();

protected constructor(options: Partial<QueueAdapterOptions> = {}) {
this.readOnlyMode = options.readOnlyMode === true;
this.allowRetries = this.readOnlyMode ? false : options.allowRetries !== false;
this.prefix = options.prefix || '';
this.description = options.description || '';
}

public getDescription(): string {
return this.description;
}

public setFormatter<T extends FormatterField>(
Expand Down
2 changes: 2 additions & 0 deletions packages/api/typings/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface QueueAdapterOptions {
readOnlyMode: boolean;
allowRetries: boolean;
prefix: string;
description: string;
}

export type BullBoardQueues = Map<string, BaseAdapter>;
Expand Down Expand Up @@ -77,6 +78,7 @@ export interface AppJob {

export interface AppQueue {
name: string;
description?: string;
counts: Record<Status, number>;
jobs: AppJob[];
pagination: Pagination;
Expand Down
29 changes: 15 additions & 14 deletions packages/ui/src/components/App.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
import React from 'react';
import { Redirect, Route, Switch } from 'react-router-dom';
import { ToastContainer } from 'react-toastify';
import { useActiveQueue } from '../hooks/useActiveQueue';
import { useScrollTopOnNav } from '../hooks/useScrollTopOnNav';
import { useStore } from '../hooks/useStore';
import { Api } from '../services/Api';
import { QueueTitle } from './QueueTitle/QueueTitle';
import { ConfirmModal } from './ConfirmModal/ConfirmModal';
import { Header } from './Header/Header';
import { Menu } from './Menu/Menu';
import { QueuePage } from './QueuePage/QueuePage';
import { RedisStats } from './RedisStats/RedisStats';
import { ConfirmModal } from './ConfirmModal/ConfirmModal';

export const App = ({ api }: { api: Api }) => {
useScrollTopOnNav();
const { state, actions, selectedStatuses, confirmProps } = useStore(api);
const activeQueue = useActiveQueue(state.data);

return (
<>
<Header>{state.data?.stats && <RedisStats stats={state.data?.stats} />}</Header>
<Header>
{!!activeQueue && <QueueTitle queue={activeQueue} />}
{state.data?.stats && <RedisStats stats={state.data?.stats} />}
</Header>
<main>
<div>
{state.loading ? (
Expand All @@ -26,18 +32,13 @@ export const App = ({ api }: { api: Api }) => {
<Switch>
<Route
path="/queue/:name"
render={({ match: { params } }) => {
const currentQueueName = decodeURIComponent(params.name);
const queue = state.data?.queues?.find((q) => q.name === currentQueueName);

return (
<QueuePage
queue={queue || undefined}
actions={actions}
selectedStatus={selectedStatuses}
/>
);
}}
render={() => (
<QueuePage
queue={activeQueue || undefined}
actions={actions}
selectedStatus={selectedStatuses}
/>
)}
/>

<Route path="/" exact>
Expand Down
72 changes: 41 additions & 31 deletions packages/ui/src/components/Header/Header.module.css
Original file line number Diff line number Diff line change
@@ -1,42 +1,52 @@
.header {
z-index: 99;
position: fixed;
top: 0;
left: 0;
width: 100%;
background: white;
transition: box-shadow 0.5s ease-in-out;
display: flex;
align-items: center;
justify-content: space-between;
height: var(--header-height);
box-sizing: border-box;
border-bottom: 1px solid #e6e7e8;
z-index: 99;
position: fixed;
top: 0;
left: 0;
width: 100%;
background: white;
transition: box-shadow 0.5s ease-in-out;
display: flex;
height: var(--header-height);
box-sizing: border-box;
border-bottom: 1px solid #e6e7e8;
}

.header > .logo {
width: var(--menu-width);
flex-basis: var(--menu-width);
flex-shrink: 0;
white-space: nowrap;
text-align: center;
font-size: 1.728rem;
height: inherit;
line-height: var(--header-height);
background-color: hsl(217, 22%, 24%);
color: #f5f8fa;
border-bottom: 1px solid rgba(0, 0, 0, 0.4);
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.1);
z-index: 2;
box-sizing: content-box;
width: var(--menu-width);
flex-basis: var(--menu-width);
flex-shrink: 0;
white-space: nowrap;
text-align: center;
font-size: 1.728rem;
height: inherit;
line-height: var(--header-height);
background-color: hsl(217, 22%, 24%);
color: #f5f8fa;
border-bottom: 1px solid rgba(0, 0, 0, 0.4);
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.1);
z-index: 2;
}

.header > .logo > img {
width: 1.2em;
margin-right: 0.3em;
margin-bottom: -0.2em;
width: 1.2em;
margin-right: 0.3em;
margin-bottom: -0.2em;
}

.header .content {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 2rem;
overflow: hidden;
}

.content.positionRight {
justify-content: flex-end;
}

.header + main {
padding-top: var(--header-height);
padding-top: var(--header-height);
}
5 changes: 4 additions & 1 deletion packages/ui/src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import cn from 'clsx';
import React, { PropsWithChildren } from 'react';
import s from './Header.module.css';
import { getStaticPath } from '../../utils/getStaticPath';
Expand All @@ -8,6 +9,8 @@ export const Header = ({ children }: PropsWithChildren<any>) => (
<img src={getStaticPath('/images/logo.svg')} alt="Bull Dashboard" />
Bull Dashboard
</div>
{children}
<div className={cn(s.content, { [s.positionRight]: React.Children.count(children) === 1 })}>
{children}
</div>
</header>
);
19 changes: 19 additions & 0 deletions packages/ui/src/components/QueueTitle/QueueTitle.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.queueTitle {
color: #718096;
overflow: hidden;
}

.name {
font-size: 1.2rem;
font-weight: 400;
}

.description {
margin: 0;
opacity: 0.85;
font-size: 0.85rem;
font-weight: 200;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
13 changes: 13 additions & 0 deletions packages/ui/src/components/QueueTitle/QueueTitle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { AppQueue } from '@bull-board/api/typings/app';
import React from 'react';
import s from './QueueTitle.module.css';

interface QueueTitleProps {
queue: AppQueue;
}
export const QueueTitle = ({ queue }: QueueTitleProps) => (
<div className={s.queueTitle}>
<h1 className={s.name}>{queue.name}</h1>
{!!queue.description && <p className={s.description}>{queue.description}</p>}
</div>
);
29 changes: 15 additions & 14 deletions packages/ui/src/components/RedisStats/RedisStats.module.css
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
.stats {
padding: 0 1rem 0.5rem;
display: flex;
color: #718096;
font-weight: 200;
align-items: center;
padding-bottom: 0.5rem;
display: flex;
color: #718096;
font-weight: 200;
align-items: center;
flex-shrink: 0;
}

.stats > div {
text-align: center;
margin: 0 0.5rem;
position: relative;
text-align: center;
margin: 0 0.5rem;
position: relative;
}

.stats span {
display: block;
font-size: 1.44rem;
margin-top: 0.125em;
display: block;
font-size: 1.44rem;
margin-top: 0.125em;
}

.stats small {
position: absolute;
white-space: nowrap;
left: 0;
position: absolute;
white-space: nowrap;
left: 0;
}
15 changes: 13 additions & 2 deletions packages/ui/src/hooks/useActiveQueue.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
import { AppQueue } from '@bull-board/api/dist/typings/app';
import { matchPath } from 'react-router';
import { useLocation } from 'react-router-dom';
import { Store } from './useStore';

export function useActiveQueue(): string | undefined {
export function useActiveQueue(data: Store['state']['data']): AppQueue | null {
const { pathname } = useLocation();

if (!data) {
return null;
}

const match = matchPath<{ name: string }>(pathname, {
path: '/queue/:name',
exact: true,
strict: true,
});

return decodeURIComponent(match?.params.name || '') || undefined;
const activeQueueName = decodeURIComponent(match?.params.name || '');

const activeQueue = data?.queues?.find((q) => q.name === activeQueueName);

return activeQueue || null;
}
7 changes: 4 additions & 3 deletions packages/ui/src/hooks/useStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,22 @@ export interface Store {

export const useStore = (api: Api): Store => {
const query = useQuery();
const activeQueue = useActiveQueue();

const [state, setState] = useState<State>({
data: null,
loading: true,
});

const activeQueue = useActiveQueue(state.data);
const { confirmProps, openConfirm } = useConfirm();

const selectedStatuses = useSelectedStatuses();

const update = () =>
api
.getQueues({
activeQueue,
status: activeQueue ? selectedStatuses[activeQueue] : undefined,
activeQueue: activeQueue?.name,
status: activeQueue ? selectedStatuses[activeQueue.name] : undefined,
page: query.get('page') || '1',
})
.then((data) => {
Expand Down

0 comments on commit 91f03fb

Please sign in to comment.