Skip to content

Commit

Permalink
add uploaded files to collection directly
Browse files Browse the repository at this point in the history
In the case of `list`, they are now added directly to the final list.
In the case of `list:paired` and `paired`, they are added to the `workingElements`; the user can then manually add them.
This is all still done within the `CollectionCreator`.

Next thing to look at is to try to only allow/wait for those uploaded files to be attempted as collection additions if they are of `ok` state.
One way to do that is to maybe use a list to keep track of uploaded items in the creator, and if those uploaded items disappear from `workingElements` (meaning they are invalid) we have explicit Toasts mentioning this.
  • Loading branch information
ahmedhamidawan committed Nov 19, 2024
1 parent 1e0c92b commit faf12b6
Show file tree
Hide file tree
Showing 7 changed files with 272 additions and 48 deletions.
3 changes: 3 additions & 0 deletions client/src/api/datatypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { type components } from "@/api";

export type CompositeFileInfo = components["schemas"]["CompositeFileInfo"];
75 changes: 59 additions & 16 deletions client/src/components/Collections/ListCollectionCreator.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import { faSquare } from "@fortawesome/free-regular-svg-icons";
import { faMinus, faSortAlphaDown, faTimes, faUndo } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { BAlert, BButton, BButtonGroup } from "bootstrap-vue";
import { computed, onMounted, ref, watch } from "vue";
import { computed, ref, watch } from "vue";
import draggable from "vuedraggable";
import type { HDASummary, HistoryItemSummary } from "@/api";
import { Toast } from "@/composables/toast";
import STATES from "@/mvc/dataset/states";
import { useDatatypesMapperStore } from "@/stores/datatypesMapperStore";
import localize from "@/utils/localization";
Expand All @@ -17,6 +18,8 @@ import FormSelectMany from "../Form/Elements/FormSelectMany/FormSelectMany.vue";
import CollectionCreator from "@/components/Collections/common/CollectionCreator.vue";
import DatasetCollectionElementView from "@/components/Collections/ListDatasetCollectionElementView.vue";
const NOT_VALID_ELEMENT_MSG: string = localize("is not a valid element for this collection");
interface Props {
historyId: string;
initialElements: HistoryItemSummary[];
Expand Down Expand Up @@ -75,15 +78,6 @@ const datatypesMapper = computed(() => datatypesMapperStore.datatypesMapper);
/** Are we filtering by datatype? */
const filterExtensions = computed(() => !!datatypesMapper.value && !!props.extensions?.length);
// TODO: This needs to be done in the other creators as well
watch(
() => props.initialElements,
() => {
// for any new/removed elements, add them to working elements
_elementsSetUp();
}
);
/** set up instance vars function */
function _instanceSetUp() {
/** Ids of elements that have been selected by the user - to preserve over renders */
Expand All @@ -107,6 +101,26 @@ function _elementsSetUp() {
// reverse the order of the elements to emulate what we have in the history panel
workingElements.value.reverse();
// for inListElements, reset their values (in order) to datasets from workingElements
const inListElementsPrev = inListElements.value;
inListElements.value = [];
inListElementsPrev.forEach((prevElem) => {
const element = workingElements.value.find((e) => e.id === prevElem.id);
const problem = _isElementInvalid(prevElem);
if (element) {
inListElements.value.push(element);
} else if (problem) {
const invalidMsg = `${prevElem.hid}: ${prevElem.name} ${problem} and ${NOT_VALID_ELEMENT_MSG}`;
invalidElements.value.push(invalidMsg);
Toast.error(invalidMsg, localize("Invalid element"));
} else {
const invalidMsg = `${prevElem.hid}: ${prevElem.name} ${localize("has been removed from the collection")}`;
invalidElements.value.push(invalidMsg);
Toast.error(invalidMsg, localize("Invalid element"));
}
});
// _ensureElementIds();
_validateElements();
_mangleDuplicateNames();
Expand Down Expand Up @@ -285,12 +299,17 @@ function onUpdateHideSourceItems(newHideSourceItems: boolean) {
hideSourceItems.value = newHideSourceItems;
}
// TODO: No need to use `onMounted` here, this can be done in the `setup`
onMounted(() => {
_instanceSetUp();
_elementsSetUp();
saveOriginalNames();
});
_instanceSetUp();
saveOriginalNames();
watch(
() => props.initialElements,
() => {
// for any new/removed elements, add them to working elements
_elementsSetUp();
},
{ immediate: true }
);
watch(
() => datatypesMapper.value,
Expand All @@ -302,6 +321,29 @@ watch(
{ immediate: true }
);
function addUploadedFiles(files: HDASummary[]) {
const returnedElements = props.fromSelection ? workingElements : inListElements;
files.forEach((f) => {
const file = props.fromSelection ? f : workingElements.value.find((e) => e.id === f.id);
const problem = _isElementInvalid(f);
if (file && !returnedElements.value.find((e) => e.id === file.id)) {
returnedElements.value.push(file);
} else if (problem) {
invalidElements.value.push("Uploaded item: " + f.name + " " + problem);
Toast.error(
localize(`Dataset ${f.hid}: ${f.name} ${problem} and is an invalid element for this collection`),
localize("Uploaded item is invalid")
);
} else {
invalidElements.value.push("Uploaded item: " + f.name + " could not be added to the collection");
Toast.error(
localize(`Dataset ${f.hid}: ${f.name} could not be added to the collection`),
localize("Uploaded item is invalid")
);
}
});
}
/** find the element in the workingElements array and update its name */
function renameElement(element: any, name: string) {
element = workingElements.value.find((e) => e.id === element.id);
Expand Down Expand Up @@ -420,6 +462,7 @@ function renameElement(element: any, name: string) {
:history-id="props.historyId"
:hide-source-items="hideSourceItems"
:extensions="extensions"
@add-uploaded-files="addUploadedFiles"
@on-update-datatype-toggle="changeDatatypeFilter"
@onUpdateHideSourceItems="onUpdateHideSourceItems"
@clicked-create="clickedCreate">
Expand Down
80 changes: 68 additions & 12 deletions client/src/components/Collections/PairCollectionCreator.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
import { faArrowsAltV } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { BAlert, BButton } from "bootstrap-vue";
import { computed, onMounted, ref } from "vue";
import { computed, ref, watch } from "vue";
import type { HDASummary, HistoryItemSummary } from "@/api";
import { Toast } from "@/composables/toast";
import STATES from "@/mvc/dataset/states";
import { useDatatypesMapperStore } from "@/stores/datatypesMapperStore";
import localize from "@/utils/localization";
Expand All @@ -14,6 +15,8 @@ import type { DatasetPair } from "../History/adapters/buildCollectionModal";
import DatasetCollectionElementView from "./ListDatasetCollectionElementView.vue";
import CollectionCreator from "@/components/Collections/common/CollectionCreator.vue";
const NOT_VALID_ELEMENT_MSG: string = localize("is not a valid element for this collection");
interface SelectedDatasetPair {
forward: HDASummary | undefined;
reverse: HDASummary | undefined;
Expand Down Expand Up @@ -71,6 +74,24 @@ const datatypesMapper = computed(() => datatypesMapperStore.datatypesMapper);
/** Are we filtering by datatype? */
const filterExtensions = computed(() => !!datatypesMapper.value && !!props.extensions?.length);
watch(
() => props.initialElements,
() => {
// initialize; and then for any new/removed elements, sync working elements
_elementsSetUp();
// TODO: fix this, now either of those will almost always be empty
if (props.fromSelection && !initialSuggestedName.value) {
initialSuggestedName.value = _guessNameForPair(
workingElements.value[0] as HDASummary,
workingElements.value[1] as HDASummary,
removeExtensions.value
);
}
},
{ immediate: true }
);
function _elementsSetUp() {
/** a list of invalid elements and the reasons they aren't valid */
invalidElements.value = [];
Expand All @@ -85,6 +106,35 @@ function _elementsSetUp() {
// reverse the order of the elements to emulate what we have in the history panel
workingElements.value.reverse();
// for inListElements, reset their values (in order) to datasets from workingElements
const inListElementsPrev = inListElements.value;
inListElements.value = { forward: undefined, reverse: undefined };
for (const key of ["forward", "reverse"]) {
const prevElem = inListElementsPrev[key as keyof SelectedDatasetPair];
if (!prevElem) {
continue;
}
const element = workingElements.value.find(
(e) => e.id === inListElementsPrev[key as keyof SelectedDatasetPair]?.id
);
const problem = _isElementInvalid(prevElem);
if (element) {
inListElements.value[key as keyof SelectedDatasetPair] = element;
} else if (problem) {
const invalidMsg = `${prevElem.hid}: ${prevElem.name} ${problem} and ${NOT_VALID_ELEMENT_MSG}`;
invalidElements.value.push(invalidMsg);
Toast.error(invalidMsg, localize("Invalid element"));
} else {
const invalidMsg = `${prevElem.hid}: ${prevElem.name} ${localize("has been removed from the collection")}`;
invalidElements.value.push(invalidMsg);
Toast.error(invalidMsg, localize("Invalid element"));
}
}
// TODO: Next thing to add is: If the user adds an uploaded file, that is eventually nuked from workingElements
// because it is invalid, Toast an error for that file by keeping track of uploaded ids in a separate list
_ensureElementIds();
_validateElements();
}
Expand Down Expand Up @@ -176,6 +226,21 @@ function swapButton() {
}
}
function addUploadedFiles(files: HDASummary[]) {
// Any added files are added to workingElements in _elementsSetUp
// The user will have to manually select the files to add them to the pair
// Check for validity of uploads
files.forEach((file) => {
const problem = _isElementInvalid(file);
if (problem) {
const invalidMsg = `${file.hid}: ${file.name} ${problem} and ${NOT_VALID_ELEMENT_MSG}`;
invalidElements.value.push(invalidMsg);
Toast.error(invalidMsg, localize("Uploaded item invalid for pair"));
}
});
}
function clickedCreate(collectionName: string) {
if (state.value !== "error" && exactlyTwoValidElements.value) {
const returnedPair = {
Expand Down Expand Up @@ -270,17 +335,6 @@ function _naiveStartingAndEndingLCS(s1: string, s2: string) {
return fwdLCS + revLCS;
}
onMounted(() => {
_elementsSetUp();
// TODO: fix this, now either of those will almost always be empty
initialSuggestedName.value = _guessNameForPair(
workingElements.value[0] as HDASummary,
workingElements.value[1] as HDASummary,
removeExtensions.value
);
});
</script>

<template>
Expand Down Expand Up @@ -377,7 +431,9 @@ onMounted(() => {
:history-id="props.historyId"
:hide-source-items="hideSourceItems"
:suggested-name="initialSuggestedName"
:extensions="props.extensions"
:extensions-toggle="removeExtensions"
@add-uploaded-files="addUploadedFiles"
@onUpdateHideSourceItems="onUpdateHideSourceItems"
@clicked-create="clickedCreate"
@remove-extensions-toggle="removeExtensionsToggle">
Expand Down
39 changes: 35 additions & 4 deletions client/src/components/Collections/PairedListCollectionCreator.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import {
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { BAlert, BButton, BButtonGroup, BCard, BCardBody, BCardHeader } from "bootstrap-vue";
import { computed, ref } from "vue";
import { computed, ref, watch } from "vue";
import draggable from "vuedraggable";
import type { HDASummary, HistoryItemSummary } from "@/api";
import { Toast } from "@/composables/toast";
import STATES from "@/mvc/dataset/states";
import { useDatatypesMapperStore } from "@/stores/datatypesMapperStore";
// import levenshteinDistance from '@/utils/levenshtein';
import localize from "@/utils/localization";
Expand All @@ -25,6 +27,7 @@ import PairedElementView from "./PairedElementView.vue";
import UnpairedDatasetElementView from "./UnpairedDatasetElementView.vue";
import CollectionCreator from "@/components/Collections/common/CollectionCreator.vue";
const NOT_VALID_ELEMENT_MSG: string = localize("is not a valid element for this collection");
const COMMON_FILTERS = {
illumina: ["_1", "_2"],
Rs: ["_R1", "_R2"],
Expand Down Expand Up @@ -173,15 +176,22 @@ const datatypesMapper = computed(() => datatypesMapperStore.datatypesMapper);
/** Are we filtering by datatype? */
const filterExtensions = computed(() => !!datatypesMapper.value && !!props.extensions?.length);
_elementsSetUp();
function removeExtensionsToggle() {
removeExtensions.value = !removeExtensions.value;
generatedPairs.value.forEach((pair, index) => {
pair.name = _guessNameForPair(pair.forward, pair.reverse, removeExtensions.value);
});
}
watch(
() => props.initialElements,
() => {
// initialize; and then for any new/removed elements, sync working elements
_elementsSetUp();
},
{ immediate: true }
);
/** Set up main data */
function _elementsSetUp() {
// a list of invalid elements and the reasons they aren't valid
Expand All @@ -195,6 +205,11 @@ function _elementsSetUp() {
// copy initial list, sort, // TODO: add ids if needed
// copy initial list, sort, add ids if needed
workingElements.value = JSON.parse(JSON.stringify(props.initialElements.slice(0)));
// TODO: Here, we do what we do in other 2 creators; we check the already built pairs
// and sync them with the workingElements, ensuring that there aren't any elements
// in built pairs that no longer exist in workingElements
// _ensureElementIds();
_validateElements();
_sortInitialList();
Expand Down Expand Up @@ -258,7 +273,7 @@ function _isElementInvalid(element: HistoryItemSummary) {
if (element.history_content_type === "dataset_collection") {
return localize("is a collection, this is not allowed");
}
var validState = element.state === "ok" || ["error", "paused"].includes(element.state);
var validState = element.state === STATES.OK || STATES.NOT_READY_STATES.includes(element.state as string);
if (!validState) {
return localize("has errored, is paused, or is not accessible");
}
Expand Down Expand Up @@ -719,6 +734,21 @@ function changeFilters(filter: keyof typeof COMMON_FILTERS) {
reverseFilter.value = COMMON_FILTERS[filter][1] as string;
}
function addUploadedFiles(files: HDASummary[]) {
// Any uploaded files are added to workingElements in _elementsSetUp
// The user will have to manually select the files to add them to the pair
// Check for validity of uploads
files.forEach((file) => {
const problem = _isElementInvalid(file);
if (problem) {
const invalidMsg = `${file.hid}: ${file.name} ${problem} and ${NOT_VALID_ELEMENT_MSG}`;
invalidElements.value.push(invalidMsg);
Toast.error(invalidMsg, localize("Uploaded item invalid for pair"));
}
});
}
async function clickedCreate(collectionName: string) {
checkForDuplicates();
if (state.value == "build") {
Expand Down Expand Up @@ -907,6 +937,7 @@ function _naiveStartingAndEndingLCS(s1: string, s2: string) {
render-extensions-toggle
:extensions-toggle="removeExtensions"
:extensions="extensions"
@add-uploaded-files="addUploadedFiles"
@onUpdateHideSourceItems="hideSourceItems = $event"
@clicked-create="clickedCreate"
@remove-extensions-toggle="removeExtensionsToggle">
Expand Down
Loading

0 comments on commit faf12b6

Please sign in to comment.