Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix collection drilling #16819

Merged
merged 9 commits into from
Oct 11, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import ExpandedItems from "@/components/History/Content/ExpandedItems";
import { updateContentFields } from "@/components/History/model/queries";
import { useCollectionElementsStore } from "@/stores/collectionElementsStore";
import { HistorySummary } from "@/stores/historyStore";
import { DCESummary, DCObject, HDCASummary } from "@/stores/services";
import { CollectionEntry, DCESummary, DCObject, HDCASummary } from "@/stores/services";

import CollectionDetails from "./CollectionDetails.vue";
import CollectionNavigation from "./CollectionNavigation.vue";
Expand All @@ -17,7 +17,7 @@ import ListingLayout from "@/components/History/Layout/ListingLayout.vue";

interface Props {
history: HistorySummary;
selectedCollections: HDCASummary[];
selectedCollections: CollectionEntry[];
showControls?: boolean;
filterable?: boolean;
}
Expand All @@ -30,8 +30,8 @@ const props = withDefaults(defineProps<Props>(), {
const collectionElementsStore = useCollectionElementsStore();

const emit = defineEmits<{
(e: "view-collection", collection: HDCASummary): void;
(e: "update:selected-collections", collections: HDCASummary[]): void;
(e: "view-collection", collection: CollectionEntry): void;
(e: "update:selected-collections", collections: CollectionEntry[]): void;
}>();

const offset = ref(0);
Expand All @@ -40,7 +40,7 @@ const dsc = computed(() => props.selectedCollections[props.selectedCollections.l
const collectionElements = computed(() => collectionElementsStore.getCollectionElements(dsc.value, offset.value));
const loading = computed(() => collectionElementsStore.isLoadingCollectionElements(dsc.value));
const jobState = computed(() => dsc.value?.job_state_summary);
const rootCollection = computed(() => props.selectedCollections[0]);
const rootCollection = computed(() => props.selectedCollections[0] as HDCASummary);
davelopez marked this conversation as resolved.
Show resolved Hide resolved
const isRoot = computed(() => dsc.value == rootCollection.value);

function updateDsc(collection: any, fields: Object | undefined) {
Expand All @@ -59,9 +59,16 @@ function onScroll(newOffset: number) {
offset.value = newOffset;
}

async function onViewSubCollection(itemObject: DCObject) {
const collection = await collectionElementsStore.getCollection(itemObject.id);
emit("view-collection", collection);
async function onViewSubCollection(itemObject: DCObject, name: string) {
// We need to convert the DCO to a CollectionEntry in order
// to be able to fetch the contents of a nested collection.
const collectionEntry: CollectionEntry = {
id: rootCollection.value.id,
name,
collection_id: itemObject.id,
collection_type: itemObject.collection_type,
};
emit("view-collection", collectionEntry);
}

watch(
Expand Down
15 changes: 9 additions & 6 deletions client/src/stores/collectionElementsStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ describe("useCollectionElementsStore", () => {
expect(store.isLoadingCollectionElements(collection1)).toEqual(false);
expect(fetchCollectionElements).toHaveBeenCalled();

const elements = store.storedCollectionElements[collection1.id];
const collection1Key = store.getCollectionKey(collection1);
const elements = store.storedCollectionElements[collection1Key];
expect(elements).toBeDefined();
expect(elements).toHaveLength(limit);
});
Expand All @@ -55,8 +56,9 @@ describe("useCollectionElementsStore", () => {
const store = useCollectionElementsStore();
const storedCount = 5;
const expectedStoredElements = Array.from({ length: storedCount }, (_, i) => mockElement(collection1.id, i));
store.storedCollectionElements[collection1.id] = expectedStoredElements;
expect(store.storedCollectionElements[collection1.id]).toHaveLength(storedCount);
const collection1Key = store.getCollectionKey(collection1);
store.storedCollectionElements[collection1Key] = expectedStoredElements;
expect(store.storedCollectionElements[collection1Key]).toHaveLength(storedCount);

const offset = 0;
const limit = 5;
Expand All @@ -70,8 +72,9 @@ describe("useCollectionElementsStore", () => {
const store = useCollectionElementsStore();
const storedCount = 3;
const expectedStoredElements = Array.from({ length: storedCount }, (_, i) => mockElement(collection1.id, i));
store.storedCollectionElements[collection1.id] = expectedStoredElements;
expect(store.storedCollectionElements[collection1.id]).toHaveLength(storedCount);
const collection1Key = store.getCollectionKey(collection1);
store.storedCollectionElements[collection1Key] = expectedStoredElements;
expect(store.storedCollectionElements[collection1Key]).toHaveLength(storedCount);

const offset = 2;
const limit = 5;
Expand All @@ -82,7 +85,7 @@ describe("useCollectionElementsStore", () => {
expect(store.isLoadingCollectionElements(collection1)).toEqual(false);
expect(fetchCollectionElements).toHaveBeenCalled();

const elements = store.storedCollectionElements[collection1.id];
const elements = store.storedCollectionElements[collection1Key];
expect(elements).toBeDefined();
// The offset was overlapping with the stored elements, so it was increased by the number of stored elements
// so it fetches the next "limit" number of elements
Expand Down
23 changes: 16 additions & 7 deletions client/src/stores/collectionElementsStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,32 @@ export const useCollectionElementsStore = defineStore("collectionElementsStore",
const loadingCollectionElements = ref<{ [key: string]: boolean }>({});
const storedCollectionElements = ref<{ [key: string]: DCESummary[] }>({});

/**
* Returns a key that can be used to store or retrieve the elements of a collection in the store.
*/
function getCollectionKey(collection: HDCASummary) {
return `${collection.id}-${collection.collection_id}`;
davelopez marked this conversation as resolved.
Show resolved Hide resolved
}

const getCollectionElements = computed(() => {
return (collection: HDCASummary, offset = 0, limit = 50) => {
const elements = storedCollectionElements.value[collection.id] ?? [];
const elements = storedCollectionElements.value[getCollectionKey(collection)] ?? [];
fetchMissingElements({ collection, offset, limit });
return elements ?? null;
};
});

const isLoadingCollectionElements = computed(() => {
return (collection: HDCASummary) => {
return loadingCollectionElements.value[collection.id] ?? false;
return loadingCollectionElements.value[getCollectionKey(collection)] ?? false;
};
});

async function fetchMissingElements(params: { collection: HDCASummary; offset: number; limit: number }) {
const collectionKey = getCollectionKey(params.collection);
try {
const maxElementCountInCollection = params.collection.element_count ?? 0;
const storedElements = storedCollectionElements.value[params.collection.id] ?? [];
const storedElements = storedCollectionElements.value[collectionKey] ?? [];
// Collections are immutable, so there is no need to fetch elements if the range we want is already stored
if (params.offset + params.limit <= storedElements.length) {
return;
Expand All @@ -38,22 +46,22 @@ export const useCollectionElementsStore = defineStore("collectionElementsStore",
return;
}

Vue.set(loadingCollectionElements.value, params.collection.id, true);
Vue.set(loadingCollectionElements.value, collectionKey, true);
const fetchedElements = await Service.fetchElementsFromHDCA({
hdca: params.collection,
offset: params.offset,
limit: params.limit,
});
const updatedElements = [...storedElements, ...fetchedElements];
Vue.set(storedCollectionElements.value, params.collection.id, updatedElements);
Vue.set(storedCollectionElements.value, collectionKey, updatedElements);
} finally {
Vue.delete(loadingCollectionElements.value, params.collection.id);
Vue.delete(loadingCollectionElements.value, collectionKey);
}
}

async function loadCollectionElements(collection: HDCASummary) {
const elements = await Service.fetchElementsFromHDCA({ hdca: collection });
Vue.set(storedCollectionElements.value, collection.id, elements);
Vue.set(storedCollectionElements.value, getCollectionKey(collection), elements);
}

function saveCollections(historyContentsPayload: HistoryContentItemBase[]) {
Expand Down Expand Up @@ -93,5 +101,6 @@ export const useCollectionElementsStore = defineStore("collectionElementsStore",
fetchCollection,
loadCollectionElements,
saveCollections,
getCollectionKey,
};
});
4 changes: 2 additions & 2 deletions client/src/stores/services/datasetCollection.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { fetcher } from "@/schema";

import { DCESummary, HDCADetailed, HDCASummary } from ".";
import { CollectionEntry, DCESummary, HDCADetailed } from ".";

const DEFAULT_LIMIT = 50;

Expand Down Expand Up @@ -33,7 +33,7 @@ export async function fetchCollectionElements(params: {
}

export async function fetchElementsFromHDCA(params: {
hdca: HDCASummary;
hdca: CollectionEntry;
offset?: number;
limit?: number;
}): Promise<DCESummary[]> {
Expand Down
21 changes: 20 additions & 1 deletion client/src/stores/services/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,28 @@
import { components } from "@/schema";

/** Minimal representation of a collection that can contain datasets or other collections.
davelopez marked this conversation as resolved.
Show resolved Hide resolved
*
* This is used as a common interface to be able to fetch the contents of a collection regardless
* of whether it is an HDCA (top level) or a nested collection (DCO).
*
* To convert a DCO to a CollectionEntry we need the parent HDCA ID and the name of the
* collection (DCE identifier). The collection_id is the ID of the DCO and collection_type as well.
*/
export interface CollectionEntry {
/**HDCA ID */
id: string;
/**DCO ID */
collection_id: string;
/**Name of the HDCA or DatasetCollectionElement identifier */
name: string;
/**Type of the collection */
collection_type: string;
}

export type DatasetSummary = components["schemas"]["HDASummary"];
export type DatasetDetails = components["schemas"]["HDADetailed"];
export type DCESummary = components["schemas"]["DCESummary"];
export type HDCASummary = components["schemas"]["HDCASummary"];
export type HDCASummary = components["schemas"]["HDCASummary"] & CollectionEntry;
export type HDCADetailed = components["schemas"]["HDCADetailed"];
export type DCObject = components["schemas"]["DCObject"];

Expand Down
Loading