Skip to content

Commit cd7c23b

Browse files
committed
Merge remote-tracking branch 'origin/develop' into fb_redirectGH1023
2 parents 7f87456 + f2a32c6 commit cd7c23b

21 files changed

Lines changed: 335 additions & 189 deletions

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.43.1-fb-redirectGH1023.0",
3+
"version": "7.45.0",
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: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,22 @@ Components, models, actions, and utility functions for LabKey applications and p
55
*Released*: TBD
66
- GitHub Issue #1023: Add redirect() helper that uses core-safeRedirect
77

8+
### version 7.45.0
9+
*Released*: 25 June 2026
10+
- GitHub Issue 1234: User management page update to use SiteUsers for inactive users grid
11+
12+
### version 7.44.1
13+
*Released*: 25 June 2026
14+
- GH Issue 1191: Don't remove product switch menu from narrow browsers
15+
- GH Issue 254: Use primary product id to get user menu items instead of current product id.
16+
17+
### version 7.44.0
18+
*Released*: 25 June 2026
19+
- Support for filtering the sample lineage graph by sample status
20+
- Add metric constant
21+
- Add `sampleStatus` field to `LineageNode`
22+
- Add `rowId` field to `SampleStatus` model
23+
824
### version 7.43.0
925
*Released*: 23 June 2026
1026
- panel.scss: disable margin on h1, h2, etc elements within panel-heading components

