Skip to content

Commit

Permalink
feat: use TestTable in BuildDetails page
Browse files Browse the repository at this point in the history
  • Loading branch information
lfjnascimento authored and WilsonNet committed Oct 25, 2024
1 parent fd741e0 commit 559df75
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 187 deletions.
14 changes: 0 additions & 14 deletions backend/kernelCI_app/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,20 +77,6 @@ class TreeDetailsSerializer(serializers.Serializer):
misc = serializers.JSONField()


class BuildTestsSerializer(serializers.Serializer):
current_path = serializers.CharField()
start_time = serializers.DateTimeField()
fail_tests = serializers.IntegerField()
error_tests = serializers.IntegerField()
miss_tests = serializers.IntegerField()
pass_tests = serializers.IntegerField()
done_tests = serializers.IntegerField()
skip_tests = serializers.IntegerField()
null_tests = serializers.IntegerField()
total_tests = serializers.IntegerField()
origins = serializers.ListField()


class GroupedTestsSerializer(serializers.Serializer):
path_group = serializers.CharField()
fail_tests = serializers.IntegerField()
Expand Down
51 changes: 14 additions & 37 deletions backend/kernelCI_app/views/buildTestsView.py
Original file line number Diff line number Diff line change
@@ -1,48 +1,25 @@
from django.http import JsonResponse
from django.views import View
from django.db.models import Q, TextField, Sum, Count, Case, When, F, Func, Value, Min
from django.db.models.functions import Concat
from django.contrib.postgres.aggregates import ArrayAgg

from kernelCI_app.models import Tests
from kernelCI_app.serializers import BuildTestsSerializer


class BuildTests(View):

def get(self, request, build_id):
path = request.GET.get('path', '')
path_level = 1

if path:
if not path.endswith('.'):
path += '.'
path_level += path.count('.')
filterQ = Q(path__startswith=path) | Q(path=path.rstrip('.'))
else:
filterQ = Q(path__startswith=path)

