@@ -767,9 +751,6 @@
});
});
});
- onPanelLoad("test-statistics", function() {
- prepareTestStatisticsTable('part', '{% url "api-test-statistics-by-part" part.pk %}')
- });
onPanelLoad("part-stock", function() {
$('#new-stock-item').click(function () {
diff --git a/src/backend/InvenTree/part/templates/part/part_sidebar.html b/src/backend/InvenTree/part/templates/part/part_sidebar.html
index 0c74a77c99..ddaf19db91 100644
--- a/src/backend/InvenTree/part/templates/part/part_sidebar.html
+++ b/src/backend/InvenTree/part/templates/part/part_sidebar.html
@@ -53,8 +53,6 @@
{% if part.testable %}
{% trans "Test Templates" as text %}
{% include "sidebar_item.html" with label="test-templates" text=text icon="fa-vial" %}
-{% trans "Test Statistics" as text %}
-{% include "sidebar_item.html" with label="test-statistics" text=text icon="fa-chart-line" %}
{% endif %}
{% if show_related %}
{% trans "Related Parts" as text %}
diff --git a/src/backend/InvenTree/stock/api.py b/src/backend/InvenTree/stock/api.py
index bf576c6e92..7734e101ab 100644
--- a/src/backend/InvenTree/stock/api.py
+++ b/src/backend/InvenTree/stock/api.py
@@ -12,7 +12,7 @@ from django.utils.translation import gettext_lazy as _
from django_filters import rest_framework as rest_filters
from drf_spectacular.types import OpenApiTypes
-from drf_spectacular.utils import extend_schema, extend_schema_field
+from drf_spectacular.utils import extend_schema_field
from rest_framework import permissions, status
from rest_framework.generics import GenericAPIView
from rest_framework.response import Response
@@ -1241,54 +1241,6 @@ class StockItemTestResultFilter(rest_filters.FilterSet):
return queryset.filter(template__key=key)
-class TestStatisticsFilter(rest_filters.FilterSet):
- """API filter for the filtering the test results belonging to a specific build."""
-
- class Meta:
- """Metaclass options."""
-
- model = StockItemTestResult
- fields = []
-
- # Created date filters
- finished_before = InvenTreeDateFilter(
- label='Finished before', field_name='finished_datetime', lookup_expr='lte'
- )
- finished_after = InvenTreeDateFilter(
- label='Finished after', field_name='finished_datetime', lookup_expr='gte'
- )
-
-
-class TestStatistics(GenericAPIView):
- """API endpoint for accessing a test statistics broken down by test templates."""
-
- queryset = StockItemTestResult.objects.all()
- serializer_class = StockSerializers.TestStatisticsSerializer
- pagination_class = None
- filterset_class = TestStatisticsFilter
- filter_backends = SEARCH_ORDER_FILTER_ALIAS
-
- @extend_schema(
- responses={200: StockSerializers.TestStatisticsSerializer(many=False)}
- )
- def get(self, request, pk, *args, **kwargs):
- """Return test execution count matrix broken down by test result."""
- instance = self.get_object()
- serializer = self.get_serializer(instance)
- if request.resolver_match.url_name == 'api-test-statistics-by-part':
- serializer.context['type'] = 'by-part'
- elif request.resolver_match.url_name == 'api-test-statistics-by-build':
- serializer.context['type'] = 'by-build'
- serializer.context['finished_datetime_after'] = self.request.query_params.get(
- 'finished_datetime_after'
- )
- serializer.context['finished_datetime_before'] = self.request.query_params.get(
- 'finished_datetime_before'
- )
- serializer.context['pk'] = pk
- return Response([serializer.data])
-
-
class StockItemTestResultList(StockItemTestResultMixin, ListCreateDestroyAPIView):
"""API endpoint for listing (and creating) a StockItemTestResult object."""
@@ -1666,27 +1618,3 @@ stock_api_urls = [
# Anything else
path('', StockList.as_view(), name='api-stock-list'),
]
-
-test_statistics_api_urls = [
- # Test statistics endpoints
- path(
- 'by-part/',
- include([
- path(
- '
/',
- TestStatistics.as_view(),
- name='api-test-statistics-by-part',
- )
- ]),
- ),
- path(
- 'by-build/',
- include([
- path(
- '/',
- TestStatistics.as_view(),
- name='api-test-statistics-by-build',
- )
- ]),
- ),
-]
diff --git a/src/backend/InvenTree/stock/models.py b/src/backend/InvenTree/stock/models.py
index 148c86057f..d4827e8fcb 100644
--- a/src/backend/InvenTree/stock/models.py
+++ b/src/backend/InvenTree/stock/models.py
@@ -34,7 +34,6 @@ import InvenTree.tasks
import report.mixins
import report.models
import stock.tasks
-from build import models as BuildModels
from common.icons import validate_icon
from common.settings import get_global_setting
from company import models as CompanyModels
@@ -48,7 +47,6 @@ from InvenTree.status_codes import (
)
from part import models as PartModels
from plugin.events import trigger_event
-from stock import models as StockModels # noqa: PLW0406
from stock.generators import generate_batch_code
from users.models import Owner
@@ -2579,67 +2577,6 @@ class StockItemTestResult(InvenTree.models.InvenTreeMetadataModel):
"""Return key for test."""
return InvenTree.helpers.generateTestKey(self.test_name)
- def calculate_test_statistics_for_test_template(
- self, query_base, test_template, ret, start, end
- ):
- """Helper function to calculate the passed/failed/total tests count per test template type."""
- query = query_base & Q(template=test_template.pk)
- if start is not None and end is not None:
- query = query & Q(started_datetime__range=(start, end))
- elif start is not None and end is None:
- query = query & Q(started_datetime__gt=start)
- elif start is None and end is not None:
- query = query & Q(started_datetime__lt=end)
-
- passed = StockModels.StockItemTestResult.objects.filter(
- query & Q(result=True)
- ).count()
- failed = StockModels.StockItemTestResult.objects.filter(
- query & ~Q(result=True)
- ).count()
- if test_template.test_name not in ret:
- ret[test_template.test_name] = {'passed': 0, 'failed': 0, 'total': 0}
- ret[test_template.test_name]['passed'] += passed
- ret[test_template.test_name]['failed'] += failed
- ret[test_template.test_name]['total'] += passed + failed
- ret['total']['passed'] += passed
- ret['total']['failed'] += failed
- ret['total']['total'] += passed + failed
- return ret
-
- def build_test_statistics(self, build_order_pk, start, end):
- """Generate a statistics matrix for each test template based on the test executions result counts."""
- build = BuildModels.Build.objects.get(pk=build_order_pk)
- if not build or not build.part.trackable:
- return {}
-
- test_templates = build.part.getTestTemplates()
- ret = {'total': {'passed': 0, 'failed': 0, 'total': 0}}
- for build_item in build.get_build_outputs():
- for test_template in test_templates:
- query_base = Q(stock_item=build_item)
- ret = self.calculate_test_statistics_for_test_template(
- query_base, test_template, ret, start, end
- )
- return ret
-
- def part_test_statistics(self, part_pk, start, end):
- """Generate a statistics matrix for each test template based on the test executions result counts."""
- part = PartModels.Part.objects.get(pk=part_pk)
-
- if not part or not part.trackable:
- return {}
-
- test_templates = part.getTestTemplates()
- ret = {'total': {'passed': 0, 'failed': 0, 'total': 0}}
- for bo in part.stock_entries():
- for test_template in test_templates:
- query_base = Q(stock_item=bo)
- ret = self.calculate_test_statistics_for_test_template(
- query_base, test_template, ret, start, end
- )
- return ret
-
stock_item = models.ForeignKey(
StockItem, on_delete=models.CASCADE, related_name='test_results'
)
diff --git a/src/backend/InvenTree/stock/serializers.py b/src/backend/InvenTree/stock/serializers.py
index 81b08d21d4..da958c67ef 100644
--- a/src/backend/InvenTree/stock/serializers.py
+++ b/src/backend/InvenTree/stock/serializers.py
@@ -904,36 +904,6 @@ class UninstallStockItemSerializer(serializers.Serializer):
item.uninstall_into_location(location, request.user, note)
-class TestStatisticsLineField(serializers.DictField):
- """DRF field definition for one column of the test statistics."""
-
- test_name = serializers.CharField()
- results = serializers.DictField(child=serializers.IntegerField(min_value=0))
-
-
-class TestStatisticsSerializer(serializers.Serializer):
- """DRF serializer class for the test statistics."""
-
- results = serializers.ListField(child=TestStatisticsLineField(), read_only=True)
-
- def to_representation(self, obj):
- """Just pass through the test statistics from the model."""
- if self.context['type'] == 'by-part':
- return obj.part_test_statistics(
- self.context['pk'],
- self.context['finished_datetime_after'],
- self.context['finished_datetime_before'],
- )
- elif self.context['type'] == 'by-build':
- return obj.build_test_statistics(
- self.context['pk'],
- self.context['finished_datetime_after'],
- self.context['finished_datetime_before'],
- )
-
- raise ValidationError(_('Unsupported statistic type: ' + self.context['type']))
-
-
class ConvertStockItemSerializer(serializers.Serializer):
"""DRF serializer class for converting a StockItem to a valid variant part."""
diff --git a/src/backend/InvenTree/stock/test_api.py b/src/backend/InvenTree/stock/test_api.py
index 397a559b3b..7535fcac59 100644
--- a/src/backend/InvenTree/stock/test_api.py
+++ b/src/backend/InvenTree/stock/test_api.py
@@ -2399,38 +2399,3 @@ class StockMetadataAPITest(InvenTreeAPITestCase):
'api-stock-item-metadata': StockItem,
}.items():
self.metatester(apikey, model)
-
-
-class StockStatisticsTest(StockAPITestCase):
- """Tests for the StockStatistics API endpoints."""
-
- fixtures = [*StockAPITestCase.fixtures, 'build']
-
- def test_test_statistics(self):
- """Test the test statistics API endpoints."""
- part = Part.objects.first()
- response = self.get(
- reverse('api-test-statistics-by-part', kwargs={'pk': part.pk}),
- {},
- expected_code=200,
- )
- self.assertEqual(response.data, [{}])
-
- # Now trackable part
- part1 = Part.objects.filter(trackable=True).first()
- response = self.get(
- reverse(
- 'api-test-statistics-by-part',
- kwargs={'pk': part1.stock_items.first().pk},
- ),
- {},
- expected_code=404,
- )
- self.assertIn('detail', response.data)
-
- # 105
-
- bld = build.models.Build.objects.first()
- url = reverse('api-test-statistics-by-build', kwargs={'pk': bld.pk})
- response = self.get(url, {}, expected_code=200)
- self.assertEqual(response.data, [{}])
diff --git a/src/backend/InvenTree/templates/js/translated/stock.js b/src/backend/InvenTree/templates/js/translated/stock.js
index 8347a69e18..b21fdcc128 100644
--- a/src/backend/InvenTree/templates/js/translated/stock.js
+++ b/src/backend/InvenTree/templates/js/translated/stock.js
@@ -74,7 +74,6 @@
duplicateStockItem,
editStockItem,
editStockLocation,
- filterTestStatisticsTableDateRange,
findStockItemBySerialNumber,
installStockItem,
loadInstalledInTable,
@@ -83,7 +82,6 @@
loadStockTestResultsTable,
loadStockTrackingTable,
loadTableFilters,
- prepareTestStatisticsTable,
mergeStockItems,
removeStockRow,
serializeStockItem,
@@ -3418,105 +3416,3 @@ function setStockStatus(items, options={}) {
}
});
}
-
-
-/*
- * Load TestStatistics table.
- */
-function loadTestStatisticsTable(table, prefix, url, options, filters = {}) {
- inventreeGet(url, filters, {
- async: true,
- success: function(data) {
- const keys = ['passed', 'failed', 'total']
- let header = '';
- let rows = []
- let passed= '';
- let failed = '';
- let total = '';
- $('.test-stat-result-cell').remove();
- $.each(data[0], function(key, value){
- if (key != "total") {
- header += '' + key + ' | ';
- keys.forEach(function(keyName) {
- var tdText = '-';
- if (value['total'] != '0' && value[keyName] != '0') {
- let percentage = ''
- if (keyName != 'total' && value[total] != 0) {
- percentage = ' (' + (100.0 * (parseFloat(value[keyName]) / parseFloat(value['total']))).toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 2}) + '%)';
- }
- tdText = value[keyName] + percentage;
- }
- rows[keyName] += '' + tdText + ' | ';
- })
- }
- });
- $('#' + prefix + '-test-statistics-table-header-id').after(header);
-
- keys.forEach(function(keyName) {
- let valueStr = data[0]['total'][keyName];
- if (keyName != 'total' && data[0]['total']['total'] != '0') {
- valueStr += ' (' + (100.0 * (parseFloat(valueStr) / parseFloat(data[0]['total']['total']))).toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 2}) + '%)';
- }
- rows[keyName] += '' + valueStr + ' | ';
- $('#' + prefix + '-test-statistics-table-body-' + keyName).after(rows[keyName]);
- });
- $('#' + prefix + '-test-statistics-table').show();
- setupFilterList(prefix + "teststatistics", table, "#filter-list-" + prefix + "teststatistics", options);
- },
- });
-}
-
-function prepareTestStatisticsTable(keyName, apiUrl)
-{
- let options = {
- custom_actions: [
- {
- icon: 'fa-calendar-week',
- actions: [
- {
- icon: 'fa-calendar-week',
- title: '{% trans "This week" %}',
- label: 'this-week',
- callback: function(data) {
- filterTestStatisticsTableDateRange(data, 'this-week', $("#test-statistics-table"), keyName + 'teststatistics', options);
- }
- },
- {
- icon: 'fa-calendar-week',
- title: '{% trans "This month" %}',
- label: 'this-month',
- callback: function(data) {
- filterTestStatisticsTableDateRange(data, 'this-month', $("#test-statistics-table"), keyName + 'teststatistics', options);
- }
- },
- ],
- }
- ],
- callback: function(table, filters, options) {
- loadTestStatisticsTable($("#test-statistics-table"), keyName, apiUrl, options, filters);
- }
- }
- setupFilterList(keyName + 'teststatistics', $("#test-statistics-table"), '#filter-list-' + keyName + 'teststatistics', options);
-
- // Load test statistics table
- loadTestStatisticsTable($("#test-statistics-table"), keyName, apiUrl, options);
-}
-
-function filterTestStatisticsTableDateRange(data, range, table, tableKey, options)
-{
- var startDateString = '';
- var d = new Date();
- if (range == "this-week") {
- startDateString = moment(new Date(d.getFullYear(), d.getMonth(), d.getDate() - (d.getDay() == 0 ? 6 : d.getDay() - 1))).format('YYYY-MM-DD');
- } else if (range == "this-month") {
- startDateString = moment(new Date(d.getFullYear(), d.getMonth(), 1)).format('YYYY-MM-DD');
- } else {
- console.warn(`Invalid range specified for filterTestStatisticsTableDateRange`);
- return;
- }
- var filters = addTableFilter(tableKey, 'finished_datetime_after', startDateString);
- removeTableFilter(tableKey, 'finished_datetime_before')
-
- reloadTableFilters(table, filters, options);
- setupFilterList(tableKey, table, "#filter-list-" + tableKey, options);
-}
diff --git a/src/backend/InvenTree/templates/js/translated/table_filters.js b/src/backend/InvenTree/templates/js/translated/table_filters.js
index f45c14e0b0..4706b493b5 100644
--- a/src/backend/InvenTree/templates/js/translated/table_filters.js
+++ b/src/backend/InvenTree/templates/js/translated/table_filters.js
@@ -469,20 +469,6 @@ function getStockTestTableFilters() {
};
}
-// Return a dictionary of filters for the "test statistics" table
-function getTestStatisticsTableFilters() {
-
- return {
- finished_datetime_after: {
- type: 'date',
- title: '{% trans "Interval start" %}',
- },
- finished_datetime_before: {
- type: 'date',
- title: '{% trans "Interval end" %}',
- }
- };
-}
// Return a dictionary of filters for the "stocktracking" table
function getStockTrackingTableFilters() {
@@ -870,8 +856,6 @@ function getAvailableTableFilters(tableKey) {
return getBuildItemTableFilters();
case 'buildlines':
return getBuildLineTableFilters();
- case 'buildteststatistics':
- return getTestStatisticsTableFilters();
case 'bom':
return getBOMTableFilters();
case 'category':
@@ -894,8 +878,6 @@ function getAvailableTableFilters(tableKey) {
return getPartTableFilters();
case 'parttests':
return getPartTestTemplateFilters();
- case 'partteststatistics':
- return getTestStatisticsTableFilters();
case 'plugins':
return getPluginTableFilters();
case 'purchaseorder':
diff --git a/src/backend/InvenTree/templates/test_statistics_table.html b/src/backend/InvenTree/templates/test_statistics_table.html
deleted file mode 100644
index 24361a3bf9..0000000000
--- a/src/backend/InvenTree/templates/test_statistics_table.html
+++ /dev/null
@@ -1,22 +0,0 @@
-{% load i18n %}
-{% load inventree_extras %}
-
-
-
-
-
-
-
-
-
-
- {% trans "Passed" %} |
-
-
- {% trans "Failed" %} |
-
-
- {% trans "Total" %} |
-
-
-
diff --git a/src/frontend/src/enums/ApiEndpoints.tsx b/src/frontend/src/enums/ApiEndpoints.tsx
index a630e908dd..8ca5dca0ae 100644
--- a/src/frontend/src/enums/ApiEndpoints.tsx
+++ b/src/frontend/src/enums/ApiEndpoints.tsx
@@ -138,8 +138,6 @@ export enum ApiEndpoints {
stock_uninstall = 'stock/:id/uninstall/',
stock_serialize = 'stock/:id/serialize/',
stock_return = 'stock/:id/return/',
- 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/',
diff --git a/src/frontend/src/pages/build/BuildDetail.tsx b/src/frontend/src/pages/build/BuildDetail.tsx
index 8b80d43b07..19c9fd7217 100644
--- a/src/frontend/src/pages/build/BuildDetail.tsx
+++ b/src/frontend/src/pages/build/BuildDetail.tsx
@@ -8,7 +8,6 @@ import {
IconList,
IconListCheck,
IconListNumbers,
- IconReportAnalytics,
IconSitemap
} from '@tabler/icons-react';
import { useMemo } from 'react';
@@ -57,7 +56,6 @@ import { BuildOrderTable } from '../../tables/build/BuildOrderTable';
import BuildOrderTestTable from '../../tables/build/BuildOrderTestTable';
import BuildOutputTable from '../../tables/build/BuildOutputTable';
import { StockItemTable } from '../../tables/stock/StockItemTable';
-import { TestStatisticsTable } from '../../tables/stock/TestStatisticsTable';
/**
* Detail page for a single Build Order
@@ -342,20 +340,6 @@ export default function BuildDetail() {
)
},
- {
- name: 'test-statistics',
- label: t`Test Statistics`,
- icon: ,
- content: (
-
- ),
- hidden: !build?.part_detail?.testable
- },
AttachmentPanel({
model_type: ModelType.build,
model_id: build.pk
diff --git a/src/frontend/src/pages/part/PartDetail.tsx b/src/frontend/src/pages/part/PartDetail.tsx
index 287e9ce7d7..cf12fb768c 100644
--- a/src/frontend/src/pages/part/PartDetail.tsx
+++ b/src/frontend/src/pages/part/PartDetail.tsx
@@ -21,7 +21,6 @@ import {
IconListTree,
IconLock,
IconPackages,
- IconReportAnalytics,
IconShoppingCart,
IconStack2,
IconTestPipe,
@@ -98,7 +97,6 @@ import { RelatedPartTable } from '../../tables/part/RelatedPartTable';
import { ReturnOrderTable } from '../../tables/sales/ReturnOrderTable';
import { SalesOrderTable } from '../../tables/sales/SalesOrderTable';
import { StockItemTable } from '../../tables/stock/StockItemTable';
-import { TestStatisticsTable } from '../../tables/stock/TestStatisticsTable';
import PartAllocationPanel from './PartAllocationPanel';
import PartPricingPanel from './PartPricingPanel';
import PartSchedulingDetail from './PartSchedulingDetail';
@@ -722,22 +720,6 @@ export default function PartDetail() {
)
},
- {
- name: 'test_statistics',
- label: t`Test Statistics`,
- icon: ,
- hidden: !part.testable,
- content: part?.pk ? (
-
- ) : (
-
- )
- },
{
name: 'related_parts',
label: t`Related Parts`,
diff --git a/src/frontend/src/tables/stock/TestStatisticsTable.tsx b/src/frontend/src/tables/stock/TestStatisticsTable.tsx
deleted file mode 100644
index ab6745a44e..0000000000
--- a/src/frontend/src/tables/stock/TestStatisticsTable.tsx
+++ /dev/null
@@ -1,124 +0,0 @@
-import { t } from '@lingui/macro';
-import { useCallback, useMemo, useState } from 'react';
-
-import { useTable } from '../../hooks/UseTable';
-import { apiUrl } from '../../states/ApiState';
-import type { TableColumn } from '../Column';
-import { InvenTreeTable } from '../InvenTreeTable';
-
-export function TestStatisticsTable({
- params = {}
-}: Readonly<{ params?: any }>) {
- const initialColumns: TableColumn[] = [];
- const [templateColumnList, setTemplateColumnList] = useState(initialColumns);
-
- const testTemplateColumns: TableColumn[] = useMemo(() => {
- const 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) {
- const 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
- const 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;
-
- const newColumns: TableColumn[] = [];
- for (const key in records[0]) {
- if (key == 'total') continue;
- const 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 (
-
- );
-}