Skip to content

Commit b8c902a

Browse files
authored
Merge pull request galaxyproject#19866 from ahmedhamidawan/fix_dce_drag_drop
[24.2] Fix drag and drop for dataset collection elements
2 parents fdee4c6 + e7fe2f0 commit b8c902a

3 files changed

Lines changed: 119 additions & 50 deletions

File tree

client/src/api/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ export type CollectionEntry = HDCASummary | SubCollection;
194194
/**
195195
* Returns true if the given entry is a top level HDCA and false for sub-collections.
196196
*/
197-
export function isHDCA(entry?: CollectionEntry): entry is HDCASummary {
197+
export function isHDCA(entry?: HistoryItemSummary | CollectionEntry): entry is HDCASummary {
198198
return (
199199
entry !== undefined && "history_content_type" in entry && entry.history_content_type === "dataset_collection"
200200
);

client/src/components/Form/Elements/FormData/FormData.vue

Lines changed: 117 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,16 @@ import { BAlert, BButton, BButtonGroup, BCollapse, BFormCheckbox, BTooltip } fro
1313
import { storeToRefs } from "pinia";
1414
import { computed, onMounted, type Ref, ref, watch } from "vue";
1515
16-
import { isDatasetElement, isDCE } from "@/api";
16+
import {
17+
type DCESummary,
18+
type HDAObject,
19+
type HistoryItemSummary,
20+
isDatasetElement,
21+
isDCE,
22+
isHDCA,
23+
isHistoryItem,
24+
} from "@/api";
25+
import type { HistoryContentType } from "@/api/datasets";
1726
import { getGalaxyInstance } from "@/app";
1827
import { useDatatypesMapper } from "@/composables/datatypesMapper";
1928
import { useUid } from "@/composables/utils/uid";
@@ -40,6 +49,8 @@ type SelectOption = {
4049
value: DataOption | null;
4150
};
4251
52+
type HistoryOrCollectionItem = HistoryItemSummary | DCESummary;
53+
4354
const props = withDefaults(
4455
defineProps<{
4556
loading?: boolean;
@@ -83,7 +94,7 @@ const currentField = ref(0);
8394
const currentHighlighting: Ref<string | null> = ref(null);
8495
8596
// Drag/Drop related values
86-
const dragData: Ref<EventData | null> = ref(null);
97+
const dragData: Ref<EventData[]> = ref([]);
8798
const dragTarget: Ref<EventTarget | null> = ref(null);
8899
89100
// Collection creator modal settings
@@ -331,35 +342,50 @@ function getSourceType(val: DataOption) {
331342
}
332343
333344
/** Add values from drag/drop or data dialog sources */
334-
function handleIncoming(incoming: Record<string, unknown>, partial = true) {
345+
function handleIncoming(incoming: Record<string, unknown> | Record<string, unknown>[], partial = true) {
335346
if (incoming) {
336347
const values = Array.isArray(incoming) ? incoming : [incoming];
337-
const extensions = values.map((v) => v.extension || v.elements_datatypes).filter((v) => (v ? true : false));
348+
349+
// ensure all incoming values are isHistoryOrCollectionItem
350+
if (!values.every(isHistoryOrCollectionItem)) {
351+
return false;
352+
}
353+
354+
const extensions = Array.from(
355+
new Set(
356+
values
357+
.map(getExtensionsForItem)
358+
.flat()
359+
.filter((v) => v !== null && v !== undefined)
360+
)
361+
) as string[];
362+
338363
if (!canAcceptDatatype(extensions)) {
339364
return false;
340365
}
341-
if (values.some((v) => !canAcceptSrc(v.history_content_type, v.collection_type))) {
366+
if (
367+
values.some((v) => {
368+
const { historyContentType } = getSrcAndContentType(v);
369+
const collectionType = "collection_type" in v && v.collection_type ? v.collection_type : undefined;
370+
return !canAcceptSrc(historyContentType, collectionType);
371+
})
372+
) {
342373
return false;
343374
}
344375
if (values.length > 0) {
345376
const incomingValues: Array<DataOption> = [];
346-
values.forEach((v) => {
377+
values.forEach((currVal) => {
347378
// Map incoming objects to data option values
348-
let newSrc;
349-
if (isDCE(v)) {
350-
if (isDatasetElement(v)) {
351-
newSrc = SOURCE.DATASET;
352-
v = v.object;
353-
} else {
354-
newSrc = SOURCE.COLLECTION_ELEMENT;
355-
}
379+
const { newSrc, datasetCollectionDataset } = getSrcAndContentType(currVal);
380+
let v: HistoryOrCollectionItem | HDAObject;
381+
if (datasetCollectionDataset) {
382+
v = datasetCollectionDataset;
356383
} else {
357-
newSrc =
358-
v.src || (v.history_content_type === "dataset_collection" ? SOURCE.COLLECTION : SOURCE.DATASET);
384+
v = currVal;
359385
}
360-
const newHid = v.hid;
386+
const newHid = isHistoryItem(v) ? v.hid : undefined;
361387
const newId = v.id;
362-
const newName = v.name ? v.name : newId;
388+
const newName = isHistoryItem(v) && v.name ? v.name : newId;
363389
const newValue: DataOption = {
364390
id: newId,
365391
src: newSrc,
@@ -370,10 +396,11 @@ function handleIncoming(incoming: Record<string, unknown>, partial = true) {
370396
keep: true,
371397
tags: [],
372398
};
373-
if (v.collection_type && props.collectionTypes?.length > 0) {
374-
if (!props.collectionTypes.includes(v.collection_type)) {
399+
if (isHistoryItem(v) && isHDCA(v) && props.collectionTypes?.length > 0) {
400+
const itemCollectionType = v.collection_type;
401+
if (!props.collectionTypes.includes(itemCollectionType)) {
375402
const mapOverType = props.collectionTypes.find((collectionType) =>
376-
v.collection_type.endsWith(collectionType)
403+
itemCollectionType.endsWith(collectionType)
377404
);
378405
if (!mapOverType) {
379406
return false;
@@ -437,6 +464,9 @@ function onBrowse() {
437464
}
438465
439466
function canAcceptDatatype(itemDatatypes: string | Array<string>) {
467+
// TODO: Shouldn't we enforce a datatype (at least "data") because of the case:
468+
// What if the drop item is a `DCESummary`, then it has no extension (?) and we
469+
// pass it as a valid item regardless of its elements' datatypes.
440470
if (!(props.extensions?.length > 0) || props.extensions.includes("data")) {
441471
return true;
442472
}
@@ -455,6 +485,39 @@ function canAcceptDatatype(itemDatatypes: string | Array<string>) {
455485
return true;
456486
}
457487
488+
/**
489+
* Given an element, determine the source and content type.
490+
* Also returns the collection element dataset object if it exists.
491+
*/
492+
function getSrcAndContentType(element: HistoryOrCollectionItem): {
493+
historyContentType: HistoryContentType;
494+
newSrc: string;
495+
datasetCollectionDataset: HDAObject | undefined;
496+
} {
497+
let historyContentType: HistoryContentType;
498+
let newSrc: string;
499+
let datasetCollectionDataset: HDAObject | undefined;
500+
if (isDCE(element)) {
501+
if (isDatasetElement(element)) {
502+
historyContentType = "dataset";
503+
newSrc = SOURCE.DATASET;
504+
datasetCollectionDataset = element.object;
505+
} else {
506+
historyContentType = "dataset_collection";
507+
newSrc = SOURCE.COLLECTION_ELEMENT;
508+
}
509+
} else {
510+
historyContentType = element.history_content_type;
511+
newSrc =
512+
"src" in element && typeof element.src === "string"
513+
? element.src
514+
: historyContentType === "dataset_collection"
515+
? SOURCE.COLLECTION
516+
: SOURCE.DATASET;
517+
}
518+
return { historyContentType, newSrc, datasetCollectionDataset };
519+
}
520+
458521
function canAcceptSrc(historyContentType: "dataset" | "dataset_collection", collectionType?: string) {
459522
if (historyContentType === "dataset") {
460523
// HDA can only be fed into data parameters, not collection parameters
@@ -514,27 +577,39 @@ function createdCollection(collection: any) {
514577
handleIncoming(collection);
515578
}
516579
580+
/**
581+
* Get the extension(s) for a given item
582+
*/
583+
function getExtensionsForItem(item: HistoryOrCollectionItem): string | string[] | null {
584+
return "extension" in item ? item.extension : "elements_datatypes" in item ? item.elements_datatypes : null;
585+
}
586+
587+
function isHistoryOrCollectionItem(item: EventData): item is HistoryOrCollectionItem {
588+
return isHistoryItem(item) || isDCE(item);
589+
}
590+
517591
// Drag/Drop event handlers
518592
function onDragEnter(evt: MouseEvent) {
519-
const eventData = eventStore.getDragData();
520-
if (eventData) {
521-
const extensions = (eventData.extension as string) || (eventData.elements_datatypes as Array<string>);
522-
let highlightingState = "success";
523-
if (!canAcceptDatatype(extensions)) {
524-
highlightingState = "warning";
525-
$emit("alert", `${extensions} is not an acceptable format for this parameter.`);
526-
} else if (
527-
!canAcceptSrc(
528-
eventData.history_content_type as "dataset" | "dataset_collection",
529-
eventData.collection_type as string
530-
)
531-
) {
532-
highlightingState = "warning";
533-
$emit("alert", `${eventData.history_content_type} is not an acceptable input type for this parameter.`);
593+
const eventData = eventStore.getDragItems();
594+
dragData.value = [];
595+
596+
for (const item of eventData) {
597+
if (isHistoryOrCollectionItem(item)) {
598+
const extensions = getExtensionsForItem(item);
599+
const { historyContentType } = getSrcAndContentType(item);
600+
const collectionType = "collection_type" in item && item.collection_type ? item.collection_type : undefined;
601+
let highlightingState = "success";
602+
if (extensions && !canAcceptDatatype(extensions)) {
603+
highlightingState = "warning";
604+
$emit("alert", `${extensions} is not an acceptable format for this parameter.`);
605+
} else if (!canAcceptSrc(historyContentType, collectionType)) {
606+
highlightingState = "warning";
607+
$emit("alert", `${historyContentType} is not an acceptable input type for this parameter.`);
608+
}
609+
currentHighlighting.value = highlightingState;
610+
dragTarget.value = evt.target;
611+
dragData.value.push(item);
534612
}
535-
currentHighlighting.value = highlightingState;
536-
dragTarget.value = evt.target;
537-
dragData.value = eventData;
538613
}
539614
}
540615
@@ -546,20 +621,14 @@ function onDragLeave(evt: MouseEvent) {
546621
}
547622
548623
function onDrop() {
549-
if (dragData.value) {
550-
let accept = false;
551-
if (eventStore.multipleDragData) {
552-
accept = handleIncoming(Object.values(dragData.value) as any, false);
553-
} else {
554-
accept = handleIncoming(dragData.value);
555-
}
556-
if (accept) {
624+
if (dragData.value.length) {
625+
if (handleIncoming(dragData.value, dragData.value.length === 1)) {
557626
currentHighlighting.value = "success";
558627
} else {
559628
currentHighlighting.value = "warning";
560629
}
561630
$emit("alert", undefined);
562-
dragData.value = null;
631+
dragData.value = [];
563632
clearHighlighting();
564633
}
565634
}

client/src/components/Form/Elements/FormData/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export type DataOption = {
22
id: string;
3-
hid: number;
3+
hid?: number;
44
is_dataset?: boolean;
55
keep: boolean;
66
batch: boolean;

0 commit comments

Comments
 (0)