Skip to content

Commit

Permalink
feat: add hardware used in tree details header
Browse files Browse the repository at this point in the history
Closes #384
  • Loading branch information
anajalvarenga committed Oct 23, 2024
1 parent f5780d9 commit 0b9070d
Show file tree
Hide file tree
Showing 10 changed files with 176 additions and 44 deletions.
5 changes: 5 additions & 0 deletions backend/kernelCI_app/views/treeDetailsSlowView.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ def __init__(self):
self.incidentsIssueRelationship = defaultdict(
lambda: IncidentInfo(incidentsCount=0)
)
self.hardwareUsed = set()

def __handle_boot_status(self, current_filter):
self.filterBootStatus.add(current_filter["value"])
Expand Down Expand Up @@ -440,6 +441,9 @@ def get(self, request, commit_hash: str | None):
testEnvironmentCompatible,
) = currentRowData

if testEnvironmentCompatible is not None:
self.hardwareUsed.add(testEnvironmentCompatible)

if (
(
len(self.filterArchitecture) > 0
Expand Down Expand Up @@ -483,6 +487,7 @@ def get(self, request, commit_hash: str | None):
"testEnvironmentCompatible": self.testEnvironmentCompatible,
"bootEnvironmentCompatible": self.bootEnvironmentCompatible,
"incidentsIssueRelationship": self.incidentsIssueRelationship,
"hardwareUsed": list(self.hardwareUsed),
},
safe=False,
)
28 changes: 20 additions & 8 deletions dashboard/src/api/TreeDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,15 @@ const mapFiltersKeysToBackendCompatible = (
return filterParam;
};

export const useBuildsTab = (
treeId: string,
filter: TTreeDetailsFilter | Record<string, never> = {},
): UseQueryResult<BuildsTab> => {
export const useBuildsTab = ({
treeId,
filter = {},
enabled = true,
}: {
treeId: string;
filter?: TTreeDetailsFilter | Record<string, never>;
enabled?: boolean;
}): UseQueryResult<BuildsTab> => {
const detailsFilter = getTargetFilter(filter, 'treeDetails');

const treeSearchParameters = useTreeSearchParameters();
Expand All @@ -105,6 +110,7 @@ export const useBuildsTab = (
queryKey: ['treeData', treeId, detailsFilter, treeSearchParameters],
queryFn: () =>
fetchTreeDetailData(treeId, treeSearchParameters, detailsFilter),
enabled,
});
};

Expand Down Expand Up @@ -135,10 +141,15 @@ const fetchTreeTestsData = async (
return res.data;
};

export const useTestsTab = (
treeId: string,
filter: TTreeDetailsFilter,
): UseQueryResult<TTreeTestsFullData> => {
export const useTestsTab = ({
treeId,
filter,
enabled = true,
}: {
treeId: string;
filter: TTreeDetailsFilter;
enabled?: boolean;
}): UseQueryResult<TTreeTestsFullData> => {
const testFilter = getTargetFilter(filter, 'test');
const treeDetailsFilter = getTargetFilter(filter, 'treeDetails');
const treeSearchParameters = useTreeSearchParameters();
Expand All @@ -156,6 +167,7 @@ export const useTestsTab = (
...testFilter,
...treeDetailsFilter,
}),
enabled,
});
};

Expand Down
36 changes: 36 additions & 0 deletions dashboard/src/components/ui/badge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"

const badgeVariants = cva(
"inline-flex items-center rounded-full border border-slate-200 px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-slate-950 focus:ring-offset-2 dark:border-slate-800 dark:focus:ring-slate-300",
{
variants: {
variant: {
default:
"border-transparent bg-slate-900 text-slate-50 hover:bg-slate-900/80 dark:bg-slate-50 dark:text-slate-900 dark:hover:bg-slate-50/80",
secondary:
"border-transparent bg-slate-100 text-slate-900 hover:bg-slate-100/80 dark:bg-slate-800 dark:text-slate-50 dark:hover:bg-slate-800/80",
destructive:
"border-transparent bg-red-500 text-slate-50 hover:bg-red-500/80 dark:bg-red-900 dark:text-slate-50 dark:hover:bg-red-900/80",
outline: "text-slate-950 dark:text-slate-50",
},
},
defaultVariants: {
variant: "default",
},
}
)

export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}

function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
)
}

