From: Lisa Knox Date: Tue, 19 Mar 2024 18:41:12 +0000 (-0400) Subject: Merge branch 'main' into 21357-favorites-names X-Git-Url: https://git.arvados.org/arvados.git/commitdiff_plain/37516bc14fdfe634c78764c15f3a8eb3a09b403c?hp=96f0b43ee4bb07e87dbeef8514a51857db069351 Merge branch 'main' into 21357-favorites-names refs #21357 Arvados-DCO-1.1-Signed-off-by: Lisa Knox --- diff --git a/services/workbench2/src/common/link-update-name.ts b/services/workbench2/src/common/link-update-name.ts new file mode 100644 index 0000000000..d9a04dfc5d --- /dev/null +++ b/services/workbench2/src/common/link-update-name.ts @@ -0,0 +1,74 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { LinkResource } from 'models/link'; +import { Dispatch } from 'redux'; +import { RootState } from 'store/store'; +import { ServiceRepository, getResourceService } from 'services/services'; +import { Resource, extractUuidKind } from 'models/resource'; + +type NameableResource = Resource & { name?: string }; + +export const verifyAndUpdateLink = async (link: LinkResource, dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise => { + //head resource should already be in the store + let headResource: Resource | undefined = getState().resources[link.headUuid]; + //if not, fetch it + if (!headResource) { + headResource = await fetchResource(link.headUuid)(dispatch, getState, services); + if (!headResource) { + if (!link.name) console.error('Could not validate link', link, 'because link head', link.headUuid, 'is not available'); + return link; + } + } + + if (validateLinkNameProp(link, headResource) === true) return link; + + const updatedLink = updateLinkNameProp(link, headResource); + updateRemoteLinkName(updatedLink)(dispatch, getState, services); + + return updatedLink; +}; + +export const verifyAndUpdateLinks = async (links: LinkResource[], dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + + const updatedLinks = links.map((link) => verifyAndUpdateLink(link, dispatch, getState, services)); + return Promise.all(updatedLinks); +}; + +const fetchResource = (uuid: string, showErrors?: boolean) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + try { + const kind = extractUuidKind(uuid); + const service = getResourceService(kind)(services); + if (service) { + const resource = await service.get(uuid, showErrors); + return resource; + } + } catch (e) { + console.error(`Could not fetch resource ${uuid}`, e); + } + return undefined; +}; + +const validateLinkNameProp = (link: LinkResource, head: NameableResource) => { + if (!link.name || link.name !== head.name) return false; + return true; +}; + +const updateLinkNameProp = (link: LinkResource, head: NameableResource) => { + const updatedLink = { ...link }; + if (head.name) updatedLink.name = head.name; + return updatedLink; +}; + +const updateRemoteLinkName = (link: LinkResource) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + try { + const kind = extractUuidKind(link.uuid); + const service = getResourceService(kind)(services); + if (service) { + service.update(link.uuid, { name: link.name }); + } + } catch (error) { + console.error('Could not update link name', link, error); + } +}; diff --git a/services/workbench2/src/components/tree/tree.tsx b/services/workbench2/src/components/tree/tree.tsx index 11a9540290..1f7aa8326d 100644 --- a/services/workbench2/src/components/tree/tree.tsx +++ b/services/workbench2/src/components/tree/tree.tsx @@ -252,7 +252,7 @@ const FlatTree = (props: FlatTreeProps) => > { (props.it.items || []) - .map((item: any) =>
{isInFavoritesTree(props.it) ? diff --git a/services/workbench2/src/services/services.ts b/services/workbench2/src/services/services.ts index cd04a65fef..48cd931127 100644 --- a/services/workbench2/src/services/services.ts +++ b/services/workbench2/src/services/services.ts @@ -131,6 +131,8 @@ export const getResourceService = (kind?: ResourceKind) => (serviceRepository: S return serviceRepository.groupsService; case ResourceKind.COLLECTION: return serviceRepository.collectionService; + case ResourceKind.LINK: + return serviceRepository.linkService; default: return undefined; } diff --git a/services/workbench2/src/store/collections/collection-update-actions.ts b/services/workbench2/src/store/collections/collection-update-actions.ts index d955c9478d..f6e52a4f80 100644 --- a/services/workbench2/src/store/collections/collection-update-actions.ts +++ b/services/workbench2/src/store/collections/collection-update-actions.ts @@ -22,6 +22,7 @@ import { updateResources } from "../resources/resources-actions"; import { loadDetailsPanel } from "../details-panel/details-panel-action"; import { getResource } from "store/resources/resources"; import { CollectionProperties } from "./collection-create-actions"; +import { loadSidePanelTreeProjects, SidePanelTreeCategory } from "store/side-panel-tree/side-panel-tree-actions"; export interface CollectionUpdateFormDialogData { uuid: string; @@ -65,6 +66,7 @@ export const updateCollection = (collection: CollectionUpdateFormDialogData) => })); dispatch(updateResources([updatedCollection])); dispatch(loadDetailsPanel(updatedCollection.uuid)); + dispatch(loadSidePanelTreeProjects(SidePanelTreeCategory.FAVORITES)) }).catch (e => { dispatch(progressIndicatorActions.STOP_WORKING(COLLECTION_UPDATE_FORM_NAME)); const error = getCommonResourceServiceError(e); diff --git a/services/workbench2/src/store/side-panel-tree/side-panel-tree-actions.ts b/services/workbench2/src/store/side-panel-tree/side-panel-tree-actions.ts index 3fad5f587a..c522139978 100644 --- a/services/workbench2/src/store/side-panel-tree/side-panel-tree-actions.ts +++ b/services/workbench2/src/store/side-panel-tree/side-panel-tree-actions.ts @@ -16,7 +16,8 @@ import { OrderBuilder } from 'services/api/order-builder'; import { ResourceKind } from 'models/resource'; import { CategoriesListReducer } from 'common/plugintypes'; import { pluginConfig } from 'plugins'; -import { LinkClass } from 'models/link'; +import { LinkClass, LinkResource } from 'models/link'; +import { verifyAndUpdateLinks } from 'common/link-update-name'; export enum SidePanelTreeCategory { PROJECTS = 'Home Projects', @@ -100,9 +101,12 @@ export const loadSidePanelTreeProjects = (projectUuid: string) => const treePicker = getTreePicker(SIDE_PANEL_TREE)(getState().treePicker); const node = treePicker ? getNode(projectUuid)(treePicker) : undefined; if (projectUuid === SidePanelTreeCategory.PUBLIC_FAVORITES) { - await dispatch(loadPublicFavoritesTree()); + const unverifiedPubFaves = await dispatch(loadPublicFavoritesTree()); + verifyAndUpdateLinkNames(unverifiedPubFaves, dispatch, getState, services); } else if (projectUuid === SidePanelTreeCategory.FAVORITES) { - await dispatch(loadFavoritesTree()); + const unverifiedFaves = await dispatch(loadFavoritesTree()); + await setFaves(unverifiedFaves, dispatch, getState, services); + verifyAndUpdateLinkNames(unverifiedFaves, dispatch, getState, services); } else if (node || projectUuid !== '') { await dispatch(loadProject(projectUuid)); } @@ -154,7 +158,46 @@ export const loadFavoritesTree = () => async (dispatch: Dispatch, getState: () = }) ); - dispatch(resourcesActions.SET_RESOURCES(items)); + return items; +}; + +const setFaves = async(links: LinkResource[], dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + + const uuids = links.map(it => it.headUuid); + const groupItems: Promise = services.groupsService.list({ + filters: new FilterBuilder() + .addIn("uuid", uuids) + .getFilters() + }); + const collectionItems: Promise = services.collectionService.list({ + filters: new FilterBuilder() + .addIn("uuid", uuids) + .getFilters() + }); + const processItems: Promise = services.containerRequestService.list({ + filters: new FilterBuilder() + .addIn("uuid", uuids) + .getFilters() + }); + + const resolvedItems = await Promise.all([groupItems, collectionItems, processItems]); + + const responseItems = resolvedItems.reduce((acc, response) => acc.concat(response.items), []); + + //setting resources here so they won't be re-fetched in validation step + dispatch(resourcesActions.SET_RESOURCES(responseItems)); +}; + +const verifyAndUpdateLinkNames = async (links: LinkResource[], dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + const verfifiedLinks = await verifyAndUpdateLinks(links, dispatch, getState, services); + + dispatch( + treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ + id: SidePanelTreeCategory.FAVORITES, + pickerId: SIDE_PANEL_TREE, + nodes: verfifiedLinks.map(item => initTreeNode({ id: item.headUuid, value: item })), + }) + ); }; export const loadPublicFavoritesTree = () => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { @@ -176,15 +219,44 @@ export const loadPublicFavoritesTree = () => async (dispatch: Dispatch, getState const { items } = await services.linkService.list(params); + const uuids = items.map(it => it.headUuid); + const groupItems: Promise = services.groupsService.list({ + filters: new FilterBuilder() + .addIn("uuid", uuids) + .addIsA("uuid", typeFilters) + .getFilters() + }); + const collectionItems: Promise = services.collectionService.list({ + filters: new FilterBuilder() + .addIn("uuid", uuids) + .addIsA("uuid", typeFilters) + .getFilters() + }); + const processItems: Promise = services.containerRequestService.list({ + filters: new FilterBuilder() + .addIn("uuid", uuids) + .addIsA("uuid", typeFilters) + .getFilters() + }); + + const resolvedItems = await Promise.all([groupItems, collectionItems, processItems]); + + const responseItems = resolvedItems.reduce((acc, response) => acc.concat(response.items), []); + + const filteredItems = items.filter(item => responseItems.some(responseItem => responseItem.uuid === item.headUuid)); + dispatch( treePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({ id: SidePanelTreeCategory.PUBLIC_FAVORITES, pickerId: SIDE_PANEL_TREE, - nodes: items.map(item => initTreeNode({ id: item.headUuid, value: item })), + nodes: filteredItems.map(item => initTreeNode({ id: item.headUuid, value: item })), }) ); - dispatch(resourcesActions.SET_RESOURCES(items)); + //setting resources here so they won't be re-fetched in validation step + dispatch(resourcesActions.SET_RESOURCES(responseItems)); + + return filteredItems; }; export const activateSidePanelTreeItem = (id: string) => diff --git a/services/workbench2/src/views-components/tree-picker/tree-picker.ts b/services/workbench2/src/views-components/tree-picker/tree-picker.ts index a6fdfefec9..1b6d2a26fd 100644 --- a/services/workbench2/src/views-components/tree-picker/tree-picker.ts +++ b/services/workbench2/src/views-components/tree-picker/tree-picker.ts @@ -70,7 +70,7 @@ const treePickerToTreeItems = (tree: Ttree, resources: ResourcesState) => const resource = resources[node.id]; return { active: node.active, - data: resource ? { ...resource, name: node.value.name || node.value } : undefined || node.value, + data: resource ? { ...resource, name: node.value.name || node.value } : node.value, id: node.id, items: items.length > 0 ? items : undefined, open: node.expanded,