From 0497d87d87e6fb5ce1a40e407ec2b64e18c5808f Mon Sep 17 00:00:00 2001 From: ahmad anwar <7552088+AhmedAnwarHafez@users.noreply.github.com> Date: Sun, 5 Jan 2025 08:47:10 +1300 Subject: [PATCH 01/15] feat: a new interface that is used for getFlow --- packages/api/typings/app.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/api/typings/app.ts b/packages/api/typings/app.ts index fcc3fc41..7c34eba0 100644 --- a/packages/api/typings/app.ts +++ b/packages/api/typings/app.ts @@ -74,6 +74,12 @@ export interface QueueJobJson { parentKey?: string; } +export interface JobTreeNode { + id: string; + name: string; + children?: JobTreeNode[]; +} + export interface QueueJobOptions { delay?: number; attempts?: number; From ebaf0f18f989b0261d7d9917d8e76a8522b8a4dd Mon Sep 17 00:00:00 2001 From: ahmad anwar <7552088+AhmedAnwarHafez@users.noreply.github.com> Date: Sun, 5 Jan 2025 08:47:40 +1300 Subject: [PATCH 02/15] feat: add getJobTree method to queue adapters - Implemented `getJobTree` method in `BaseAdapter` for retrieving job tree structure. - Added empty implementation in `BullAdapter` to return an empty array. - Developed full functionality in `BullMQAdapter` to fetch and map job tree nodes from the queue. This enhances the API for better job management and visualization. --- packages/api/src/queueAdapters/base.ts | 3 +++ packages/api/src/queueAdapters/bull.ts | 6 +++++ packages/api/src/queueAdapters/bullMQ.ts | 31 +++++++++++++++++++++++- 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/packages/api/src/queueAdapters/base.ts b/packages/api/src/queueAdapters/base.ts index 79e26b2a..54253ed7 100644 --- a/packages/api/src/queueAdapters/base.ts +++ b/packages/api/src/queueAdapters/base.ts @@ -3,6 +3,7 @@ import { JobCleanStatus, JobCounts, JobStatus, + JobTreeNode, QueueAdapterOptions, QueueJob, QueueJobOptions, @@ -56,6 +57,8 @@ export abstract class BaseAdapter { public abstract getJob(id: string): Promise; + public abstract getJobTree(id: string): Promise; + public abstract getJobCounts(): Promise; public abstract getJobs( diff --git a/packages/api/src/queueAdapters/bull.ts b/packages/api/src/queueAdapters/bull.ts index 71cfdee5..eab1c343 100644 --- a/packages/api/src/queueAdapters/bull.ts +++ b/packages/api/src/queueAdapters/bull.ts @@ -4,6 +4,7 @@ import { JobCleanStatus, JobCounts, JobStatus, + JobTreeNode, QueueAdapterOptions, QueueJobOptions, Status, @@ -40,6 +41,11 @@ export class BullAdapter extends BaseAdapter { return this.queue.getJob(id).then((job) => job && this.alignJobData(job)); } + public getJobTree(): Promise { + // Bull doesn't support Flow, so an empty array is returned + return Promise.resolve([]); + } + public getJobs(jobStatuses: JobStatus<'bull'>[], start?: number, end?: number): Promise { return this.queue.getJobs(jobStatuses, start, end).then((jobs) => jobs.map(this.alignJobData)); } diff --git a/packages/api/src/queueAdapters/bullMQ.ts b/packages/api/src/queueAdapters/bullMQ.ts index da218ac7..a1281b6c 100644 --- a/packages/api/src/queueAdapters/bullMQ.ts +++ b/packages/api/src/queueAdapters/bullMQ.ts @@ -1,8 +1,9 @@ -import { Job, Queue } from 'bullmq'; +import { FlowProducer, Job, JobNode, Queue } from 'bullmq'; import { JobCleanStatus, JobCounts, JobStatus, + JobTreeNode, QueueAdapterOptions, QueueJobOptions, Status, @@ -42,6 +43,34 @@ export class BullMQAdapter extends BaseAdapter { return this.queue.getJob(id); } + public async getJobTree(id: string): Promise { + const client = await this.queue.client; + const flow = new FlowProducer({ connection: client }); + const tree = await flow.getFlow({ + queueName: this.getName(), + id, + }); + + if (!tree || !tree.children) { + return []; + } + + const mapTree = (node: JobNode): JobTreeNode => { + const newTreeNode: JobTreeNode = { + name: node.job.name, + id: node.job.id ?? '', + }; + + if (node.children && node.children.length > 0) { + newTreeNode.jobTree = node.children.map(mapTree); + } + + return newTreeNode; + }; + + return tree.children?.map(mapTree); + } + public getJobs(jobStatuses: JobStatus[], start?: number, end?: number): Promise { return this.queue.getJobs(jobStatuses, start, end); } From 0acaec378becf3df80099d13b0f3bad692756325 Mon Sep 17 00:00:00 2001 From: ahmad anwar <7552088+AhmedAnwarHafez@users.noreply.github.com> Date: Sun, 5 Jan 2025 08:48:15 +1300 Subject: [PATCH 03/15] refactor: update JobTreeNode interface to use jobTree instead of children Modified the JobTreeNode interface in app.ts to replace the optional children property with jobTree, enhancing the structure for representing job hierarchies in the API. --- packages/api/typings/app.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api/typings/app.ts b/packages/api/typings/app.ts index 7c34eba0..0085f03f 100644 --- a/packages/api/typings/app.ts +++ b/packages/api/typings/app.ts @@ -77,7 +77,7 @@ export interface QueueJobJson { export interface JobTreeNode { id: string; name: string; - children?: JobTreeNode[]; + jobTree?: JobTreeNode[]; } export interface QueueJobOptions { From c7cf45aa42450bccb889dc946890968b7641a891 Mon Sep 17 00:00:00 2001 From: ahmad anwar <7552088+AhmedAnwarHafez@users.noreply.github.com> Date: Sun, 5 Jan 2025 08:49:49 +1300 Subject: [PATCH 04/15] feat: enhance getJobState to include jobTree in response Updated the getJobState function to extract jobId from the request parameters and retrieve the job tree structure using the new getJobTree method. The response now includes the job tree, improving job management and visualization capabilities in the API. --- packages/api/src/handlers/job.ts | 5 ++++- packages/api/typings/responses.ts | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/api/src/handlers/job.ts b/packages/api/src/handlers/job.ts index 4e0e7723..70175a2f 100644 --- a/packages/api/src/handlers/job.ts +++ b/packages/api/src/handlers/job.ts @@ -5,17 +5,20 @@ import { BaseAdapter } from '../queueAdapters/base'; import { formatJob } from './queues'; async function getJobState( - _req: BullBoardRequest, + req: BullBoardRequest, job: QueueJob, queue: BaseAdapter ): Promise { + const { jobId } = req.params; const status = await job.getState(); + const jobTree = await queue.getJobTree(jobId); return { status: 200, body: { job: formatJob(job, queue), status, + jobTree: jobTree ?? [], }, }; } diff --git a/packages/api/typings/responses.ts b/packages/api/typings/responses.ts index e9676c75..7feb1853 100644 --- a/packages/api/typings/responses.ts +++ b/packages/api/typings/responses.ts @@ -1,4 +1,4 @@ -import { AppJob, AppQueue, Status } from './app'; +import { AppJob, AppQueue, JobTreeNode, Status } from './app'; export interface GetQueuesResponse { queues: AppQueue[]; @@ -7,4 +7,5 @@ export interface GetQueuesResponse { export interface GetJobResponse { job: AppJob; status: Status; + jobTree: JobTreeNode[]; } From e8e98d6612d358769852b4c3cd74e2e7427c324d Mon Sep 17 00:00:00 2001 From: ahmad anwar <7552088+AhmedAnwarHafez@users.noreply.github.com> Date: Sun, 5 Jan 2025 15:28:59 +1300 Subject: [PATCH 05/15] feat: enhance BullMQAdapter to support async job tree mapping Updated the BullMQAdapter's mapTree function to handle asynchronous job state retrieval and ensure proper mapping of job trees. The JobTreeNode interface has been modified to include queueName and status properties, improving the representation of job hierarchies in the API. --- packages/api/src/queueAdapters/bullMQ.ts | 8 +++++--- packages/api/typings/app.ts | 2 ++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/api/src/queueAdapters/bullMQ.ts b/packages/api/src/queueAdapters/bullMQ.ts index a1281b6c..daefd86e 100644 --- a/packages/api/src/queueAdapters/bullMQ.ts +++ b/packages/api/src/queueAdapters/bullMQ.ts @@ -55,20 +55,22 @@ export class BullMQAdapter extends BaseAdapter { return []; } - const mapTree = (node: JobNode): JobTreeNode => { + const mapTree = async (node: JobNode): Promise => { const newTreeNode: JobTreeNode = { name: node.job.name, + queueName: node.job.queueName, id: node.job.id ?? '', + status: await this.queue.getJobState(node.job.id ?? ''), }; if (node.children && node.children.length > 0) { - newTreeNode.jobTree = node.children.map(mapTree); + newTreeNode.jobTree = await Promise.all(node.children.map(mapTree)); } return newTreeNode; }; - return tree.children?.map(mapTree); + return Promise.all(tree.children?.map(mapTree)); } public getJobs(jobStatuses: JobStatus[], start?: number, end?: number): Promise { diff --git a/packages/api/typings/app.ts b/packages/api/typings/app.ts index 0085f03f..ed17933f 100644 --- a/packages/api/typings/app.ts +++ b/packages/api/typings/app.ts @@ -77,6 +77,8 @@ export interface QueueJobJson { export interface JobTreeNode { id: string; name: string; + status: Status | 'unknown'; + queueName: string; jobTree?: JobTreeNode[]; } From 82ce0adddd3e7765527b8da517aac059ce6300d4 Mon Sep 17 00:00:00 2001 From: ahmad anwar <7552088+AhmedAnwarHafez@users.noreply.github.com> Date: Sun, 5 Jan 2025 15:29:21 +1300 Subject: [PATCH 06/15] feat: implement JobTree component and enhance job management - Added a new JobTree component to visualize job hierarchies. - Introduced JobTree.module.css for styling the JobTree component. - Updated useJob hook to include jobTree state management. - Modified JobPage to render JobTree with job data. - Enhanced localization files to include "Children" translations for job tree representation. - Refactored JobCard and related components to improve layout and structure. These changes improve the user interface for job management and provide better visualization of job relationships. --- .../ui/src/components/JobCard/JobCard.tsx | 2 - .../src/components/JobTree/JobTree.module.css | 48 +++++++++++++++++++ .../ui/src/components/JobTree/JobTree.tsx | 27 +++++++++++ packages/ui/src/hooks/useJob.ts | 16 +++++-- packages/ui/src/pages/JobPage/JobPage.tsx | 4 +- .../ui/src/static/locales/en-US/messages.json | 3 +- .../ui/src/static/locales/fr-FR/messages.json | 3 +- .../ui/src/static/locales/pt-BR/messages.json | 3 +- .../ui/src/static/locales/zh-CN/messages.json | 3 +- 9 files changed, 97 insertions(+), 12 deletions(-) create mode 100644 packages/ui/src/components/JobTree/JobTree.module.css create mode 100644 packages/ui/src/components/JobTree/JobTree.tsx diff --git a/packages/ui/src/components/JobCard/JobCard.tsx b/packages/ui/src/components/JobCard/JobCard.tsx index f21aa04b..7944c97d 100644 --- a/packages/ui/src/components/JobCard/JobCard.tsx +++ b/packages/ui/src/components/JobCard/JobCard.tsx @@ -73,7 +73,6 @@ export const JobCard = ({ )} -
@@ -102,7 +101,6 @@ export const JobCard = ({
- div { + background-color: var(--card-bg); + box-shadow: var(--card-shadow); + border-radius: 0.25rem; + padding: 1em; + position: relative; + } + ul > li:last-child { + margin-top: 1em; + } +} diff --git a/packages/ui/src/components/JobTree/JobTree.tsx b/packages/ui/src/components/JobTree/JobTree.tsx new file mode 100644 index 00000000..9afabd5a --- /dev/null +++ b/packages/ui/src/components/JobTree/JobTree.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { JobTreeNode } from '@bull-board/api/typings/app'; +import s from './JobTree.module.css'; +import { Link } from 'react-router-dom'; + +export function JobTree({ jobTree }: { jobTree: JobTreeNode[] }) { + return ( +
    + {jobTree.map((job) => ( +
  • +
    +
    + + {job.name} + + {job.status.toUpperCase()} +
    + + {job.queueName} + +
    + {job.jobTree && job.jobTree.length > 0 && } +
  • + ))} +
+ ); +} diff --git a/packages/ui/src/hooks/useJob.ts b/packages/ui/src/hooks/useJob.ts index f2617691..0085ec7c 100644 --- a/packages/ui/src/hooks/useJob.ts +++ b/packages/ui/src/hooks/useJob.ts @@ -1,4 +1,4 @@ -import { AppJob, JobRetryStatus } from '@bull-board/api/typings/app'; +import { AppJob, JobRetryStatus, JobTreeNode } from '@bull-board/api/typings/app'; import { useTranslation } from 'react-i18next'; import { create } from 'zustand'; import { JobActions, Status } from '../../typings/app'; @@ -15,14 +15,17 @@ export type JobState = { job: AppJob | null; status: Status; loading: boolean; - updateJob(job: AppJob, status: Status): void; + jobTree: JobTreeNode[]; + updateJob(job: AppJob, status: Status, tree: JobTreeNode[]): void; }; const useQueuesStore = create((set) => ({ job: null, status: 'latest', loading: true, - updateJob: (job: AppJob, status: Status) => set(() => ({ job, status, loading: false })), + jobTree: [], + updateJob: (job: AppJob, status: Status, jobTree: JobTreeNode[]) => + set(() => ({ job, status, loading: false, jobTree })), })); export function useJob(): Omit & { actions: JobActions } { @@ -42,11 +45,13 @@ export function useJob(): Omit & { actions: JobActions } }) ); - const { job, status, loading, updateJob: setState } = useQueuesStore((state) => state); + const { job, status, jobTree, loading, updateJob: setState } = useQueuesStore((state) => state); const { openConfirm } = useConfirm(); const getJob = () => - api.getJob(activeQueueName, activeJobId).then(({ job, status }) => setState(job, status)); + api + .getJob(activeQueueName, activeJobId) + .then(({ job, status, jobTree }) => setState(job, status, jobTree)); const pollJob = () => useInterval(getJob, pollingInterval > 0 ? pollingInterval * 1000 : null, [activeQueueName]); @@ -82,6 +87,7 @@ export function useJob(): Omit & { actions: JobActions } return { job, + jobTree, status, loading, actions: { diff --git a/packages/ui/src/pages/JobPage/JobPage.tsx b/packages/ui/src/pages/JobPage/JobPage.tsx index fada608b..34e528d3 100644 --- a/packages/ui/src/pages/JobPage/JobPage.tsx +++ b/packages/ui/src/pages/JobPage/JobPage.tsx @@ -12,6 +12,7 @@ import { useModal } from '../../hooks/useModal'; import { useSelectedStatuses } from '../../hooks/useSelectedStatuses'; import { links } from '../../utils/links'; import buttonS from '../../components/Button/Button.module.css'; +import { JobTree } from '../../components/JobTree/JobTree'; const AddJobModalLazy = React.lazy(() => import('../../components/AddJobModal/AddJobModal').then(({ AddJobModal }) => ({ @@ -32,7 +33,7 @@ export const JobPage = () => { const history = useHistory(); const queue = useActiveQueue(); - const { job, status, actions } = useJob(); + const { job, status, actions, jobTree } = useJob(); const selectedStatuses = useSelectedStatuses(); const modal = useModal<'updateJobData' | 'addJob'>(); @@ -81,6 +82,7 @@ export const JobPage = () => { readOnlyMode={queue.readOnlyMode} allowRetries={(job.isFailed || queue.allowCompletedRetries) && queue.allowRetries} /> + {modal.isMounted('addJob') && ( Date: Sun, 5 Jan 2025 17:22:41 +1300 Subject: [PATCH 07/15] feat: enhance JobTree component styling and structure --- .../src/components/JobTree/JobTree.module.css | 40 ++++++++++++++----- .../ui/src/components/JobTree/JobTree.tsx | 4 +- .../ui/src/pages/JobPage/JobPage.module.css | 3 ++ packages/ui/src/pages/JobPage/JobPage.tsx | 7 +++- 4 files changed, 41 insertions(+), 13 deletions(-) create mode 100644 packages/ui/src/pages/JobPage/JobPage.module.css diff --git a/packages/ui/src/components/JobTree/JobTree.module.css b/packages/ui/src/components/JobTree/JobTree.module.css index d44df88c..90db2ece 100644 --- a/packages/ui/src/components/JobTree/JobTree.module.css +++ b/packages/ui/src/components/JobTree/JobTree.module.css @@ -2,47 +2,67 @@ font-weight: 700; text-decoration: none; color: var(--text-color); + &:hover { + text-decoration: underline; + } } .nodeQueue { font-weight: 300; text-decoration: none; color: var(--text-color); + &:hover { + text-decoration: underline; + } } .nodeStatus { font-weight: 300; font-size: 0.7rem; + font-family: 'Courier New', Courier, monospace; color: var(--text-muted); } .nodeSubHeader { display: flex; - justify-content: space-between; + flex-direction: row; + gap: 1em; } +/* + * Credit: + * https://stackoverflow.com/a/14424029/1017055 + */ .node { + margin-left: -2em; ul { - padding: 1em; + padding: 0; margin: 0; list-style-type: none; position: relative; } li { - margin-top: 1em; - margin-left: 1em; list-style-type: none; border-left: 2px solid var(--separator-color); - width: 100%; + margin-left: 2em; } li > div { - background-color: var(--card-bg); - box-shadow: var(--card-shadow); - border-radius: 0.25rem; - padding: 1em; + padding-left: 1em; position: relative; + padding-top: 1em; + } + li div::before { + content: ''; + position: absolute; + top: 0; + left: -2px; + bottom: 55%; + width: 0.75em; + border: 2px solid var(--separator-color); + border-top: 0 none transparent; + border-right: 0 none transparent; } ul > li:last-child { - margin-top: 1em; + border-left: 2px solid transparent; } } diff --git a/packages/ui/src/components/JobTree/JobTree.tsx b/packages/ui/src/components/JobTree/JobTree.tsx index 9afabd5a..a1eaa22a 100644 --- a/packages/ui/src/components/JobTree/JobTree.tsx +++ b/packages/ui/src/components/JobTree/JobTree.tsx @@ -8,12 +8,12 @@ export function JobTree({ jobTree }: { jobTree: JobTreeNode[] }) {
    {jobTree.map((job) => (
  • -
    +
    + {job.status.toUpperCase()} {job.name} - {job.status.toUpperCase()}
    {job.queueName} diff --git a/packages/ui/src/pages/JobPage/JobPage.module.css b/packages/ui/src/pages/JobPage/JobPage.module.css new file mode 100644 index 00000000..d410ed98 --- /dev/null +++ b/packages/ui/src/pages/JobPage/JobPage.module.css @@ -0,0 +1,3 @@ +.containerTitle { + color: var(--text-muted); +} diff --git a/packages/ui/src/pages/JobPage/JobPage.tsx b/packages/ui/src/pages/JobPage/JobPage.tsx index 34e528d3..6ce2a6d8 100644 --- a/packages/ui/src/pages/JobPage/JobPage.tsx +++ b/packages/ui/src/pages/JobPage/JobPage.tsx @@ -13,6 +13,8 @@ import { useSelectedStatuses } from '../../hooks/useSelectedStatuses'; import { links } from '../../utils/links'; import buttonS from '../../components/Button/Button.module.css'; import { JobTree } from '../../components/JobTree/JobTree'; +import cardS from '../../components/JobCard/JobCard.module.css'; +import s from '../../components/JobCard/JobCard.module.css'; const AddJobModalLazy = React.lazy(() => import('../../components/AddJobModal/AddJobModal').then(({ AddJobModal }) => ({ @@ -82,7 +84,10 @@ export const JobPage = () => { readOnlyMode={queue.readOnlyMode} allowRetries={(job.isFailed || queue.allowCompletedRetries) && queue.allowRetries} /> - +
    + Children jobs + +
    {modal.isMounted('addJob') && ( Date: Sun, 5 Jan 2025 18:29:17 +1300 Subject: [PATCH 08/15] fix: conditionally render JobTree component based on jobTree length Updated JobPage to only display the JobTree component when there are child jobs available. This change improves the user interface by preventing unnecessary rendering of the JobTree when it is empty, enhancing overall performance and user experience. --- packages/ui/src/pages/JobPage/JobPage.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/pages/JobPage/JobPage.tsx b/packages/ui/src/pages/JobPage/JobPage.tsx index 6ce2a6d8..95c73227 100644 --- a/packages/ui/src/pages/JobPage/JobPage.tsx +++ b/packages/ui/src/pages/JobPage/JobPage.tsx @@ -84,10 +84,12 @@ export const JobPage = () => { readOnlyMode={queue.readOnlyMode} allowRetries={(job.isFailed || queue.allowCompletedRetries) && queue.allowRetries} /> -
    - Children jobs - -
    + {jobTree.length > 0 && ( +
    + Children jobs + +
    + )} {modal.isMounted('addJob') && ( Date: Sun, 5 Jan 2025 18:46:07 +1300 Subject: [PATCH 09/15] feat: included parent data to allow for easier navigation in frontend --- packages/api/src/handlers/queues.ts | 1 + packages/api/typings/app.ts | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/packages/api/src/handlers/queues.ts b/packages/api/src/handlers/queues.ts index 4de6b99f..568cbf46 100644 --- a/packages/api/src/handlers/queues.ts +++ b/packages/api/src/handlers/queues.ts @@ -32,6 +32,7 @@ export const formatJob = (job: QueueJob, queue: BaseAdapter): AppJob => { name: queue.format('name', jobProps, jobProps.name || ''), returnValue: queue.format('returnValue', jobProps.returnvalue), isFailed: !!jobProps.failedReason || (Array.isArray(stacktrace) && stacktrace.length > 0), + parent: jobProps.parent, }; }; diff --git a/packages/api/typings/app.ts b/packages/api/typings/app.ts index ed17933f..3c730d78 100644 --- a/packages/api/typings/app.ts +++ b/packages/api/typings/app.ts @@ -72,6 +72,10 @@ export interface QueueJobJson { returnvalue: any; opts: any; parentKey?: string; + parent?: { + id: string; + queueKey: string; + }; } export interface JobTreeNode { @@ -121,6 +125,7 @@ export interface AppJob { data: QueueJobJson['data']; returnValue: QueueJobJson['returnvalue']; isFailed: boolean; + parent: QueueJobJson['parent']; } export type QueueType = 'bull' | 'bullmq'; From acc93c4962057a3c93fad030195ff3f1217bc05b Mon Sep 17 00:00:00 2001 From: ahmad anwar <7552088+AhmedAnwarHafez@users.noreply.github.com> Date: Mon, 6 Jan 2025 10:58:44 +1300 Subject: [PATCH 10/15] ui: improved styling when viewing flow jobs --- .../src/components/JobTree/JobTree.module.css | 87 ++++++++++++------- .../ui/src/components/JobTree/JobTree.tsx | 46 +++++++--- .../ui/src/pages/JobPage/JobPage.module.css | 3 - packages/ui/src/pages/JobPage/JobPage.tsx | 10 +-- 4 files changed, 94 insertions(+), 52 deletions(-) delete mode 100644 packages/ui/src/pages/JobPage/JobPage.module.css diff --git a/packages/ui/src/components/JobTree/JobTree.module.css b/packages/ui/src/components/JobTree/JobTree.module.css index 90db2ece..e938c892 100644 --- a/packages/ui/src/components/JobTree/JobTree.module.css +++ b/packages/ui/src/components/JobTree/JobTree.module.css @@ -29,40 +29,63 @@ gap: 1em; } +.parentNodeContainer { + padding: 0; +} + +.parentJob { + font-size: 1.25em; +} + /* * Credit: * https://stackoverflow.com/a/14424029/1017055 */ -.node { - margin-left: -2em; - ul { - padding: 0; - margin: 0; - list-style-type: none; - position: relative; - } - li { - list-style-type: none; - border-left: 2px solid var(--separator-color); - margin-left: 2em; - } - li > div { - padding-left: 1em; - position: relative; - padding-top: 1em; - } - li div::before { - content: ''; - position: absolute; - top: 0; - left: -2px; - bottom: 55%; - width: 0.75em; - border: 2px solid var(--separator-color); - border-top: 0 none transparent; - border-right: 0 none transparent; - } - ul > li:last-child { - border-left: 2px solid transparent; - } +ul { + padding: 0; + margin: 0; + list-style-type: none; + position: relative; +} +li { + list-style-type: none; + border-left: 2px solid var(--separator-color); + margin-left: 2em; +} + +li > div { + padding-left: 1em; + position: relative; + padding-top: 1em; +} +li div::before { + content: ''; + position: absolute; + top: 0; + left: -2px; + bottom: 55%; + width: 0.75em; + border: 2px solid var(--separator-color); + border-top: 0 none transparent; + border-right: 0 none transparent; +} + +ul > li:last-child { + border-left: 2px solid transparent; +} + +.parentNode > div::before { + content: ''; + position: absolute; + top: 0; + left: -2px; + bottom: 55%; + width: 5em; + border: 0 none transparent; + border-top: 0 none transparent; + border-right: 0 none transparent; +} + +li.parentNode { + border: 0 none transparent; } diff --git a/packages/ui/src/components/JobTree/JobTree.tsx b/packages/ui/src/components/JobTree/JobTree.tsx index a1eaa22a..f4a4a708 100644 --- a/packages/ui/src/components/JobTree/JobTree.tsx +++ b/packages/ui/src/components/JobTree/JobTree.tsx @@ -1,25 +1,51 @@ import React from 'react'; -import { JobTreeNode } from '@bull-board/api/typings/app'; -import s from './JobTree.module.css'; import { Link } from 'react-router-dom'; +import { AppJob, JobTreeNode } from '@bull-board/api/typings/app'; +import s from './JobTree.module.css'; -export function JobTree({ jobTree }: { jobTree: JobTreeNode[] }) { +export function JobTree({ jobTree, job }: { job: AppJob; jobTree: JobTreeNode[] }) { + return ( +
      +
    • +
      + {job.parent ? ( + + [parent] + + ) : ( +

      {job.parent ? job.name : `${job.name} (root)`}

      + )} +
      +
    • + {jobTree.length > 0 && ( +
    • + +
    • + )} +
    + ); +} + +export function JobTreeNodes({ jobTree }: { jobTree: JobTreeNode[] }) { return (
      {jobTree.map((job) => (
    • -
      -
      - {job.status.toUpperCase()} +
      + {job.status.toUpperCase()} +
      {job.name} + + {job.queueName} +
      - - {job.queueName} -
      - {job.jobTree && job.jobTree.length > 0 && } + {job.jobTree && job.jobTree.length > 0 && }
    • ))}
    diff --git a/packages/ui/src/pages/JobPage/JobPage.module.css b/packages/ui/src/pages/JobPage/JobPage.module.css deleted file mode 100644 index d410ed98..00000000 --- a/packages/ui/src/pages/JobPage/JobPage.module.css +++ /dev/null @@ -1,3 +0,0 @@ -.containerTitle { - color: var(--text-muted); -} diff --git a/packages/ui/src/pages/JobPage/JobPage.tsx b/packages/ui/src/pages/JobPage/JobPage.tsx index 95c73227..cebfc93b 100644 --- a/packages/ui/src/pages/JobPage/JobPage.tsx +++ b/packages/ui/src/pages/JobPage/JobPage.tsx @@ -14,7 +14,6 @@ import { links } from '../../utils/links'; import buttonS from '../../components/Button/Button.module.css'; import { JobTree } from '../../components/JobTree/JobTree'; import cardS from '../../components/JobCard/JobCard.module.css'; -import s from '../../components/JobCard/JobCard.module.css'; const AddJobModalLazy = React.lazy(() => import('../../components/AddJobModal/AddJobModal').then(({ AddJobModal }) => ({ @@ -84,12 +83,9 @@ export const JobPage = () => { readOnlyMode={queue.readOnlyMode} allowRetries={(job.isFailed || queue.allowCompletedRetries) && queue.allowRetries} /> - {jobTree.length > 0 && ( -
    - Children jobs - -
    - )} +
    + +
    {modal.isMounted('addJob') && ( Date: Mon, 6 Jan 2025 11:10:53 +1300 Subject: [PATCH 11/15] refactor: used helper to construct url --- packages/ui/src/components/JobTree/JobTree.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/components/JobTree/JobTree.tsx b/packages/ui/src/components/JobTree/JobTree.tsx index f4a4a708..83fa427c 100644 --- a/packages/ui/src/components/JobTree/JobTree.tsx +++ b/packages/ui/src/components/JobTree/JobTree.tsx @@ -2,18 +2,18 @@ import React from 'react'; import { Link } from 'react-router-dom'; import { AppJob, JobTreeNode } from '@bull-board/api/typings/app'; import s from './JobTree.module.css'; +import { links } from '../../utils/links'; export function JobTree({ jobTree, job }: { job: AppJob; jobTree: JobTreeNode[] }) { + const queueName = job.parent?.queueKey.split(':')[1]; return (
    • - {job.parent ? ( - + {job.parent && queueName ? ( + [parent] + {queueName} {job.parent.id} ) : (

      {job.parent ? job.name : `${job.name} (root)`}

      From 36b1761b184eec7d76230525fd96bc0a81e59d9d Mon Sep 17 00:00:00 2001 From: ahmad anwar <7552088+AhmedAnwarHafez@users.noreply.github.com> Date: Mon, 6 Jan 2025 11:26:20 +1300 Subject: [PATCH 12/15] fix: removed queueName --- packages/ui/src/components/JobTree/JobTree.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/ui/src/components/JobTree/JobTree.tsx b/packages/ui/src/components/JobTree/JobTree.tsx index 83fa427c..de37d115 100644 --- a/packages/ui/src/components/JobTree/JobTree.tsx +++ b/packages/ui/src/components/JobTree/JobTree.tsx @@ -13,7 +13,6 @@ export function JobTree({ jobTree, job }: { job: AppJob; jobTree: JobTreeNode[] {job.parent && queueName ? ( [parent] - {queueName} {job.parent.id} ) : (

      {job.parent ? job.name : `${job.name} (root)`}

      From b8445237b174abe37e6956be0614e0b7f0094e82 Mon Sep 17 00:00:00 2001 From: ahmad anwar <7552088+AhmedAnwarHafez@users.noreply.github.com> Date: Mon, 6 Jan 2025 13:19:25 +1300 Subject: [PATCH 13/15] fix: added jobTree to depenedencies array to allow for page reload --- packages/ui/src/hooks/useJob.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/hooks/useJob.ts b/packages/ui/src/hooks/useJob.ts index 0085ec7c..f64efc7f 100644 --- a/packages/ui/src/hooks/useJob.ts +++ b/packages/ui/src/hooks/useJob.ts @@ -54,7 +54,10 @@ export function useJob(): Omit & { actions: JobActions } .then(({ job, status, jobTree }) => setState(job, status, jobTree)); const pollJob = () => - useInterval(getJob, pollingInterval > 0 ? pollingInterval * 1000 : null, [activeQueueName]); + useInterval(getJob, pollingInterval > 0 ? pollingInterval * 1000 : null, [ + activeQueueName, + jobTree, + ]); const withConfirmAndUpdate = getConfirmFor(activeJobId ? getJob : updateQueues, openConfirm); From 98d3b02a476e97827baf0767f805849172e052c2 Mon Sep 17 00:00:00 2001 From: ahmad anwar <7552088+AhmedAnwarHafez@users.noreply.github.com> Date: Tue, 7 Jan 2025 06:32:28 +1300 Subject: [PATCH 14/15] ui: improved styling --- .../src/components/JobTree/JobTree.module.css | 38 +++++++++++++------ .../ui/src/components/JobTree/JobTree.tsx | 36 +++++++++--------- 2 files changed, 45 insertions(+), 29 deletions(-) diff --git a/packages/ui/src/components/JobTree/JobTree.module.css b/packages/ui/src/components/JobTree/JobTree.module.css index e938c892..d49b41d2 100644 --- a/packages/ui/src/components/JobTree/JobTree.module.css +++ b/packages/ui/src/components/JobTree/JobTree.module.css @@ -29,36 +29,37 @@ gap: 1em; } -.parentNodeContainer { - padding: 0; -} - .parentJob { font-size: 1.25em; } +.parentNodeContainer { + padding: 0; +} + /* * Credit: * https://stackoverflow.com/a/14424029/1017055 */ -ul { +.parentNodeContainer ul { padding: 0; margin: 0; list-style-type: none; position: relative; } -li { +.parentNodeContainer li { list-style-type: none; border-left: 2px solid var(--separator-color); margin-left: 2em; } -li > div { +.parentNodeContainer li > div { padding-left: 1em; position: relative; - padding-top: 1em; + padding-top: 0.5em; } -li div::before { + +.parentNodeContainer li div::before { content: ''; position: absolute; top: 0; @@ -70,11 +71,11 @@ li div::before { border-right: 0 none transparent; } -ul > li:last-child { +.parentNodeContainer ul > li:last-child { border-left: 2px solid transparent; } -.parentNode > div::before { +.parentNodeContainer > div::before { content: ''; position: absolute; top: 0; @@ -86,6 +87,19 @@ ul > li:last-child { border-right: 0 none transparent; } -li.parentNode { +.parentNodeContainer li.parentNodeContainer { border: 0 none transparent; } + +.parentNode { + border-left: none !important; + padding-left: 0; +} + +.parentNode > div { + padding-left: 0; +} + +.parentNode > div::before { + display: none; +} diff --git a/packages/ui/src/components/JobTree/JobTree.tsx b/packages/ui/src/components/JobTree/JobTree.tsx index de37d115..f836f6c4 100644 --- a/packages/ui/src/components/JobTree/JobTree.tsx +++ b/packages/ui/src/components/JobTree/JobTree.tsx @@ -7,24 +7,26 @@ import { links } from '../../utils/links'; export function JobTree({ jobTree, job }: { job: AppJob; jobTree: JobTreeNode[] }) { const queueName = job.parent?.queueKey.split(':')[1]; return ( -
        -
      • -
        - {job.parent && queueName ? ( - - [parent] - - ) : ( -

        {job.parent ? job.name : `${job.name} (root)`}

        - )} -
        -
      • - {jobTree.length > 0 && ( -
      • - +
        +
          +
        • +
          + {job.parent && queueName ? ( + + [parent] + + ) : ( +

          {job.parent ? job.name : `${job.name} (root)`}

          + )} +
        • - )} -
        + {jobTree.length > 0 && ( +
      • + +
      • + )} +
      +
      ); } From 90f45673bc0b92f2f569c51e3d0eb4bc064761ca Mon Sep 17 00:00:00 2001 From: ahmad anwar <7552088+AhmedAnwarHafez@users.noreply.github.com> Date: Tue, 7 Jan 2025 07:34:35 +1300 Subject: [PATCH 15/15] fix: remove "CHILDREN" key from localization files --- packages/ui/src/static/locales/en-US/messages.json | 3 +-- packages/ui/src/static/locales/fr-FR/messages.json | 3 +-- packages/ui/src/static/locales/pt-BR/messages.json | 3 +-- packages/ui/src/static/locales/zh-CN/messages.json | 3 +-- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/ui/src/static/locales/en-US/messages.json b/packages/ui/src/static/locales/en-US/messages.json index 13b4e9fb..d3caa4a7 100644 --- a/packages/ui/src/static/locales/en-US/messages.json +++ b/packages/ui/src/static/locales/en-US/messages.json @@ -51,8 +51,7 @@ "DATA": "Data", "OPTIONS": "Options", "LOGS": "Logs", - "ERROR": "Error", - "CHILDREN": "Children" + "ERROR": "Error" } }, "QUEUE": { diff --git a/packages/ui/src/static/locales/fr-FR/messages.json b/packages/ui/src/static/locales/fr-FR/messages.json index 772a1cf0..4f14095f 100644 --- a/packages/ui/src/static/locales/fr-FR/messages.json +++ b/packages/ui/src/static/locales/fr-FR/messages.json @@ -53,8 +53,7 @@ "DATA": "Données", "OPTIONS": "Options", "LOGS": "Journaux", - "ERROR": "Erreur", - "CHILDREN": "Enfants" + "ERROR": "Erreur" } }, "QUEUE": { diff --git a/packages/ui/src/static/locales/pt-BR/messages.json b/packages/ui/src/static/locales/pt-BR/messages.json index 350da4ad..48d7070d 100644 --- a/packages/ui/src/static/locales/pt-BR/messages.json +++ b/packages/ui/src/static/locales/pt-BR/messages.json @@ -53,8 +53,7 @@ "DATA": "Dados", "OPTIONS": "Opções", "LOGS": "Logs", - "ERROR": "Erros", - "CHILDREN": "Filhos" + "ERROR": "Erros" } }, "QUEUE": { diff --git a/packages/ui/src/static/locales/zh-CN/messages.json b/packages/ui/src/static/locales/zh-CN/messages.json index 07ff2e82..9b1f5ebd 100644 --- a/packages/ui/src/static/locales/zh-CN/messages.json +++ b/packages/ui/src/static/locales/zh-CN/messages.json @@ -50,8 +50,7 @@ "DATA": "数据", "OPTIONS": "选项", "LOGS": "日志", - "ERROR": "错误", - "CHILDREN": "子作业" + "ERROR": "错误" } }, "QUEUE": {