Skip to content

Commit 258bdc3

Browse files
authored
Alan's 25.7 Issues (#1823)
* Fix Issue 53141
1 parent e291a44 commit 258bdc3

File tree

5 files changed

+86
-66
lines changed

5 files changed

+86
-66
lines changed

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.53.1",
3+
"version": "6.53.2",
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: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# @labkey/components
22
Components, models, actions, and utility functions for LabKey applications and pages
33

4+
### version 6.53.2
5+
*Released*: 3 July 2025
6+
* Issue 53141: Should set a dirty bit when setting or updating the hit selection criteria for an assay
7+
48
### version 6.53.1
59
*Released*: 3 July 2025
610
- Issue 53153: Disable value validation for expInput, aliquotParent columns

packages/components/src/internal/FilterCriteriaModal.tsx

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ import { useLoadableState } from './useLoadableState';
1212
import { LoadingSpinner } from './components/base/LoadingSpinner';
1313
import { ChoicesListItem } from './components/base/ChoicesListItem';
1414
import { FilterExpressionView } from './components/search/FilterExpressionView';
15-
import { useAppContext } from './AppContext';
1615
import { FilterCriteriaColumns } from './components/assay/models';
1716
import { AssayProtocolModel } from './components/domainproperties/assay/models';
1817
import { Alert } from './components/base/Alert';
18+
import { ComponentsAPIWrapper, getDefaultAPIWrapper } from './APIWrapper';
1919

