diff --git a/dashboard/src/components/Accordion/Accordion.stories.tsx b/dashboard/src/components/Accordion/Accordion.stories.tsx deleted file mode 100644 index ef8ebec..0000000 --- a/dashboard/src/components/Accordion/Accordion.stories.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import Accordion, { IAccordionItems } from './Accordion'; - -const meta = { - title: 'Accordion', - component: Accordion, - parameters: { - layout: 'centered', - }, - tags: ['autodocs'], -} satisfies Meta<typeof Accordion>; - -export default meta; -type Story = StoryObj<typeof meta>; - -const itemsBuild: IAccordionItems[] = [ - { - trigger: { - config: 'config', - compiler: 'compiler', - date: 'dd/mm/yyyy', - buildErrors: 5, - buildTime: '500 seconds', - status: 'valid', - }, - content: <span>AAAAa</span>, - }, - { - trigger: { - config: 'config', - compiler: 'compiler', - date: 'dd/mm/yyyy', - buildErrors: 5, - buildTime: '500 seconds', - status: 'valid', - }, - content: <span>AAAAa</span>, - }, - { - trigger: { - config: 'config', - compiler: 'compiler', - date: 'dd/mm/yyyy', - buildErrors: 5, - buildTime: '500 seconds', - status: 'valid', - }, - content: <span>AAAAa</span>, - }, -]; - -const headersBuild = [ - 'config', - 'compiler', - 'date', - 'build errors', - 'build time', - 'status', -]; - -const itemsTest: IAccordionItems[] = [ - { - trigger: { - testPlans: 'aaaaaaa', - testErrors: 5, - testSuccessfull: 12, - status: 'invalid', - }, - content: <span>AAAAa</span>, - }, - { - trigger: { - testPlans: 'aaaaaaasdkjmla', - testErrors: 5, - testSuccessfull: 12, - status: 'valid', - }, - content: <span>BBB</span>, - }, -]; -const headersTest = ['test plan', 'test results', 'status']; - -export const Builds: Story = { - args: { - items: itemsBuild, - headers: headersBuild, - type: 'build', - }, -}; - -export const Tests: Story = { - args: { - items: itemsTest, - headers: headersTest, - type: 'test', - }, -}; diff --git a/dashboard/src/components/Accordion/Accordion.tsx b/dashboard/src/components/Accordion/Accordion.tsx index 1193964..6114ecc 100644 --- a/dashboard/src/components/Accordion/Accordion.tsx +++ b/dashboard/src/components/Accordion/Accordion.tsx @@ -1,7 +1,9 @@ -import { ReactNode, useMemo } from 'react'; +import { ReactElement, ReactNode, useMemo } from 'react'; import { MdCheck, MdClose } from 'react-icons/md'; +import { FormattedMessage } from 'react-intl'; + import { TableBody, TableCell, TableRow } from '../ui/table'; import BaseTable from '../Table/BaseTable'; import { @@ -13,7 +15,7 @@ import ColoredCircle from '../ColoredCircle/ColoredCircle'; import { ItemType } from '../ListingItem/ListingItem'; export interface IAccordion { - headers: string[]; + headers?: ReactElement[]; items: IAccordionItems[]; type: 'build' | 'test'; } @@ -44,11 +46,20 @@ export type AccordionItemTestsTrigger = { status?: 'valid' | 'invalid'; }; -const Accordion = ({ headers, items, type }: IAccordion): JSX.Element => { - const accordionTableHeader = useMemo( - () => headers.map(header => <span key={header}>{header}</span>), - [headers], - ); +const headersBuilds = [ + <FormattedMessage key="treeDetails.config" id="treeDetails.config" />, + <FormattedMessage key="treeDetails.compiler" id="treeDetails.compiler" />, + <FormattedMessage key="treeDetails.date" id="treeDetails.date" />, + <FormattedMessage + key="treeDetails.buildErrors" + id="treeDetails.buildErrors" + />, + <FormattedMessage key="treeDetails.buildTime" id="treeDetails.buildTime" />, + <FormattedMessage key="treeDetails.status" id="treeDetails.status" />, +]; + +const Accordion = ({ items, type }: IAccordion): JSX.Element => { + const accordionTableHeader = type === 'build' ? headersBuilds : []; return ( <BaseTable diff --git a/dashboard/src/components/Table/BuildsTable.tsx b/dashboard/src/components/Table/BuildsTable.tsx deleted file mode 100644 index bc51d98..0000000 --- a/dashboard/src/components/Table/BuildsTable.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import Accordion, { IAccordionItems } from '../Accordion/Accordion'; - -interface IBuildsTable { - buildsData?: IAccordionItems[]; -} - -const BuildsTable = ({ buildsData }: IBuildsTable): JSX.Element => { - return ( - <div> - {buildsData && ( - <Accordion - type="build" - headers={[ - 'Config', - 'Compiler', - 'Date', - 'Build Errors', - 'Build Time', - 'Status', - ]} - items={buildsData} - /> - )} - </div> - ); -}; - -export default BuildsTable; diff --git a/dashboard/src/components/Tabs/TreeDetails/TreeDetailsBuildTab.tsx b/dashboard/src/components/Tabs/TreeDetails/TreeDetailsBuildTab.tsx index 8e9c416..6459b0c 100644 --- a/dashboard/src/components/Tabs/TreeDetails/TreeDetailsBuildTab.tsx +++ b/dashboard/src/components/Tabs/TreeDetails/TreeDetailsBuildTab.tsx @@ -1,13 +1,16 @@ import { FormattedMessage } from 'react-intl'; -import { useMemo } from 'react'; +import { useCallback, useMemo, useState } from 'react'; import CardsGroup from '@/components/CardsGroup/CardsGroup'; -import { Colors } from '@/components/StatusChart/StatusCharts'; +import { Colors, IStatusChart } from '@/components/StatusChart/StatusCharts'; import { ITreeDetails } from '@/routes/TreeDetails/TreeDetails'; -import BuildsTable from '@/components/Table/BuildsTable'; import { TableInfo } from '@/components/Table/TableInfo'; import { usePagination } from '@/hooks/usePagination'; +import Accordion from '@/components/Accordion/Accordion'; +import { Button } from '@/components/ui/button'; +import { IListingContent } from '@/components/ListingContent/ListingContent'; +import { ISummary } from '@/components/Summary/Summary'; interface ITreeDetailsBuildTab { treeDetailsData?: ITreeDetails; @@ -16,19 +19,42 @@ interface ITreeDetailsBuildTab { const TreeDetailsBuildTab = ({ treeDetailsData, }: ITreeDetailsBuildTab): JSX.Element => { + const [filterBy, setFilterBy] = useState<'error' | 'success' | 'all'>('all'); const accordionContent = useMemo(() => { return treeDetailsData?.builds.map(row => ({ trigger: { ...row, - buildTime: `${row.buildTime?.split('.')[0]} ${(<FormattedMessage id="global.seconds" />)}`, - date: row.date?.split(' ')[0], + config: row.config ?? '-', + compiler: row.compiler ?? '-', + buildTime: row.buildTime ? ( + <span> + {typeof row.buildTime === 'number' + ? Math.floor(row.buildTime) + ' ' + : row.buildTime} + <FormattedMessage id="global.seconds" /> + </span> + ) : ( + '-' + ), + date: row.date?.split('T')[0], }, content: <></>, })); }, [treeDetailsData?.builds]); + const filteredContent = + filterBy === 'error' + ? accordionContent?.filter( + row => row.trigger.buildErrors && row.trigger.buildErrors > 0, + ) + : filterBy === 'success' + ? accordionContent?.filter( + row => row.trigger.status && row.trigger.status === 'valid', + ) + : accordionContent; + const { startIndex, endIndex, onClickGoForward, onClickGoBack } = - usePagination(accordionContent?.length ?? 0, ITEMS_PER_PAGE); + usePagination(filteredContent?.length ?? 0, ITEMS_PER_PAGE); const cards = useMemo( () => [ { @@ -57,12 +83,12 @@ const TreeDetailsBuildTab = ({ color: Colors.Gray, }, ], - }, + } as IStatusChart, { items: treeDetailsData?.configs ?? [], title: <FormattedMessage id="treeDetails.configs" />, type: 'listing', - }, + } as IListingContent, { summaryBody: treeDetailsData?.archs ?? [], title: <FormattedMessage id="treeDetails.summary" />, @@ -74,7 +100,7 @@ const TreeDetailsBuildTab = ({ />, ], type: 'summary', - }, + } as ISummary, ], [ treeDetailsData?.archs, @@ -85,15 +111,42 @@ const TreeDetailsBuildTab = ({ ], ); + const onClickFilter = useCallback((type: 'error' | 'success' | 'all') => { + setFilterBy(type); + }, []); + return ( <div className="flex flex-col gap-8 pt-4"> <CardsGroup cards={cards} /> - {accordionContent && ( + {filteredContent && ( <div className="flex flex-col gap-4"> <div className="text-lg"> <FormattedMessage id="treeDetails.builds" /> </div> - <div className="flex justify-end"> + <div className="flex flex-row justify-between"> + <div> + <Button + variant="outline" + className="rounded-l-full border border-black" + onClick={() => onClickFilter('all')} + > + <FormattedMessage id="global.all" /> + </Button> + <Button + variant="outline" + className="rounded-none border border-black" + onClick={() => onClickFilter('success')} + > + <FormattedMessage id="global.successful" /> + </Button> + <Button + variant="outline" + className="rounded-r-full border border-black" + onClick={() => onClickFilter('error')} + > + <FormattedMessage id="global.errors" /> + </Button> + </div> <TableInfo startIndex={startIndex + 1} endIndex={endIndex} @@ -103,8 +156,9 @@ const TreeDetailsBuildTab = ({ onClickForward={onClickGoForward} /> </div> - <BuildsTable - buildsData={accordionContent?.slice(startIndex, endIndex)} + <Accordion + type="build" + items={filteredContent.slice(startIndex, endIndex)} /> <div className="flex justify-end"> <TableInfo diff --git a/dashboard/src/locales/messages/index.ts b/dashboard/src/locales/messages/index.ts index 2be4132..9e8c264 100644 --- a/dashboard/src/locales/messages/index.ts +++ b/dashboard/src/locales/messages/index.ts @@ -3,9 +3,12 @@ import { LOCALES } from '../constants'; export const messages = { [LOCALES.EN_US]: { global: { - filters: 'Filters', + all: 'All', cleanAll: 'Clean all', + errors: 'Errors', + filters: 'Filters', seconds: 'sec', + successful: 'Successful', }, routes: { deviceMonitor: 'Devices', @@ -22,11 +25,16 @@ export const messages = { arch: 'Arch', boots: 'Boots', builds: 'Builds', + buildErrors: 'Build errors', buildStatus: 'Build status', + buildTime: 'BuildTime', + config: 'Config', configs: 'Configs', compiler: 'Compiler', + date: 'Date', executed: 'Executed', summary: 'Summary', + status: 'Status', tests: 'Tests', }, treeTable: { diff --git a/dashboard/src/routes/TreeDetails/TreeDetails.tsx b/dashboard/src/routes/TreeDetails/TreeDetails.tsx index 340854c..81dddf4 100644 --- a/dashboard/src/routes/TreeDetails/TreeDetails.tsx +++ b/dashboard/src/routes/TreeDetails/TreeDetails.tsx @@ -58,7 +58,7 @@ const TreeDetails = (): JSX.Element => { date: value.start_time, buildTime: value.duration, compiler: value.compiler, - buildErrors: value.test_status.error, + buildErrors: value.test_status?.error ?? 0, status: value.valid ? 'valid' : 'invalid', }));