Skip to content

Commit d0b1633

Browse files
committed
Molecule and PS bulk import by file
1 parent 88e80f2 commit d0b1633

7 files changed

Lines changed: 135 additions & 20 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": "7.34.0",
3+
"version": "7.34.1-fb-moleculeImport.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: 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 7.X
5+
*Released*: X May 2026
6+
- Molecule and PS bulk import by file
7+
- TODO
8+
49
### version 7.34.0
510
*Released*: 5 May 2026
611
- Accessibility improvements for app pages: Colors

packages/components/src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ import {
6868
isSetEqual,
6969
joinMultiValueForExport,
7070
makeCommaSeparatedString,
71+
makeDataCountMsg,
7172
parseScientificInt,
7273
pronoun,
7374
quoteValueWithDelimiters,
@@ -194,6 +195,7 @@ import {
194195
} from './internal/components/editable/actions';
195196
import {
196197
clearSelected,
198+
getDataClassesFromTransactionIds,
197199
getGridIdsFromTransactionId,
198200
getSampleTypesFromTransactionIds,
199201
getSelected,
@@ -1420,6 +1422,7 @@ export {
14201422
getSamplesTestAPIWrapper,
14211423
getSampleTypeDetails,
14221424
getSampleTypesFromTransactionIds,
1425+
getDataClassesFromTransactionIds,
14231426
getSchemaQuery,
14241427
getSearchFilterObj,
14251428
getSearchFilterObjs,
@@ -1541,6 +1544,7 @@ export {
15411544
LOOK_AND_FEEL_METRIC,
15421545
LookupSelectInput,
15431546
makeCommaSeparatedString,
1547+
makeDataCountMsg,
15441548
makeQueryInfo,
15451549
makeTestActions,
15461550
makeTestISelectRowsResult,

packages/components/src/internal/actions.ts

Lines changed: 82 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,10 @@ import { resolveErrorMessage } from './util/messaging';
4343

4444
import { ViewInfo } from './ViewInfo';
4545
import { createGridModelId } from './models';
46-
import { SAMPLES_KEY } from './app/constants';
46+
import { SAMPLES_KEY, SOURCES_KEY } from './app/constants';
4747
import { SCHEMAS } from './schemas';
48+
import { selectRows } from './query/selectRows';
49+
import { caseInsensitive } from './util/utils';
4850

4951
export function selectAll(
5052
key: string,
@@ -62,11 +64,18 @@ export function selectAll(
6264
});
6365
}
6466

67+
type DataTypeRowIdsFromTransactionIds = {
68+
rowIds: string[];
69+
dataTypeIds: Record<number, number>; // todo rename to count
70+
dataTypes?: string[];
71+
dataTypeNameIds?: Record<string, number>;
72+
}
73+
6574
export async function getGridIdsFromTransactionId(
6675
transactionAuditId: number | string,
6776
dataType: string,
6877
containerPath?: string
69-
): Promise<string[]> {
78+
): Promise<DataTypeRowIdsFromTransactionIds> {
7079
if (!transactionAuditId) return;
7180

7281
const failureMsg = `There was a problem retrieving the ${dataType} from the last action.`;
@@ -88,7 +97,11 @@ export async function getGridIdsFromTransactionId(
8897
}
8998

9099
// The server returns numbers, so we coerce to string; If we don't, it can lead to bugs (and has).
91-
return response.rowIds.map(rowId => rowId.toString());
100+
const rowIds = response.rowIds.map(rowId => rowId.toString());
101+
return {
102+
rowIds,
103+
dataTypeIds: response['dataTypeIds']
104+
}
92105
}
93106

94107
export async function selectGridIdsFromTransactionId(
@@ -97,33 +110,85 @@ export async function selectGridIdsFromTransactionId(
97110
transactionAuditId: number | string,
98111
dataType: string,
99112
actions: Actions
100-
): Promise<string[]> {
113+
): Promise<DataTypeRowIdsFromTransactionIds> {
101114
if (!transactionAuditId) return undefined;
102115

103116
const modelId = createGridModelId(gridIdPrefix, schemaQuery);
104117
const selected = await getGridIdsFromTransactionId(transactionAuditId, dataType);
105-
actions.replaceSelections(modelId, selected);
118+
actions.replaceSelections(modelId, selected.rowIds);
106119
return selected;
107120
}
108121

109-
type SampleTypesFromTransactionIds = { rowIds: string[]; sampleTypes: string[] };
110122

111-
export async function getSampleTypesFromTransactionIds(
112-
transactionAuditId: number | string
113-
): Promise<SampleTypesFromTransactionIds> {
123+
async function getDataTypesFromTransactionId(
124+
transactionAuditId: number | string,
125+
auditDataType: string,
126+
schemaName: string,
127+
queryName: string,
128+
typeColumn: string
129+
): Promise<DataTypeRowIdsFromTransactionIds> {
114130
if (!transactionAuditId) return undefined;
115131

116-
const rowIds = await getGridIdsFromTransactionId(transactionAuditId, SAMPLES_KEY);
117-
const sampleTypes = await selectDistinctRows({
118-
schemaName: SCHEMAS.EXP_TABLES.MATERIALS.schemaName,
119-
queryName: SCHEMAS.EXP_TABLES.MATERIALS.queryName,
120-
column: 'SampleSet/Name',
132+
const { rowIds, dataTypeIds } = await getGridIdsFromTransactionId(transactionAuditId, auditDataType);
133+
const distinct = await selectDistinctRows({
134+
schemaName,
135+
queryName,
136+
column: typeColumn,
121137
filterArray: [Filter.create('RowId', rowIds, Filter.Types.IN)],
122138
});
139+
return { rowIds, dataTypeIds, dataTypes: distinct.values };
140+
}
141+
142+
export function getSampleTypesFromTransactionIds(
143+
transactionAuditId: number | string
144+
): Promise<DataTypeRowIdsFromTransactionIds> {
145+
return getDataTypesFromTransactionId(
146+
transactionAuditId,
147+
SAMPLES_KEY,
148+
SCHEMAS.EXP_TABLES.MATERIALS.schemaName,
149+
SCHEMAS.EXP_TABLES.MATERIALS.queryName,
150+
'SampleSet/Name'
151+
);
152+
}
153+
154+
export async function getDataClassesFromTransactionIds(
155+
transactionAuditId: number | string
156+
): Promise<DataTypeRowIdsFromTransactionIds> {
157+
const results = await getDataTypesFromTransactionId(
158+
transactionAuditId,
159+
SOURCES_KEY,
160+
SCHEMAS.EXP_TABLES.DATA.schemaName,
161+
SCHEMAS.EXP_TABLES.DATA.queryName,
162+
'DataClass/Name'
163+
);
164+
165+
if (!results)
166+
return undefined;
167+
168+
const { dataTypeIds, dataTypes } = results;
169+
const dataTypeLcMap = Object.fromEntries((dataTypes ?? []).map(dt => [dt.toLowerCase(), dt]));
170+
171+
const dataTypeNameIds = {};
172+
if (dataTypeIds) {
173+
const dataClasses = await selectRows({
174+
schemaQuery: SCHEMAS.EXP_TABLES.DATA_CLASSES,
175+
columns: ['Name', 'RowId'],
176+
filterArray: [Filter.create('rowId', Object.keys(dataTypeIds), Filter.Types.IN)],
177+
containerFilter: Query.containerFilter.currentPlusProjectAndShared,
178+
})
179+
180+
181+
dataClasses.rows.forEach(row => {
182+
const dataClassLc = caseInsensitive(row, 'Name')?.value?.toLowerCase();
183+
const rowId = caseInsensitive(row, 'RowId').value;
184+
dataTypeNameIds[dataTypeLcMap[dataClassLc]] = dataTypeIds[rowId];
185+
});
186+
}
187+
123188
return {
124-
rowIds,
125-
sampleTypes: sampleTypes.values,
126-
};
189+
...results,
190+
dataTypeNameIds
191+
}
127192
}
128193

129194
export interface ExportOptions {

packages/components/src/internal/util/utils.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import {
5656
splitMultiValueForImport,
5757
stringToHtmlId,
5858
styleStringToObj,
59+
makeDataCountMsg,
5960
toLowerSafe,
6061
uncapitalizeFirstChar,
6162
unorderedEqual,
@@ -2081,3 +2082,33 @@ describe('stringToHtmlId', () => {
20812082
expect(stringToHtmlId('my-id')).toBe('my-id');
20822083
});
20832084
});
2085+
2086+
describe('makeDataCountMsg', () => {
2087+
test('empty object', () => {
2088+
expect(makeDataCountMsg({})).toBe('');
2089+
});
2090+
2091+
test('all zero or null counts are ignored', () => {
2092+
expect(makeDataCountMsg({ Molecule: 0, ProtSequence: null })).toBe('');
2093+
});
2094+
2095+
test('single type, count of 1 — no pluralization', () => {
2096+
expect(makeDataCountMsg({ Molecule: 1 })).toBe('1 Molecule');
2097+
});
2098+
2099+
test('single type, count > 1 — pluralized with s', () => {
2100+
expect(makeDataCountMsg({ Molecule: 3 })).toBe('3 Molecules');
2101+
});
2102+
2103+
test('multiple types', () => {
2104+
expect(makeDataCountMsg({ Molecule: 2, Compound: 1 })).toBe('2 Molecules and 1 Compound');
2105+
});
2106+
2107+
test('multiple types, some zero', () => {
2108+
expect(makeDataCountMsg({ Molecule: 2, ProtSequence: 0, Compound: 1 })).toBe('2 Molecules and 1 Compound');
2109+
});
2110+
2111+
test('three types', () => {
2112+
expect(makeDataCountMsg({ Molecule: 1, ProtSequence: 4, Compound: 2 })).toBe('1 Molecule, 4 ProtSequences and 2 Compounds');
2113+
});
2114+
});

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -931,3 +931,13 @@ export function hasIdentifiedCol(schemaQuery: SchemaQuery): boolean {
931931
const isCompound = schemaQuery.isEqual(SCHEMAS.DATA_CLASSES.COMPOUND, false);
932932
return isNucSeq || isProtSeq || isMolecule || isCompound;
933933
}
934+
935+
export function makeDataCountMsg(dataCounts: Record<string, number>): string {
936+
const parts = [];
937+
for (const [noun, count] of Object.entries(dataCounts)) {
938+
if (!count) continue;
939+
parts.push(`${count} ${count > 1 ? noun + 's' : noun}`);
940+
}
941+
942+
return makeCommaSeparatedString(parts);
943+
}

0 commit comments

Comments
 (0)