Skip to content

Commit 9af227b

Browse files
authored
Amounts and units polish, part 2 (#1902)
1 parent 1dfbb3f commit 9af227b

19 files changed

+654
-202
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": "7.1.0",
3+
"version": "7.1.1",
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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
# @labkey/components
22
Components, models, actions, and utility functions for LabKey applications and pages
33

4+
### version 7.1.1
5+
*Released*: 8 December 2025
6+
- Sample Amount/Units polish: part 2
7+
- Introduced a two-tier unit selection system (Amount Type, Display Units) in sample designer
8+
- Added new measurement units (ug, ng) with updated display precision for existing units
9+
- Added a new formsy rule `sampleAmount` for validating sample amount/units input on forms
10+
- Disallow large amount (>1.79769E308) for amounts input on form, editable grid, and sample storage editor
11+
412
### version 7.1.0
513
*Released*: 3 December 2025
614
- ChartBuilderModal

packages/components/src/internal/components/domainproperties/samples/SampleTypeDesigner.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -369,8 +369,7 @@ export class SampleTypeDesignerImpl extends React.PureComponent<InjectedBaseDoma
369369
return;
370370
}
371371

372-
const metricUnitRequired = metricUnitProps?.metricUnitRequired;
373-
const isValid = model.isValid(defaultSampleFieldConfig, metricUnitRequired);
372+
const isValid = model.isValid(defaultSampleFieldConfig);
374373

375374
this.props.onFinish(isValid, () => this.saveDomain(false, comment ?? auditUserComment));
376375

@@ -386,7 +385,7 @@ export class SampleTypeDesignerImpl extends React.PureComponent<InjectedBaseDoma
386385
} else if (getDuplicateAlias(model.parentAliases, true).size > 0) {
387386
exception =
388387
'Duplicate parent alias header found: ' + getDuplicateAlias(model.parentAliases, true).join(', ');
389-
} else if (!model.isMetricUnitValid(metricUnitRequired)) {
388+
} else if (!model.isMetricUnitValid()) {
390389
exception = metricUnitProps?.metricUnitLabel + ' field is required.';
391390
} else {
392391
exception = model.domain.getFirstFieldError();

packages/components/src/internal/components/domainproperties/samples/SampleTypePropertiesPanel.test.tsx

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ import { DomainDetails, DomainPanelStatus } from '../models';
99

1010
import { getTestAPIWrapper } from '../../../APIWrapper';
1111

12-
import { SampleTypePropertiesPanel } from './SampleTypePropertiesPanel';
12+
import { getValidUnitKinds, SampleTypePropertiesPanel, UnitKinds } from './SampleTypePropertiesPanel';
1313
import { SampleTypeModel } from './models';
14+
import { UNITS_KIND } from '../../../util/measurement';
1415

1516
describe('SampleTypePropertiesPanel', () => {
1617
const BASE_PROPS = {
@@ -114,15 +115,15 @@ describe('SampleTypePropertiesPanel', () => {
114115
container = renderWithAppContext(
115116
<SampleTypePropertiesPanel
116117
{...BASE_PROPS}
118+
dataClassAliasCaption="Source"
119+
dataClassParentageLabel="source"
120+
dataClassTypeCaption="source type"
121+
includeDataClasses
117122
model={sampleTypeModel}
118123
parentOptions={[{ schema: 'exp.data' }]}
119-
includeDataClasses
120-
useSeparateDataClassesAliasMenu
121124
sampleAliasCaption="Parent"
122125
sampleTypeCaption="sample type"
123-
dataClassAliasCaption="Source"
124-
dataClassTypeCaption="source type"
125-
dataClassParentageLabel="source"
126+
useSeparateDataClassesAliasMenu
126127
/>
127128
);
128129
});
@@ -154,7 +155,6 @@ describe('SampleTypePropertiesPanel', () => {
154155
metricUnitProps={{
155156
includeMetricUnitProperty: true,
156157
metricUnitLabel: 'Display Units',
157-
metricUnitRequired: true,
158158
metricUnitHelpMsg: 'Sample storage volume will be displayed using the selected metric unit.',
159159
metricUnitOptions: [
160160
{ id: 'mL', label: 'ml' },
@@ -180,9 +180,9 @@ describe('SampleTypePropertiesPanel', () => {
180180
renderWithAppContext(
181181
<SampleTypePropertiesPanel
182182
{...BASE_PROPS}
183-
showLinkToStudy
184183
appPropertiesOnly={false}
185184
model={sampleTypeModelWithTimepoint}
185+
showLinkToStudy
186186
/>
187187
);
188188
});
@@ -201,9 +201,9 @@ describe('SampleTypePropertiesPanel', () => {
201201
renderWithAppContext(
202202
<SampleTypePropertiesPanel
203203
{...BASE_PROPS}
204-
showLinkToStudy={false}
205204
appPropertiesOnly={false}
206205
model={sampleTypeModelWithTimepoint}
206+
showLinkToStudy={false}
207207
/>
208208
);
209209
});
@@ -255,11 +255,11 @@ describe('SampleTypePropertiesPanel', () => {
255255
renderWithAppContext(
256256
<SampleTypePropertiesPanel
257257
{...BASE_PROPS}
258-
model={SampleTypeModel.create(data)}
259-
appPropertiesOnly={false}
260258
aliquotNamePatternProps={{
261259
showAliquotNameExpression: true,
262260
}}
261+
appPropertiesOnly={false}
262+
model={SampleTypeModel.create(data)}
263263
namePreviews={[null, 'S-parentSample-1']}
264264
/>
265265
);
@@ -271,3 +271,30 @@ describe('SampleTypePropertiesPanel', () => {
271271
expect(aliquotField.textContent).toEqual('Aliquot Naming Pattern');
272272
});
273273
});
274+
275+
describe('getValidUnitKinds', () => {
276+
test('returns all unit kinds when lockUnitKind and metricUnit are undefined', () => {
277+
const result = getValidUnitKinds();
278+
expect(result).toEqual(Object.values(UnitKinds));
279+
});
280+
281+
test('returns all unit kinds when lockUnitKind is false', () => {
282+
const result = getValidUnitKinds(false, 'mL');
283+
expect(result).toEqual(Object.values(UnitKinds));
284+
});
285+
286+
test('returns NONE and the unit kind matching the metricUnit when lockUnitKind is true', () => {
287+
const result = getValidUnitKinds(true, 'mL');
288+
expect(result).toEqual([UnitKinds[UNITS_KIND.NONE], UnitKinds[UNITS_KIND.VOLUME]]);
289+
});
290+
291+
test('returns only NONE when lockUnitKind is true and metricUnit is invalid', () => {
292+
const result = getValidUnitKinds(true, 'invalidUnit');
293+
expect(result).toEqual([UnitKinds[UNITS_KIND.NONE]]);
294+
});
295+
296+
test('returns only all when lockUnitKind is true and metricUnit is undefined', () => {
297+
const result = getValidUnitKinds(true);
298+
expect(result).toEqual(Object.values(UnitKinds));
299+
});
300+
});

0 commit comments

Comments
 (0)