2
0
mirror of https://github.com/inventree/InvenTree.git synced 2026-04-04 10:31:03 +00:00

[API] Enhanced filtering for generic parameters (#11383)

* Enhanced filtering for parameter templates

- Allow filtering by model ID as well as model type

* Enhanced filtering for ParameterTemplate

- Required for the parameteric data tables
- Enable filtering by base model ID
- Enable filtering by related model ID

* Bump API version

* Remove outdated comments

* Fix typo

* Remove debug statement

* Added unit tests

* Playwright tests

* Fix unit test

* Bump requirements

* Revert
This commit is contained in:
Oliver
2026-02-20 09:32:33 +11:00
committed by GitHub
parent fc1bfe876c
commit dd423dccd6
12 changed files with 524 additions and 88 deletions

View File

@@ -101,12 +101,18 @@ function ParameterCell({
*/
export default function ParametricDataTable({
modelType,
modelId,
relatedModel,
relatedModelId,
endpoint,
queryParams,
customFilters,
customColumns
}: {
modelType: ModelType;
modelId?: number;
relatedModel?: string;
relatedModelId?: number;
endpoint: ApiEndpoints | string;
queryParams?: Record<string, any>;
customFilters?: TableFilter[];
@@ -125,8 +131,12 @@ export default function ParametricDataTable({
.get(apiUrl(ApiEndpoints.parameter_template_list), {
params: {
active: true,
ordering: 'name',
for_model: modelType,
exists_for_model: modelType
exists_for_model: modelType,
exists_for_model_id: modelId ?? undefined,
exists_for_related_model: relatedModel ?? undefined,
exists_for_related_model_id: relatedModelId ?? undefined
}
})
.then((response) => response.data);

View File

@@ -56,6 +56,8 @@ export default function ParametricPartTable({
return (
<ParametricDataTable
modelType={ModelType.part}
relatedModel={'category'}
relatedModelId={categoryId}
endpoint={ApiEndpoints.part_list}
customColumns={customColumns}
customFilters={customFilters}

View File

@@ -180,3 +180,33 @@ export const toggleColumnSorting = async (page: Page, columnName: string) => {
await page.waitForTimeout(50);
await page.waitForLoadState('networkidle');
};
// Display the 'table' view
export const showTableView = async (page: Page) => {
await page
.getByRole('button', { name: 'segmented-icon-control-table' })
.click();
await page.waitForLoadState('networkidle');
};
// Display the 'parameteric' view
export const showParametricView = async (page: Page) => {
await page
.getByRole('button', { name: 'segmented-icon-control-parametric' })
.click();
await page.waitForLoadState('networkidle');
};
// Display the 'calendar' view
export const showCalendarView = async (page: Page) => {
await page
.getByRole('button', { name: 'segmented-icon-control-calendar' })
.click();
await page.waitForLoadState('networkidle');
};
// Check for an expected number of columns in the visible table
export const expectTableColumnCount = async (page: Page, count: number) => {
const columns = page.locator('table thead tr th');
await expect(columns).toHaveCount(count);
};

View File

@@ -7,7 +7,10 @@ import {
getRowFromCell,
loadTab,
navigate,
setTableChoiceFilter
setTableChoiceFilter,
showCalendarView,
showParametricView,
showTableView
} from '../helpers.ts';
import { doCachedLogin } from '../login.ts';
@@ -17,18 +20,10 @@ test('Build - Index', async ({ browser }) => {
await loadTab(page, 'Build Orders');
// Ensure all data views are available
await page
.getByRole('button', { name: 'segmented-icon-control-parametric' })
.click();
await page
.getByRole('button', { name: 'segmented-icon-control-calendar' })
.click();
await showParametricView(page);
await showCalendarView(page);
await page.getByRole('button', { name: 'action-button-next-month' }).click();
await page
.getByRole('button', { name: 'segmented-icon-control-table' })
.click();
await showTableView(page);
});
test('Build Order - Basic Tests', async ({ browser }) => {

View File

@@ -1,5 +1,10 @@
import { test } from '../baseFixtures.js';
import { clickOnParamFilter, loadTab, navigate } from '../helpers.js';
import {
clickOnParamFilter,
loadTab,
navigate,
showParametricView
} from '../helpers.js';
import { doCachedLogin } from '../login.js';
test('Company', async ({ browser }) => {
@@ -49,9 +54,7 @@ test('Company - Parameters', async ({ browser }) => {
});
// Show parametric view
await page
.getByRole('button', { name: 'segmented-icon-control-parametric' })
.click();
await showParametricView(page);
// Filter by "payment terms" parameter value
await clickOnParamFilter(page, 'Payment Terms');

View File

@@ -4,10 +4,13 @@ import {
clickOnParamFilter,
clickOnRowMenu,
deletePart,
expectTableColumnCount,
getRowFromCell,
loadTab,
navigate,
setTableChoiceFilter
setTableChoiceFilter,
showParametricView,
showTableView
} from '../helpers';
import { doCachedLogin } from '../login';
import { setPluginState, setSettingState } from '../settings';
@@ -499,6 +502,58 @@ test('Parts - Attachments', async ({ browser }) => {
await page.getByRole('button', { name: 'Cancel' }).click();
});
test('Parts - Parameters by Category', async ({ browser }) => {
const page = await doCachedLogin(browser, { url: 'part/category/4/parts' });
await showParametricView(page);
// Check for expected parameter columns
for (const col of [
'Total Stock',
'Capacitance [F]',
'Power [W]',
'Resistance [ohms]'
]) {
await page.getByRole('button', { name: col }).waitFor();
}
await expectTableColumnCount(page, 9);
// Now let's go to the "resistors" category
await navigate(page, 'part/category/5/parts');
await showParametricView(page);
// Fewer parameter templates displayed here
await expectTableColumnCount(page, 7);
// Check for expected parameter columns
for (const col of [
'Total Stock',
'Tolerance [percent]',
'Power [W]',
'Resistance [ohms]'
]) {
await page.getByRole('button', { name: col }).waitFor();
}
// Finally, let's go to the "capacitors" category, which has a different set of parameter templates
await navigate(page, 'part/category/6/parts');
await showParametricView(page);
await expectTableColumnCount(page, 7);
for (const col of [
'Total Stock',
'Tolerance [percent]',
'Polarized',
'Capacitance [F]'
]) {
await page.getByRole('button', { name: col }).waitFor();
}
// Reset to the table view
await showTableView(page);
});
test('Parts - Parameters', async ({ browser }) => {
const page = await doCachedLogin(browser, { url: 'part/69/parameters' });
@@ -562,10 +617,8 @@ test('Parts - Parameter Filtering', async ({ browser }) => {
const page = await doCachedLogin(browser, { url: 'part/' });
await loadTab(page, 'Parts', true);
await page
.getByRole('button', { name: 'segmented-icon-control-parametric' })
.click();
await showParametricView(page);
await clearTableFilters(page);
// All parts should be available (no filters applied)

View File

@@ -9,7 +9,10 @@ import {
loadTab,
navigate,
openFilterDrawer,
setTableChoiceFilter
setTableChoiceFilter,
showCalendarView,
showParametricView,
showTableView
} from '../helpers.ts';
import { doCachedLogin } from '../login.ts';
@@ -18,26 +21,14 @@ test('Purchasing - Index', async ({ browser }) => {
// Purchase Orders tab
await loadTab(page, 'Purchase Orders');
await page
.getByRole('button', { name: 'segmented-icon-control-parametric' })
.click();
await page
.getByRole('button', { name: 'segmented-icon-control-calendar' })
.click();
await page.getByRole('button', { name: 'calendar-select-month' }).waitFor();
await page
.getByRole('button', { name: 'segmented-icon-control-table' })
.click();
await showParametricView(page);
await showCalendarView(page);
await showTableView(page);
// Suppliers tab
await loadTab(page, 'Suppliers');
await page
.getByRole('button', { name: 'segmented-icon-control-parametric' })
.click();
await page
.getByRole('button', { name: 'segmented-icon-control-table' })
.click();
await showParametricView(page);
await showTableView(page);
// Check for expected values
await clearTableFilters(page);
@@ -45,12 +36,8 @@ test('Purchasing - Index', async ({ browser }) => {
// Supplier parts tab
await loadTab(page, 'Supplier Parts');
await page
.getByRole('button', { name: 'segmented-icon-control-parametric' })
.click();
await page
.getByRole('button', { name: 'segmented-icon-control-table' })
.click();
await showParametricView(page);
await showTableView(page);
// Check for expected values
await clearTableFilters(page);
@@ -60,12 +47,8 @@ test('Purchasing - Index', async ({ browser }) => {
// Manufacturers tab
await loadTab(page, 'Manufacturers');
await page
.getByRole('button', { name: 'segmented-icon-control-parametric' })
.click();
await page
.getByRole('button', { name: 'segmented-icon-control-table' })
.click();
await showParametricView(page);
await showTableView(page);
// Check for expected values
await clearTableFilters(page);
@@ -76,12 +59,8 @@ test('Purchasing - Index', async ({ browser }) => {
// Manufacturer parts tab
await loadTab(page, 'Manufacturer Parts');
await page
.getByRole('button', { name: 'segmented-icon-control-parametric' })
.click();
await page
.getByRole('button', { name: 'segmented-icon-control-table' })
.click();
await showParametricView(page);
await showTableView(page);
// Check for expected values
await clearTableFilters(page);

View File

@@ -5,7 +5,10 @@ import {
clickOnRowMenu,
globalSearch,
loadTab,
setTableChoiceFilter
setTableChoiceFilter,
showCalendarView,
showParametricView,
showTableView
} from '../helpers.ts';
import { doCachedLogin } from '../login.ts';
@@ -18,15 +21,9 @@ test('Sales Orders - Tabs', async ({ browser }) => {
await loadTab(page, 'Sales Orders');
await page.waitForURL('**/web/sales/index/salesorders');
await page
.getByRole('button', { name: 'segmented-icon-control-parametric' })
.click();
await page
.getByRole('button', { name: 'segmented-icon-control-calendar' })
.click();
await page
.getByRole('button', { name: 'segmented-icon-control-table' })
.click();
await showParametricView(page);
await showCalendarView(page);
await showTableView(page);
// Pending Shipments panel
await loadTab(page, 'Pending Shipments');
@@ -37,25 +34,15 @@ test('Sales Orders - Tabs', async ({ browser }) => {
await loadTab(page, 'Return Orders');
await page.getByRole('cell', { name: 'NOISE-COMPLAINT' }).waitFor();
await page
.getByRole('button', { name: 'segmented-icon-control-parametric' })
.click();
await page
.getByRole('button', { name: 'segmented-icon-control-calendar' })
.click();
await page
.getByRole('button', { name: 'segmented-icon-control-table' })
.click();
await showParametricView(page);
await showCalendarView(page);
await showTableView(page);
// Customers
await loadTab(page, 'Customers');
await page
.getByRole('button', { name: 'segmented-icon-control-parametric' })
.click();
await page
.getByRole('button', { name: 'segmented-icon-control-table' })
.click();
await showParametricView(page);
await showTableView(page);
await page.getByText('Customer A').click();
await loadTab(page, 'Notes');