2020
type BaseFilterCriteriaField = Omit<FilterCriteria, 'op' | 'value'>;
2121
interface FilterCriteriaField extends BaseFilterCriteriaField {
@@ -100,16 +100,21 @@ FilterCriteriaChoice.displayName = 'FilterCriteriaChoice';
100100
* openTo: The propertyId of the domain field you want to open the modal to
101101
*/
102102
interface Props {
103+
api?: ComponentsAPIWrapper;
103104
onClose: () => void;
104105
onSave: (filterCriteria: FilterCriteriaMap) => void;
105106
openTo?: number;
106107
protocolModel: AssayProtocolModel;
107108
}
108109

109-
export const FilterCriteriaModal: FC<Props> = memo(({ onClose, onSave, openTo, protocolModel }) => {
110-
const { api } = useAppContext();
110+
export const FilterCriteriaModal: FC<Props> = memo(props => {
111+
// Note: we cannot fetch api from useAppContext, because this component can be rendered in LKS, which does not set
112+
// up an AppContext
113+
const { api = getDefaultAPIWrapper(), onClose, onSave, openTo, protocolModel } = props;
111114
const { protocolId, container } = protocolModel;
112115
const domain = useMemo(() => protocolModel.getDomainByNameSuffix('Data'), [protocolModel]);
116+
// Intentionally not using withRouteLeave, that is handled above this component, after onSave is called
117+
const [isDirty, setIsDirty] = useState(false);
113118
const [filterCriteria, setFilterCriteria] = useState<FilterCriteriaMap>(() => {
114119
return domain.fields.reduce((result, field) => {
115120
if (field.filterCriteria) result.set(field.propertyId, [...field.filterCriteria]);
@@ -135,6 +140,7 @@ export const FilterCriteriaModal: FC<Props> = memo(({ onClose, onSave, openTo, p
135140

136141
const onFieldFilterUpdate = useCallback(
137142
(newFilters: Filter.IFilter[]) => {
143+
setIsDirty(true);
138144
setFilterCriteria(current => {
139145
const filterCriteriaField = filterCriteriaFields.find(field => field.propertyId === selectedFieldId);
140146
// Use the referencePropertyId if it exists, because all filterCriteria are stored on the parent field
@@ -186,7 +192,14 @@ export const FilterCriteriaModal: FC<Props> = memo(({ onClose, onSave, openTo, p
186192
const hasFields = fieldsToRender !== undefined && fieldsToRender.length > 0;
187193

188194
return (
189-
<Modal bsSize="lg" title="Hit Selection Criteria" onCancel={onClose} onConfirm={onConfirm} confirmText="Apply">
195+
<Modal
196+
bsSize="lg"
197+
canConfirm={isDirty}
198+
confirmText="Apply"
199+
onCancel={onClose}
200+
onConfirm={onConfirm}
201+
title="Hit Selection Criteria"
202+
>
190203
{loading && <LoadingSpinner />}
191204
{!loading && !error && (
192205
<div className="filter-criteria-modal-body field-modal__container row">

packages/components/src/internal/components/domainproperties/assay/AssayDesignerPanels.tsx

Lines changed: 62 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -123,27 +123,27 @@ const AssayDomainForm: FC<AssayDomainFormProps> = memo(props => {
123123
]);
124124
return (
125125
<DomainForm
126-
key={domain.domainId || index}
127126
api={api}
128-
index={domain.domainId || index}
129-
domainIndex={index}
127+
appDomainHeaderRenderer={appDomainHeaderRenderer}
128+
appPropertiesOnly={hideAdvancedProperties}
129+
controlledCollapse
130130
domain={domain}
131+
domainFormDisplayOptions={displayOptions}
132+
domainIndex={index}
131133
headerPrefix={headerPrefix}
132-
controlledCollapse
134+
helpTopic={null} // null so that we don't show the "learn more about this tool" link for these domains
135+
index={domain.domainId || index}
133136
initCollapsed={currentPanelIndex !== index + DOMAIN_PANEL_INDEX}
134-
validate={validatePanel === index + DOMAIN_PANEL_INDEX}
137+
key={domain.domainId || index}
138+
modelDomains={protocolModel.domains}
139+
onChange={onChange}
140+
onToggle={onToggle}
135141
panelStatus={
136142
protocolModel.isNew()
137143
? getDomainPanelStatus(index + DOMAIN_PANEL_INDEX, currentPanelIndex, visitedPanels, firstState)
138144
: 'COMPLETE'
139145
}
140-
helpTopic={null} // null so that we don't show the "learn more about this tool" link for these domains
141-
onChange={onChange}
142-
onToggle={onToggle}
143-
appDomainHeaderRenderer={appDomainHeaderRenderer}
144-
modelDomains={protocolModel.domains}
145-
appPropertiesOnly={hideAdvancedProperties}
146-
domainFormDisplayOptions={displayOptions}
146+
validate={validatePanel === index + DOMAIN_PANEL_INDEX}
147147
>
148148
<div>{domain.description}</div>
149149
</DomainForm>
@@ -336,40 +336,43 @@ export class AssayDesignerPanelsImpl extends React.PureComponent<Props, State> {
336336
};
337337

338338
saveFilterCriteria = (filterCriteria: FilterCriteriaMap) => {
339-
this.setState(current => {
340-
const protocolModel = current.protocolModel;
341-
const resultsIndex = current.protocolModel.domains.findIndex((domain: DomainDesign): boolean =>
342-
domain.isNameSuffixMatch('Data')
343-
);
344-
const domains = current.protocolModel.domains;
345-
let resultsDomain = domains.get(resultsIndex);
346-
// Clear the existing values first
347-
let fields = resultsDomain.fields.map(f => f.set('filterCriteria', []) as DomainField).toList();
348-
349-
filterCriteria.forEach((fieldCriteria, propertyId) => {
350-
const domainFieldIdx = fields.findIndex(d => d.propertyId === propertyId);
351-
352-
if (domainFieldIdx < 0) {
353-
console.warn(`Unable to find domain field with property id ${propertyId}`);
354-
return;
355-
}
339+
this.setState(
340+
current => {
341+
const protocolModel = current.protocolModel;
342+
const resultsIndex = current.protocolModel.domains.findIndex((domain: DomainDesign): boolean =>
343+
domain.isNameSuffixMatch('Data')
344+
);
345+
const domains = current.protocolModel.domains;
346+
let resultsDomain = domains.get(resultsIndex);
347+
// Clear the existing values first
348+
let fields = resultsDomain.fields.map(f => f.set('filterCriteria', []) as DomainField).toList();
356349

357-
let domainField = fields.get(domainFieldIdx);
358-
domainField = domainField.set('filterCriteria', fieldCriteria) as DomainField;
359-
fields = fields.set(domainFieldIdx, domainField);
360-
});
350+
filterCriteria.forEach((fieldCriteria, propertyId) => {
351+
const domainFieldIdx = fields.findIndex(d => d.propertyId === propertyId);
361352

362-
resultsDomain = resultsDomain.set('fields', fields) as DomainDesign;
353+
if (domainFieldIdx < 0) {
354+
console.warn(`Unable to find domain field with property id ${propertyId}`);
355+
return;
356+
}
363357

364-
return {
365-
modalOpen: false,
366-
openTo: undefined,
367-
protocolModel: protocolModel.set(
368-
'domains',
369-
protocolModel.domains.set(resultsIndex, resultsDomain)
370-
) as AssayProtocolModel,
371-
};
372-
});
358+
let domainField = fields.get(domainFieldIdx);
359+
domainField = domainField.set('filterCriteria', fieldCriteria) as DomainField;
360+
fields = fields.set(domainFieldIdx, domainField);
361+
});
362+
363+
resultsDomain = resultsDomain.set('fields', fields) as DomainDesign;
364+
365+
return {
366+
modalOpen: false,
367+
openTo: undefined,
368+
protocolModel: protocolModel.set(
369+
'domains',
370+
protocolModel.domains.set(resultsIndex, resultsDomain)
371+
) as AssayProtocolModel,
372+
};
373+
},
374+
() => this.props.onChange?.(this.state.protocolModel)
375+
);
373376
};
374377

375378
togglePropertiesPanel = (collapsed, callback): void => {
@@ -409,32 +412,32 @@ export class AssayDesignerPanelsImpl extends React.PureComponent<Props, State> {
409412

410413
return (
411414
<BaseDomainDesigner
412-
name={protocolModel.name}
413-
exception={protocolModel.exception}
414415
domains={protocolModel.domains}
416+
exception={protocolModel.exception}
415417
hasValidProperties={protocolModel.hasValidProperties()}
416-
visitedPanels={visitedPanels}
417-
submitting={submitting}
418+
name={protocolModel.name}
418419
onCancel={onCancel}
419420
onFinish={this.onFinish}
420421
saveBtnText={saveBtnText}
421422
showUserComment={!initModel.isNew() && appPropertiesOnly}
423+
submitting={submitting}
424+
visitedPanels={visitedPanels}
422425
>
423426
<FilterCriteriaContext.Provider value={filterCriteriaState}>
424427
<AssayPropertiesPanel
425-
model={protocolModel}
426-
onChange={this.onAssayPropertiesChange}
427-
controlledCollapse
428-
initCollapsed={currentPanelIndex !== PROPERTIES_PANEL_INDEX}
429-
panelStatus={panelStatus}
430-
validate={validatePanel === PROPERTIES_PANEL_INDEX}
431428
appPropertiesOnly={appPropertiesOnly}
429+
canRename={isGpat}
430+
controlledCollapse
432431
hideAdvancedProperties={hideAdvancedProperties}
433432
hideStudyProperties={
434433
!!domainFormDisplayOptions && domainFormDisplayOptions.hideStudyPropertyTypes
435434
}
435+
initCollapsed={currentPanelIndex !== PROPERTIES_PANEL_INDEX}
436+
model={protocolModel}
437+
onChange={this.onAssayPropertiesChange}
436438
onToggle={this.togglePropertiesPanel}
437-
canRename={isGpat}
439+
panelStatus={panelStatus}
440+
validate={validatePanel === PROPERTIES_PANEL_INDEX}
438441
/>
439442
{/* Note: We cannot filter this array because onChange needs the correct index for each domain */}
440443
{protocolModel.domains.toArray().map((domain, i) => {
@@ -445,16 +448,16 @@ export class AssayDesignerPanelsImpl extends React.PureComponent<Props, State> {
445448
<AssayDomainForm
446449
api={api}
447450
appDomainHeaders={appDomainHeaders}
451+
currentPanelIndex={currentPanelIndex}
448452
domain={domain}
449453
domainFormDisplayOptions={domainFormDisplayOptions}
454+
firstState={firstState}
450455
headerPrefix={initModel?.name}
451456
index={i}
452457
key={domain.name}
453458
onDomainChange={this.onDomainChange}
454-
protocolModel={protocolModel}
455-
currentPanelIndex={currentPanelIndex}
456-
firstState={firstState}
457459
onTogglePanel={onTogglePanel}
460+
protocolModel={protocolModel}
458461
validatePanel={validatePanel}
459462
visitedPanels={visitedPanels}
460463
/>
@@ -463,17 +466,17 @@ export class AssayDesignerPanelsImpl extends React.PureComponent<Props, State> {
463466
{modalOpen && (
464467
<FilterCriteriaModal
465468
onClose={this.closeModal}
466-
openTo={openTo}
467469
onSave={this.saveFilterCriteria}
470+
openTo={openTo}
468471
protocolModel={protocolModel}
469472
/>
470473
)}
471474
</FilterCriteriaContext.Provider>
472475
{appPropertiesOnly && allowFolderExclusion && (
473476
<DataTypeFoldersPanel
474477
controlledCollapse
475-
dataTypeRowId={protocolModel?.protocolId}
476478
dataTypeName={protocolModel?.name}
479+
dataTypeRowId={protocolModel?.protocolId}
477480
entityDataType={AssayRunDataType}
478481
initCollapsed={currentPanelIndex !== protocolModel.domains.size + 1}
479482
onToggle={this.toggleFoldersPanel}

0 commit comments

Comments
 (0)