export { Badge, badgeVariants }
1 change: 1 addition & 0 deletions dashboard/src/locales/messages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ export const messages = {
'treeDetails.executed': 'Executed',
'treeDetails.failed': 'Failed',
'treeDetails.failedBoots': 'Failed boots',
'treeDetails.hardwareUsed': 'Hardware Used',
'treeDetails.inconclusiveBoots': 'Inconclusive Boots',
'treeDetails.inconclusiveBuilds': 'Inconclusive Builds',
'treeDetails.inconclusiveTests': 'Inconclusive Tests',
Expand Down
5 changes: 4 additions & 1 deletion dashboard/src/pages/TreeDetails/Tabs/Boots/BootsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ const BootsTab = ({ reqFilter }: BootsTabProps): JSX.Element => {
const { treeId } = useParams({
from: '/tree/$treeId/',
});
const { isLoading, data, error } = useTestsTab(treeId ?? '', reqFilter);
const { isLoading, data, error } = useTestsTab({
treeId: treeId ?? '',
filter: reqFilter,
});

if (error || !treeId) {
return (
Expand Down
33 changes: 32 additions & 1 deletion dashboard/src/pages/TreeDetails/Tabs/TestCards.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FormattedMessage } from 'react-intl';
import { memo } from 'react';
import { memo, useMemo } from 'react';

import { Link } from '@tanstack/react-router';

Expand All @@ -20,6 +20,8 @@ import { TIssue } from '@/types/general';
import { NoIssueFound } from '@/components/Issue/IssueSection';
import { ScrollArea } from '@/components/ui/scroll-area';

import { Badge } from '@/components/ui/badge';

import FilterLink from '../TreeDetailsFilterLink';

interface IConfigList extends Pick<TTreeTestsData, 'configStatusCounts'> {
Expand Down Expand Up @@ -289,3 +291,32 @@ const IssuesList = ({ issues, title }: IIssuesList): JSX.Element => {
};

export const MemoizedIssuesList = memo(IssuesList);

interface IHardwareUsed {
title: IBaseCard['title'];
hardwareUsed?: string[];
}

const HardwareUsed = ({ hardwareUsed, title }: IHardwareUsed): JSX.Element => {
const hardwareSorted = useMemo(() => {
return hardwareUsed?.sort().map(hardware => {
return (
<Badge key={hardware} variant="outline" className="text-sm font-normal">
{hardware}
</Badge>
);
});
}, [hardwareUsed]);

return (
<BaseCard
title={title}
content={
<div className="flex flex-row flex-wrap gap-4 p-4">
{hardwareSorted}
</div>
}
/>
);
};
export const MemoizedHardwareUsed = memo(HardwareUsed);
5 changes: 4 additions & 1 deletion dashboard/src/pages/TreeDetails/Tabs/Tests/TestsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ interface TestsTabProps {

const TestsTab = ({ reqFilter }: TestsTabProps): JSX.Element => {
const { treeId } = useParams({ from: '/tree/$treeId/' });
const { isLoading, data, error } = useTestsTab(treeId ?? '', reqFilter);
const { isLoading, data, error } = useTestsTab({
treeId: treeId ?? '',
filter: reqFilter,
});

if (error || !treeId) {
return (
Expand Down
102 changes: 71 additions & 31 deletions dashboard/src/pages/TreeDetails/TreeDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useMemo } from 'react';

import { FormattedMessage } from 'react-intl';

import { useBuildsTab } from '@/api/TreeDetails';
import { useBuildsTab, useTestsTab } from '@/api/TreeDetails';
import { IListingItem } from '@/components/ListingItem/ListingItem';
import { ISummaryItem } from '@/components/Summary/Summary';
import {
Expand Down Expand Up @@ -34,10 +34,13 @@ import { sanitizeTableValue } from '@/components/Table/tableUtils';

import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/Tooltip';

import QuerySwitcher from '@/components/QuerySwitcher/QuerySwitcher';

import TreeDetailsFilter, { mapFilterToReq } from './TreeDetailsFilter';
import TreeDetailsTab from './Tabs/TreeDetailsTab';

import TreeDetailsFilterList from './TreeDetailsFilterList';
import { MemoizedHardwareUsed } from './Tabs/TestCards';

export interface ITreeDetails {
archs: ISummaryItem[];
Expand Down Expand Up @@ -112,7 +115,32 @@ function TreeDetails(): JSX.Element {

const reqFilter = mapFilterToReq(diffFilter);

const { isLoading, data } = useBuildsTab(treeId ?? '', reqFilter);
const isBuildTab =
searchParams.currentTreeDetailsTab === 'treeDetails.builds';

const {
isLoading: buildIsLoading,
data: buildData,
status: buildStatus,
} = useBuildsTab({
treeId: treeId ?? '',
filter: reqFilter,
enabled: isBuildTab,
});

const {
isLoading: testsIsLoading,
data: testsData,
status: testStatus,
} = useTestsTab({
treeId: treeId ?? '',
filter: reqFilter,
enabled: !isBuildTab || (buildStatus === 'success' && !!buildData),
});

const isLoading = useMemo(() => {
return isBuildTab ? buildIsLoading : testsIsLoading;
}, [isBuildTab, testsIsLoading, buildIsLoading]);

const filterListElement = useMemo(
() => <TreeDetailsFilterList filter={diffFilter} />,
Expand All @@ -122,20 +150,20 @@ function TreeDetails(): JSX.Element {
//TODO: at some point `treeUrl` should be returned in `data`
const treeUrl = useMemo(() => {
let url = '';
if (!data) return '';
Object.entries(data.builds).some(([, build]) => {
if (!buildData) return '';
Object.entries(buildData.builds).some(([, build]) => {
if (build.git_repository_url) {
url = build.git_repository_url;
return true;
}
});
return url;
}, [data]);
}, [buildData]);

const treeDetailsData: ITreeDetails | undefined = useMemo(() => {
if (data) {
if (buildData) {
const configsData: IListingItem[] = Object.entries(
data.summary.configs,
buildData.summary.configs,
).map(([key, value]) => ({
text: key,
errors: value.invalid,
Expand All @@ -144,7 +172,7 @@ function TreeDetails(): JSX.Element {
}));

const archData: ISummaryItem[] = Object.entries(
data.summary.architectures,
buildData.summary.architectures,
).map(([key, value]) => ({
arch: {
text: key,
Expand All @@ -156,39 +184,39 @@ function TreeDetails(): JSX.Element {
}));

const buildSummaryData: BuildStatus = {
valid: data.summary.builds.valid,
invalid: data.summary.builds.invalid,
null: data.summary.builds.null,
valid: buildData.summary.builds.valid,
invalid: buildData.summary.builds.invalid,
null: buildData.summary.builds.null,
};

const buildsData: AccordionItemBuilds[] = Object.entries(data.builds).map(
([, value]) => ({
id: value.id,
config: value.config_name,
date: value.start_time,
buildTime: value.duration,
compiler: value.compiler,
buildErrors: isBuildError(value),
status:
value.valid === null ? 'null' : value.valid ? 'valid' : 'invalid',
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,
}),
);
const buildsData: AccordionItemBuilds[] = Object.entries(
buildData.builds,
).map(([, value]) => ({
id: value.id,
config: value.config_name,
date: value.start_time,
buildTime: value.duration,
compiler: value.compiler,
buildErrors: isBuildError(value),
status:
value.valid === null ? 'null' : value.valid ? 'valid' : 'invalid',
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,
}));

return {
archs: archData,
configs: configsData,
buildsSummary: buildSummaryData,
builds: buildsData,
issues: data.issues,
issues: buildData.issues,
};
}
}, [data]);
}, [buildData]);

if (isLoading)
return (
Expand Down Expand Up @@ -223,6 +251,18 @@ function TreeDetails(): JSX.Element {
gitUrl={treeInfo?.gitUrl}
/>
</div>
<QuerySwitcher
status={testStatus}
data={testsData}
skeletonClassname="h-[200px] bg-lightGray"
>
<div className="mt-5">
<MemoizedHardwareUsed
title={<FormattedMessage id="treeDetails.hardwareUsed" />}
hardwareUsed={testsData?.hardwareUsed}
/>
</div>
</QuerySwitcher>
<div className="relative mt-10 flex flex-col pb-2">
<div className="absolute right-0 top-[-16px]">
<TreeDetailsFilter paramFilter={diffFilter} treeUrl={treeUrl} />
Expand Down
2 changes: 1 addition & 1 deletion dashboard/src/pages/TreeDetails/TreeDetailsFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ const TreeDetailsFilter = ({
}: ITreeDetailsFilter): JSX.Element => {
const { treeId } = useParams({ from: '/tree/$treeId/' });

const { data, isLoading } = useBuildsTab(treeId);
const { data, isLoading } = useBuildsTab({ treeId });

const navigate = useNavigate({
from: '/tree/$treeId',
Expand Down
3 changes: 2 additions & 1 deletion dashboard/src/types/tree/TreeDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ type StatusCounts = {
[key in Status]: number | undefined;
};

type EnvironmentCompatibleCounts = {
export type EnvironmentCompatibleCounts = {
[key: string]: number;
};

Expand Down Expand Up @@ -139,6 +139,7 @@ export type TTreeTestsFullData = {
testIssues: TIssue[];
testEnvironmentCompatible: EnvironmentCompatibleCounts;
bootEnvironmentCompatible: EnvironmentCompatibleCounts;
hardwareUsed: string[];
};

export type PossibleTabs = Extract<
Expand Down

0 comments on commit 0b9070d

Please sign in to comment.