feat(frontend): listExportableParams enumerates export options
This commit is contained in:
@@ -21,3 +21,35 @@ export function extractValue(status, param) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Build the grouped, ordered list of exportable parameters.
|
||||
// Session metrics (total, success_rate, then each dynamic count category found
|
||||
// in the data) come first; then every field defined in the daily template,
|
||||
// including inactive ones (historical data may exist).
|
||||
export function listExportableParams(dailyTemplate, statuses) {
|
||||
const params = [
|
||||
{ id: '__total__', label: 'Total attempts', group: 'metrics', kind: 'total' },
|
||||
{ id: '__success_rate__', label: 'Success rate', group: 'metrics', kind: 'success_rate' },
|
||||
];
|
||||
|
||||
const cats = new Set();
|
||||
for (const s of statuses ?? []) {
|
||||
const counts = s?.analysis_summary?.counts;
|
||||
if (counts && typeof counts === 'object') {
|
||||
for (const k of Object.keys(counts)) cats.add(k);
|
||||
}
|
||||
}
|
||||
for (const cat of [...cats].sort()) {
|
||||
params.push({ id: `__cat__${cat}`, label: cat, group: 'metrics', kind: 'category', category: cat });
|
||||
}
|
||||
|
||||
for (const f of dailyTemplate ?? []) {
|
||||
if (f.builtin) {
|
||||
params.push({ id: `b:${f.fieldId}`, label: f.label, group: 'daily', kind: 'builtin', statusKey: f.key });
|
||||
} else {
|
||||
params.push({ id: `c:${f.fieldId}`, label: f.label, group: 'daily', kind: 'custom', fieldId: f.fieldId });
|
||||
}
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { extractValue } from '../src/lib/dataExport';
|
||||
import { listExportableParams } from '../src/lib/dataExport';
|
||||
|
||||
const status = {
|
||||
vitals: 'ok',
|
||||
@@ -31,3 +32,41 @@ describe('extractValue', () => {
|
||||
expect(extractValue({}, { kind: 'builtin', statusKey: 'notes' })).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
const dailyTemplate = [
|
||||
{ fieldId: 'b-vitals', key: 'vitals', label: 'Vitals', builtin: true, active: true },
|
||||
{ fieldId: 'c-weight', key: 'weight', label: 'Weight (g)', builtin: false, active: false },
|
||||
];
|
||||
const statuses = [
|
||||
{ analysis_summary: { counts: { Success: 1, Failure: 0 } } },
|
||||
{ analysis_summary: { counts: { Success: 2, Other: 1 } } },
|
||||
{ analysis_summary: null },
|
||||
];
|
||||
|
||||
describe('listExportableParams', () => {
|
||||
const params = listExportableParams(dailyTemplate, statuses);
|
||||
it('starts with the two fixed session metrics', () => {
|
||||
expect(params.slice(0, 2)).toEqual([
|
||||
expect.objectContaining({ id: '__total__', label: 'Total attempts', group: 'metrics', kind: 'total' }),
|
||||
expect.objectContaining({ id: '__success_rate__', label: 'Success rate', group: 'metrics', kind: 'success_rate' }),
|
||||
]);
|
||||
});
|
||||
it('adds one metrics entry per dynamic count category, sorted', () => {
|
||||
const cats = params.filter((p) => p.kind === 'category').map((p) => p.label);
|
||||
expect(cats).toEqual(['Failure', 'Other', 'Success']);
|
||||
});
|
||||
it('includes every template field (incl. inactive) in the daily group', () => {
|
||||
const daily = params.filter((p) => p.group === 'daily');
|
||||
expect(daily).toEqual([
|
||||
expect.objectContaining({ label: 'Vitals', kind: 'builtin', statusKey: 'vitals' }),
|
||||
expect.objectContaining({ label: 'Weight (g)', kind: 'custom', fieldId: 'c-weight' }),
|
||||
]);
|
||||
});
|
||||
it('gives every param a unique id', () => {
|
||||
const ids = params.map((p) => p.id);
|
||||
expect(new Set(ids).size).toBe(ids.length);
|
||||
});
|
||||
it('tolerates empty/missing inputs', () => {
|
||||
expect(listExportableParams(undefined, undefined).length).toBe(2); // just the two fixed metrics
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user