From d5e4763ca267714584112c20b168f41bf2a98e11 Mon Sep 17 00:00:00 2001 From: Divyansh Singh Date: Sat, 11 Jan 2025 23:04:18 +0530 Subject: [PATCH] feat: sort queues by status on the dashboard --- packages/api/typings/app.ts | 3 ++ packages/ui/src/components/Icons/Sort.tsx | 12 ++++++ .../QueueSortingDropdown.tsx | 40 +++++++++++++++++++ packages/ui/src/hooks/useQueues.ts | 32 ++++++++++++--- .../OverviewPage/OverviewPage.module.css | 12 +++++- .../src/pages/OverviewPage/OverviewPage.tsx | 21 ++++++++++ .../ui/src/static/locales/en-US/messages.json | 10 ++++- packages/ui/typings/app.d.ts | 2 + 8 files changed, 125 insertions(+), 7 deletions(-) create mode 100644 packages/ui/src/components/Icons/Sort.tsx create mode 100644 packages/ui/src/components/QueueSortingDropdown/QueueSortingDropdown.tsx diff --git a/packages/api/typings/app.ts b/packages/api/typings/app.ts index fcc3fc41a..535abf67b 100644 --- a/packages/api/typings/app.ts +++ b/packages/api/typings/app.ts @@ -132,6 +132,8 @@ export interface AppQueue { type: QueueType; } +export type QueueSortKey = 'alphabetical' | keyof AppQueue['counts']; + export type HTTPMethod = 'get' | 'post' | 'put' | 'patch'; export type HTTPStatus = 200 | 204 | 404 | 405 | 500; @@ -213,6 +215,7 @@ export type UIConfig = Partial<{ boardTitle: string; boardLogo: { path: string; width?: number | string; height?: number | string }; miscLinks: Array; + queueSortOptions: Array<{ key: string; label: string }>; favIcon: FavIcon; locale: { lng?: string }; dateFormats?: DateFormats; diff --git a/packages/ui/src/components/Icons/Sort.tsx b/packages/ui/src/components/Icons/Sort.tsx new file mode 100644 index 000000000..170d74ec1 --- /dev/null +++ b/packages/ui/src/components/Icons/Sort.tsx @@ -0,0 +1,12 @@ +import React from 'react'; + +export const SortIcon = () => ( + + + + + + +); \ No newline at end of file diff --git a/packages/ui/src/components/QueueSortingDropdown/QueueSortingDropdown.tsx b/packages/ui/src/components/QueueSortingDropdown/QueueSortingDropdown.tsx new file mode 100644 index 000000000..655180b90 --- /dev/null +++ b/packages/ui/src/components/QueueSortingDropdown/QueueSortingDropdown.tsx @@ -0,0 +1,40 @@ +import { QueueSortKey, UIConfig } from '@bull-board/api/typings/app'; +import { Item, Portal, Root, Trigger } from '@radix-ui/react-dropdown-menu'; +import React from 'react'; +import { DropdownContent } from '../DropdownContent/DropdownContent'; +import { SortIcon } from '../Icons/Sort'; +import { Button } from '../Button/Button'; + +type QueueSortingDropdownProps = { + sortOptions: UIConfig['queueSortOptions']; + className: string; + sortHandler: (sortKey: QueueSortKey) => void; +}; + +export const QueueSortingDropdown = ({ sortOptions = [], className, sortHandler }: QueueSortingDropdownProps) => { + const [selectedSort, setSelectedSort] = React.useState(sortOptions[0].key); + + return ( + + + + + + + + {sortOptions.map((option) => ( + { + setSelectedSort(option.key); + sortHandler(option.key as QueueSortKey); + }}> +

{option.label}

+
+ ))} +
+
+
+ ); +}; diff --git a/packages/ui/src/hooks/useQueues.ts b/packages/ui/src/hooks/useQueues.ts index 9f811c271..112f7e1aa 100644 --- a/packages/ui/src/hooks/useQueues.ts +++ b/packages/ui/src/hooks/useQueues.ts @@ -1,6 +1,6 @@ -import { JobCleanStatus, JobRetryStatus } from '@bull-board/api/typings/app'; +import { JobCleanStatus, JobRetryStatus, QueueSortKey } from '@bull-board/api/typings/app'; import { GetQueuesResponse } from '@bull-board/api/typings/responses'; -import { useCallback } from 'react'; +import { useCallback, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { create } from 'zustand'; import { QueueActions } from '../../typings/app'; @@ -26,6 +26,7 @@ const useQueuesStore = create((set) => ({ })); export function useQueues(): Omit & { actions: QueueActions } { + const [activeQueueSortKey, setActiveQueueSortKey] = useState('alphabetical'); const query = useQuery(); const { t } = useTranslation(); const api = useApi(); @@ -52,16 +53,23 @@ export function useQueues(): Omit & { actions: Queu jobsPerPage, }) .then((data) => { - setState(data.queues); + const sortedQueues = data.queues ? [...data.queues].sort((a, b) => { + if (activeQueueSortKey === 'alphabetical') { + return a.name.localeCompare(b.name); + } + + return b.counts[activeQueueSortKey] - a.counts[activeQueueSortKey]; + }) : []; + setState(sortedQueues); }) // eslint-disable-next-line no-console .catch((error) => console.error('Failed to poll', error)), - [activeQueueName, jobsPerPage, selectedStatuses] + [activeQueueName, jobsPerPage, selectedStatuses, activeQueueSortKey] ); const pollQueues = () => useInterval(updateQueues, pollingInterval > 0 ? pollingInterval * 1000 : null, [ - selectedStatuses, + selectedStatuses, activeQueueSortKey ]); const withConfirmAndUpdate = getConfirmFor(updateQueues, openConfirm); @@ -115,6 +123,19 @@ export function useQueues(): Omit & { actions: Queu jobOptions: Record ) => withConfirmAndUpdate(() => api.addJob(queueName, jobName, jobData, jobOptions), '', false); + const sortQueues = useCallback((sortKey: QueueSortKey) => { + setActiveQueueSortKey(sortKey); + const sortedQueues = queues ? [...queues].sort((a, b) => { + if (sortKey === 'alphabetical') { + return a.name.localeCompare(b.name); + } + + return b.counts[sortKey] - a.counts[sortKey]; + }) : []; + + setState(sortedQueues); + }, [queues]); // Added dependency array + return { queues, loading, @@ -128,6 +149,7 @@ export function useQueues(): Omit & { actions: Queu resumeQueue, emptyQueue, addJob, + sortQueues, }, }; } diff --git a/packages/ui/src/pages/OverviewPage/OverviewPage.module.css b/packages/ui/src/pages/OverviewPage/OverviewPage.module.css index bac24fe63..000796f2c 100644 --- a/packages/ui/src/pages/OverviewPage/OverviewPage.module.css +++ b/packages/ui/src/pages/OverviewPage/OverviewPage.module.css @@ -3,7 +3,7 @@ grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); grid-gap: 2rem; list-style: none; - margin: 2rem 0 0; + margin: 1.5rem 0 0; padding: 0; } @@ -21,3 +21,13 @@ } } } + +.dropdown { + margin: 1.5rem 100% 0; + transform: translateX(-100%); + position: relative; + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.5rem; +} \ No newline at end of file diff --git a/packages/ui/src/pages/OverviewPage/OverviewPage.tsx b/packages/ui/src/pages/OverviewPage/OverviewPage.tsx index 56068b045..b55b21b49 100644 --- a/packages/ui/src/pages/OverviewPage/OverviewPage.tsx +++ b/packages/ui/src/pages/OverviewPage/OverviewPage.tsx @@ -8,6 +8,8 @@ import { useQuery } from '../../hooks/useQuery'; import { useQueues } from '../../hooks/useQueues'; import { links } from '../../utils/links'; import s from './OverviewPage.module.css'; +import { QueueSortingDropdown } from '../../components/QueueSortingDropdown/QueueSortingDropdown'; +import { QueueSortKey } from '@bull-board/api/typings/app'; export const OverviewPage = () => { const { t } = useTranslation(); @@ -18,10 +20,29 @@ export const OverviewPage = () => { const selectedStatus = query.get('status') as Status; const queuesToView = queues?.filter((queue) => !selectedStatus || queue.counts[selectedStatus] > 0) || []; + + const sortHandler = (sortKey: QueueSortKey) => { + actions.sortQueues(sortKey); + } + return (
+ + {queuesToView.length > 0 && (
    {queuesToView.map((queue) => ( diff --git a/packages/ui/src/static/locales/en-US/messages.json b/packages/ui/src/static/locales/en-US/messages.json index d3caa4a71..ff47dbedc 100644 --- a/packages/ui/src/static/locales/en-US/messages.json +++ b/packages/ui/src/static/locales/en-US/messages.json @@ -8,7 +8,15 @@ "DASHBOARD": { "JOBS_COUNT_one": "{{count}} Job", "JOBS_COUNT": "{{count}} Jobs", - "NO_FILTERED_MESSAGE": "There are no queues that have a job with \"{{status}}\" status.\nClick here to clear the filter." + "NO_FILTERED_MESSAGE": "There are no queues that have a job with \"{{status}}\" status.\nClick here to clear the filter.", + "SORTING": { + "FAILED": "Failed Jobs", + "DELAYED": "Delayed Jobs", + "WAITING": "Waiting Jobs", + "ACTIVE": "Active Jobs", + "COMPLETED": "Completed Jobs", + "ALPHABETICAL": "Alphabetical (A-Z)" + } }, "JOB": { "DELAY_CHANGED": "*Delay changed; the new run time is currently unknown", diff --git a/packages/ui/typings/app.d.ts b/packages/ui/typings/app.d.ts index 3acd255c4..20aa846b2 100644 --- a/packages/ui/typings/app.d.ts +++ b/packages/ui/typings/app.d.ts @@ -3,6 +3,7 @@ import { AppQueue, JobCleanStatus, JobRetryStatus, + QueueSortKey, Status, } from '@bull-board/api/typings/app'; @@ -25,6 +26,7 @@ export interface QueueActions { jobData: any, jobOptions: any ) => () => Promise; + sortQueues: (sortKey: QueueSortKey) => void; } export interface JobActions {