packages/components/src/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,7 @@ import {
395395
getSampleStatus,
396396
getSampleStatusColor,
397397
getSampleStatusContainerFilter,
398+
getSampleStatusFromSampleRow,
398399
getSampleStatusType,
399400
isAllSamplesSchema,
400401
isSampleOperationPermitted,
@@ -422,7 +423,7 @@ import {
422423
TestLineageAPIWrapper,
423424
} from './internal/components/lineage/actions';
424425
import { withLineage } from './internal/components/lineage/withLineage';
425-
import { DEFAULT_LINEAGE_DISTANCE } from './internal/components/lineage/constants';
426+
import { DEFAULT_LINEAGE_DISTANCE, LINEAGE_GRAPH_FILTER_METRIC} from './internal/components/lineage/constants';
426427
import {
427428
LINEAGE_DIRECTIONS,
428429
LINEAGE_GROUPING_GENERATIONS,
@@ -527,6 +528,7 @@ import {
527528
EntityCreationType,
528529
INDEPENDENT_SAMPLE_CREATION,
529530
POOLED_SAMPLE_CREATION,
531+
SampleState,
530532
SampleStateType,
531533
} from './internal/components/samples/models';
532534
import { DEFAULT_ALIQUOT_NAMING_PATTERN, SampleTypeModel } from './internal/components/domainproperties/samples/models';
@@ -1407,6 +1409,7 @@ export {
14071409
getSampleStatus,
14081410
getSampleStatusColor,
14091411
getSampleStatusContainerFilter,
1412+
getSampleStatusFromSampleRow,
14101413
getSampleStatusType,
14111414
getSamplesTestAPIWrapper,
14121415
getSampleTypeDetails,
@@ -1513,6 +1516,7 @@ export {
15131516
LabelHelpTip,
15141517
LabelOverlay,
15151518
LINEAGE_DIRECTIONS,
1519+
LINEAGE_GRAPH_FILTER_METRIC,
15161520
LINEAGE_GROUPING_GENERATIONS,
15171521
LineageDepthLimitMessage,
15181522
LineageFilter,
@@ -1658,6 +1662,7 @@ export {
16581662
SampleParentDataType,
16591663
SamplePropertyDataType,
16601664
SamplesEditButtonSections,
1665+
SampleState,
16611666
SampleStateType,
16621667
SampleStatusLegend,
16631668
SampleStatusRenderer,

packages/components/src/internal/components/lineage/constants.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
LineageURLResolvers,
1818
} from './types';
1919
import { SAMPLE_STATE_COLOR_COLUMN_NAME, SAMPLE_STATE_TYPE_COLUMN_NAME } from '../samples/constants';
20-
20+
export const LINEAGE_GRAPH_FILTER_METRIC = "LineageGraphFilter";
2121
// Default depth to fetch with the lineage API
2222
export const DEFAULT_LINEAGE_DISTANCE = 5;
2323
export const DEFAULT_LINEAGE_DIRECTION = LINEAGE_DIRECTIONS.Children;

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

Lines changed: 89 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
* Copyright (c) 2020-2026 LabKey Corporation. All rights reserved. No portion of this work may be reproduced
33
* in any form or by any electronic or mechanical means without written permission from LabKey Corporation.
44
*/
5-
import { applyLineageOptions, generateNodesAndEdges, LineageIO, LineageNode, LineageResult } from './models';
5+
import { applyLineageOptions, generateNodesAndEdges, Lineage, LineageIO, LineageNode, LineageResult } from './models';
66
import { DEFAULT_LINEAGE_OPTIONS } from './constants';
7-
import { LineageFilter } from './types';
7+
import { LineageFilter, LineageOptions } from './types';
88

99
describe('lineage model', () => {
1010
describe('applyLineageOptions', () => {
@@ -33,6 +33,7 @@ describe('lineage model', () => {
3333
describe('LineageIO.applyConfig', () => {
3434
const lineageObj = {
3535
container: 'container',
36+
containerPath: '/container',
3637
created: '2022-01-20',
3738
createdBy: 'me',
3839
modified: '2022-01-21',
@@ -235,13 +236,13 @@ describe('lineage model', () => {
235236
const parentLsid = 'parent-lsid';
236237

237238
const childNode = LineageNode.create(childLsid, {
238-
parents: [{ lsid: parentLsid, name: '&4[0' }],
239-
name: '&4[0_1001'
239+
parents: [{ lsid: parentLsid }],
240+
name: '&4[0_1001',
240241
});
241242

242243
const parentNode = LineageNode.create(parentLsid, {
243-
children: [{ lsid: childLsid, name: '&4[0_1001' }],
244-
name: '&4[0'
244+
children: [{ lsid: childLsid }],
245+
name: '&4[0',
245246
});
246247

247248
const result = LineageResult.create({
@@ -262,5 +263,87 @@ describe('lineage model', () => {
262263
expect(nodes[childNode.lsid].level).toEqual(0);
263264
expect(nodes[childNode.lsid].label).toEqual('&4[0_1001');
264265
});
266+
267+
it('GH Issue #1256: Lineage graph can become disconnected with single aliquot and multiple derivatives', () => {
268+
// Parent sample with 2 derived samples and 1 aliquot including derivation runs
269+
const result = LineageResult.create({
270+
nodes: {
271+
'aliquot-lsid': {
272+
cpasType: 'sample-type-lsid',
273+
materialLineageType: 'Aliquot',
274+
type: 'Sample',
275+
lsid: 'aliquot-lsid',
276+
name: 'Aliquot-1',
277+
expType: 'Material',
278+
parents: [{ lsid: 'aliquot-run-lsid' }],
279+
},
280+
'aliquot-run-lsid': {
281+
cpasType: 'urn:lsid:labkey.org:Protocol:SampleAliquotProtocol',
282+
type: 'Run',
283+
lsid: 'aliquot-run-lsid',
284+
children: [{ lsid: 'aliquot-lsid' }],
285+
name: 'Create aliquot from Parent-1',
286+
expType: 'ExperimentRun',
287+
parents: [{ lsid: 'parent-lsid' }],
288+
},
289+
'derived-one-lsid': {
290+
cpasType: 'sample-type-lsid',
291+
materialLineageType: 'Derivative',
292+
type: 'Sample',
293+
lsid: 'derived-one-lsid',
294+
name: 'Derived-1',
295+
expType: 'Material',
296+
parents: [{ lsid: 'derivation-run-lsid' }],
297+
},
298+
'derived-two-lsid': {
299+
cpasType: 'sample-type-lsid',
300+
materialLineageType: 'Derivative',
301+
type: 'Sample',
302+
lsid: 'derived-two-lsid',
303+
name: 'Derived-2',
304+
expType: 'Material',
305+
parents: [{ lsid: 'derivation-run-lsid' }],
306+
},
307+
'derivation-run-lsid': {
308+
cpasType: 'urn:lsid:labkey.org:Protocol:SampleDerivationProtocol',
309+
type: 'Run',
310+
lsid: 'derivation-run-lsid',
311+
children: [{ lsid: 'derived-two-lsid' }, { lsid: 'derived-one-lsid' }],
312+
name: 'Derive 2 samples from Parent-1',
313+
expType: 'ExperimentRun',
314+
parents: [{ lsid: 'parent-lsid' }],
315+
},
316+
'parent-lsid': {
317+
cpasType: 'sample-type-lsid',
318+
materialLineageType: 'RootMaterial',
319+
type: 'Sample',
320+
lsid: 'parent-lsid',
321+
children: [{ lsid: 'aliquot-run-lsid' }, { lsid: 'derivation-run-lsid' }],
322+
name: 'Parent-1',
323+
expType: 'Material',
324+
},
325+
},
326+
seed: 'parent-lsid',
327+
});
328+
329+
const options: LineageOptions = {
330+
filters: [new LineageFilter('type', ['Sample'])],
331+
};
332+
333+
const lineage = new Lineage({ result });
334+
const nodesAndEdges = generateNodesAndEdges(lineage.filterResult(options), options);
335+
336+
// Runs have been filtered out
337+
const expectedNodes = new Set(['aliquot-lsid', 'derived-one-lsid', 'derived-two-lsid', 'parent-lsid']);
338+
expect(new Set(Object.keys(nodesAndEdges.nodes))).toEqual(expectedNodes);
339+
340+
// Previously, this would have only two edges where the edge between the parent and aliquot was missing
341+
const expectedEdges = new Set([
342+
'parent-lsid||aliquot-lsid',
343+
'parent-lsid||derived-one-lsid',
344+
'parent-lsid||derived-two-lsid',
345+
]);
346+
expect(new Set(Object.keys(nodesAndEdges.edges))).toEqual(expectedEdges);
347+
});
265348
});
266349
});

packages/components/src/internal/components/lineage/models.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ export class LineageNode
291291
steps: undefined,
292292
type: undefined,
293293
materialLineageType: undefined,
294+
sampleStatus: undefined,
294295
url: undefined,
295296

296297
// computed properties
@@ -332,6 +333,7 @@ export class LineageNode
332333
declare steps: List<LineageRunStep>;
333334
declare type: string;
334335
declare materialLineageType: string;
336+
declare sampleStatus: number;
335337
declare url: string;
336338

337339
// computed properties
@@ -400,11 +402,11 @@ export class LineageResult extends ImmutableRecord({
400402
});
401403
}
402404

403-
filterIn(field: string, value: string | string[] | undefined): LineageResult {
405+
filterIn(field: string, value: string | string[] | number[] | number | undefined): LineageResult {
404406
return LineageResult._filter(this, field, value, true);
405407
}
406408

407-
filterOut(field: string, value: string | string[] | undefined): LineageResult {
409+
filterOut(field: string, value: string | string[] | number[] | number | undefined): LineageResult {
408410
return LineageResult._filter(this, field, value, false);
409411
}
410412

@@ -418,7 +420,7 @@ export class LineageResult extends ImmutableRecord({
418420
private static _filter(
419421
result: LineageResult,
420422
field: string,
421-
value: string | string[] | undefined,
423+
value: number | number[] | string | string[] | undefined,
422424
filterIn: boolean
423425
): LineageResult {
424426
if (field === undefined) throw new Error('field must not be undefined');
@@ -457,22 +459,23 @@ export class LineageResult extends ImmutableRecord({
457459

458460
/**
459461
* When 'filterIn' is true, returns true if the node[field] is equal to the value or any of the array item values.
460-
* When value is undefined, it is treated as a wildcard -- any value is allowed as long as the
462+
* When value is undefined, it is treated as a wildcard -- any value is allowed as long as the field exists
461463
*
462464
* When 'filterIn' is false, returns true if the node[field] is not equal to the value or any of the array item values.
463-
* When value is undefined, the node must not have contain a value for the field.
465+
* When value is undefined, the node must not contain a value for the field.
464466
*/
465467
private static _matches(
466468
node: LineageNode,
467469
field: string,
468-
value: string | string[] | undefined,
470+
value: number | number[] | string | string[] | undefined,
469471
filterIn: boolean
470472
): boolean {
471473
if (filterIn) {
472474
if (value === undefined) {
473475
// true if the field exists on node
474476
return node.has(field);
475477
} else if (Array.isArray(value)) {
478+
// @ts-expect-error number or string possible
476479
return value.indexOf(node[field]) > -1;
477480
} else {
478481
return node[field] === value;
@@ -482,6 +485,7 @@ export class LineageResult extends ImmutableRecord({
482485
// true if the field does not exist on node
483486
return !node.has(field);
484487
} else if (Array.isArray(value)) {
488+
// @ts-expect-error number or string possible
485489
return value.indexOf(node[field]) === -1;
486490
} else {
487491
return node[field] !== value;
@@ -1137,6 +1141,9 @@ function applyCombineSize(
11371141
combinedLineageNodes.push(
11381142
combineNodes(lsid, aliquotEdges, nodes, options, dir, visEdges, visNodes, nodesInCombinedNode, depth)
11391143
);
1144+
} else {
1145+
// GH Issue #1256
1146+
addEdges(lsid, null, visEdges, List(aliquotEdges), nodesInCombinedNode, dir);
11401147
}
11411148

11421149
if (nonAliquotEdges.length >= options.grouping.combineSize) {

packages/components/src/internal/components/lineage/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ export interface LineageGroupingOptions {
4242

4343
export class LineageFilter {
4444
field: string;
45-
value: string[];
45+
value: number[] | string[];
4646

47-
constructor(field: string, value: string[]) {
47+
constructor(field: string, value: number[] | string[]) {
4848
this.field = field;
4949
this.value = value;
5050
}

packages/components/src/internal/components/navigation/NavigationAPIWrapper.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export interface NavigationAPIWrapper {
1818
containerId: string,
1919
containerPath?: string
2020
) => Promise<ProductMenuModel>;
21-
loadUserMenu: (currentProductId: string, containerPath?: string) => Promise<MenuSectionModel>;
21+
loadUserMenu: (productId: string, containerPath?: string) => Promise<MenuSectionModel>;
2222
}
2323

2424
export class ServerNavigationAPIWrapper implements NavigationAPIWrapper {
@@ -45,9 +45,9 @@ export class ServerNavigationAPIWrapper implements NavigationAPIWrapper {
4545
}
4646
};
4747

48-
loadUserMenu = async (currentProductId: string, containerPath?: string): Promise<MenuSectionModel> => {
48+
loadUserMenu = async (productId: string, containerPath?: string): Promise<MenuSectionModel> => {
4949
try {
50-
return await getUserMenuSection(currentProductId, containerPath);
50+
return await getUserMenuSection(productId, containerPath);
5151
} catch (e) {
5252
return undefined;
5353
}

packages/components/src/internal/components/navigation/NavigationBar.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ describe('NavigationBar', () => {
6565
maxRows: 1,
6666
markAllNotificationsRead,
6767
serverActivity: new ServerNotificationModel(),
68+
onRead: jest.fn(),
6869
};
6970

7071
test('default props', async () => {

0 commit comments

Comments
 (0)