diff --git a/client/src/api/index.ts b/client/src/api/index.ts index 6f9ce9d0213d..798bde4aa951 100644 --- a/client/src/api/index.ts +++ b/client/src/api/index.ts @@ -312,4 +312,8 @@ export type ObjectExportTaskResponse = components["schemas"]["ObjectExportTaskRe export type ExportObjectRequestMetadata = components["schemas"]["ExportObjectRequestMetadata"]; export type ExportObjectResultMetadata = components["schemas"]["ExportObjectResultMetadata"]; +export type SampleSheetColumnDefinition = components["schemas"]["SampleSheetColumnDefinitionModel"]; +export type SampleSheetColumnDefinitionType = SampleSheetColumnDefinition["type"]; +export type SampleSheetColumnDefinitions = SampleSheetColumnDefinition[] | null; + export type AsyncTaskResultSummary = components["schemas"]["AsyncTaskResultSummary"]; diff --git a/client/src/components/Collections/CollectionCreatorModal.vue b/client/src/components/Collections/CollectionCreatorModal.vue index 6bd17b96ab1e..4370f56b6863 100644 --- a/client/src/components/Collections/CollectionCreatorModal.vue +++ b/client/src/components/Collections/CollectionCreatorModal.vue @@ -17,6 +17,7 @@ import ListCollectionCreator from "./ListCollectionCreator.vue"; import PairCollectionCreator from "./PairCollectionCreator.vue"; import PairedListCollectionCreator from "./PairedListCollectionCreator.vue"; import PairedOrUnpairedListCollectionCreator from "./PairedOrUnpairedListCollectionCreator.vue"; +import SampleSheetCollectionCreator from "./SampleSheetCollectionCreator.vue"; import Heading from "@/components/Common/Heading.vue"; import GenericItem from "@/components/History/Content/GenericItem.vue"; import LoadingSpan from "@/components/LoadingSpan.vue"; @@ -323,6 +324,15 @@ function resetModal() { :extensions="props.extensions" @clicked-create="createPairedCollection" @on-cancel="hideModal" /> + diff --git a/client/src/components/Collections/SampleSheetCollectionCreator.vue b/client/src/components/Collections/SampleSheetCollectionCreator.vue new file mode 100644 index 000000000000..6a949c3f656e --- /dev/null +++ b/client/src/components/Collections/SampleSheetCollectionCreator.vue @@ -0,0 +1,52 @@ + + + diff --git a/client/src/components/Collections/SampleSheetWizard.vue b/client/src/components/Collections/SampleSheetWizard.vue new file mode 100644 index 000000000000..1511041243b5 --- /dev/null +++ b/client/src/components/Collections/SampleSheetWizard.vue @@ -0,0 +1,139 @@ + + + diff --git a/client/src/components/Collections/sheet/DownloadWorkbookButton.vue b/client/src/components/Collections/sheet/DownloadWorkbookButton.vue new file mode 100644 index 000000000000..aec1084ec9e8 --- /dev/null +++ b/client/src/components/Collections/sheet/DownloadWorkbookButton.vue @@ -0,0 +1,22 @@ + + + diff --git a/client/src/components/Collections/sheet/SampleSheetGrid.vue b/client/src/components/Collections/sheet/SampleSheetGrid.vue new file mode 100644 index 000000000000..22ef1f33caf5 --- /dev/null +++ b/client/src/components/Collections/sheet/SampleSheetGrid.vue @@ -0,0 +1,247 @@ + + + diff --git a/client/src/components/Collections/sheet/workbooks.ts b/client/src/components/Collections/sheet/workbooks.ts new file mode 100644 index 000000000000..021a77c5ff29 --- /dev/null +++ b/client/src/components/Collections/sheet/workbooks.ts @@ -0,0 +1,33 @@ +import { type SampleSheetColumnDefinition, type SampleSheetColumnDefinitions } from "@/api"; +import { withPrefix } from "@/utils/redirect"; + +export function getDownloadWorkbookUrl(columnDefinitions: SampleSheetColumnDefinitions, initialRows?: string[][]) { + const columnDefinitionsJson = JSON.stringify(columnDefinitions); + const columnDefinitionsJsonBase64 = Buffer.from(columnDefinitionsJson).toString("base64"); + let url = withPrefix(`/api/sample_sheet_workbook/generate?column_definitions=${columnDefinitionsJsonBase64}`); + if (initialRows) { + const initialRowsJson = JSON.stringify(initialRows); + const initialRowsJsonBase64 = Buffer.from(initialRowsJson).toString("base64"); + url = `${url}&initial_rows=${initialRowsJsonBase64}`; + } + return url; +} + +export function downloadWorkbook(columnDefinitions: SampleSheetColumnDefinitions, initialRows?: string[][]) { + const url = getDownloadWorkbookUrl(columnDefinitions, initialRows); + window.location.assign(url); +} + +export function initialValue(columnDefinition: SampleSheetColumnDefinition) { + switch (columnDefinition.type) { + case "int": + return 0; + case "float": + return 0.0; + case "boolean": + return false; + case "string": + default: + return ""; + } +} diff --git a/client/src/components/Form/Elements/FormData/FormData.vue b/client/src/components/Form/Elements/FormData/FormData.vue index b13dd87ae8e9..80663d75aae7 100644 --- a/client/src/components/Form/Elements/FormData/FormData.vue +++ b/client/src/components/Form/Elements/FormData/FormData.vue @@ -33,6 +33,7 @@ const COLLECTION_TYPE_TO_LABEL: Record = { list: "list", "list:paired": "list of dataset pairs", paired: "dataset pair", + sample_sheet: "sample sheet derived", }; type SelectOption = { @@ -88,7 +89,7 @@ const dragTarget: Ref = ref(null); // Collection creator modal settings const collectionModalShow = ref(false); -const collectionModalType = ref<"list" | "list:paired" | "paired">("list"); +const collectionModalType = ref<"list" | "list:paired" | "paired" | "sample_sheet">("list"); const { currentHistoryId } = storeToRefs(useHistoryStore()); const restrictsExtensions = computed(() => { const extensions = props.extensions; @@ -497,7 +498,7 @@ function canAcceptSrc(historyContentType: "dataset" | "dataset_collection", coll } } -const collectionTypesWithBuilders = ["list", "list:paired", "paired"]; +const collectionTypesWithBuilders = ["list", "list:paired", "paired", "list:paired_or_unpaired", "sample_sheet"]; /** Allowed collection types for collection creation */ const effectiveCollectionTypes = props.collectionTypes?.filter((collectionType) => @@ -508,7 +509,7 @@ function buildNewCollection(collectionType: string) { if (!collectionTypesWithBuilders.includes(collectionType)) { throw Error(`Unknown collection type: ${collectionType}`); } - collectionModalType.value = collectionType as "list" | "list:paired" | "paired"; + collectionModalType.value = collectionType as "list" | "list:paired" | "paired" | "sample_sheet"; collectionModalShow.value = true; } diff --git a/client/src/components/History/adapters/buildCollectionModal.ts b/client/src/components/History/adapters/buildCollectionModal.ts index cf1b24ba8ebe..35225d2cbbc9 100644 --- a/client/src/components/History/adapters/buildCollectionModal.ts +++ b/client/src/components/History/adapters/buildCollectionModal.ts @@ -14,8 +14,7 @@ import jQuery from "jquery"; import type { HDASummary, HistoryItemSummary } from "@/api"; import RULE_BASED_COLLECTION_CREATOR from "@/components/Collections/RuleBasedCollectionCreatorModal"; -export type CollectionType = "list" | "paired" | "list:paired" | "rules"; - +export type CollectionType = "list" | "paired" | "list:paired" | "rules" | "list:paired_or_unpaired" | "sample_sheet"; interface HasName { name: string | null; } diff --git a/client/src/components/Workflow/Editor/Forms/FormCollectionType.vue b/client/src/components/Workflow/Editor/Forms/FormCollectionType.vue index a93cb5ded6f1..77b5aa8161f2 100644 --- a/client/src/components/Workflow/Editor/Forms/FormCollectionType.vue +++ b/client/src/components/Workflow/Editor/Forms/FormCollectionType.vue @@ -23,6 +23,7 @@ const collectionTypeOptions = [ { value: "list", label: "List of Datasets" }, { value: "paired", label: "Dataset Pair" }, { value: "list:paired", label: "List of Dataset Pairs" }, + { value: "sample_sheet", label: "Sample Sheet of Datasets" }, ]; function updateValue(newValue: string | undefined) { diff --git a/client/src/components/Workflow/Editor/Forms/FormColumnDefinition.vue b/client/src/components/Workflow/Editor/Forms/FormColumnDefinition.vue new file mode 100644 index 000000000000..0068bacbadbf --- /dev/null +++ b/client/src/components/Workflow/Editor/Forms/FormColumnDefinition.vue @@ -0,0 +1,235 @@ + + + + + diff --git a/client/src/components/Workflow/Editor/Forms/FormColumnDefinitionType.vue b/client/src/components/Workflow/Editor/Forms/FormColumnDefinitionType.vue new file mode 100644 index 000000000000..c8ec62eb08d1 --- /dev/null +++ b/client/src/components/Workflow/Editor/Forms/FormColumnDefinitionType.vue @@ -0,0 +1,52 @@ + + + diff --git a/client/src/components/Workflow/Editor/Forms/FormColumnDefinitions.vue b/client/src/components/Workflow/Editor/Forms/FormColumnDefinitions.vue new file mode 100644 index 000000000000..5512a4d56f79 --- /dev/null +++ b/client/src/components/Workflow/Editor/Forms/FormColumnDefinitions.vue @@ -0,0 +1,157 @@ + + + + + diff --git a/client/src/components/Workflow/Editor/Forms/FormInputCollection.vue b/client/src/components/Workflow/Editor/Forms/FormInputCollection.vue index ea17f09fb50d..ef5379b0e0d1 100644 --- a/client/src/components/Workflow/Editor/Forms/FormInputCollection.vue +++ b/client/src/components/Workflow/Editor/Forms/FormInputCollection.vue @@ -1,6 +1,7 @@