From 0c0aee77f4f2ff902b4749f346e65c18d46a70ae Mon Sep 17 00:00:00 2001 From: Alexandr Date: Tue, 25 Aug 2026 14:37:50 +0200 Subject: [PATCH 1/2] MILAB-6818: export the study setup without its data deriveTemplateParams carried a fully populated state whenever every file in it was a storage reference, and stripped it otherwise. That made the same export mean one thing for one study and another for the next. Always strip instead: datasets and metadata columns travel as configured, emptied of everything keyed by sample or by group, and the block a template seeds waits for its files while the user re-adds data into the datasets that are already there. Samples go with the files that produced them, so sampleIds and sampleLabels stay behind as well. Drops the portability machinery (isDatasetPortable, collectDatasetHandles) along with it. --- model/src/template_params.ts | 85 ++++++------------------------------ 1 file changed, 14 insertions(+), 71 deletions(-) diff --git a/model/src/template_params.ts b/model/src/template_params.ts index 22cc767..3cd4c9b 100644 --- a/model/src/template_params.ts +++ b/model/src/template_params.ts @@ -1,6 +1,4 @@ import type { BlockParams } from "@platforma-open/milaboratories.samples-and-data.kind"; -import type { ImportFileHandle } from "@platforma-sdk/model"; -import { assertNever, isImportFileHandleIndex } from "@platforma-sdk/model"; import { isGroupedDataset } from "./args"; import type { BlockData, DSAny, DSContent, MTColumn } from "./args"; @@ -8,27 +6,22 @@ import type { BlockData, DSAny, DSContent, MTColumn } from "./args"; * Derives the params a project exported as a template hands the block it seeds — * the inverse of the data model's `init`. * - * Either the whole study travels or none of its data does. The block's state is - * one connected thing — datasets key their files by sample or by group, sample - * groups and multiplexing rules name those samples, metadata values are keyed by - * them, and `args` holds the pieces to each other — so carrying part of it means - * choosing which invariants to break. When every file can be resolved from where - * the block lands, the state goes as it stands; when any file is local to this - * machine, only the study's setup goes and the new project imports its own data. + * The study's setup travels, its data does not: every dataset and every metadata + * column goes as configured, emptied of everything keyed by sample or by group. + * The block the template seeds is the study as it was set up, waiting for its + * files — the user re-adds data into the datasets that were already there, in + * place of rebuilding them. + * + * Data is dropped whether or not it could travel. A file is portable only when + * it is a storage reference (`index://` names a `{storageId, path}` any + * installation carrying that storage can resolve, while `upload://` carries a + * local path signed with the installation's own secret), so carrying files + * would make a template mean one thing for one study and another for the next. + * Samples go with the files that produced them, so `sampleIds` and + * `sampleLabels` stay behind too — a sample without its file is a row with + * metadata and no data, for the user to delete. */ export function deriveTemplateParams(data: BlockData): BlockParams { - if (data.datasets.every(isDatasetPortable)) { - return { - datasets: data.datasets, - metadata: data.metadata, - sampleIds: data.sampleIds, - sampleLabelColumnLabel: data.sampleLabelColumnLabel, - sampleLabels: data.sampleLabels, - h5adFilesToPreprocess: data.h5adFilesToPreprocess, - seuratFilesToPreprocess: data.seuratFilesToPreprocess, - }; - } - return { datasets: data.datasets.map(stripDatasetData), metadata: data.metadata.map(stripColumnValues), @@ -38,56 +31,6 @@ export function deriveTemplateParams(data: BlockData): BlockParams { // Internals -/** - * Tells whether a dataset holds no file that is bound to this machine. - * - * A storage reference (`index://`) names a `{storageId, path}` any installation - * carrying that storage can resolve; an upload handle carries a local path - * signed with the installation's own secret and resolves nowhere else. A slot - * with no file yet is neither, so it does not stand in the way. - */ -function isDatasetPortable(ds: DSAny): boolean { - return collectDatasetHandles(ds.content).every((h) => h == null || isImportFileHandleIndex(h)); -} - -/** - * Collects every file slot a dataset holds, empty ones included. - * - * Exhaustive over the dataset kinds: a kind added to `DSContent` and not handled - * here fails to compile on `assertNever`, rather than silently reporting that - * the new kind holds no files — which would make it look portable and send its - * local uploads into a template. - */ -function collectDatasetHandles(content: DSContent): (ImportFileHandle | null | undefined)[] { - switch (content.type) { - case "Fastq": - case "MultiplexedFastq": - case "CellRangerMTX": - return Object.values(content.data).flatMap((group) => Object.values(group)); - case "MultilaneFastq": - return Object.values(content.data).flatMap((lanes) => - Object.values(lanes).flatMap((group) => Object.values(group)), - ); - case "TaggedFastq": - return Object.values(content.data).flatMap((records) => - records.flatMap((record) => Object.values(record.files)), - ); - case "TaggedXsv": - return Object.values(content.data).flatMap((records) => records.map((record) => record.file)); - case "Fasta": - case "Xsv": - case "H5AD": - case "H5": - case "Seurat": - case "BulkCountMatrix": - case "MultiSampleH5AD": - case "MultiSampleSeurat": - return Object.values(content.data); - default: - return assertNever(content); - } -} - /** * Strips everything a dataset holds per sample or per group, keeping how the * dataset is configured: its type, read indices, tag names, barcode tags, xsv From 8c727c9c01bec2f1ccf31b6636667fe8b116ff7e Mon Sep 17 00:00:00 2001 From: Alexandr Date: Tue, 25 Aug 2026 15:49:01 +0200 Subject: [PATCH 2/2] MILAB-6818: add changeset --- .changeset/afraid-pugs-repeat.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/afraid-pugs-repeat.md diff --git a/.changeset/afraid-pugs-repeat.md b/.changeset/afraid-pugs-repeat.md new file mode 100644 index 0000000..57c67df --- /dev/null +++ b/.changeset/afraid-pugs-repeat.md @@ -0,0 +1,6 @@ +--- +"@platforma-open/milaboratories.samples-and-data.model": minor +"@platforma-open/milaboratories.samples-and-data": minor +--- + +Export the study setup without its data: template params now always strip datasets and metadata columns down to their configuration, dropping everything keyed by sample or by group (files, sample ids and labels), instead of carrying a fully populated state when every file happened to be a storage reference.