diff --git a/src/components/modals/SearchUserModal.vue b/src/components/modals/SearchUserModal.vue index ab77550ccefe1ab2b575d4a20e3e09b4da1f2358..85f24c3167c77a967f8288b128ef081f0499e235 100644 --- a/src/components/modals/SearchUserModal.vue +++ b/src/components/modals/SearchUserModal.vue @@ -60,6 +60,11 @@ function searchUser(name: string) { formState.potentialUsers = userSuggestions.filter( (user) => store.currentUID != user.uid, ); + const uidToName: Record<string, string> = {}; + for (const user of userSuggestions) { + uidToName[user.uid] = user.display_name; + } + store.addUidToNameMapping(uidToName); }) .catch((err) => { formState.error = true; diff --git a/src/components/object-storage/BucketListItem.vue b/src/components/object-storage/BucketListItem.vue index c2baf08f56c9cff948115eed1043e41a9e121c8d..a1db0ae77fa2f80099523ab93c1dc26b94ccb542 100644 --- a/src/components/object-storage/BucketListItem.vue +++ b/src/components/object-storage/BucketListItem.vue @@ -161,7 +161,7 @@ onMounted(() => { href="#" data-bs-toggle="modal" :data-bs-target="'#view-permission-modal' + randomIDSuffix" - >View</a + >Show</a > </td> </tr> @@ -185,7 +185,7 @@ onMounted(() => { href="#" data-bs-toggle="modal" data-bs-target="#permission-list-modal" - >View</a + >Show</a > </td> </tr> diff --git a/src/components/object-storage/modals/PermissionListModal.vue b/src/components/object-storage/modals/PermissionListModal.vue index fea164a855ba0c9295229360c24621f51f6b9fe4..1d5c4d020a74e2bf737026c03ad5610cb095b41e 100644 --- a/src/components/object-storage/modals/PermissionListModal.vue +++ b/src/components/object-storage/modals/PermissionListModal.vue @@ -95,8 +95,12 @@ onBeforeMount(() => { :data-bs-target="'#permission-list-edit-modal' + randomIDSuffix" > <div class="row"> - <span class="text-info col-2">{{ permission.permission }}</span> - <span class="col-9"> {{ permission.grantee_display_name }}</span> + <span class="text-info col-2 text-start">{{ + permission.permission + }}</span> + <span class="col-9 text-center"> + {{ permission.grantee_display_name }}</span + > </div> </button> </div> diff --git a/src/stores/users.ts b/src/stores/users.ts index 040703865e110053e74747ce5897031be038c027..8a8d258ea9fec6797f0c38067a38e0fbeea1f1b6 100644 --- a/src/stores/users.ts +++ b/src/stores/users.ts @@ -108,28 +108,57 @@ export const useAuthStore = defineStore({ }, logout() { this.$reset(); + localStorage.clear(); useWorkflowExecutionStore().$reset(); useBucketStore().$reset(); useWorkflowStore().$reset(); useS3KeyStore().$reset(); useS3ObjectStore().$reset(); }, - fetchUsernames(uids: string[]): Promise<User[]> { + async addUidToNameMapping(mapping: Record<string, string>): Promise<void> { + for (const uid of Object.keys(mapping)) { + this.userMapping[uid] = mapping[uid]; + localStorage.setItem(uid, mapping[uid]); + } + }, + async fetchUsernames(uids: string[]): Promise<string[]> { const filteredIds = uids - .filter((uid) => !this.userMapping[uid]) // filter null UIDs and already present UIDs + .filter((uid) => !this.userMapping[uid]) // filter already present UIDs .filter( // filter unique UIDs (modeId, index, array) => array.findIndex((val) => val === modeId) === index, ); - return Promise.all( - filteredIds.map((uid) => UserService.userGetUser(uid)), - ).then((users) => { - for (const user of users) { - this.userMapping[user.uid] = user.display_name; + // If all uids are already in the store, then return them + if (filteredIds.length === 0) { + return uids.map((uid) => this.userMapping[uid]); + } + const missingIds: string[] = []; + const storedNames = filteredIds.map((uid) => localStorage.getItem(uid)); + + for (const index in storedNames) { + // if uid was not cached, mark it to fetch it from backend + if (storedNames[index] == null) { + missingIds.push(filteredIds[index]); + } else { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.userMapping[filteredIds[index]] = storedNames[index]!; } - return users; - }); + } + // If all uids could be resolved from cache, return them + if (missingIds.length === 0) { + return uids.map((uid) => this.userMapping[uid]); + } + // fetch missing users from backend + const fetchedUsers = await Promise.all( + missingIds.map((uid) => UserService.userGetUser(uid)), + ); + // Put users in store + for (const user of fetchedUsers) { + this.userMapping[user.uid] = user.display_name; + localStorage.setItem(user.uid, user.display_name); + } + return uids.map((uid) => this.userMapping[uid]); }, }, }); diff --git a/src/stores/workflows.ts b/src/stores/workflows.ts index 29c8e8d4dea182522fd20bccce3ffd10619a34ba..044f74b605ad1ecc0499accaa679d003b2bd5ab6 100644 --- a/src/stores/workflows.ts +++ b/src/stores/workflows.ts @@ -23,11 +23,14 @@ export const useWorkflowStore = defineStore({ state: () => ({ workflowMapping: {}, + nameMapping: {}, comprehensiveWorkflowMapping: {}, modeMapping: {}, + modeNameMapping: {}, arbitraryWorkflows: {}, }) as { workflowMapping: Record<string, WorkflowOut>; + nameMapping: Record<string, string>; comprehensiveWorkflowMapping: Record<string, WorkflowOut>; modeMapping: Record<string, WorkflowModeOut>; arbitraryWorkflows: Record<string, WorkflowIn>; @@ -59,8 +62,16 @@ export const useWorkflowStore = defineStore({ } return mapping; }, + getName(): (objectID: string) => string | undefined { + return (objectID) => + this.nameMapping[objectID] ?? localStorage.getItem(objectID); + }, }, actions: { + __addNameToMapping(key: string, value: string) { + this.nameMapping[key] = value; + localStorage.setItem(key, value); + }, fetchWorkflows(onFinally?: () => void): Promise<WorkflowOut[]> { if (Object.keys(this.workflowMapping).length > 0) { onFinally?.(); @@ -69,6 +80,10 @@ export const useWorkflowStore = defineStore({ .then((workflows) => { for (const workflow of workflows) { this.workflowMapping[workflow.workflow_id] = workflow; + this.__addNameToMapping(workflow.workflow_id, workflow.name); + for (const version of workflow.versions) { + this.__addNameToMapping(version.git_commit_hash, version.version); + } } this.fetchWorkflowModes( workflows @@ -94,6 +109,10 @@ export const useWorkflowStore = defineStore({ .then((workflows) => { for (const workflow of workflows) { this.comprehensiveWorkflowMapping[workflow.workflow_id] = workflow; + this.__addNameToMapping(workflow.workflow_id, workflow.name); + for (const version of workflow.versions) { + this.__addNameToMapping(version.git_commit_hash, version.version); + } } this.fetchWorkflowModes( workflows @@ -113,6 +132,10 @@ export const useWorkflowStore = defineStore({ return WorkflowService.workflowListWorkflows(undefined, [Status.CREATED]) .then((workflows) => { for (const workflow of workflows) { + this.__addNameToMapping(workflow.workflow_id, workflow.name); + for (const version of workflow.versions) { + this.__addNameToMapping(version.git_commit_hash, version.version); + } if ( this.comprehensiveWorkflowMapping[workflow.workflow_id] != undefined @@ -162,6 +185,10 @@ export const useWorkflowStore = defineStore({ comprehensive ? Object.values(Status) : undefined, ) .then((workflow) => { + this.__addNameToMapping(workflow.workflow_id, workflow.name); + for (const version of workflow.versions) { + this.__addNameToMapping(version.git_commit_hash, version.version); + } if (comprehensive) { this.comprehensiveWorkflowMapping[workflow_id] = workflow; } else { @@ -191,6 +218,7 @@ export const useWorkflowStore = defineStore({ ), ).then((modes) => { for (const mode of modes) { + this.__addNameToMapping(mode.mode_id, mode.name); this.modeMapping[mode.mode_id] = mode; } return modes; @@ -253,6 +281,10 @@ export const useWorkflowStore = defineStore({ ): Promise<WorkflowVersion> { return WorkflowService.workflowUpdateWorkflow(workflow_id, version).then( (updatedVersion) => { + this.__addNameToMapping( + updatedVersion.git_commit_hash, + updatedVersion.version, + ); if (this.comprehensiveWorkflowMapping[workflow_id] == undefined) { this.fetchWorkflow(workflow_id, true); } else { @@ -266,6 +298,8 @@ export const useWorkflowStore = defineStore({ }, deleteWorkflow(workflow_id: string): Promise<void> { return WorkflowService.workflowDeleteWorkflow(workflow_id).then(() => { + delete this.nameMapping[workflow_id]; + localStorage.removeItem(workflow_id); delete this.workflowMapping[workflow_id]; delete this.comprehensiveWorkflowMapping[workflow_id]; }); @@ -275,6 +309,11 @@ export const useWorkflowStore = defineStore({ (workflowOut) => { this.comprehensiveWorkflowMapping[workflowOut.workflow_id] = workflowOut; + this.__addNameToMapping(workflowOut.workflow_id, workflowOut.name); + this.__addNameToMapping( + workflow.git_commit_hash, + workflowOut.versions[0].version, + ); return workflowOut; }, ); diff --git a/src/views/object-storage/BucketView.vue b/src/views/object-storage/BucketView.vue index 1beac90e27ba4549ec39d648a04a1e7df1496684..6665e36cc10efc9df2405f462a8c48e1513d101e 100644 --- a/src/views/object-storage/BucketView.vue +++ b/src/views/object-storage/BucketView.vue @@ -635,7 +635,7 @@ function getObjectFileName(key: string): string { <!-- Show content of bucket --> <div v-else> <!-- Table header --> - <table class="table table-hover caption-top align-middle"> + <table class="table table-sm table-hover caption-top align-middle"> <caption> Displaying {{ diff --git a/src/views/workflows/ListWorkflowExecutionsView.vue b/src/views/workflows/ListWorkflowExecutionsView.vue index 691068cdcbebb185d4a77036c4e4016d984c738d..6013459f6181a637eba8b95bf2ca03de372969d4 100644 --- a/src/views/workflows/ListWorkflowExecutionsView.vue +++ b/src/views/workflows/ListWorkflowExecutionsView.vue @@ -78,9 +78,32 @@ const deleteModalString = computed<string>(() => { // Functions // ----------------------------------------------------------------------------- function updateExecutions() { - executionRepository.fetchExecutions(() => { - executionsState.loading = false; - }); + executionRepository + .fetchExecutions(() => { + executionsState.loading = false; + }) + .then((executions) => { + Promise.all( + executions + .filter((executions) => executions.workflow_id) + .filter( + // filter executions with unique workflow ids + (execution, index, array) => + array.findIndex( + (val) => val.workflow_id === execution.workflow_id, + ) === index, + ) + .filter( + (execution) => + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + workflowRepository.getName(execution.workflow_id!) == undefined, + ) + .map((execution) => + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + workflowRepository.fetchWorkflow(execution.workflow_id!), + ), + ); + }); } function refreshExecutions() { @@ -127,7 +150,6 @@ function refreshRunningWorkflowExecution() { } onMounted(() => { - workflowRepository.fetchWorkflows(); updateExecutions(); intervalId = setInterval(refreshRunningWorkflowExecution, 5000); new Tooltip("#refreshExecutionsButton"); @@ -231,11 +253,8 @@ onUnmounted(() => { }, }" > - {{ - workflowRepository.workflowMapping[execution.workflow_id]?.name - }}@{{ - workflowRepository.versionMapping[execution.workflow_version_id] - ?.version + {{ workflowRepository.getName(execution.workflow_id) }}@{{ + workflowRepository.getName(execution.workflow_version_id) }} </router-link> </td>