mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-17 12:35:46 +00:00
Added test statistics (#7164)
* Added test statistics Fixed #5995 * Bump API version * Fix javascript varible scopes * Fix javascript exports * Remove duplicated import * Add files modified by the pre-commit scripts * Move test statistics API urls to a separate endpoint * Merge test-statistics urls * Undo unrelated changes * Formatting fix * Fix API urls for test statistics in PUI * Fix prefixing in test statistic functions --------- Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
This commit is contained in:
@ -115,6 +115,8 @@ export enum ApiEndpoints {
|
||||
stock_assign = 'stock/assign/',
|
||||
stock_status = 'stock/status/',
|
||||
stock_install = 'stock/:id/install',
|
||||
build_test_statistics = 'test-statistics/by-build/:id',
|
||||
part_test_statistics = 'test-statistics/by-part/:id',
|
||||
|
||||
// Generator API endpoints
|
||||
generate_batch_code = 'generate/batch-code/',
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
IconNotes,
|
||||
IconPaperclip,
|
||||
IconQrcode,
|
||||
IconReportAnalytics,
|
||||
IconSitemap
|
||||
} from '@tabler/icons-react';
|
||||
import { useMemo } from 'react';
|
||||
@ -53,6 +54,7 @@ import { BuildOrderTable } from '../../tables/build/BuildOrderTable';
|
||||
import BuildOutputTable from '../../tables/build/BuildOutputTable';
|
||||
import { AttachmentTable } from '../../tables/general/AttachmentTable';
|
||||
import { StockItemTable } from '../../tables/stock/StockItemTable';
|
||||
import { TestStatisticsTable } from '../../tables/stock/TestStatisticsTable';
|
||||
|
||||
/**
|
||||
* Detail page for a single Build Order
|
||||
@ -305,6 +307,20 @@ export default function BuildDetail() {
|
||||
<Skeleton />
|
||||
)
|
||||
},
|
||||
{
|
||||
name: 'test-statistics',
|
||||
label: t`Test Statistics`,
|
||||
icon: <IconReportAnalytics />,
|
||||
content: (
|
||||
<TestStatisticsTable
|
||||
params={{
|
||||
pk: build.pk,
|
||||
apiEndpoint: ApiEndpoints.build_test_statistics
|
||||
}}
|
||||
/>
|
||||
),
|
||||
hidden: !build?.part_detail?.trackable
|
||||
},
|
||||
{
|
||||
name: 'attachments',
|
||||
label: t`Attachments`,
|
||||
|
@ -15,6 +15,7 @@ import {
|
||||
IconNotes,
|
||||
IconPackages,
|
||||
IconPaperclip,
|
||||
IconReportAnalytics,
|
||||
IconShoppingCart,
|
||||
IconStack2,
|
||||
IconTestPipe,
|
||||
@ -85,6 +86,7 @@ import { ManufacturerPartTable } from '../../tables/purchasing/ManufacturerPartT
|
||||
import { SupplierPartTable } from '../../tables/purchasing/SupplierPartTable';
|
||||
import { SalesOrderTable } from '../../tables/sales/SalesOrderTable';
|
||||
import { StockItemTable } from '../../tables/stock/StockItemTable';
|
||||
import { TestStatisticsTable } from '../../tables/stock/TestStatisticsTable';
|
||||
import PartPricingPanel from './PartPricingPanel';
|
||||
|
||||
/**
|
||||
@ -630,6 +632,22 @@ export default function PartDetail() {
|
||||
<Skeleton />
|
||||
)
|
||||
},
|
||||
{
|
||||
name: 'test_statistics',
|
||||
label: t`Test Statistics`,
|
||||
icon: <IconReportAnalytics />,
|
||||
hidden: !part.trackable,
|
||||
content: part?.pk ? (
|
||||
<TestStatisticsTable
|
||||
params={{
|
||||
pk: part.pk,
|
||||
apiEndpoint: ApiEndpoints.part_test_statistics
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Skeleton />
|
||||
)
|
||||
},
|
||||
{
|
||||
name: 'related_parts',
|
||||
label: t`Related Parts`,
|
||||
|
136
src/frontend/src/tables/stock/TestStatisticsTable.tsx
Normal file
136
src/frontend/src/tables/stock/TestStatisticsTable.tsx
Normal file
@ -0,0 +1,136 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { AddItemButton } from '../../components/buttons/AddItemButton';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { UserRoles } from '../../enums/Roles';
|
||||
import { stockLocationFields } from '../../forms/StockForms';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import {
|
||||
useCreateApiFormModal,
|
||||
useEditApiFormModal
|
||||
} from '../../hooks/UseForm';
|
||||
import { useTable } from '../../hooks/UseTable';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import { TableColumn } from '../Column';
|
||||
import { BooleanColumn, DescriptionColumn } from '../ColumnRenderers';
|
||||
import { TableFilter } from '../Filter';
|
||||
import { InvenTreeTable } from '../InvenTreeTable';
|
||||
|
||||
export function TestStatisticsTable({ params = {} }: { params?: any }) {
|
||||
const initialColumns: TableColumn[] = [];
|
||||
const [templateColumnList, setTemplateColumnList] = useState(initialColumns);
|
||||
|
||||
const testTemplateColumns: TableColumn[] = useMemo(() => {
|
||||
let data = templateColumnList ?? [];
|
||||
return data;
|
||||
}, [templateColumnList]);
|
||||
|
||||
const tableColumns: TableColumn[] = useMemo(() => {
|
||||
const firstColumn: TableColumn = {
|
||||
accessor: 'col_0',
|
||||
title: '',
|
||||
sortable: true,
|
||||
switchable: false,
|
||||
noWrap: true
|
||||
};
|
||||
|
||||
const lastColumn: TableColumn = {
|
||||
accessor: 'col_total',
|
||||
sortable: true,
|
||||
switchable: false,
|
||||
noWrap: true,
|
||||
title: t`Total`
|
||||
};
|
||||
|
||||
return [firstColumn, ...testTemplateColumns, lastColumn];
|
||||
}, [testTemplateColumns]);
|
||||
|
||||
function statCountString(count: number, total: number) {
|
||||
if (count > 0) {
|
||||
let percentage =
|
||||
' (' +
|
||||
((100.0 * count) / total).toLocaleString(undefined, {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 2
|
||||
}) +
|
||||
'%)';
|
||||
return count.toString() + percentage;
|
||||
}
|
||||
return '-';
|
||||
}
|
||||
|
||||
// Format the test results based on the returned data
|
||||
const formatRecords = useCallback((records: any[]): any[] => {
|
||||
// interface needed to being able to dynamically assign keys
|
||||
interface ResultRow {
|
||||
[key: string]: string;
|
||||
}
|
||||
// Construct a list of test templates
|
||||
let results: ResultRow[] = [
|
||||
{ id: 'row_passed', col_0: t`Passed` },
|
||||
{ id: 'row_failed', col_0: t`Failed` },
|
||||
{ id: 'row_total', col_0: t`Total` }
|
||||
];
|
||||
let columnIndex = 0;
|
||||
|
||||
columnIndex = 1;
|
||||
|
||||
let newColumns: TableColumn[] = [];
|
||||
for (let key in records[0]) {
|
||||
if (key == 'total') continue;
|
||||
let acc = 'col_' + columnIndex.toString();
|
||||
|
||||
const resultKeys = ['passed', 'failed', 'total'];
|
||||
|
||||
results[0][acc] = statCountString(
|
||||
records[0][key]['passed'],
|
||||
records[0][key]['total']
|
||||
);
|
||||
results[1][acc] = statCountString(
|
||||
records[0][key]['failed'],
|
||||
records[0][key]['total']
|
||||
);
|
||||
results[2][acc] = records[0][key]['total'].toString();
|
||||
|
||||
newColumns.push({
|
||||
accessor: 'col_' + columnIndex.toString(),
|
||||
title: key
|
||||
});
|
||||
columnIndex++;
|
||||
}
|
||||
|
||||
setTemplateColumnList(newColumns);
|
||||
|
||||
results[0]['col_total'] = statCountString(
|
||||
records[0]['total']['passed'],
|
||||
records[0]['total']['total']
|
||||
);
|
||||
results[1]['col_total'] = statCountString(
|
||||
records[0]['total']['failed'],
|
||||
records[0]['total']['total']
|
||||
);
|
||||
results[2]['col_total'] = records[0]['total']['total'].toString();
|
||||
|
||||
return results;
|
||||
}, []);
|
||||
|
||||
const table = useTable('teststatistics');
|
||||
|
||||
return (
|
||||
<InvenTreeTable
|
||||
url={apiUrl(params.apiEndpoint, params.pk) + '/'}
|
||||
tableState={table}
|
||||
columns={tableColumns}
|
||||
props={{
|
||||
dataFormatter: formatRecords,
|
||||
enableDownload: false,
|
||||
enableSearch: false,
|
||||
enablePagination: false
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user