mirror of
https://github.com/inventree/InvenTree.git
synced 2026-07-05 06:32:55 +00:00
SelectionList Updates (#12139)
* Adjust panel layout * edit list on click * Optionally fetch selection list items * Display in DetailDrawer * Fix component locations * Refactor entry table * Add new entry * Disable if locked * Only validate choices if provided via API * Mark "choices" as read-only * Prevent delete of locked items * Add more API unit tests * Bump API version * Adjust unit tests * Default include choices * Updated playwright test * Improve test robustness
This commit is contained in:
@@ -284,3 +284,22 @@ export function useParameterFields({
|
||||
user
|
||||
]);
|
||||
}
|
||||
|
||||
export function selectionListFields(): ApiFormFieldSet {
|
||||
return {
|
||||
name: {},
|
||||
description: {},
|
||||
active: {},
|
||||
source_plugin: {},
|
||||
source_string: {}
|
||||
};
|
||||
}
|
||||
|
||||
export function selectionEntryFields(): ApiFormFieldSet {
|
||||
return {
|
||||
value: {},
|
||||
label: {},
|
||||
description: {},
|
||||
active: {}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
import type { ApiFormFieldSet, ApiFormFieldType } from '@lib/types/Forms';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { Table } from '@mantine/core';
|
||||
import { useMemo } from 'react';
|
||||
import RemoveRowButton from '../components/buttons/RemoveRowButton';
|
||||
import { StandaloneField } from '../components/forms/StandaloneField';
|
||||
import type { TableFieldRowProps } from '../components/forms/fields/TableField';
|
||||
|
||||
function BuildAllocateLineRow({
|
||||
props
|
||||
}: Readonly<{
|
||||
props: TableFieldRowProps;
|
||||
}>) {
|
||||
const valueField: ApiFormFieldType = useMemo(() => {
|
||||
return {
|
||||
field_type: 'string',
|
||||
name: 'value',
|
||||
required: true,
|
||||
value: props.item.value,
|
||||
onValueChange: (value: any) => {
|
||||
props.changeFn(props.idx, 'value', value);
|
||||
}
|
||||
};
|
||||
}, [props]);
|
||||
|
||||
const labelField: ApiFormFieldType = useMemo(() => {
|
||||
return {
|
||||
field_type: 'string',
|
||||
name: 'label',
|
||||
required: true,
|
||||
value: props.item.label,
|
||||
onValueChange: (value: any) => {
|
||||
props.changeFn(props.idx, 'label', value);
|
||||
}
|
||||
};
|
||||
}, [props]);
|
||||
|
||||
const descriptionField: ApiFormFieldType = useMemo(() => {
|
||||
return {
|
||||
field_type: 'string',
|
||||
name: 'description',
|
||||
required: true,
|
||||
value: props.item.description,
|
||||
onValueChange: (value: any) => {
|
||||
props.changeFn(props.idx, 'description', value);
|
||||
}
|
||||
};
|
||||
}, [props]);
|
||||
|
||||
const activeField: ApiFormFieldType = useMemo(() => {
|
||||
return {
|
||||
field_type: 'boolean',
|
||||
name: 'active',
|
||||
required: true,
|
||||
value: props.item.active,
|
||||
onValueChange: (value: any) => {
|
||||
props.changeFn(props.idx, 'active', value);
|
||||
}
|
||||
};
|
||||
}, [props]);
|
||||
|
||||
return (
|
||||
<Table.Tr key={`table-row-${props.item.id ?? props.idx}`}>
|
||||
<Table.Td>
|
||||
<StandaloneField fieldName='value' fieldDefinition={valueField} />
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<StandaloneField fieldName='label' fieldDefinition={labelField} />
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<StandaloneField
|
||||
fieldName='description'
|
||||
fieldDefinition={descriptionField}
|
||||
/>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<StandaloneField fieldName='active' fieldDefinition={activeField} />
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<RemoveRowButton onClick={() => props.removeFn(props.idx)} />
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
);
|
||||
}
|
||||
|
||||
export function selectionListFields(): ApiFormFieldSet {
|
||||
return {
|
||||
name: {},
|
||||
description: {},
|
||||
active: {},
|
||||
locked: {},
|
||||
source_plugin: {},
|
||||
source_string: {},
|
||||
choices: {
|
||||
label: t`Entries`,
|
||||
description: t`List of entries to choose from`,
|
||||
field_type: 'table',
|
||||
value: [],
|
||||
headers: [
|
||||
{ title: t`Value` },
|
||||
{ title: t`Label` },
|
||||
{ title: t`Description` },
|
||||
{ title: t`Active` }
|
||||
],
|
||||
modelRenderer: (row: TableFieldRowProps) => (
|
||||
<BuildAllocateLineRow props={row} />
|
||||
),
|
||||
addRow: () => {
|
||||
return {
|
||||
value: '',
|
||||
label: '',
|
||||
description: '',
|
||||
active: true
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -32,6 +32,8 @@ import { PanelGroup } from '../../../../components/panels/PanelGroup';
|
||||
import { GlobalSettingList } from '../../../../components/settings/SettingList';
|
||||
import { Loadable } from '../../../../functions/loading';
|
||||
import { useUserState } from '../../../../states/UserState';
|
||||
import ParameterTemplateTable from '../../../../tables/general/ParameterTemplateTable';
|
||||
import SelectionListTable from '../../../../tables/settings/SelectionListTable';
|
||||
|
||||
const ReportTemplatePanel = Loadable(
|
||||
lazy(() => import('./ReportTemplatePanel'))
|
||||
@@ -69,8 +71,6 @@ const MachineManagementPanel = Loadable(
|
||||
lazy(() => import('./MachineManagementPanel'))
|
||||
);
|
||||
|
||||
const ParameterPanel = Loadable(lazy(() => import('./ParameterPanel')));
|
||||
|
||||
const ErrorReportTable = Loadable(
|
||||
lazy(() => import('../../../../tables/settings/ErrorTable'))
|
||||
);
|
||||
@@ -192,7 +192,14 @@ export default function AdminCenter() {
|
||||
name: 'parameters',
|
||||
label: t`Parameters`,
|
||||
icon: <IconList />,
|
||||
content: <ParameterPanel />,
|
||||
content: <ParameterTemplateTable />,
|
||||
hidden: !user.hasViewRole(UserRoles.part)
|
||||
},
|
||||
{
|
||||
name: 'selection-lists',
|
||||
label: t`Selection Lists`,
|
||||
icon: <IconList />,
|
||||
content: <SelectionListTable />,
|
||||
hidden: !user.hasViewRole(UserRoles.part)
|
||||
},
|
||||
{
|
||||
@@ -272,6 +279,7 @@ export default function AdminCenter() {
|
||||
id: 'plm',
|
||||
label: t`PLM`,
|
||||
panelIDs: [
|
||||
'selection-lists',
|
||||
'parameters',
|
||||
'category-parameters',
|
||||
'location-types',
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { Accordion } from '@mantine/core';
|
||||
|
||||
import { StylishText } from '@lib/components/StylishText';
|
||||
import ParameterTemplateTable from '../../../../tables/general/ParameterTemplateTable';
|
||||
import SelectionListTable from '../../../../tables/part/SelectionListTable';
|
||||
|
||||
export default function ParameterPanel() {
|
||||
return (
|
||||
<Accordion multiple defaultValue={['parameter-templates']}>
|
||||
<Accordion.Item value='parameter-templates' key='parameter-templates'>
|
||||
<Accordion.Control>
|
||||
<StylishText size='lg'>{t`Parameter Templates`}</StylishText>
|
||||
</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<ParameterTemplateTable />
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item value='selection-lists' key='selection-lists'>
|
||||
<Accordion.Control>
|
||||
<StylishText size='lg'>{t`Selection Lists`}</StylishText>
|
||||
</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<SelectionListTable />
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { Accordion, Alert, LoadingOverlay, Stack } from '@mantine/core';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { AddItemButton } from '@lib/components/AddItemButton';
|
||||
import { RowDeleteAction, RowEditAction } from '@lib/components/RowActions';
|
||||
import { StylishText } from '@lib/components/StylishText';
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import useTable from '@lib/hooks/UseTable';
|
||||
import type { ApiFormFieldSet } from '@lib/types/Forms';
|
||||
import type { TableColumn } from '@lib/types/Tables';
|
||||
import { IconLock } from '@tabler/icons-react';
|
||||
import { EditApiForm } from '../../components/forms/ApiForm';
|
||||
import {
|
||||
selectionEntryFields,
|
||||
selectionListFields
|
||||
} from '../../forms/CommonForms';
|
||||
import {
|
||||
useCreateApiFormModal,
|
||||
useDeleteApiFormModal,
|
||||
useEditApiFormModal
|
||||
} from '../../hooks/UseForm';
|
||||
import { useInstance } from '../../hooks/UseInstance';
|
||||
import { BooleanColumn, DescriptionColumn } from '../ColumnRenderers';
|
||||
import { InvenTreeTable } from '../InvenTreeTable';
|
||||
|
||||
function SelectionListEntriesTable({
|
||||
id,
|
||||
locked
|
||||
}: Readonly<{ id: string; locked: boolean }>) {
|
||||
const table = useTable('selectionlist-entries');
|
||||
|
||||
const columns: TableColumn[] = useMemo(
|
||||
() => [
|
||||
{ accessor: 'value', sortable: true },
|
||||
{ accessor: 'label', sortable: true },
|
||||
DescriptionColumn({}),
|
||||
BooleanColumn({ accessor: 'active' })
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
const entryFields: ApiFormFieldSet = selectionEntryFields();
|
||||
|
||||
const [selectedEntry, setSelectedEntry] = useState<number | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
// Construct the dynamic URL to edit (or delete) the selected entry
|
||||
const selectedEntryUrl: string = useMemo(() => {
|
||||
let url = apiUrl(ApiEndpoints.selectionentry_list, undefined, { id });
|
||||
|
||||
if (selectedEntry) {
|
||||
url += `${selectedEntry}/`;
|
||||
}
|
||||
|
||||
return url;
|
||||
}, [id, selectedEntry]);
|
||||
|
||||
const createEntry = useCreateApiFormModal({
|
||||
url: ApiEndpoints.selectionentry_list,
|
||||
pathParams: { id },
|
||||
title: t`Add Selection Entry`,
|
||||
fields: {
|
||||
...entryFields,
|
||||
list: {
|
||||
value: id,
|
||||
hidden: true
|
||||
}
|
||||
},
|
||||
table: table
|
||||
});
|
||||
|
||||
const editEntry = useEditApiFormModal({
|
||||
url: selectedEntryUrl,
|
||||
title: t`Edit Selection Entry`,
|
||||
fields: entryFields,
|
||||
table: table
|
||||
});
|
||||
|
||||
const deleteEntry = useDeleteApiFormModal({
|
||||
url: selectedEntryUrl,
|
||||
title: t`Delete Selection Entry`,
|
||||
table: table
|
||||
});
|
||||
|
||||
const tableActions = useMemo(() => {
|
||||
if (locked) return [];
|
||||
return [
|
||||
<AddItemButton
|
||||
key='add-entry'
|
||||
onClick={() => createEntry.open()}
|
||||
tooltip={t`Add Entry`}
|
||||
/>
|
||||
];
|
||||
}, [locked, createEntry]);
|
||||
|
||||
const rowActions = useCallback(
|
||||
(record: any) => {
|
||||
if (locked) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
RowEditAction({
|
||||
onClick: () => {
|
||||
console.log('record:', record);
|
||||
setSelectedEntry(record.id);
|
||||
editEntry.open();
|
||||
}
|
||||
}),
|
||||
RowDeleteAction({
|
||||
onClick: () => {
|
||||
console.log('record:', record);
|
||||
setSelectedEntry(record.id);
|
||||
deleteEntry.open();
|
||||
}
|
||||
})
|
||||
];
|
||||
},
|
||||
[editEntry, deleteEntry, locked]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{createEntry.modal}
|
||||
{editEntry.modal}
|
||||
{deleteEntry.modal}
|
||||
<InvenTreeTable
|
||||
url={apiUrl(ApiEndpoints.selectionentry_list, undefined, { id })}
|
||||
tableState={table}
|
||||
columns={columns}
|
||||
props={{
|
||||
enableSearch: true,
|
||||
enableSelection: !locked,
|
||||
enableBulkDelete: !locked,
|
||||
tableActions: tableActions,
|
||||
rowActions: locked ? undefined : rowActions
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function SelectionListDrawer({
|
||||
id,
|
||||
refreshTable
|
||||
}: Readonly<{
|
||||
id: string;
|
||||
refreshTable: () => void;
|
||||
}>) {
|
||||
const {
|
||||
instance,
|
||||
refreshInstance,
|
||||
instanceQuery: { isFetching }
|
||||
} = useInstance({
|
||||
endpoint: ApiEndpoints.selectionlist_list,
|
||||
pk: id
|
||||
});
|
||||
|
||||
const selectionFields: ApiFormFieldSet = useMemo(() => {
|
||||
return selectionListFields();
|
||||
}, []);
|
||||
|
||||
if (isFetching) {
|
||||
return <LoadingOverlay visible />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
{instance.locked && (
|
||||
<Alert color='red' icon={<IconLock />} title={t`Locked`}>
|
||||
{t`This selection list is locked and cannot be edited.`}
|
||||
</Alert>
|
||||
)}
|
||||
<Accordion defaultValue={['details', 'entries']} multiple>
|
||||
<Accordion.Item key='details' value='details'>
|
||||
<Accordion.Control>
|
||||
<StylishText size='lg'>{t`Selection List Details`}</StylishText>
|
||||
</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<fieldset
|
||||
disabled={instance.locked}
|
||||
style={{ border: 'none', padding: 0, margin: 0 }}
|
||||
>
|
||||
<EditApiForm
|
||||
props={{
|
||||
url: ApiEndpoints.selectionlist_list,
|
||||
pk: id,
|
||||
fields: selectionFields,
|
||||
onFormSuccess: () => {
|
||||
refreshTable();
|
||||
refreshInstance();
|
||||
}
|
||||
}}
|
||||
id={`selection-list-drawer-${id}`}
|
||||
/>
|
||||
</fieldset>
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item key='entries' value='entries'>
|
||||
<Accordion.Control>
|
||||
<StylishText size='lg'>{t`Selection List Entries`}</StylishText>
|
||||
</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<SelectionListEntriesTable
|
||||
id={id}
|
||||
locked={instance?.locked ?? false}
|
||||
/>
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
+20
-15
@@ -1,5 +1,6 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { AddItemButton } from '@lib/components/AddItemButton';
|
||||
import {
|
||||
@@ -7,26 +8,28 @@ import {
|
||||
RowDeleteAction,
|
||||
RowEditAction
|
||||
} from '@lib/components/RowActions';
|
||||
import { DetailDrawer } from '@lib/components/nav/DetailDrawer';
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { UserRoles } from '@lib/enums/Roles';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import useTable from '@lib/hooks/UseTable';
|
||||
import type { TableColumn } from '@lib/types/Tables';
|
||||
import { selectionListFields } from '../../forms/selectionListFields';
|
||||
import { selectionListFields } from '../../forms/CommonForms';
|
||||
import {
|
||||
useCreateApiFormModal,
|
||||
useDeleteApiFormModal,
|
||||
useEditApiFormModal
|
||||
useDeleteApiFormModal
|
||||
} from '../../hooks/UseForm';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import { BooleanColumn, DescriptionColumn } from '../ColumnRenderers';
|
||||
import { InvenTreeTable } from '../InvenTreeTable';
|
||||
import SelectionListDrawer from './SelectionListDrawer';
|
||||
|
||||
/**
|
||||
* Table for displaying list of selectionlist items
|
||||
*/
|
||||
export default function SelectionListTable() {
|
||||
const table = useTable('selectionlist');
|
||||
const navigate = useNavigate();
|
||||
|
||||
const user = useUserState();
|
||||
|
||||
@@ -70,14 +73,6 @@ export default function SelectionListTable() {
|
||||
number | undefined
|
||||
>(undefined);
|
||||
|
||||
const editSelectionList = useEditApiFormModal({
|
||||
url: ApiEndpoints.selectionlist_list,
|
||||
pk: selectedSelectionList,
|
||||
title: t`Edit Selection List`,
|
||||
fields: selectionListFields(),
|
||||
table: table
|
||||
});
|
||||
|
||||
const deleteSelectionList = useDeleteApiFormModal({
|
||||
url: ApiEndpoints.selectionlist_list,
|
||||
pk: selectedSelectionList,
|
||||
@@ -91,8 +86,7 @@ export default function SelectionListTable() {
|
||||
RowEditAction({
|
||||
hidden: !user.hasChangeRole(UserRoles.admin),
|
||||
onClick: () => {
|
||||
setSelectedSelectionList(record.pk);
|
||||
editSelectionList.open();
|
||||
navigate(`${record.pk}/`);
|
||||
}
|
||||
}),
|
||||
RowDeleteAction({
|
||||
@@ -120,8 +114,16 @@ export default function SelectionListTable() {
|
||||
return (
|
||||
<>
|
||||
{newSelectionList.modal}
|
||||
{editSelectionList.modal}
|
||||
{deleteSelectionList.modal}
|
||||
<DetailDrawer
|
||||
title={t`Selection List`}
|
||||
size='xl'
|
||||
renderContent={(id) =>
|
||||
id ? (
|
||||
<SelectionListDrawer id={id} refreshTable={table.refreshTable} />
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
<InvenTreeTable
|
||||
url={apiUrl(ApiEndpoints.selectionlist_list)}
|
||||
tableState={table}
|
||||
@@ -129,7 +131,10 @@ export default function SelectionListTable() {
|
||||
props={{
|
||||
rowActions: rowActions,
|
||||
tableActions: tableActions,
|
||||
enableDownload: true
|
||||
enableDownload: true,
|
||||
onRowClick: (record) => {
|
||||
navigate(`${record.pk}/`);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
@@ -77,6 +77,7 @@ test('Parts - Image Selection', async ({ browser }) => {
|
||||
await page
|
||||
.getByRole('tabpanel', { name: 'Part Details' })
|
||||
.locator('img')
|
||||
.first()
|
||||
.hover();
|
||||
await page
|
||||
.getByRole('button', { name: 'action-button-select-from-' })
|
||||
@@ -94,6 +95,7 @@ test('Parts - Image Selection', async ({ browser }) => {
|
||||
await page
|
||||
.getByRole('tabpanel', { name: 'Part Details' })
|
||||
.locator('img')
|
||||
.first()
|
||||
.hover();
|
||||
await page
|
||||
.getByRole('button', { name: 'action-button-delete-image' })
|
||||
|
||||
@@ -408,29 +408,8 @@ test('Settings - Admin - Parameter', async ({ browser }) => {
|
||||
await page.getByRole('button', { name: 'admin' }).click();
|
||||
await page.getByRole('menuitem', { name: 'Admin Center' }).click();
|
||||
|
||||
await loadTab(page, 'Parameters', true);
|
||||
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Clean old template data if exists
|
||||
await page
|
||||
.getByRole('cell', { name: 'my custom parameter', exact: true })
|
||||
.waitFor({ timeout: 500 })
|
||||
.then(async (cell) => {
|
||||
await page
|
||||
.getByRole('cell', { name: 'my custom parameter' })
|
||||
.locator('..')
|
||||
.getByLabel('row-action-menu-')
|
||||
.click();
|
||||
await page.getByRole('menuitem', { name: 'Delete' }).click();
|
||||
await page.getByRole('button', { name: 'Delete' }).click();
|
||||
})
|
||||
.catch(() => {});
|
||||
|
||||
// Allow time for the table to load
|
||||
await page.getByRole('button', { name: 'Selection Lists' }).click();
|
||||
await page.waitForLoadState('networkidle');
|
||||
await loadTab(page, 'Selection Lists');
|
||||
|
||||
// Check for expected entry
|
||||
await page.getByRole('cell', { name: 'Animals', exact: true }).waitFor();
|
||||
@@ -456,38 +435,65 @@ test('Settings - Admin - Parameter', async ({ browser }) => {
|
||||
await page.getByLabel('action-button-add-selection-').click();
|
||||
await page.getByLabel('text-field-name').fill('some list');
|
||||
await page.getByLabel('text-field-description').fill('Listdescription');
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
|
||||
// Select the new list to edit entries
|
||||
await page.getByRole('cell', { name: 'some list' }).click();
|
||||
await page.getByRole('button', { name: 'Selection List Entries' }).waitFor();
|
||||
await page.getByRole('button', { name: 'Selection List Details' }).click();
|
||||
|
||||
// Add an entry to the selection list
|
||||
await page.getByRole('button', { name: 'action-button-add-new-row' }).click();
|
||||
await page.getByRole('button', { name: 'action-button-add-entry' }).click();
|
||||
await page.getByRole('textbox', { name: 'text-field-value' }).fill('HW');
|
||||
await page
|
||||
.getByRole('textbox', { name: 'text-field-label' })
|
||||
.fill('Hardwood');
|
||||
|
||||
await page
|
||||
.getByRole('row', { name: 'boolean-field-active action-' })
|
||||
.getByLabel('text-field-description')
|
||||
.getByRole('textbox', { name: 'text-field-description' })
|
||||
.fill('Hardwood materials');
|
||||
await page.getByRole('cell', { name: 'boolean-field-active' }).click();
|
||||
|
||||
await page.waitForTimeout(100);
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
await page.getByRole('cell', { name: 'some list' }).waitFor();
|
||||
|
||||
await page.getByLabel('action-button-add-parameter').waitFor();
|
||||
await page.getByLabel('action-button-add-parameter').click();
|
||||
await page.getByLabel('text-field-name').fill('my custom parameter');
|
||||
await page.getByLabel('text-field-description').fill('description');
|
||||
await page.getByRole('cell', { name: 'HW' }).waitFor();
|
||||
|
||||
// Next, navigate to the "Parameters" tab
|
||||
await navigate(page, 'settings/admin/parameters/');
|
||||
|
||||
await loadTab(page, 'Parameters', true);
|
||||
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Clean old template data if exists
|
||||
await page
|
||||
.locator('div')
|
||||
.filter({ hasText: /^Search\.\.\.$/ })
|
||||
.nth(2)
|
||||
.getByRole('cell', { name: 'my custom parameter', exact: true })
|
||||
.waitFor({ timeout: 500 })
|
||||
.then(async (cell) => {
|
||||
await page
|
||||
.getByRole('cell', { name: 'my custom parameter' })
|
||||
.locator('..')
|
||||
.getByLabel('row-action-menu-')
|
||||
.click();
|
||||
await page.getByRole('menuitem', { name: 'Delete' }).click();
|
||||
await page.getByRole('button', { name: 'Delete' }).click();
|
||||
})
|
||||
.catch(() => {});
|
||||
|
||||
// Create a new parameter, using the "Hardwood" selection list entry as the value
|
||||
await page
|
||||
.getByRole('button', { name: 'action-button-add-parameter-' })
|
||||
.click();
|
||||
await page
|
||||
.getByRole('option', { name: 'some list' })
|
||||
.locator('div')
|
||||
.first()
|
||||
.click();
|
||||
.getByRole('textbox', { name: 'text-field-name' })
|
||||
.fill('my custom parameter');
|
||||
await page
|
||||
.getByRole('combobox', { name: 'related-field-selectionlist' })
|
||||
.fill('some');
|
||||
await page.getByRole('option', { name: 'some list' }).click();
|
||||
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
await page.getByRole('cell', { name: 'my custom parameter' }).click();
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Fill parameter
|
||||
await navigate(page, 'part/104/parameters/');
|
||||
|
||||
Reference in New Issue
Block a user