From b61559c56326550757d25e7814044481aff39a49 Mon Sep 17 00:00:00 2001 From: David Roper Date: Wed, 22 Oct 2025 11:17:41 -0400 Subject: [PATCH 1/5] feat: add function to split the group and $ off of the subject id in export --- .../instrument-records/instrument-records.service.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/api/src/instrument-records/instrument-records.service.ts b/apps/api/src/instrument-records/instrument-records.service.ts index 2017e3dd1..2f4502764 100644 --- a/apps/api/src/instrument-records/instrument-records.service.ts +++ b/apps/api/src/instrument-records/instrument-records.service.ts @@ -38,6 +38,13 @@ type ExpandDataType = success: false; }; +function afterFirstDollar(str: string) { + const match = /\$(.*)/.exec(str); + if (!match) return str; + if (!match[1]) return str; + return match[1]; +} + @Injectable() export class InstrumentRecordsService { constructor( @@ -180,7 +187,7 @@ export class InstrumentRecordsService { sessionId: record.session.id, sessionType: record.session.type, subjectAge: record.subject.dateOfBirth ? yearsPassed(record.subject.dateOfBirth) : null, - subjectId: record.subject.id, + subjectId: afterFirstDollar(record.subject.id), subjectSex: record.subject.sex, timestamp: record.date.toISOString(), value: measureValue @@ -203,7 +210,7 @@ export class InstrumentRecordsService { sessionId: record.session.id, sessionType: record.session.type, subjectAge: record.subject.dateOfBirth ? yearsPassed(record.subject.dateOfBirth) : null, - subjectId: record.subject.id, + subjectId: afterFirstDollar(record.subject.id), subjectSex: record.subject.sex, timestamp: record.date.toISOString(), value: arrayEntry.measureValue From 9743f954f24a72566d2d2122ff228ec7ce2a9ed0 Mon Sep 17 00:00:00 2001 From: David Roper Date: Wed, 22 Oct 2025 11:18:20 -0400 Subject: [PATCH 2/5] feat: add spliter function, now group and subject id in two separate columns --- .../src/hooks/useInstrumentVisualization.ts | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/apps/web/src/hooks/useInstrumentVisualization.ts b/apps/web/src/hooks/useInstrumentVisualization.ts index d59d5f4c2..211f69a3f 100644 --- a/apps/web/src/hooks/useInstrumentVisualization.ts +++ b/apps/web/src/hooks/useInstrumentVisualization.ts @@ -36,6 +36,15 @@ export function useInstrumentVisualization({ params }: UseInstrumentVisualizatio const [minDate, setMinDate] = useState(null); const [instrumentId, setInstrumentId] = useState(null); + function splitFirstDollar(str: string) { + const match = /^(.*?)\$(.*)$/.exec(str); + if (!match) { + return [str, str]; + } + const [, before, after] = match; + return [before, after]; + } + const instrument = useInstrument(instrumentId) as AnyUnilingualScalarInstrument; const instrumentInfoQuery = useInstrumentInfoQuery({ @@ -70,7 +79,10 @@ export function useInstrumentVisualization({ params }: UseInstrumentVisualizatio const makeWideRows = () => { const columnNames = Object.keys(exportRecords[0]!); return exportRecords.map((item) => { - const obj: { [key: string]: any } = { subjectId: params.subjectId }; + const obj: { [key: string]: any } = { + GroupID: splitFirstDollar(params.subjectId)[0], + subjectId: splitFirstDollar(params.subjectId)[1] + }; for (const key of columnNames) { const val = item[key]; if (key === '__date__') { @@ -99,8 +111,10 @@ export function useInstrumentVisualization({ params }: UseInstrumentVisualizatio objVal.forEach((arrayItem) => { Object.entries(arrayItem as object).forEach(([arrKey, arrItem]) => { longRecord.push({ + GroupID: splitFirstDollar(params.subjectId)[0], + // eslint-disable-next-line perfectionist/sort-objects Date: toBasicISOString(date), - SubjectID: params.subjectId, + SubjectID: splitFirstDollar(params.subjectId)[1], Variable: `${objKey}-${arrKey}`, // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, perfectionist/sort-objects Value: arrItem @@ -109,8 +123,10 @@ export function useInstrumentVisualization({ params }: UseInstrumentVisualizatio }); } else { longRecord.push({ + GroupID: splitFirstDollar(params.subjectId)[0], + // eslint-disable-next-line perfectionist/sort-objects Date: toBasicISOString(date), - SubjectID: params.subjectId, + SubjectID: splitFirstDollar(params.subjectId)[1], Value: objVal, Variable: objKey }); From 6e8965a4374339faba0e0bf82a9809dbeb52812d Mon Sep 17 00:00:00 2001 From: David Roper Date: Wed, 22 Oct 2025 11:28:51 -0400 Subject: [PATCH 3/5] feat: switch back to afterfirstdollar function, use group id from current group --- .../src/hooks/useInstrumentVisualization.ts | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/apps/web/src/hooks/useInstrumentVisualization.ts b/apps/web/src/hooks/useInstrumentVisualization.ts index 211f69a3f..d5a08a330 100644 --- a/apps/web/src/hooks/useInstrumentVisualization.ts +++ b/apps/web/src/hooks/useInstrumentVisualization.ts @@ -36,13 +36,11 @@ export function useInstrumentVisualization({ params }: UseInstrumentVisualizatio const [minDate, setMinDate] = useState(null); const [instrumentId, setInstrumentId] = useState(null); - function splitFirstDollar(str: string) { - const match = /^(.*?)\$(.*)$/.exec(str); - if (!match) { - return [str, str]; - } - const [, before, after] = match; - return [before, after]; + function afterFirstDollar(str: string) { + const match = /\$(.*)/.exec(str); + if (!match) return str; + if (!match[1]) return str; + return match[1]; } const instrument = useInstrument(instrumentId) as AnyUnilingualScalarInstrument; @@ -80,8 +78,8 @@ export function useInstrumentVisualization({ params }: UseInstrumentVisualizatio const columnNames = Object.keys(exportRecords[0]!); return exportRecords.map((item) => { const obj: { [key: string]: any } = { - GroupID: splitFirstDollar(params.subjectId)[0], - subjectId: splitFirstDollar(params.subjectId)[1] + GroupID: currentGroup ? currentGroup.id : 'root', + subjectId: afterFirstDollar(params.subjectId) }; for (const key of columnNames) { const val = item[key]; @@ -111,10 +109,10 @@ export function useInstrumentVisualization({ params }: UseInstrumentVisualizatio objVal.forEach((arrayItem) => { Object.entries(arrayItem as object).forEach(([arrKey, arrItem]) => { longRecord.push({ - GroupID: splitFirstDollar(params.subjectId)[0], + GroupID: currentGroup ? currentGroup.id : 'root', // eslint-disable-next-line perfectionist/sort-objects Date: toBasicISOString(date), - SubjectID: splitFirstDollar(params.subjectId)[1], + SubjectID: afterFirstDollar(params.subjectId), Variable: `${objKey}-${arrKey}`, // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, perfectionist/sort-objects Value: arrItem @@ -123,10 +121,10 @@ export function useInstrumentVisualization({ params }: UseInstrumentVisualizatio }); } else { longRecord.push({ - GroupID: splitFirstDollar(params.subjectId)[0], + GroupID: currentGroup ? currentGroup.id : 'root', // eslint-disable-next-line perfectionist/sort-objects Date: toBasicISOString(date), - SubjectID: splitFirstDollar(params.subjectId)[1], + SubjectID: afterFirstDollar(params.subjectId), Value: objVal, Variable: objKey }); From 110b2ec6c0020f7572ffbf2a76ba07955f557ba2 Mon Sep 17 00:00:00 2001 From: David Roper Date: Wed, 22 Oct 2025 11:29:05 -0400 Subject: [PATCH 4/5] test: update tests --- .../__tests__/useInstrumentVisualization.test.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/web/src/hooks/__tests__/useInstrumentVisualization.test.ts b/apps/web/src/hooks/__tests__/useInstrumentVisualization.test.ts index 52a6ed7f5..c86bf8190 100644 --- a/apps/web/src/hooks/__tests__/useInstrumentVisualization.test.ts +++ b/apps/web/src/hooks/__tests__/useInstrumentVisualization.test.ts @@ -72,7 +72,9 @@ describe('useInstrumentVisualization', () => { const [filename, getContentFn] = mockDownloadFn.mock.calls[0] ?? []; expect(filename).toContain('.csv'); const csvContents = getContentFn(); - expect(csvContents).toMatch(`subjectId,Date,someValue\r\ntestId,${toBasicISOString(FIXED_TEST_DATE)},abc`); + expect(csvContents).toMatch( + `GroupID,subjectId,Date,someValue\r\ntestGroupId,testId,${toBasicISOString(FIXED_TEST_DATE)},abc` + ); }); }); describe('TSV', () => { @@ -85,7 +87,9 @@ describe('useInstrumentVisualization', () => { const [filename, getContentFn] = mockDownloadFn.mock.calls[0] ?? []; expect(filename).toContain('.tsv'); const tsvContents = getContentFn(); - expect(tsvContents).toMatch(`subjectId\tDate\tsomeValue\r\ntestId\t${toBasicISOString(FIXED_TEST_DATE)}\tabc`); + expect(tsvContents).toMatch( + `GroupID\tsubjectId\tDate\tsomeValue\r\ntestGroupId\ttestId\t${toBasicISOString(FIXED_TEST_DATE)}\tabc` + ); }); }); describe('CSV Long', () => { @@ -100,7 +104,7 @@ describe('useInstrumentVisualization', () => { expect(filename).toContain('.csv'); const csvLongContents = getContentFn(); expect(csvLongContents).toMatch( - `Date,SubjectID,Value,Variable\r\n${toBasicISOString(FIXED_TEST_DATE)},testId,abc,someValue` + `GroupID,Date,SubjectID,Value,Variable\r\ntestGroupId,${toBasicISOString(FIXED_TEST_DATE)},testId,abc,someValue` ); }); }); @@ -116,7 +120,7 @@ describe('useInstrumentVisualization', () => { expect(filename).toMatch('.tsv'); const tsvLongContents = getContentFn(); expect(tsvLongContents).toMatch( - `Date\tSubjectID\tValue\tVariable\r\n${toBasicISOString(FIXED_TEST_DATE)}\ttestId\tabc\tsomeValue` + `GroupID\tDate\tSubjectID\tValue\tVariable\r\ntestGroupId\t${toBasicISOString(FIXED_TEST_DATE)}\ttestId\tabc\tsomeValue` ); }); }); From d7cf820aef0acbee3a646f4fce8fc8e651fb8576 Mon Sep 17 00:00:00 2001 From: David Roper Date: Wed, 22 Oct 2025 13:06:21 -0400 Subject: [PATCH 5/5] fix: add code rabbit suggestion --- apps/api/src/instrument-records/instrument-records.service.ts | 1 + apps/web/src/hooks/useInstrumentVisualization.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/apps/api/src/instrument-records/instrument-records.service.ts b/apps/api/src/instrument-records/instrument-records.service.ts index 2f4502764..3c415247e 100644 --- a/apps/api/src/instrument-records/instrument-records.service.ts +++ b/apps/api/src/instrument-records/instrument-records.service.ts @@ -39,6 +39,7 @@ type ExpandDataType = }; function afterFirstDollar(str: string) { + if (!str) return str; const match = /\$(.*)/.exec(str); if (!match) return str; if (!match[1]) return str; diff --git a/apps/web/src/hooks/useInstrumentVisualization.ts b/apps/web/src/hooks/useInstrumentVisualization.ts index d5a08a330..097606668 100644 --- a/apps/web/src/hooks/useInstrumentVisualization.ts +++ b/apps/web/src/hooks/useInstrumentVisualization.ts @@ -37,6 +37,7 @@ export function useInstrumentVisualization({ params }: UseInstrumentVisualizatio const [instrumentId, setInstrumentId] = useState(null); function afterFirstDollar(str: string) { + if (!str) return str; const match = /\$(.*)/.exec(str); if (!match) return str; if (!match[1]) return str;