diff --git a/client/src/api/schema/schema.ts b/client/src/api/schema/schema.ts index a61861be3eaf..b3ed1d67b287 100644 --- a/client/src/api/schema/schema.ts +++ b/client/src/api/schema/schema.ts @@ -4609,6 +4609,40 @@ export interface paths { patch?: never; trace?: never; }; + "/api/tools/fetch/workbook": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Generate a template workbook to use with the activity builder UI */ + get: operations["tools__fetch_workbook_download"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/tools/fetch/workbook/parse": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Generate a template workbook to use with the activity builder UI */ + post: operations["tools__fetch_workbook_parse"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/api/tools/{tool_id}/icon": { parameters: { query?: never; @@ -16872,6 +16906,88 @@ export interface components { * @default [] */ PageSummaryList: components["schemas"]["PageSummary"][]; + /** ParseFetchWorkbook */ + ParseFetchWorkbook: { + /** + * Workbook Content (Base 64 encoded) + * @description The workbook content (the contents of the xlsx file) that have been base64 encoded. + */ + content: string; + }; + /** ParseLogEntry */ + ParseLogEntry: { + /** Message */ + message: string; + }; + /** ParsedColumn */ + ParsedColumn: { + /** Title */ + title: string; + /** + * Type + * @enum {string} + */ + type: + | "list_identifiers" + | "paired_identifier" + | "paired_or_unpaired_identifier" + | "collection_name" + | "name_tag" + | "tags" + | "group_tags" + | "name" + | "dbkey" + | "hash_sha1" + | "hash_md5" + | "hash_sha256" + | "hash_sha512" + | "file_type" + | "url" + | "url_deferred" + | "info" + | "ftp_path"; + /** Type Index */ + type_index: number; + }; + /** ParsedFetchWorkbookForCollections */ + ParsedFetchWorkbookForCollections: { + /** + * Collection Type + * @enum {string} + */ + collection_type: "list" | "list:paired" | "list:list" | "list:list:paired" | "list:paired_or_unpaired"; + /** Columns */ + columns: components["schemas"]["ParsedColumn"][]; + /** Parse Log */ + parse_log: components["schemas"]["ParseLogEntry"][]; + /** Rows */ + rows: { + [key: string]: string; + }[]; + /** + * Workbook Type + * @default collection + * @enum {string} + */ + workbook_type: "datasets" | "collection" | "collections"; + }; + /** ParsedFetchWorkbookForDatasets */ + ParsedFetchWorkbookForDatasets: { + /** Columns */ + columns: components["schemas"]["ParsedColumn"][]; + /** Parse Log */ + parse_log: components["schemas"]["ParseLogEntry"][]; + /** Rows */ + rows: { + [key: string]: string; + }[]; + /** + * Workbook Type + * @default datasets + * @enum {string} + */ + workbook_type: "datasets" | "collection" | "collections"; + }; /** PastedDataElement */ PastedDataElement: { /** Md5 */ @@ -36370,6 +36486,99 @@ export interface operations { }; }; }; + tools__fetch_workbook_download: { + parameters: { + query?: { + /** @description Generate a workbook for simple datasets or a collection. */ + type?: "datasets" | "collection" | "collections"; + /** @description Generate workbook for specified collection type (not all collection types are supported) */ + collection_type?: "list" | "list:paired" | "list:list" | "list:list:paired" | "list:paired_or_unpaired"; + /** @description Filename of the workbook download to generate */ + filename?: string | null; + }; + header?: { + /** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */ + "run-as"?: string | null; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Request Error */ + "4XX": { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageExceptionModel"]; + }; + }; + /** @description Server Error */ + "5XX": { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageExceptionModel"]; + }; + }; + }; + }; + tools__fetch_workbook_parse: { + parameters: { + query?: never; + header?: { + /** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */ + "run-as"?: string | null; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ParseFetchWorkbook"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": + | components["schemas"]["ParsedFetchWorkbookForDatasets"] + | components["schemas"]["ParsedFetchWorkbookForCollections"]; + }; + }; + /** @description Request Error */ + "4XX": { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageExceptionModel"]; + }; + }; + /** @description Server Error */ + "5XX": { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageExceptionModel"]; + }; + }; + }; + }; get_icon_api_tools__tool_id__icon_get: { parameters: { query?: never; diff --git a/client/src/components/Collections/BuildFileSetWizard.vue b/client/src/components/Collections/BuildFileSetWizard.vue index eab888d1e4bb..0871935695ab 100644 --- a/client/src/components/Collections/BuildFileSetWizard.vue +++ b/client/src/components/Collections/BuildFileSetWizard.vue @@ -2,24 +2,39 @@ import { BCardGroup } from "bootstrap-vue"; import { computed, ref } from "vue"; +import { GalaxyApi } from "@/api"; import { getGalaxyInstance } from "@/app"; import { attemptCreate, type CollectionCreatorComponent } from "@/components/Collections/common/useCollectionCreator"; +import { useWorkbookDropHandling } from "@/components/Collections/common/useWorkbooks"; import { rawToTable } from "@/components/Collections/tables"; +import { forBuilder, type ForBuilderResponse } from "@/components/Collections/wizard/fetchWorkbooks"; import { useWizard } from "@/components/Common/Wizard/useWizard"; import { useToolRouting } from "@/composables/route"; import localize from "@/utils/localization"; -import type { RemoteFile, RulesCreatingWhat, RulesSourceFrom } from "./wizard/types"; +import type { + ParsedFetchWorkbook, + ParsedFetchWorkbookForCollectionCollectionType, + RuleBuilderMapping, + RuleBuilderOptions, + RuleElementsType, + RulesCreatingWhat, + RuleSelectionType, + RulesSourceFrom, +} from "./wizard/types"; import { useFileSetSources } from "./wizard/useFileSetSources"; +import ConfigureFetchWorkbook from "./wizard/ConfigureFetchWorkbook.vue"; import CreatingWhat from "./wizard/CreatingWhat.vue"; import PasteData from "./wizard/PasteData.vue"; import SelectDataset from "./wizard/SelectDataset.vue"; import SelectFolder from "./wizard/SelectFolder.vue"; -import SourceFromCollection from "./wizard/SourceFromCollection.vue"; +import SourceFromCollectionApplyRules from "./wizard/SourceFromCollectionApplyRules.vue"; import SourceFromDatasetAsTable from "./wizard/SourceFromDatasetAsTable.vue"; import SourceFromPastedData from "./wizard/SourceFromPastedData.vue"; import SourceFromRemoteFiles from "./wizard/SourceFromRemoteFiles.vue"; +import SourceFromWorkbook from "./wizard/SourceFromWorkbook.vue"; +import UploadFetchWorkbook from "./wizard/UploadFetchWorkbook.vue"; import GenericWizard from "@/components/Common/Wizard/GenericWizard.vue"; import RuleCollectionBuilder from "@/components/RuleCollectionBuilder.vue"; @@ -45,8 +60,14 @@ const sourceInstructions = computed(() => { return `${creatingWhatTitle.value} can be created from a set or files, URIs, or existing datasets.`; }); const collectionCreator = ref(); +const workbookCompleted = ref(false); +// workbook generation options +const workbookCollectionType = ref("list"); +const workbookIncludeCollectionName = ref(false); const sourceFrom = ref("remote_files"); +// if we upload a workbook - just jump to the end with these properties +const parsedWorkbookUploadProps = ref(undefined); const { routeToTool } = useToolRouting(); @@ -83,6 +104,18 @@ const wizard = useWizard({ isValid: () => sourceFrom.value === "dataset_as_table" && tabularDatasetContents.value.length > 0, isSkippable: () => sourceFrom.value !== "dataset_as_table", }, + "configure-workbook": { + label: "Configure workbook", + instructions: "Configure a workbook to fill with URIs and metadata", + isValid: () => sourceFrom.value === "workbook" && workbookCollectionType.value !== undefined, + isSkippable: () => sourceFrom.value !== "workbook" || creatingWhat.value !== "collections", + }, + "upload-workbook": { + label: "Upload workbook", + instructions: "Upload a workbook containing with URIs and metadata", + isValid: () => sourceFrom.value === "workbook" && workbookCompleted.value, + isSkippable: () => sourceFrom.value !== "workbook", + }, "rule-builder": { label: "Specify Rules", instructions: "Use this form to describe rules for importing", @@ -101,25 +134,15 @@ const importButtonLabel = computed(() => { }); const emit = defineEmits(["dismiss", "created"]); -type SelectionType = "raw" | "remote_files"; -type ElementsType = RemoteFile[] | string[][]; - -// it would be nice to have a real type from the rule builder but -// it is older code. This is really outlining what this component can -// produce and not what the rule builder can consume which is a wide -// superset of this. -interface Entry { - dataType: RulesCreatingWhat; - ftpUploadSite?: string; - elements?: ElementsType | undefined; - content?: string; - selectionType: SelectionType; -} const ruleBuilderModalEntryProps = computed(() => { - let elements: ElementsType | undefined = undefined; - let selectionType: SelectionType = "raw"; - if (sourceFrom.value == "remote_files") { + let elements: RuleElementsType | undefined = undefined; + let initialMappings: RuleBuilderMapping | undefined = undefined; + let selectionType: RuleSelectionType = "raw"; + if (parsedWorkbookUploadProps.value) { + elements = parsedWorkbookUploadProps.value.initialElements; + initialMappings = parsedWorkbookUploadProps.value.initialMapping; + } else if (sourceFrom.value == "remote_files") { elements = uris.value; selectionType = "remote_files"; } else if (sourceFrom.value == "pasted_table") { @@ -127,11 +150,12 @@ const ruleBuilderModalEntryProps = computed(() => { } else if (sourceFrom.value == "dataset_as_table") { elements = tabularDatasetContents.value; } - const entry: Entry = { + const entry: RuleBuilderOptions = { dataType: creatingWhat.value, ftpUploadSite: props.ftpUploadSite, selectionType: selectionType, elements: elements, + initialMappings: initialMappings, }; return entry; }); @@ -172,6 +196,14 @@ function setSourceForm(newValue: RulesSourceFrom) { sourceFrom.value = newValue; } +function setWorkbookCollectionType(newValue: ParsedFetchWorkbookForCollectionCollectionType) { + workbookCollectionType.value = newValue; +} + +function setWorkbookIncludeCollectionName(newValue: boolean) { + workbookIncludeCollectionName.value = newValue; +} + function onRuleState(newRuleState: boolean) { ruleState.value = newRuleState; } @@ -180,10 +212,68 @@ function onRuleCreate() { // axios response data for job currently sent, not really used but wanted to document what is available. emit("created"); } + +const dropWorkbookTitle = computed(() => { + return localize( + "If you have a completed data import workbook just drop it here or click this icon to upload your local file and skip ahead to the end of the data import wizard." + ); +}); + +function handleUploadedData(response: ParsedFetchWorkbook) { + sourceFrom.value = "workbook"; + const builderPropsFromUpload = forBuilder(response); + creatingWhat.value = builderPropsFromUpload.rulesCreatingWhat; + parsedWorkbookUploadProps.value = builderPropsFromUpload; + workbookCompleted.value = true; + wizard.goTo("rule-builder"); +} + +async function handleWorkbook(base64Content: string) { + const parseBody = { + content: base64Content, + }; + const { data, error } = await GalaxyApi().POST("/api/tools/fetch/workbook/parse", { + body: parseBody, + }); + if (data) { + handleUploadedData(data); + } else { + console.log(error); + uploadErrorMessage.value = "There was an error processing the file."; + } +} + +const { + browseFiles, + dropZoneClasses, + faUpload, + FontAwesomeIcon, + handleDrop, + HiddenWorkbookUploadInput, + isDragging, + onFileUpload, + uploadErrorMessage, + uploadRef, +} = useWorkbookDropHandling(handleWorkbook); + + diff --git a/client/src/components/Collections/common/useCollectionCreator.ts b/client/src/components/Collections/common/useCollectionCreator.ts index 876e28b42652..6bdee90319ec 100644 --- a/client/src/components/Collections/common/useCollectionCreator.ts +++ b/client/src/components/Collections/common/useCollectionCreator.ts @@ -67,11 +67,9 @@ export function useCollectionCreator(props: CommonCollectionBuilderProps, emit?: if (emit) { watch(collectionName, (newValue) => { - console.log("name upated..."); emit("name", newValue); }); watch(validInput, (newValue) => { - console.log("emitting..."); emit("input-valid", newValue); }); } diff --git a/client/src/components/Collections/common/useWorkbooks.ts b/client/src/components/Collections/common/useWorkbooks.ts new file mode 100644 index 000000000000..86043e9b3526 --- /dev/null +++ b/client/src/components/Collections/common/useWorkbooks.ts @@ -0,0 +1,101 @@ +import { faUpload } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; +import { computed, ref } from "vue"; + +import HiddenWorkbookUploadInput from "@/components/Collections/wizard/HiddenWorkbookUploadInput.vue"; + +export const fileToBase64 = (file: File): Promise => + new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => resolve(reader.result as string); + reader.onerror = (error) => reject(error); + reader.readAsDataURL(file); + }); + +export type WorkbookHandler = (workbookContentBase64: string) => Promise; + +export function useWorkbookDropHandling(workbookHandler: WorkbookHandler) { + const uploadErrorMessage = ref(undefined); + const isDragging = ref(false); + const isProcessingUpload = ref(false); + + async function onFileUpload(event: Event) { + const input = event.target as HTMLInputElement; + const file = input.files?.[0] ?? null; + if (file) { + const base64Content = await readAsBase64(file); + workbookHandler(base64Content); + } + } + + async function readAsBase64(file: File) { + const fileContent = await fileToBase64(file); + const base64Content = fileContent.split(",")[1] as string; + return base64Content; + } + + function checkDrop(event: DragEvent): File | undefined { + const file = event.dataTransfer?.files[0]; + if (!file || !file.name.endsWith(".xlsx")) { + uploadErrorMessage.value = "Please drop a valid XLSX file."; + return undefined; + } + return file; + } + + const handleDrop = async (event: DragEvent) => { + const file = checkDrop(event); + if (!file) { + return; + } + + isDragging.value = false; + isProcessingUpload.value = true; + try { + // Read and base64 encode the file + const base64Content = await readAsBase64(file); + await workbookHandler(base64Content); + } catch (error) { + console.error("Error uploading file:", error); + uploadErrorMessage.value = "There was an error processing the file."; + } finally { + isProcessingUpload.value = false; + } + }; + + const uploadRef = ref>(); + + interface HasBrowse { + browse: () => void; + } + + function browseFiles() { + const ref = uploadRef.value; + if (ref) { + (ref as unknown as HasBrowse).browse(); + } + } + + const dropZoneClasses = computed(() => { + const classes = ["dropzone"]; + if (isDragging.value) { + classes.push("highlight"); + } + return classes; + }); + + return { + browseFiles, + dropZoneClasses, + FontAwesomeIcon, + faUpload, + readAsBase64, + isDragging, + isProcessingUpload, + handleDrop, + HiddenWorkbookUploadInput, + onFileUpload, + uploadErrorMessage, + uploadRef, + }; +} diff --git a/client/src/components/Collections/wizard/CardDownloadWorkbook.vue b/client/src/components/Collections/wizard/CardDownloadWorkbook.vue new file mode 100644 index 000000000000..61e632024bdf --- /dev/null +++ b/client/src/components/Collections/wizard/CardDownloadWorkbook.vue @@ -0,0 +1,20 @@ + + + diff --git a/client/src/components/Collections/wizard/CardEditWorkbook.vue b/client/src/components/Collections/wizard/CardEditWorkbook.vue new file mode 100644 index 000000000000..10fe17b728eb --- /dev/null +++ b/client/src/components/Collections/wizard/CardEditWorkbook.vue @@ -0,0 +1,15 @@ + + + diff --git a/client/src/components/Collections/wizard/CardUploadWorkbook.vue b/client/src/components/Collections/wizard/CardUploadWorkbook.vue new file mode 100644 index 000000000000..a98f3243246c --- /dev/null +++ b/client/src/components/Collections/wizard/CardUploadWorkbook.vue @@ -0,0 +1,58 @@ + + + + diff --git a/client/src/components/Collections/wizard/ConfigureFetchWorkbook.vue b/client/src/components/Collections/wizard/ConfigureFetchWorkbook.vue new file mode 100644 index 000000000000..d265efcde36e --- /dev/null +++ b/client/src/components/Collections/wizard/ConfigureFetchWorkbook.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/client/src/components/Collections/wizard/HiddenWorkbookUploadInput.vue b/client/src/components/Collections/wizard/HiddenWorkbookUploadInput.vue new file mode 100644 index 000000000000..0e54ba459dc1 --- /dev/null +++ b/client/src/components/Collections/wizard/HiddenWorkbookUploadInput.vue @@ -0,0 +1,24 @@ + + + diff --git a/client/src/components/Collections/wizard/SourceFromCollection.vue b/client/src/components/Collections/wizard/SourceFromCollectionApplyRules.vue similarity index 100% rename from client/src/components/Collections/wizard/SourceFromCollection.vue rename to client/src/components/Collections/wizard/SourceFromCollectionApplyRules.vue diff --git a/client/src/components/Collections/wizard/SourceFromWorkbook.vue b/client/src/components/Collections/wizard/SourceFromWorkbook.vue new file mode 100644 index 000000000000..6c2daa1b90e8 --- /dev/null +++ b/client/src/components/Collections/wizard/SourceFromWorkbook.vue @@ -0,0 +1,41 @@ + + + diff --git a/client/src/components/Collections/wizard/UploadFetchWorkbook.vue b/client/src/components/Collections/wizard/UploadFetchWorkbook.vue new file mode 100644 index 000000000000..428d17fbc46b --- /dev/null +++ b/client/src/components/Collections/wizard/UploadFetchWorkbook.vue @@ -0,0 +1,51 @@ + + + diff --git a/client/src/components/Collections/wizard/WhichWorkbookCollectionType.vue b/client/src/components/Collections/wizard/WhichWorkbookCollectionType.vue new file mode 100644 index 000000000000..e05697967f6c --- /dev/null +++ b/client/src/components/Collections/wizard/WhichWorkbookCollectionType.vue @@ -0,0 +1,92 @@ + + + diff --git a/client/src/components/Collections/wizard/fetchWorkbooks.test.ts b/client/src/components/Collections/wizard/fetchWorkbooks.test.ts new file mode 100644 index 000000000000..1cc0946d842e --- /dev/null +++ b/client/src/components/Collections/wizard/fetchWorkbooks.test.ts @@ -0,0 +1,33 @@ +import { forBuilder } from "./fetchWorkbooks"; +import type { ParsedFetchWorkbook } from "./types"; + +describe("forBuilder", () => { + it("should return the correct ForBuilderResponse for a valid ParsedFetchWorkbook", () => { + const parsedWorkbook: ParsedFetchWorkbook = { + rows: [ + { list_identifiers: "Row1", url: "http://example.com/1", dbkey: "db1" }, + { list_identifiers: "Row2", url: "http://example.com/2", dbkey: "db2" }, + ], + columns: [ + { type: "list_identifiers", title: "Name", type_index: 0 }, + { type: "url", title: "URI", type_index: 0 }, + { type: "dbkey", title: "Genome", type_index: 0 }, + ], + workbook_type: "datasets", + parse_log: [], + }; + + const result = forBuilder(parsedWorkbook); + + expect(result.initialElements).toEqual([ + ["Row1", "http://example.com/1", "db1"], + ["Row2", "http://example.com/2", "db2"], + ]); + expect(result.rulesCreatingWhat).toBe("datasets"); + expect(result.initialMapping).toEqual([ + { type: "list_identifiers", columns: [0] }, + { type: "url", columns: [1] }, + { type: "dbkey", columns: [2] }, + ]); + }); +}); diff --git a/client/src/components/Collections/wizard/fetchWorkbooks.ts b/client/src/components/Collections/wizard/fetchWorkbooks.ts new file mode 100644 index 000000000000..79bed4141f46 --- /dev/null +++ b/client/src/components/Collections/wizard/fetchWorkbooks.ts @@ -0,0 +1,79 @@ +// utilities for populating the rule builder from parsed "fetch workbook"s. +import type { + ColumnMappingType, + ParsedFetchWorkbook, + ParsedFetchWorkbookColumn, + RawRowData, + RuleBuilderMapping, + RulesCreatingWhat, +} from "./types"; + +export function hasData(parsedWorkbook: ParsedFetchWorkbook): boolean { + return parsedWorkbook.rows.length > 0; +} + +export interface ForBuilderResponse { + initialElements: string[][]; + rulesCreatingWhat: RulesCreatingWhat; + initialMapping: RuleBuilderMapping; +} + +export function forBuilder(parsedWorkbook: ParsedFetchWorkbook): ForBuilderResponse { + const initialElements: RawRowData = []; + const rulesCreatingWhat = creatingWhat(parsedWorkbook); + for (const row of parsedWorkbook.rows) { + const rowAsString: string[] = []; + for (const column of parsedWorkbook.columns) { + const rowKey: string = columnToRowKey(column); + const cellValue = row[rowKey]; + if (cellValue === undefined) { + throw Error("Error processing server response."); + } + rowAsString.push(cellValue); + } + initialElements.push(rowAsString); + } + const initialMapping = buildInitialMapping(parsedWorkbook); + return { + initialElements, + initialMapping, + rulesCreatingWhat, + }; +} + +function columnToRowKey(column: ParsedFetchWorkbookColumn): string { + if (column.type_index == 0) { + return column.type; + } else { + return `${column.type}_${column.type_index}`; + } +} + +function creatingWhat(parsedWorkbook: ParsedFetchWorkbook): RulesCreatingWhat { + if (parsedWorkbook.workbook_type == "datasets") { + return "datasets"; + } else { + return "collections"; + } +} + +function buildInitialMapping(parsedWorkbook: ParsedFetchWorkbook): RuleBuilderMapping { + const columnMappings: RuleBuilderMapping = []; + for (let index = 0; index < parsedWorkbook.columns.length; index++) { + const column = parsedWorkbook.columns[index] as ParsedFetchWorkbookColumn; + const type: ColumnMappingType = column.type; + if (column.type_index > 0) { + for (const columnMapping of columnMappings) { + if (columnMapping.type == type) { + columnMapping.columns.push(index); + } + } + } else { + columnMappings.push({ + type: type, + columns: [index], + }); + } + } + return columnMappings; +} diff --git a/client/src/components/Collections/wizard/types.ts b/client/src/components/Collections/wizard/types.ts index 911bbe3e850e..7ce84043291c 100644 --- a/client/src/components/Collections/wizard/types.ts +++ b/client/src/components/Collections/wizard/types.ts @@ -1,8 +1,42 @@ import type { components } from "@/api/schema"; +import type { MAPPING_TARGETS } from "@/components/RuleBuilder/rule-definitions"; export type RulesCreatingWhat = "datasets" | "collections"; -export type RulesSourceFrom = "remote_files" | "pasted_table" | "dataset_as_table" | "collection"; +export type RulesSourceFrom = "remote_files" | "pasted_table" | "dataset_as_table" | "collection" | "workbook"; export type ListUriResponse = components["schemas"]["ListUriResponse"]; export type RemoteFile = components["schemas"]["RemoteFile"]; export type RemoteDirectory = components["schemas"]["RemoteDirectory"]; +export type ParsedFetchWorkbookForCollections = components["schemas"]["ParsedFetchWorkbookForCollections"]; +export type ParsedFetchWorkbookForDatasets = components["schemas"]["ParsedFetchWorkbookForDatasets"]; +export type ParsedFetchWorkbook = ParsedFetchWorkbookForCollections | ParsedFetchWorkbookForDatasets; +export type ParsedFetchWorkbookColumn = components["schemas"]["ParsedColumn"]; +export type ParsedFetchWorkbookForCollectionCollectionType = + components["schemas"]["ParsedFetchWorkbookForCollections"]["collection_type"]; + +export type RawRowData = string[][]; + +// types and helpers around initializing the rule builder with data +export type RuleSelectionType = "raw" | "remote_files"; +export type RuleElementsType = RemoteFile[] | string[][]; +export type ColumnMappingType = keyof typeof MAPPING_TARGETS; +export type ParsedFetchWorkbookColumnType = ParsedFetchWorkbookColumn["type"]; + +export interface RuleBuilderSingleMapping { + type: ColumnMappingType; + columns: number[]; +} +export type RuleBuilderMapping = RuleBuilderSingleMapping[]; + +// it would be nice to have a real type from the rule builder but +// it is older code. This is really outlining what this component can +// produce and not what the rule builder can consume which is a wide +// superset of this. +export interface RuleBuilderOptions { + dataType: RulesCreatingWhat; + ftpUploadSite?: string; + elements?: RuleElementsType | undefined; + content?: string; + selectionType: RuleSelectionType; + initialMappings?: RuleBuilderMapping; +} diff --git a/client/src/components/Collections/wizard/workbook-dropzones.scss b/client/src/components/Collections/wizard/workbook-dropzones.scss new file mode 100644 index 000000000000..3f7c822753b4 --- /dev/null +++ b/client/src/components/Collections/wizard/workbook-dropzones.scss @@ -0,0 +1,9 @@ +@import "theme/blue.scss"; + +.dropzone.highlight { + border-width: 2px; + border-color: $border-color; + border-style: dashed; + border-radius: $border-radius-large; + -moz-border-radius: $border-radius-large; +} diff --git a/client/src/components/Form/Elements/FormData/FormData.vue b/client/src/components/Form/Elements/FormData/FormData.vue index dfadf6d9a1e0..707c324c9c60 100644 --- a/client/src/components/Form/Elements/FormData/FormData.vue +++ b/client/src/components/Form/Elements/FormData/FormData.vue @@ -592,7 +592,7 @@ function canAcceptSrc(historyContentType: "dataset" | "dataset_collection", coll } } -const collectionTypesWithBuilders = [ +const collectionTypesWithBuilders: CollectionBuilderType[] = [ "list", "list:paired", "paired", @@ -602,8 +602,8 @@ const collectionTypesWithBuilders = [ ]; /** Allowed collection types for collection creation */ -const effectiveCollectionTypes = props.collectionTypes?.filter((collectionType) => - collectionTypesWithBuilders.includes(collectionType) +const effectiveCollectionTypes = props.collectionTypes?.filter((collectionType: string) => + (collectionTypesWithBuilders as string[]).includes(collectionType) ); const currentCollectionTypeTab = ref( diff --git a/client/src/components/Help/HelpText.vue b/client/src/components/Help/HelpText.vue index b35a420a7dae..f24ea8c4fa74 100644 --- a/client/src/components/Help/HelpText.vue +++ b/client/src/components/Help/HelpText.vue @@ -23,13 +23,4 @@ const helpTarget = ref(); - +