result = (
Tests.objects
.filter(filterQ, build=build_id,)
.values(
path_sublevel=Func(
F('path'), Value('.'), Value(path_level),
function='SPLIT_PART', output_field=TextField()))
.annotate(
fail_tests=Sum(Case(When(status='FAIL', then=1), default=0)),
error_tests=Sum(Case(When(status='ERROR', then=1), default=0)),
miss_tests=Sum(Case(When(status='MISS', then=1), default=0)),
pass_tests=Sum(Case(When(status='PASS', then=1), default=0)),
done_tests=Sum(Case(When(status='DONE', then=1), default=0)),
skip_tests=Sum(Case(When(status='SKIP', then=1), default=0)),
null_tests=Sum(Case(When(status__isnull=True, then=1), default=0)),
total_tests=Count('id'),
current_path=Concat(Value(path), F('path_sublevel'), output_field=TextField()),
origins=ArrayAgg('origin', distinct=True),
start_time=Min('start_time'),
)
result = Tests.objects.filter(build_id=build_id).values(
'id', 'duration', 'status', 'path', 'start_time'
)

serializer = BuildTestsSerializer(result, many=True)
return JsonResponse(serializer.data, safe=False)
camel_case_result = [
{
'id': test['id'],
'duration': test['duration'],
'status': test['status'],
'path': test['path'],
'startTime': test['start_time'],
}
for test in result
]

return JsonResponse(camel_case_result, safe=False)
55 changes: 23 additions & 32 deletions backend/requests/build-tests-get.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,35 +12,26 @@ http 'http://localhost:8000/api/build/kernelci:kernelci.org:66a1d00e546da93e297e
# X-Frame-Options: DENY

# [
# {
# "current_path": "baseline",
# "start_time": "2024-07-25T04:12:19.892000Z",
# "fail_tests": 0,
# "error_tests": 0,
# "miss_tests": 0,
# "pass_tests": 88,
# "done_tests": 0,
# "skip_tests": 0,
# "null_tests": 0,
# "total_tests": 88,
# "origins": [
# "kernelci"
# ]
# },
# {
# "current_path": "baseline_fvp",
# "start_time": "2024-07-25T04:20:06.543000Z",
# "fail_tests": 0,
# "error_tests": 0,
# "miss_tests": 0,
# "pass_tests": 4,
# "done_tests": 0,
# "skip_tests": 0,
# "null_tests": 0,
# "total_tests": 4,
# "origins": [
# "kernelci"
# ]
# }
# ...
# ]
# {
# "id": "kernelci:kernelci.org:66a1d0d262f8cae67a7e7095",
# "duration": null,
# "status": "PASS",
# "path": "baseline.login",
# "startTime": "2024-07-25T04:13:06.105Z"
# },
# {
# "id": "kernelci:kernelci.org:66a1d0a38e8d1053a47e707c",
# "duration": null,
# "status": "PASS",
# "path": "baseline.login",
# "startTime": "2024-07-25T04:12:19.892Z"
# },
# {
# "id": "kernelci:kernelci.org:66a1d0a38e8d1053a47e707e",
# "duration": null,
# "status": "PASS",
# "path": "baseline.dmesg.emerg",
# "startTime": "2024-07-25T04:12:19.927Z"
# },
# ...
# ]
17 changes: 6 additions & 11 deletions dashboard/src/api/buildTests.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,20 @@
import { useQuery, UseQueryResult } from '@tanstack/react-query';

import { TBuildTests } from '@/types/general';
import { TestHistory } from '@/types/general';

import http from './api';

const fetchBuildTestsData = async (
buildId: string,
path: string,
): Promise<TBuildTests[]> => {
const params = path ? { path } : {};
const res = await http.get(`/api/build/${buildId}/tests`, { params });
const fetchBuildTestsData = async (buildId: string): Promise<TestHistory[]> => {
const res = await http.get(`/api/build/${buildId}/tests`);

return res.data;
};

export const useBuildTests = (
buildId: string,
path = '',
): UseQueryResult<TBuildTests[]> => {
): UseQueryResult<TestHistory[]> => {
return useQuery({
queryKey: ['buildTests', buildId, path],
queryFn: () => fetchBuildTestsData(buildId, path),
queryKey: ['buildTests', buildId],
queryFn: () => fetchBuildTestsData(buildId),
});
};
111 changes: 34 additions & 77 deletions dashboard/src/pages/BuildDetails/BuildDetailsTestSection.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,23 @@
import { useCallback, useMemo, useState } from 'react';
import { FormattedMessage, useIntl } from 'react-intl';

import { RiProhibited2Line } from 'react-icons/ri';

import { useNavigate, useSearch } from '@tanstack/react-router';

import { useCallback } from 'react';

import { Separator } from '@/components/ui/separator';

import BaseTable from '@/components/Table/BaseTable';
import { TableInfo } from '@/components/Table/TableInfo';
import { TableCell, TableRow } from '@/components/ui/table';
import { useBuildTests } from '@/api/buildTests';
import { usePagination } from '@/hooks/usePagination';
import { MessagesKey } from '@/locales/messages';
import { GroupedTestStatus } from '@/components/Status/Status';
import { formatDate } from '@/utils/utils';
import { ItemsPerPageValues } from '@/utils/constants/general';

import { TestsTableFilter } from '@/types/tree/TreeDetails';

import { TestsTable } from '../TreeDetails/Tabs/Tests/TestsTable';

interface IBuildDetailsTestSection {
buildId: string;
}

const headerLabelIds: MessagesKey[] = [
'global.origins',
'global.name',
'buildDetails.testResults',
'buildDetails.startTime',
];

const ITEMS_PER_PAGE = 10;

const NoTestFound = (): JSX.Element => (
<div className="flex flex-col items-center py-6 text-weakGray">
<RiProhibited2Line className="h-14 w-14" />
Expand All @@ -41,64 +31,29 @@ const BuildDetailsTestSection = ({
buildId,
}: IBuildDetailsTestSection): JSX.Element => {
const intl = useIntl();
const [itemsPerPage, setItemsPerPage] = useState(ItemsPerPageValues[0]);
const [pathParam, setPathParam] = useState('');
const { data, error } = useBuildTests(buildId, pathParam);
const data_len = data?.length || 0;
const { startIndex, endIndex, onClickGoForward, onClickGoBack } =
usePagination(data_len, ITEMS_PER_PAGE);

const onClickName = useCallback(
(e: React.MouseEvent<HTMLTableCellElement>) => {
if (e.target instanceof HTMLTableCellElement) {
setPathParam(e.target.innerText);
}
},
[],
);
const { data, error } = useBuildTests(buildId);
const { tableFilter } = useSearch({
from: '/tree/$treeId/build/$buildId/',
});

const headers = useMemo(() => {
return headerLabelIds.map(labelId => (
<FormattedMessage key={labelId} id={labelId} />
));
}, []);
const navigate = useNavigate({ from: '/tree/$treeId/build/$buildId' });

const rows = useMemo(() => {
if (!data || error) return <></>;
return data.slice(startIndex, endIndex).map(test => (
<TableRow key={test.current_path}>
<TableCell>{test.origins.join(', ')}</TableCell>
<TableCell onClick={onClickName}>{test.current_path}</TableCell>

<TableCell className="flex flex-row gap-1">
<GroupedTestStatus
pass={test.pass_tests}
done={test.done_tests}
miss={test.miss_tests}
fail={test.fail_tests}
skip={test.skip_tests}
error={test.error_tests}
/>
</TableCell>
<TableCell>{formatDate(test.start_time)}</TableCell>
</TableRow>
));
}, [data, error, onClickName, startIndex, endIndex]);

const tableInfoElement = (
<div className="flex flex-col items-end">
<TableInfo
itemName="global.tests"
startIndex={startIndex + 1}
endIndex={endIndex}
totalTrees={data_len}
itemsPerPageValues={ItemsPerPageValues}
itemsPerPageSelected={itemsPerPage}
onChangeItemsPerPage={setItemsPerPage}
onClickBack={onClickGoBack}
onClickForward={onClickGoForward}
/>
</div>
const onClickFilter = useCallback(
(filter: TestsTableFilter): void => {
navigate({
search: previousParams => {
return {
...previousParams,
tableFilter: {
bootsTable: previousParams.tableFilter.bootsTable,
buildsTable: previousParams.tableFilter.buildsTable,
testsTable: filter,
},
};
},
});
},
[navigate],
);

const hasTest = data && data.length > 0 && !error;
Expand All @@ -110,9 +65,11 @@ const BuildDetailsTestSection = ({
<Separator className="my-6 bg-darkGray" />
{hasTest ? (
<div className="flex flex-col gap-6">
{tableInfoElement}
<BaseTable headers={headers}>{rows}</BaseTable>
{tableInfoElement}
<TestsTable
testHistory={data}
onClickFilter={onClickFilter}
tableFilter={tableFilter}
/>
</div>
) : (
<NoTestFound />
Expand Down
3 changes: 2 additions & 1 deletion dashboard/src/pages/TreeDetails/Tabs/Boots/BootsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import { FormattedMessage, useIntl } from 'react-intl';
import {
possibleTestsTableFilter,
TestByCommitHash,
TestHistory,
TestsTableFilter,
TTestByCommitHashResponse,
} from '@/types/tree/TreeDetails';
Expand All @@ -36,6 +35,8 @@ import {
TableRow,
} from '@/components/ui/table';

import { TestHistory } from '@/types/general';

import BaseTable, { TableHead } from '@/components/Table/BaseTable';

import TableStatusFilter from '@/components/Table/TableStatusFilter';
Expand Down
9 changes: 9 additions & 0 deletions dashboard/src/routes/tree/$treeId/build/$buildId/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import { createFileRoute } from '@tanstack/react-router';

import { z } from 'zod';

import BuildDetails from '@/pages/BuildDetails/BuildDetails';

import { zTableFilterInfo } from '@/types/tree/TreeDetails';

const buildDetailsSearchSchema = z.object({
tableFilter: zTableFilterInfo,
});

export const Route = createFileRoute('/tree/$treeId/build/$buildId/')({
component: BuildDetails,
validateSearch: buildDetailsSearchSchema,
});
16 changes: 1 addition & 15 deletions dashboard/src/types/general.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,6 @@ export type TIndividualTest = {
hardware?: string[];
};

export type TBuildTests = {
current_path: string;
start_time: string;
origins: string[];
fail_tests: number;
error_tests: number;
miss_tests: number;
pass_tests: number;
done_tests: number;
skip_tests: number;
null_tests: number;
total_tests: number;
};

export type TIssue = {
id: string;
comment?: string;
Expand All @@ -52,4 +38,4 @@ export type TestHistory = {
id: string;
duration?: number;
hardware?: string[];
};
};

0 comments on commit 559df75

Please sign in to comment.