Skip to content

Commit 41c6243

Browse files
authored
Misc assay issues fixes for 25.7 - part 3 (#1816)
### version 6.52.3 *Released*: 27 June 2025 - GitHub Issue 787: DomainField JSON file import should respect required boolean for SampleId field - Issue 53325: withQueryModels to use col fieldKey instead of name when setting omittedColumns for errant calculated fields
1 parent 43e0e90 commit 41c6243

8 files changed

Lines changed: 101 additions & 46 deletions

File tree

packages/components/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/components/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@labkey/components",
3-
"version": "6.52.2",
3+
"version": "6.52.3",
44
"description": "Components, models, actions, and utility functions for LabKey applications and pages",
55
"sideEffects": false,
66
"files": [

packages/components/releaseNotes/components.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
# @labkey/components
22
Components, models, actions, and utility functions for LabKey applications and pages
33

4+
### version 6.52.3
5+
*Released*: 27 June 2025
6+
- GitHub Issue 787: DomainField JSON file import should respect required boolean for SampleId field
7+
- Issue 53325: withQueryModels to use col fieldKey instead of name when setting omittedColumns for errant calculated fields
8+
49
### version 6.52.2
510
*Released*: 27 June 2025
611
- Issue 53326: Don't filter `QuerySelect` in `PrintLabelsModal`

packages/components/src/internal/app/actions.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
LABKEY_WEBSOCKET,
1010
PIPELINE_JOB_NOTIFICATION_EVENT_ERROR,
1111
PIPELINE_JOB_NOTIFICATION_EVENT_START,
12-
PIPELINE_JOB_NOTIFICATION_EVENT_SUCCESS
12+
PIPELINE_JOB_NOTIFICATION_EVENT_SUCCESS,
1313
} from '../constants';
1414

1515
import {
@@ -121,7 +121,7 @@ export function registerWebSocketListeners(
121121
): void {
122122
if (notificationListeners) {
123123
notificationListeners.forEach(listener => {
124-
LABKEY_WEBSOCKET.addServerEventListener(listener, function (evt) {
124+
LABKEY_WEBSOCKET?.addServerEventListener(listener, function (evt) {
125125
// not checking evt.wasClean since we want this event for all user sessions
126126
window.setTimeout(() => store.dispatch({ type: SERVER_NOTIFICATIONS_INVALIDATE }), 1000);
127127
});
@@ -130,7 +130,7 @@ export function registerWebSocketListeners(
130130

131131
if (menuReloadListeners) {
132132
menuReloadListeners.forEach(listener => {
133-
LABKEY_WEBSOCKET.addServerEventListener(listener, function (evt) {
133+
LABKEY_WEBSOCKET?.addServerEventListener(listener, function (evt) {
134134
// not checking evt.wasClean since we want this event for all user sessions
135135
window.setTimeout(() => {
136136
store.dispatch(menuReload());
@@ -147,5 +147,5 @@ const PIPELINE_JOB_NOTIFICATION_EVENTS = [
147147
];
148148

149149
export function registerPipelineWebSocketListeners(store): void {
150-
registerWebSocketListeners(store, PIPELINE_JOB_NOTIFICATION_EVENTS, PIPELINE_JOB_NOTIFICATION_EVENTS)
150+
registerWebSocketListeners(store, PIPELINE_JOB_NOTIFICATION_EVENTS, PIPELINE_JOB_NOTIFICATION_EVENTS);
151151
}

packages/components/src/internal/app/utils.ts

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -68,22 +68,22 @@ declare var LABKEY: LabKey;
6868
// Type definition not provided for event codes so here we provide our own
6969
// Source: https://www.iana.org/assignments/websocket/websocket.xml#close-code-number
7070
export enum CloseEventCode {
71-
NORMAL_CLOSURE = 1000,
72-
GOING_AWAY = 1001,
73-
PROTOCOL_ERROR = 1002,
74-
UNSUPPORTED_DATA = 1003,
75-
RESERVED = 1004,
76-
NO_STATUS_RCVD = 1005,
7771
ABNORMAL_CLOSURE = 1006,
72+
BAD_GATEWAY = 1014,
73+
GOING_AWAY = 1001,
74+
INTERNAL_ERROR = 1011,
7875
INVALID_FRAME_PAYLOAD_DATA = 1007,
79-
POLICY_VIOLATION = 1008,
8076
MESSAGE_TOO_BIG = 1009,
8177
MISSING_EXT = 1010,
82-
INTERNAL_ERROR = 1011,
78+
NO_STATUS_RCVD = 1005,
79+
NORMAL_CLOSURE = 1000,
80+
POLICY_VIOLATION = 1008,
81+
PROTOCOL_ERROR = 1002,
82+
RESERVED = 1004,
8383
SERVICE_RESTART = 1012,
84-
TRY_AGAIN_LATER = 1013,
85-
BAD_GATEWAY = 1014,
8684
TLS_HANDSHAKE = 1015,
85+
TRY_AGAIN_LATER = 1013,
86+
UNSUPPORTED_DATA = 1003,
8787
}
8888

8989
export function userCanReadAssays(user: User): boolean {
@@ -250,13 +250,13 @@ export function isProductFoldersDataListingScopedToFolder(moduleContext?: Module
250250
return resolveModuleContext(moduleContext)?.query?.[EXPERIMENTAL_PRODUCT_FOLDER_DATA_LISTING_SCOPED] === true;
251251
}
252252

253-
export function getFolderDataExclusion(moduleContext?: ModuleContext): { [key: string]: number[] } {
253+
export function getFolderDataExclusion(moduleContext?: ModuleContext): Record<string, number[]> {
254254
return resolveModuleContext(moduleContext)?.samplemanagement?.[FOLDER_DATA_TYPE_EXCLUSIONS];
255255
}
256256

257257
export function setFolderDataExclusion(
258258
moduleContext: ModuleContext,
259-
dataTypeExclusions: { [key: string]: number[] }
259+
dataTypeExclusions: Record<string, number[]>
260260
): ModuleContext {
261261
// side-effect set global moduleContext
262262
if (LABKEY?.moduleContext?.samplemanagement) {
@@ -559,7 +559,6 @@ export function addAssaysSectionConfig(
559559
emptyText: 'No assays have been defined',
560560
filteredEmptyText: 'No assays available',
561561
iconURL: imageURL('_images', 'assay.svg'),
562-
headerURLParams: { 'allassaysgrid.sort': 'Name', 'activeassaysgrid.sort': 'Name' }, // Issue 52472
563562
});
564563
if (user && user.hasDesignAssaysPermission()) {
565564
assaysMenuConfig = assaysMenuConfig.merge({

packages/components/src/internal/components/domainproperties/models.test.ts

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,38 +55,38 @@ import {
5555
DomainDesign,
5656
DomainField,
5757
DomainIndex,
58-
IDomainIndex,
5958
FieldErrors,
6059
getValidValuesDetailStr,
6160
getValidValuesFromArray,
6261
IDomainField,
62+
IDomainIndex,
6363
isPropertyTypeAllowed,
6464
isValidTextChoiceValue,
6565
PropertyValidator,
6666
PropertyValidatorProperties,
6767
} from './models';
6868
import {
6969
BOOLEAN_RANGE_URI,
70+
CALCULATED_CONCEPT_URI,
7071
CONCEPT_CODE_CONCEPT_URI,
72+
DATE_RANGE_URI,
73+
DATETIME_RANGE_URI,
74+
DERIVATION_DATA_SCOPES,
7175
DOMAIN_FIELD_FULLY_LOCKED,
7276
DOMAIN_FIELD_NOT_LOCKED,
7377
DOMAIN_FIELD_PARTIALLY_LOCKED,
78+
DOMAIN_FIELD_PRIMARY_KEY_LOCKED,
7479
INT_RANGE_URI,
7580
MULTILINE_RANGE_URI,
76-
PHILEVEL_NOT_PHI,
7781
PHILEVEL_FULL_PHI,
7882
PHILEVEL_LIMITED_PHI,
83+
PHILEVEL_NOT_PHI,
7984
PHILEVEL_RESTRICTED_PHI,
8085
SAMPLE_TYPE_CONCEPT_URI,
8186
STORAGE_UNIQUE_ID_CONCEPT_URI,
8287
STRING_RANGE_URI,
8388
TEXT_CHOICE_CONCEPT_URI,
84-
DERIVATION_DATA_SCOPES,
85-
DOMAIN_FIELD_PRIMARY_KEY_LOCKED,
86-
DATETIME_RANGE_URI,
87-
DATE_RANGE_URI,
8889
TIME_RANGE_URI,
89-
CALCULATED_CONCEPT_URI,
9090
} from './constants';
9191

9292
beforeAll(() => {
@@ -1419,3 +1419,54 @@ describe('isValidTextChoiceValue', () => {
14191419
expect(isValidTextChoiceValue(' a ')).toBeTruthy();
14201420
});
14211421
});
1422+
1423+
describe('resolveBaseProperties', () => {
1424+
test('infer SampleId', () => {
1425+
let field = DomainField.resolveBaseProperties({ name: 'OtherId' });
1426+
expect(field.dataType).toBe(TEXT_TYPE);
1427+
expect(field.conceptURI).toBe(undefined);
1428+
expect(field.rangeURI).toBe(undefined);
1429+
expect(field.required).toBe(undefined);
1430+
1431+
field = DomainField.resolveBaseProperties({
1432+
propertyId: 1,
1433+
name: 'SampleId',
1434+
});
1435+
expect(field.dataType).toBe(LOOKUP_TYPE);
1436+
expect(field.conceptURI).toBe(undefined);
1437+
expect(field.rangeURI).toBe(undefined);
1438+
expect(field.required).toBe(undefined);
1439+
1440+
field = DomainField.resolveBaseProperties({ name: 'SampleId' });
1441+
expect(field.dataType).toBe(SAMPLE_TYPE);
1442+
expect(field.conceptURI).toBe(SAMPLE_TYPE.conceptURI);
1443+
expect(field.rangeURI).toBe(SAMPLE_TYPE.rangeURI);
1444+
expect(field.required).toBe(true);
1445+
1446+
// GitHub Issue 787
1447+
field = DomainField.resolveBaseProperties({ name: 'SampleId', required: false });
1448+
expect(field.dataType).toBe(SAMPLE_TYPE);
1449+
expect(field.conceptURI).toBe(SAMPLE_TYPE.conceptURI);
1450+
expect(field.rangeURI).toBe(SAMPLE_TYPE.rangeURI);
1451+
expect(field.required).toBe(false);
1452+
});
1453+
1454+
test('lockType', () => {
1455+
expect(DomainField.resolveBaseProperties({}).lockType).toBe(DOMAIN_FIELD_NOT_LOCKED);
1456+
expect(DomainField.resolveBaseProperties({ lockType: DOMAIN_FIELD_PARTIALLY_LOCKED }).lockType).toBe(
1457+
DOMAIN_FIELD_PARTIALLY_LOCKED
1458+
);
1459+
expect(DomainField.resolveBaseProperties({ lockType: DOMAIN_FIELD_FULLY_LOCKED }).lockType).toBe(
1460+
DOMAIN_FIELD_FULLY_LOCKED
1461+
);
1462+
expect(DomainField.resolveBaseProperties({ name: 'test1' }, List(['test2'])).lockType).toBe(
1463+
DOMAIN_FIELD_NOT_LOCKED
1464+
);
1465+
expect(DomainField.resolveBaseProperties({ name: 'test1' }, List(['test1'])).lockType).toBe(
1466+
DOMAIN_FIELD_PARTIALLY_LOCKED
1467+
);
1468+
expect(DomainField.resolveBaseProperties({ name: 'TEST1' }, List(['test1'])).lockType).toBe(
1469+
DOMAIN_FIELD_PARTIALLY_LOCKED
1470+
);
1471+
});
1472+
});

packages/components/src/internal/components/domainproperties/models.tsx

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
import { fromJS, List, Map as ImmutableMap, Record as ImmutableRecord } from 'immutable';
16+
import { fromJS, Map as ImmutableMap, Record as ImmutableRecord, List } from 'immutable';
1717
import { ActionURL, Domain, Filter, getServerContext, Utils } from '@labkey/api';
1818
import React, { ReactNode } from 'react';
1919

@@ -128,7 +128,7 @@ export interface ITypeDependentProps {
128128
}
129129

130130
export interface FieldDetails {
131-
detailsInfo: { [key: string]: string };
131+
detailsInfo: Record<string, string>;
132132
ontologyLookupIndices: number[];
133133
}
134134

@@ -490,7 +490,7 @@ export class DomainDesign
490490
appPropertiesOnly: boolean,
491491
hasOntologyModule: boolean,
492492
showFilterCriteria: boolean
493-
): List<GridColumn | DomainPropertiesGridColumn> {
493+
): List<DomainPropertiesGridColumn | GridColumn> {
494494
const selectionCol = new GridColumn({
495495
index: GRID_SELECTION_INDEX,
496496
title: GRID_SELECTION_INDEX,
@@ -504,9 +504,9 @@ export class DomainDesign
504504
const changes = List.of({ id: formInputId, value: !selected });
505505
return (
506506
<DomainDesignerCheckbox
507+
checked={selected}
507508
className="domain-summary-selection"
508509
id={formInputId}
509-
checked={selected}
510510
onChange={() => {
511511
onFieldsChange(changes, fieldIndex, false);
512512
}}
@@ -524,7 +524,7 @@ export class DomainDesign
524524
const fieldIndex = row.get('fieldIndex');
525525

526526
return (
527-
<a onClick={() => scrollFunction(fieldIndex)} className="clickable">
527+
<a className="clickable" onClick={() => scrollFunction(fieldIndex)}>
528528
{text}
529529
</a>
530530
);
@@ -556,8 +556,8 @@ export class DomainDesign
556556
}
557557

558558
export interface IDomainIndex {
559-
columns: string[] | List<string>;
560-
type: 'primary' | 'unique' | 'nonunique';
559+
columns: List<string> | string[];
560+
type: 'nonunique' | 'primary' | 'unique';
561561
}
562562

563563
export class DomainIndex
@@ -568,7 +568,7 @@ export class DomainIndex
568568
implements IDomainIndex
569569
{
570570
declare columns: List<string>;
571-
declare type: 'primary' | 'unique' | 'nonunique';
571+
declare type: 'nonunique' | 'primary' | 'unique';
572572

573573
static fromJS(rawIndices: IDomainIndex[]): List<DomainIndex> {
574574
let indices = List<DomainIndex>();
@@ -643,7 +643,7 @@ export class ConditionalFormat
643643
declare textColor?: string;
644644
declare backgroundColor?: string;
645645

646-
constructor(values?: { [key: string]: any }) {
646+
constructor(values?: Record<string, any>) {
647647
// filter is a reserved work on Records so change to formatFilter and update for HASANYVALUE lacking a filter symbol
648648
if (values && !values.get('formatFilter')) {
649649
values = values.set(
@@ -693,7 +693,7 @@ export class PropertyValidatorProperties
693693
declare validValues: string[];
694694
declare valueUpdates: Record<string, string>;
695695

696-
constructor(values?: { [key: string]: any }) {
696+
constructor(values?: Record<string, any>) {
697697
if (typeof values?.failOnMatch === 'string') {
698698
values.failOnMatch = values.failOnMatch.toLowerCase() === 'true';
699699
}
@@ -860,8 +860,6 @@ interface ILookupConfig {
860860
}
861861

862862
export interface IDomainField {
863-
PHI?: string;
864-
URL?: string;
865863
conceptImportColumn?: string;
866864
conceptLabelColumn?: string;
867865
conceptSubtree?: string;
@@ -894,6 +892,7 @@ export interface IDomainField {
894892
mvEnabled?: boolean;
895893
name: string;
896894
original: Partial<IDomainField>;
895+
PHI?: string;
897896
primaryKey?: boolean;
898897
principalConceptCode?: string;
899898
propertyId?: number;
@@ -913,6 +912,7 @@ export interface IDomainField {
913912
textChoiceValidator?: PropertyValidator;
914913
uniqueConstraint?: boolean;
915914
updatedField: boolean;
915+
URL?: string;
916916
visible: boolean;
917917
}
918918

@@ -938,7 +938,7 @@ export interface FilterCriteria {
938938
op: string;
939939
propertyId: number;
940940
referencePropertyId?: number;
941-
value: string | number | boolean;
941+
value: boolean | number | string;
942942
}
943943
// Note: this is a regular Javascript Map, not an Immutable Map
944944
export type FilterCriteriaMap = Map<number, FilterCriteria[]>;
@@ -1179,7 +1179,7 @@ export class DomainField
11791179
field.dataType = SAMPLE_TYPE;
11801180
field.conceptURI = SAMPLE_TYPE.conceptURI;
11811181
field.rangeURI = SAMPLE_TYPE.rangeURI;
1182-
field.required = true;
1182+
field.required = !!(raw.required ?? true); // GitHub Issue 787
11831183
}
11841184
}
11851185

@@ -1387,10 +1387,10 @@ export class DomainField
13871387

13881388
static defaultValues(prop: string, type: PropDescType): any {
13891389
switch (prop) {
1390-
case DOMAIN_FIELD_MEASURE:
1391-
return type === INTEGER_TYPE || type === DOUBLE_TYPE;
13921390
case DOMAIN_FIELD_DIMENSION:
13931391
return type === LOOKUP_TYPE || type === USERS_TYPE || type === SAMPLE_TYPE;
1392+
case DOMAIN_FIELD_MEASURE:
1393+
return type === INTEGER_TYPE || type === DOUBLE_TYPE;
13941394
default:
13951395
return false;
13961396
}
@@ -1455,7 +1455,7 @@ export class DomainField
14551455
message: FIELD_EMPTY_TEXT_CHOICE_WARNING_MSG,
14561456
severity: SEVERITY_LEVEL_WARN,
14571457
});
1458-
details.push(<DomainRowWarning key="domain-row-text-choice-error" fieldError={fieldError} />);
1458+
details.push(<DomainRowWarning fieldError={fieldError} key="domain-row-text-choice-error" />);
14591459
}
14601460
}
14611461
period = '. ';
@@ -2130,7 +2130,7 @@ export interface IAppDomainHeader {
21302130
onDomainChange?: (index: number, updatedDomain: DomainDesign) => void;
21312131
}
21322132

2133-
export type DomainPanelStatus = 'INPROGRESS' | 'TODO' | 'COMPLETE' | 'NONE';
2133+
export type DomainPanelStatus = 'COMPLETE' | 'INPROGRESS' | 'NONE' | 'TODO';
21342134

21352135
export interface IDomainFormDisplayOptions {
21362136
derivationDataScopeConfig?: IDerivationDataScope;

packages/components/src/public/QueryModel/withQueryModels.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -737,7 +737,7 @@ export function withQueryModels<Props>(
737737
const calcFieldNames = model.queryInfo
738738
.getAllColumns()
739739
.filter(c => c.isCalculatedField)
740-
.map(c => c.name);
740+
.map(c => c.fieldKey); // Issue 53325
741741
let rowsError = resolveErrorMessage(error, 'data', undefined, 'load');
742742

743743
if (rowsError === undefined) {

0 commit comments

Comments
 (0)