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 8db25d243c6d..dfd0ae54918d 100644 --- a/client/src/components/Collections/CollectionCreatorModal.vue +++ b/client/src/components/Collections/CollectionCreatorModal.vue @@ -16,6 +16,7 @@ import type { CollectionType, DatasetPair } from "../History/adapters/buildColle import ListCollectionCreator from "./ListCollectionCreator.vue"; import PairCollectionCreator from "./PairCollectionCreator.vue"; import PairedListCollectionCreator from "./PairedListCollectionCreator.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"; @@ -313,6 +314,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..c75802281c68 --- /dev/null +++ b/client/src/components/Collections/sheet/SampleSheetGrid.vue @@ -0,0 +1,261 @@ + + + diff --git a/client/src/components/Collections/sheet/workbooks.ts b/client/src/components/Collections/sheet/workbooks.ts new file mode 100644 index 000000000000..62e819cd96c0 --- /dev/null +++ b/client/src/components/Collections/sheet/workbooks.ts @@ -0,0 +1,19 @@ +import { 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); +} diff --git a/client/src/components/Form/Elements/FormData/FormData.vue b/client/src/components/Form/Elements/FormData/FormData.vue index b13dd87ae8e9..0590639b7ebe 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", "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 53a41b424914..5b03fc060003 100644 --- a/client/src/components/History/adapters/buildCollectionModal.ts +++ b/client/src/components/History/adapters/buildCollectionModal.ts @@ -14,7 +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" | "sample_sheet"; export type DatasetPair = { forward: HDASummary; reverse: HDASummary; 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..1e74cb43b04f --- /dev/null +++ b/client/src/components/Workflow/Editor/Forms/FormColumnDefinition.vue @@ -0,0 +1,56 @@ + + + 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..84aea614868e --- /dev/null +++ b/client/src/components/Workflow/Editor/Forms/FormColumnDefinitionType.vue @@ -0,0 +1,48 @@ + + + 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 @@