From a89f14f2a78a861a89275e69a46ab70a8ceab411 Mon Sep 17 00:00:00 2001 From: Mariana Sartorato Date: Fri, 19 Jul 2024 11:03:59 -0300 Subject: [PATCH] feat(#71): add accordion item content --- .../src/components/Accordion/Accordion.tsx | 44 ++--- .../Accordion/BuildAccordionContent.tsx | 156 ++++++++++++++++++ .../src/components/CardsGroup/CardsGroup.tsx | 7 +- .../src/components/LinkGroup/LinkGroup.tsx | 35 ++++ .../components/LinkWithIcon/LinkWithIcon.tsx | 27 +++ .../components/StatusChart/StatusCharts.tsx | 93 ++++++----- .../Tabs/TreeDetails/TreeDetailsBuildTab.tsx | 21 ++- .../src/routes/TreeDetails/TreeDetails.tsx | 36 ++-- dashboard/src/types/tree/TreeDetails.tsx | 46 +++++- 9 files changed, 373 insertions(+), 92 deletions(-) create mode 100644 dashboard/src/components/Accordion/BuildAccordionContent.tsx create mode 100644 dashboard/src/components/LinkGroup/LinkGroup.tsx create mode 100644 dashboard/src/components/LinkWithIcon/LinkWithIcon.tsx diff --git a/dashboard/src/components/Accordion/Accordion.tsx b/dashboard/src/components/Accordion/Accordion.tsx index 6114ecc..1e235d8 100644 --- a/dashboard/src/components/Accordion/Accordion.tsx +++ b/dashboard/src/components/Accordion/Accordion.tsx @@ -1,9 +1,11 @@ -import { ReactElement, ReactNode, useMemo } from 'react'; +import { ReactElement, useMemo } from 'react'; import { MdCheck, MdClose } from 'react-icons/md'; import { FormattedMessage } from 'react-intl'; +import { AccordionItemBuilds } from '@/types/tree/TreeDetails'; + import { TableBody, TableCell, TableRow } from '../ui/table'; import BaseTable from '../Table/BaseTable'; import { @@ -14,6 +16,8 @@ import { import ColoredCircle from '../ColoredCircle/ColoredCircle'; import { ItemType } from '../ListingItem/ListingItem'; +import AccordionBuildContent from './BuildAccordionContent'; + export interface IAccordion { headers?: ReactElement[]; items: IAccordionItems[]; @@ -21,8 +25,7 @@ export interface IAccordion { } export interface IAccordionItems { - trigger: AccordionItemBuildsTrigger | AccordionItemTestsTrigger; - content?: ReactNode; + accordionData: AccordionItemBuilds | AccordionItemTestsTrigger; } interface ICustomAccordionTableBody { @@ -30,15 +33,6 @@ interface ICustomAccordionTableBody { type: 'build' | 'test'; } -export type AccordionItemBuildsTrigger = { - config?: string; - compiler?: string; - date?: string; - buildErrors?: number; - buildTime?: string; - status?: 'valid' | 'invalid'; -}; - export type AccordionItemTestsTrigger = { testPlans?: string; testSuccessfull?: number; @@ -81,13 +75,21 @@ const AccordionTableBody = ({ {type === 'build' ? ( - + ) : ( - + )} - {item.content} + +
+ {type === 'build' ? ( + + ) : ( + <> + )} +
+
)), @@ -97,8 +99,10 @@ const AccordionTableBody = ({ return {accordionItems}; }; -const AccordionBuildsTrigger = ({ trigger }: IAccordionItems): JSX.Element => { - const triggerInfo = trigger as AccordionItemBuildsTrigger; +const AccordionBuildsTrigger = ({ + accordionData, +}: IAccordionItems): JSX.Element => { + const triggerInfo = accordionData as AccordionItemBuilds; return ( <> {triggerInfo.config} @@ -123,8 +127,10 @@ const AccordionBuildsTrigger = ({ trigger }: IAccordionItems): JSX.Element => { ); }; -const AccordionTestsTrigger = ({ trigger }: IAccordionItems): JSX.Element => { - const triggerInfo = trigger as AccordionItemTestsTrigger; +const AccordionTestsTrigger = ({ + accordionData, +}: IAccordionItems): JSX.Element => { + const triggerInfo = accordionData as AccordionItemTestsTrigger; return ( <> {triggerInfo.testPlans} diff --git a/dashboard/src/components/Accordion/BuildAccordionContent.tsx b/dashboard/src/components/Accordion/BuildAccordionContent.tsx new file mode 100644 index 0000000..9d8af0b --- /dev/null +++ b/dashboard/src/components/Accordion/BuildAccordionContent.tsx @@ -0,0 +1,156 @@ +import { MdFolderOpen } from 'react-icons/md'; + +import { useMemo } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import { AccordionItemBuilds } from '@/types/tree/TreeDetails'; + +import StatusChartMemoized, { Colors } from '../StatusChart/StatusCharts'; + +import LinksGroup from '../LinkGroup/LinkGroup'; + +import { IAccordionItems } from './Accordion'; + +export interface IBuildAccordionContent { + testStatus: { + failTests: number; + errorTests: number; + passTests: number; + skipTests: number; + }; + kernelImage?: string; + buildLogs?: string; + kernelConfig?: string; + dtb?: string; + systemMap?: string; + modules?: string; +} + +export interface ILinksGroup { + kernelImage?: string; + buildLogs?: string; + kernelConfig?: string; + dtb?: string; + systemMap?: string; + modules?: string; +} + +const AccordionBuildContent = ({ + accordionData, +}: IAccordionItems): JSX.Element => { + const contentData = accordionData as AccordionItemBuilds; + const chartElements = useMemo(() => { + return contentData.testStatus?.passTests || + contentData.testStatus?.skipTests || + contentData.testStatus?.errorTests || + contentData.testStatus?.failTests + ? [ + { + value: contentData.testStatus?.passTests ?? 0, + label: , + color: Colors.Green, + }, + { + value: + (contentData.testStatus?.failTests ?? 0) + + (contentData.testStatus?.errorTests ?? 0), + label: , + color: Colors.Red, + }, + { + value: contentData.testStatus?.skipTests ?? 0, + label: , + color: Colors.Gray, + }, + ] + : [ + { + value: 1, + label: , + color: Colors.Gray, + showValue: false, + }, + ]; + }, [ + contentData.testStatus?.errorTests, + contentData.testStatus?.failTests, + contentData.testStatus?.passTests, + contentData.testStatus?.skipTests, + ]); + + const links = useMemo( + () => [ + contentData.kernelImage + ? { + title: , + icon: , + linkText: {`kernel/${contentData.kernelImage}`}, + } + : undefined, + contentData.kernelConfig + ? { + title: , + icon: , + link: contentData.kernelConfig, + linkText: , + } + : undefined, + contentData.dtb + ? { + title: , + icon: , + link: contentData.dtb, + linkText: , + } + : undefined, + contentData.buildLogs + ? { + title: , + icon: , + link: contentData.buildLogs, + linkText: , + } + : undefined, + contentData.systemMap + ? { + title: , + icon: , + link: contentData.systemMap, + linkText: , + } + : undefined, + contentData.modules + ? { + title: , + icon: , + link: contentData.modules, + linkText: , + } + : undefined, + ], + [ + contentData.buildLogs, + contentData.dtb, + contentData.kernelConfig, + contentData.kernelImage, + contentData.modules, + contentData.systemMap, + ], + ); + + return ( +
+
+ } + elements={chartElements} + /> +
+ +
+ ); +}; + +export default AccordionBuildContent; diff --git a/dashboard/src/components/CardsGroup/CardsGroup.tsx b/dashboard/src/components/CardsGroup/CardsGroup.tsx index d38b837..5d2767a 100644 --- a/dashboard/src/components/CardsGroup/CardsGroup.tsx +++ b/dashboard/src/components/CardsGroup/CardsGroup.tsx @@ -19,8 +19,8 @@ const CardsGroup = ({ cards }: ICardsGroup): JSX.Element => { const cardsList = useMemo(() => { return cards.map(card => ( {card.title} ?? ''} content={} /> )); @@ -40,7 +40,8 @@ const CardContent = ({ card }: ICardContent): JSX.Element => { /> ); } else if (card.type === 'chart') { - return ; + const chartData = { ...card, title: undefined }; + return ; } else { return <>; } diff --git a/dashboard/src/components/LinkGroup/LinkGroup.tsx b/dashboard/src/components/LinkGroup/LinkGroup.tsx new file mode 100644 index 0000000..1fc27c1 --- /dev/null +++ b/dashboard/src/components/LinkGroup/LinkGroup.tsx @@ -0,0 +1,35 @@ +import { ReactElement, useMemo } from 'react'; + +import LinkWithIcon from '../LinkWithIcon/LinkWithIcon'; + +interface ILinkGroup { + links: (ILink | undefined)[]; +} + +interface ILink { + linkText: JSX.Element; + title?: ReactElement; + link?: string; + icon?: ReactElement; +} + +const LinksGroup = ({ links }: ILinkGroup): JSX.Element => { + const linkGroup = useMemo(() => { + return links?.map( + link => + link && ( + + ), + ); + }, [links]); + return ( +
{linkGroup}
+ ); +}; + +export default LinksGroup; diff --git a/dashboard/src/components/LinkWithIcon/LinkWithIcon.tsx b/dashboard/src/components/LinkWithIcon/LinkWithIcon.tsx new file mode 100644 index 0000000..19e7d14 --- /dev/null +++ b/dashboard/src/components/LinkWithIcon/LinkWithIcon.tsx @@ -0,0 +1,27 @@ +import { ReactElement } from 'react'; + +interface ILinkWithIcon { + title?: string | ReactElement; + linkText?: string | ReactElement; + link?: string; + icon?: ReactElement; +} + +const LinkWithIcon = ({ + title, + linkText, + icon, + link, +}: ILinkWithIcon): JSX.Element => { + return ( + + ); +}; + +export default LinkWithIcon; diff --git a/dashboard/src/components/StatusChart/StatusCharts.tsx b/dashboard/src/components/StatusChart/StatusCharts.tsx index 961ead7..e4e10fc 100644 --- a/dashboard/src/components/StatusChart/StatusCharts.tsx +++ b/dashboard/src/components/StatusChart/StatusCharts.tsx @@ -10,8 +10,9 @@ import ColoredCircle from '../ColoredCircle/ColoredCircle'; type StatusChartValues = { value: number; - label: string; + label: ReactElement; color: Colors; + showValue?: boolean; }; export enum Colors { @@ -28,7 +29,7 @@ export interface IStatusChart { decreaseElement?: StatusChartValues; pieCentralLabel?: string; pieCentralDescription?: ReactElement; - title: ReactElement; + title?: ReactElement; type: 'chart'; } @@ -52,13 +53,17 @@ const StatusChart = ({ decreaseElement, pieCentralLabel, pieCentralDescription, + title, }: IStatusChart): JSX.Element => { const showChart = elements.some(element => element.value > 0); const dataSeries = useMemo(() => { return [ { - data: elements, + data: elements.map(element => ({ + ...element, + label: '', + })), innerRadius: 50, outerRadius: 80, }, @@ -69,28 +74,31 @@ const StatusChart = ({ return <>; } return ( -
- - } - /> - -
- - +
+ {title} +
+ + } + /> + +
+ + +
); @@ -114,25 +122,26 @@ const getColorClassName = (color: Colors): string => { }; const ChartLegend = ({ chartValues }: IChartLegend): JSX.Element => { - return ( -
- {chartValues.map(chartValue => ( -
- {chartValue && ( -
- -
- )} -
- {chartValue?.value} - {chartValue?.label} + const legend = useMemo(() => { + return chartValues.map(chartValue => ( +
+ {chartValue && ( +
+
+ )} +
+ {chartValue?.showValue && ( + {chartValue?.value} + )} + {chartValue?.label}
- ))} -
- ); +
+ )); + }, [chartValues]); + return
{legend}
; }; const RegressionsStatus = ({ diff --git a/dashboard/src/components/Tabs/TreeDetails/TreeDetailsBuildTab.tsx b/dashboard/src/components/Tabs/TreeDetails/TreeDetailsBuildTab.tsx index 6459b0c..f578c80 100644 --- a/dashboard/src/components/Tabs/TreeDetails/TreeDetailsBuildTab.tsx +++ b/dashboard/src/components/Tabs/TreeDetails/TreeDetailsBuildTab.tsx @@ -22,7 +22,7 @@ const TreeDetailsBuildTab = ({ const [filterBy, setFilterBy] = useState<'error' | 'success' | 'all'>('all'); const accordionContent = useMemo(() => { return treeDetailsData?.builds.map(row => ({ - trigger: { + accordionData: { ...row, config: row.config ?? '-', compiler: row.compiler ?? '-', @@ -37,19 +37,26 @@ const TreeDetailsBuildTab = ({ '-' ), date: row.date?.split('T')[0], + testStatus: { + failTests: row.testStatus?.failTests, + errorTests: row.testStatus?.errorTests, + passTests: row.testStatus?.passTests, + skipTests: row.testStatus?.skipTests, + }, }, - content: <>, })); }, [treeDetailsData?.builds]); const filteredContent = filterBy === 'error' ? accordionContent?.filter( - row => row.trigger.buildErrors && row.trigger.buildErrors > 0, + row => + row.accordionData.buildErrors && row.accordionData.buildErrors > 0, ) : filterBy === 'success' ? accordionContent?.filter( - row => row.trigger.status && row.trigger.status === 'valid', + row => + row.accordionData.status && row.accordionData.status === 'valid', ) : accordionContent; @@ -69,17 +76,17 @@ const TreeDetailsBuildTab = ({ elements: [ { value: treeDetailsData?.buildsSummary.valid ?? 0, - label: 'Valid', + label: , color: Colors.Green, }, { value: treeDetailsData?.buildsSummary.invalid ?? 0, - label: 'Invalid', + label: , color: Colors.Red, }, { value: treeDetailsData?.buildsSummary.null ?? 0, - label: 'Null', + label: , color: Colors.Gray, }, ], diff --git a/dashboard/src/routes/TreeDetails/TreeDetails.tsx b/dashboard/src/routes/TreeDetails/TreeDetails.tsx index 303bfd1..23ff618 100644 --- a/dashboard/src/routes/TreeDetails/TreeDetails.tsx +++ b/dashboard/src/routes/TreeDetails/TreeDetails.tsx @@ -6,8 +6,8 @@ import { useTreeDetails } from '@/api/TreeDetails'; import TreeDetailsTab from '@/components/Tabs/TreeDetailsTab'; import { IListingItem } from '@/components/ListingItem/ListingItem'; import { ISummaryItem } from '@/components/Summary/Summary'; -import { AccordionItemBuildsTrigger } from '@/components/Accordion/Accordion'; import { + AccordionItemBuilds, Results, TTreeDetailsFilter, TreeDetails as TreeDetailsType, @@ -19,7 +19,7 @@ export interface ITreeDetails { archs: ISummaryItem[]; configs: IListingItem[]; buildsSummary: Results; - builds: AccordionItemBuildsTrigger[]; + builds: AccordionItemBuilds[]; } const TreeDetails = (): JSX.Element => { @@ -58,16 +58,28 @@ const TreeDetails = (): JSX.Element => { null: data.summary.builds.null, }; - const buildsData: AccordionItemBuildsTrigger[] = Object.entries( - data.builds, - ).map(([, value]) => ({ - config: value.config_name, - date: value.start_time, - buildTime: value.duration, - compiler: value.compiler, - buildErrors: value.test_status?.error ?? 0, - status: value.valid ? 'valid' : 'invalid', - })); + const buildsData: AccordionItemBuilds[] = Object.entries(data.builds).map( + ([, value]) => ({ + config: value.config_name, + date: value.start_time, + buildTime: value.duration, + compiler: value.compiler, + buildErrors: value.test_status?.error_tests ?? 0, + status: value.valid ? 'valid' : 'invalid', + testStatus: { + failTests: value.test_status?.fail_tests ?? 0, + passTests: value.test_status?.pass_tests ?? 0, + errorTests: value.test_status?.error_tests ?? 0, + skipTests: value.test_status?.skip_tests ?? 0, + }, + buildLogs: value.log_url, + kernelConfig: value.config_url, + kernelImage: value.misc ? value.misc['kernel_type'] : undefined, + dtb: value.misc ? value.misc['dtb'] : undefined, + systemMap: value.misc ? value.misc['system_map'] : undefined, + modules: value.misc ? value.misc['modules'] : undefined, + }), + ); setTreeDetailsData({ archs: archData, diff --git a/dashboard/src/types/tree/TreeDetails.tsx b/dashboard/src/types/tree/TreeDetails.tsx index e0b9e17..e492a04 100644 --- a/dashboard/src/types/tree/TreeDetails.tsx +++ b/dashboard/src/types/tree/TreeDetails.tsx @@ -11,16 +11,44 @@ type TreeDetailsBuild = { git_repository_branch: string; git_repository_url: string; test_status: { - fail: number; - error: number; - miss: number; - pass: number; - done: number; - skip: number; - null: number; - total: number; + fail_tests: number; + error_tests: number; + miss_tests: number; + pass_tests: number; + done_tests: number; + skip_tests: number; + null_tests: number; + total_tests: number; }; - misc: JSON | null; + misc: ITreeDetailsMisc | null; +}; + +interface ITreeDetailsMisc { + kernel_type?: string; + dtb?: string; + modules?: string; + system_map?: string; +} + +export type AccordionItemBuilds = { + config?: string; + compiler?: string; + date?: string; + buildErrors?: number; + buildTime?: string; + status?: 'valid' | 'invalid'; + testStatus?: { + failTests: number; + errorTests: number; + passTests: number; + skipTests: number; + }; + kernelImage?: string; + buildLogs?: string; + kernelConfig?: string; + dtb?: string; + systemMap?: string; + modules?: string; }; export type TreeDetails = {