diff --git a/packages/components/package-lock.json b/packages/components/package-lock.json index fa27adb420..0350f3eb22 100644 --- a/packages/components/package-lock.json +++ b/packages/components/package-lock.json @@ -1,12 +1,12 @@ { "name": "@labkey/components", - "version": "6.57.0", + "version": "6.58.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@labkey/components", - "version": "6.57.0", + "version": "6.58.0", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", diff --git a/packages/components/package.json b/packages/components/package.json index c78cdb7ef7..b1dfc3de24 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@labkey/components", - "version": "6.57.0", + "version": "6.58.0", "description": "Components, models, actions, and utility functions for LabKey applications and pages", "sideEffects": false, "files": [ diff --git a/packages/components/releaseNotes/components.md b/packages/components/releaseNotes/components.md index 36d0398940..30936ae2ec 100644 --- a/packages/components/releaseNotes/components.md +++ b/packages/components/releaseNotes/components.md @@ -1,6 +1,11 @@ # @labkey/components Components, models, actions, and utility functions for LabKey applications and pages +### version 6.58.0 +*Released*: 4 August 2025 +- GitHub Issue 783: Hide domain designer field Advanced Settings that are not currently implemented in app +- GitHub Issue 788: Domain Designer better handling of invalid lookup query value for Sample data type + ### version 6.57.0 *Released*: 30 July 2025 - Add file system audit events for apps diff --git a/packages/components/src/internal/components/domainproperties/AdvancedSettings.test.tsx b/packages/components/src/internal/components/domainproperties/AdvancedSettings.test.tsx index 5d8b7327d3..e5b576fe62 100644 --- a/packages/components/src/internal/components/domainproperties/AdvancedSettings.test.tsx +++ b/packages/components/src/internal/components/domainproperties/AdvancedSettings.test.tsx @@ -27,6 +27,12 @@ import { import { AdvancedSettings } from './AdvancedSettings'; import { DomainField } from './models'; +// mock isApp() to return false for this test case, affects MV enabled checkbox and Default Value Type +jest.mock('../../app/utils', () => ({ + ...jest.requireActual('../../app/utils'), + isApp: () => false, +})); + describe('AdvancedSettings', () => { const _fieldName = 'Marty'; const _title = 'Advanced Settings and Properties'; @@ -114,7 +120,7 @@ describe('AdvancedSettings', () => { // Verify mvEnabled id = createFormInputId(DOMAIN_FIELD_MVENABLED, _domainIndex, _index); const mvEnabled = document.querySelector('#' + id); - expect(mvEnabled.getAttribute('checked')).toBeNull(); + expect(mvEnabled.getAttribute('checked')).toBeNull(); // Note mock of isApp above // Verify recommendedVariable id = createFormInputId(DOMAIN_FIELD_RECOMMENDEDVARIABLE, _domainIndex, _index); @@ -129,7 +135,7 @@ describe('AdvancedSettings', () => { // Verify default type id = createFormInputId(DOMAIN_FIELD_DEFAULT_VALUE_TYPE, _domainIndex, _index); const defaultType = document.querySelector('#' + id); - expect(defaultType.textContent).toEqual('Editable defaultLast enteredFixed value'); + expect(defaultType.textContent).toEqual('Editable defaultLast enteredFixed value'); // Note mock of isApp above // Verify buttons const btns = document.getElementsByClassName('btn'); diff --git a/packages/components/src/internal/components/domainproperties/AdvancedSettings.tsx b/packages/components/src/internal/components/domainproperties/AdvancedSettings.tsx index 6de4dd032f..e6329ad068 100644 --- a/packages/components/src/internal/components/domainproperties/AdvancedSettings.tsx +++ b/packages/components/src/internal/components/domainproperties/AdvancedSettings.tsx @@ -3,7 +3,7 @@ import { List } from 'immutable'; import { ActionURL } from '@labkey/api'; import { Modal } from '../../Modal'; -import { getSubmitButtonClass } from '../../app/utils'; +import { getSubmitButtonClass, isApp } from '../../app/utils'; import { ADVANCED_FIELD_EDITOR_TOPIC, @@ -60,7 +60,6 @@ interface AdvancedSettingsProps { } interface AdvancedSettingsState { - PHI?: string; defaultDisplayValue?: string; defaultValueType?: string; dimension?: boolean; @@ -68,7 +67,8 @@ interface AdvancedSettingsState { hidden?: boolean; measure?: boolean; mvEnabled?: boolean; - phiLevels?: Array<{ label: string; value: string; }>; + PHI?: string; + phiLevels?: { label: string; value: string }[]; recommendedVariable?: boolean; shownInDetailsView?: boolean; shownInInsertView?: boolean; @@ -243,6 +243,9 @@ export class AdvancedSettings extends React.PureComponent { const { field, showDefaultValueSettings } = this.props; + // GitHub Issue #783: we don't yet support default values in the App + if (isApp()) return false; + // some domains just don't support default values if (!showDefaultValueSettings) return false; @@ -262,9 +265,9 @@ export class AdvancedSettings extends React.PureComponentThese options configure how and in which views this field will be visible. Show field on default view of the grid @@ -272,17 +275,17 @@ export class AdvancedSettings extends React.PureComponent Show on update form when updating a single row of data Show on insert form when updating a single row of data @@ -290,9 +293,9 @@ export class AdvancedSettings extends React.PureComponent Show on details page for a single row @@ -309,21 +312,23 @@ export class AdvancedSettings extends React.PureComponentDefault Value Options
- +
@@ -339,9 +344,9 @@ export class AdvancedSettings extends React.PureComponent Set Default Values @@ -375,16 +380,16 @@ export class AdvancedSettings extends React.PureComponent
- +
{!isLoaded && ( )} + {field && !field.lookupIsValid && ( + + )} {isLoaded && ( @@ -164,10 +175,10 @@ export class SampleFieldOptions extends PureComponent {
Lookup Validator
Ensure Value Exists in Lookup Target diff --git a/packages/components/src/internal/components/domainproperties/actions.test.ts b/packages/components/src/internal/components/domainproperties/actions.test.ts index 085428a891..b581b56fcd 100644 --- a/packages/components/src/internal/components/domainproperties/actions.test.ts +++ b/packages/components/src/internal/components/domainproperties/actions.test.ts @@ -22,8 +22,8 @@ import { QueryColumn } from '../../../public/QueryColumn'; import { ConceptModel, OntologyModel } from '../ontology/models'; import { - TEST_LKSM_PROFESSIONAL_MODULE_CONTEXT, TEST_LKS_STARTER_MODULE_CONTEXT, + TEST_LKSM_PROFESSIONAL_MODULE_CONTEXT, TEST_LKSM_STARTER_MODULE_CONTEXT, } from '../../productFixtures'; @@ -821,7 +821,7 @@ describe('domain properties actions', () => { expect(field.dataType).toBe(SAMPLE_TYPE); expect(field.lookupSchema).toBe('exp'); expect(field.lookupQuery).toBe('Materials'); - expect(field.lookupQueryValue).toBe('http://www.w3.org/2001/XMLSchema#int|Materials'); + expect(field.lookupQueryValue).toBe('http://www.w3.org/2001/XMLSchema#int|all'); }); test('updateDataType Sample data type, saved field', () => { @@ -830,7 +830,7 @@ describe('domain properties actions', () => { expect(field.dataType).toBe(SAMPLE_TYPE); expect(field.lookupSchema).toBe('exp'); expect(field.lookupQuery).toBe('Materials'); - expect(field.lookupQueryValue).toBe('http://www.w3.org/2001/XMLSchema#int|Materials'); + expect(field.lookupQueryValue).toBe('http://www.w3.org/2001/XMLSchema#int|all'); }); test('updateDataType Sample data type, saved lookup field', () => { @@ -839,7 +839,7 @@ describe('domain properties actions', () => { expect(field.dataType).toBe(SAMPLE_TYPE); expect(field.lookupSchema).toBe('exp'); expect(field.lookupQuery).toBe('Materials'); - expect(field.lookupQueryValue).toBe('http://www.w3.org/2001/XMLSchema#int|Materials'); + expect(field.lookupQueryValue).toBe('http://www.w3.org/2001/XMLSchema#int|all'); }); test('updateDomainField principalConceptCode', () => { diff --git a/packages/components/src/internal/components/domainproperties/actions.ts b/packages/components/src/internal/components/domainproperties/actions.ts index 598ea8f374..d06aa05d03 100644 --- a/packages/components/src/internal/components/domainproperties/actions.ts +++ b/packages/components/src/internal/components/domainproperties/actions.ts @@ -866,6 +866,7 @@ export function updateDataType(field: DomainField, value: any): DomainField { rangeURI: dataType.rangeURI, lookupSchema: dataType.lookupSchema, lookupQuery: dataType.lookupQuery, + lookupIsValid: true, sourceOntology: undefined, conceptSubtree: undefined, conceptLabelColumn: undefined, diff --git a/packages/components/src/internal/components/domainproperties/models.test.ts b/packages/components/src/internal/components/domainproperties/models.test.ts index 175e76a60e..f0edf0a680 100644 --- a/packages/components/src/internal/components/domainproperties/models.test.ts +++ b/packages/components/src/internal/components/domainproperties/models.test.ts @@ -64,6 +64,7 @@ import { isValidTextChoiceValue, PropertyValidator, PropertyValidatorProperties, + resolveLookupQueryValue, } from './models'; import { BOOLEAN_RANGE_URI, @@ -1011,7 +1012,20 @@ describe('DomainField', () => { field = field.merge({ propertyId: 0, updatedField: true }) as DomainField; expect(field.getDetailsArray().join('')).toBe('Updated'); - field = field.merge({ dataType: SAMPLE_TYPE, lookupSchema: 'exp', lookupQuery: 'SampleType1' }) as DomainField; + field = field.merge({ + dataType: SAMPLE_TYPE, + lookupSchema: 'exp', + lookupQuery: 'SampleType1', + lookupIsValid: false, + }) as DomainField; + expect(field.getDetailsArray().join('')).toBe('Updated. SampleType1[object Object]'); + + field = field.merge({ + dataType: SAMPLE_TYPE, + lookupSchema: 'exp', + lookupQuery: 'SampleType1', + lookupIsValid: true, + }) as DomainField; expect(field.getDetailsArray().join('')).toBe('Updated. SampleType1'); field = field.merge({ dataType: LOOKUP_TYPE }) as DomainField; @@ -1470,3 +1484,41 @@ describe('resolveBaseProperties', () => { ); }); }); + +describe('resolveLookupQueryValue', () => { + test('isSampleType', () => { + expect(resolveLookupQueryValue(TEXT_TYPE, 'schema', 'query', false)).toBe( + 'http://www.w3.org/2001/XMLSchema#string|query' + ); + expect(resolveLookupQueryValue(TEXT_TYPE, 'schema', 'query', true)).toBe( + 'http://www.w3.org/2001/XMLSchema#string|query' + ); + expect(resolveLookupQueryValue(TEXT_TYPE, 'exp', 'query', false)).toBe( + 'http://www.w3.org/2001/XMLSchema#string|query' + ); + expect(resolveLookupQueryValue(TEXT_TYPE, 'exp', 'query', true)).toBe( + 'http://www.w3.org/2001/XMLSchema#string|query' + ); + expect(resolveLookupQueryValue(TEXT_TYPE, 'exp', 'Materials', false)).toBe( + 'http://www.w3.org/2001/XMLSchema#string|Materials' + ); + expect(resolveLookupQueryValue(TEXT_TYPE, 'exp', 'Materials', true)).toBe( + 'http://www.w3.org/2001/XMLSchema#int|all' + ); + }); + + test('lookupType', () => { + expect(resolveLookupQueryValue(TEXT_TYPE, 'schema', 'query', false)).toBe( + 'http://www.w3.org/2001/XMLSchema#string|query' + ); + expect(resolveLookupQueryValue(INTEGER_TYPE, 'schema', 'query', false)).toBe( + 'http://www.w3.org/2001/XMLSchema#int|query' + ); + expect(resolveLookupQueryValue(DATETIME_TYPE, 'schema', 'query', false)).toBe( + 'http://www.w3.org/2001/XMLSchema#dateTime|query' + ); + expect(resolveLookupQueryValue(SAMPLE_TYPE, 'schema', 'query', false)).toBe( + 'http://www.w3.org/2001/XMLSchema#int|query' + ); + }); +}); diff --git a/packages/components/src/internal/components/domainproperties/models.tsx b/packages/components/src/internal/components/domainproperties/models.tsx index bf33a78ebf..21c3d149af 100644 --- a/packages/components/src/internal/components/domainproperties/models.tsx +++ b/packages/components/src/internal/components/domainproperties/models.tsx @@ -138,7 +138,7 @@ export interface DomainPropertiesGridColumn { sortable: boolean; } -export const SAMPLE_TYPE_OPTION_VALUE = `${SAMPLE_TYPE.rangeURI}|all`; +export const SAMPLE_TYPE_ALL_OPTION_VALUE = `${SAMPLE_TYPE.rangeURI}|all`; interface IDomainDesign { allowAttachmentProperties: boolean; @@ -1124,12 +1124,13 @@ export class DomainField } static resolveLookupConfig(rawField: Partial, dataType: PropDescType): ILookupConfig { + const isSampleType = dataType === SAMPLE_TYPE; const lookupType = LOOKUP_TYPE.set('rangeURI', rawField.rangeURI) as PropDescType; const lookupContainer = rawField.lookupContainer === null ? undefined : rawField.lookupContainer; const lookupSchema = resolveLookupSchema(rawField, dataType); const lookupQuery = rawField.lookupQuery || (dataType === SAMPLE_TYPE ? SCHEMAS.EXP_TABLES.MATERIALS.queryName : undefined); - const lookupQueryValue = encodeLookup(lookupQuery, lookupType); + const lookupQueryValue = resolveLookupQueryValue(lookupType, lookupSchema, lookupQuery, isSampleType); return { lookupContainer, @@ -1422,6 +1423,17 @@ export class DomainField ? ALL_SAMPLES_DISPLAY_TEXT : this.lookupQuery; details.push(period + detailsText); + + if (!this.lookupIsValid) { + const fieldError = new DomainFieldError({ + extraInfo: + 'The selected sample type may have been deleted or renamed. Expand the row to select a valid sample lookup.', + message: 'Invalid sample type', + severity: SEVERITY_LEVEL_ERROR, + }); + details.push(); + } + period = '. '; } else if (this.dataType.isLookup() && this.lookupSchema && this.lookupQuery) { // only show the query as a link in LKS, for now @@ -1542,6 +1554,23 @@ function resolveLookupSchema(rawField: Partial, dataType: PropDesc return undefined; } +// export for jest testing +export function resolveLookupQueryValue( + lookupType: PropDescType, + lookupSchema: string, + lookupQuery: string, + isSampleType = false +): string { + if ( + isSampleType && + lookupSchema === SCHEMAS.EXP_TABLES.SCHEMA && + lookupQuery === SCHEMAS.EXP_TABLES.MATERIALS.queryName + ) { + return SAMPLE_TYPE_ALL_OPTION_VALUE; + } + return encodeLookup(lookupQuery, lookupType); +} + export function updateSampleField(field: Partial, sampleQueryValue?: string): DomainField { const { queryName, rangeURI = INT_RANGE_URI } = decodeLookup(sampleQueryValue); const lookupType = field.lookupType || LOOKUP_TYPE; @@ -1554,6 +1583,7 @@ export function updateSampleField(field: Partial, sampleQueryValue? lookupQueryValue: sampleQueryValue, lookupType: field.lookupType.set('rangeURI', rangeURI), lookupValidator: LOOKUP_VALIDATOR, + lookupIsValid: true, propertyValidators: List([LOOKUP_VALIDATOR]), rangeURI, } @@ -1561,9 +1591,10 @@ export function updateSampleField(field: Partial, sampleQueryValue? // use the samples. table for specific sample types lookupSchema: SCHEMAS.SAMPLE_SETS.SCHEMA, lookupQuery: queryName, - lookupQueryValue: sampleQueryValue || SAMPLE_TYPE_OPTION_VALUE, + lookupQueryValue: sampleQueryValue, lookupType: lookupType.set('rangeURI', rangeURI), lookupValidator: LOOKUP_VALIDATOR, + lookupIsValid: true, propertyValidators: List([LOOKUP_VALIDATOR]), rangeURI, };