From 5c9a93036e0e694fcd22353caad5bc354ea6229d Mon Sep 17 00:00:00 2001 From: Csaky Date: Wed, 23 Aug 2023 15:22:14 -0700 Subject: [PATCH] Divide COMS object search request to limit URL length --- frontend/src/services/objectService.ts | 41 +++++++++++++++++++++++++- frontend/src/store/objectStore.ts | 10 +++---- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/frontend/src/services/objectService.ts b/frontend/src/services/objectService.ts index 587e1f54..636b63f2 100644 --- a/frontend/src/services/objectService.ts +++ b/frontend/src/services/objectService.ts @@ -1,5 +1,6 @@ import { comsAxios } from './interceptors'; import { setDispositionHeader } from '@/utils/utils'; +import { ConfigService } from './index'; import type { AxiosRequestConfig } from 'axios'; import type { GetMetadataOptions, GetObjectTaggingOptions, MetadataPair, SearchObjectsOptions, Tag } from '@/types'; @@ -227,7 +228,45 @@ export default { searchObjects(params: SearchObjectsOptions = {}, headers: any = {},) { // remove objectId array if its first element is undefined if (params.objectId && params.objectId[0] === undefined) delete params.objectId; - return comsAxios().get(PATH, { params: params, headers: headers }); + + if (params.objectId) { + /** + * split calls to COMS if query params (eg objectId's) + * will cause url length to excede 2000 characters + * see: https://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers + * + * TODO: consider creating a utils function + * eg: `divideParam(params, attr)` + * ... return Promise.all(divideParam(params, objectId).map(zparam => comsAxios().get(PATH, {params: zparam, headers: headers}); + */ + const urlLimit = 2000; + // caculate number of characters taken by base url and query params other than objectId: + const { objectId: objectIds, ...otherParams } = params; + const url = new URL(`${new ConfigService().getConfig().coms.apiPath}${PATH}`); + url.search = new URLSearchParams(otherParams); + const otherCharacters = url.toString().length; + + // number of allowed objectId's in each group - 48 chars for each objectId (&objectId[]=) + const space = urlLimit - otherCharacters; + const groupSize = Math.floor(space / 48); + + // loop through each group and push COMS result to `groups` array + const iterations = Math.ceil(objectIds.length / groupSize); + const groups = []; + + for (let i = 0; i < iterations; i++) { + const ids = objectIds.slice(i * groupSize, ((i * groupSize) + groupSize)); + groups.push(comsAxios().get(PATH, { params: { ...params, objectId: ids }, headers: headers })); + } + + return Promise.all(groups) + .then((results) => ({ data: results.flatMap(result => result.data) })); + } + + // else just call COMS once + else { + return comsAxios().get(PATH, { params: params, headers: headers }); + } }, /** diff --git a/frontend/src/store/objectStore.ts b/frontend/src/store/objectStore.ts index 6f8842e7..8042f411 100644 --- a/frontend/src/store/objectStore.ts +++ b/frontend/src/store/objectStore.ts @@ -110,8 +110,8 @@ export const useObjectStore = defineStore('object', () => { } async function fetchObjects( - params: ObjectSearchPermissionsOptions = {}, - tagset?: Array, + params: ObjectSearchPermissionsOptions = {}, + tagset?: Array, metadata?: Array) { try { appStore.beginIndeterminateLoading(); @@ -137,7 +137,7 @@ export const useObjectStore = defineStore('object', () => { } } - response = (await objectService.searchObjects({ + response = await objectService.searchObjects({ bucketId: params.bucketId ? [params.bucketId] : undefined, objectId: uniqueIds, tagset: tagset ? tagset.reduce((acc, cur) => ({ ...acc, [cur.key]: cur.value }), {}) : undefined, @@ -146,7 +146,7 @@ export const useObjectStore = defineStore('object', () => { // TODO: Verify if needed after versioning implemented deleteMarker: false, latest: true - }, headers)).data; + }, headers).then(r => r.data); // Remove old values matching search parameters const matches = (x: COMSObject) => ( @@ -159,7 +159,7 @@ export const useObjectStore = defineStore('object', () => { // Merge and assign state.objects.value = difference.concat(response); - // Track all the object IDs in this bucket that the user would have access to in the table + // Track all the object IDs in this bucket that the user would have access to in the table // (even if filters are applied) if(!tagset?.length && !metadata?.length) { state.unfilteredObjectIds.value = state.objects.value