From dee0cb67a02c25f0f3174681579d0898d880caa3 Mon Sep 17 00:00:00 2001 From: Eric Biagiotti Date: Thu, 24 Oct 2019 13:46:06 -0400 Subject: [PATCH] 15840: Project panel uses collection attributes for file count and size - Previously, the project panel loaded the entire collection file tree to sum file count and total size. Now it uses the file_count and file_size_total collection attributes. - Also updated the collection service to not do any tree manipulation when using propfind for files. Arvados-DCO-1.1-Signed-off-by: Eric Biagiotti --- src/models/collection.ts | 2 ++ .../collection-service-files-response.ts | 8 +---- .../collection-service/collection-service.ts | 8 ++--- src/store/advanced-tab/advanced-tab.tsx | 6 ++-- .../collection-panel-files-actions.ts | 13 ++++---- .../project-panel-middleware-service.ts | 17 ---------- .../resources-data/resources-data-actions.ts | 13 -------- .../resources-data/resources-data-reducer.ts | 33 ------------------- src/store/resources-data/resources-data.ts | 8 ----- src/store/store.ts | 2 -- src/store/tree-picker/tree-picker-actions.ts | 8 ++++- .../data-explorer/renderers.tsx | 6 ++-- .../details-panel/collection-details.tsx | 4 +-- .../details-panel/details-data.tsx | 5 ++- .../details-panel/details-panel.tsx | 11 +++---- .../collection-panel/collection-panel.tsx | 19 +++++------ 16 files changed, 43 insertions(+), 120 deletions(-) delete mode 100644 src/store/resources-data/resources-data-actions.ts delete mode 100644 src/store/resources-data/resources-data-reducer.ts delete mode 100644 src/store/resources-data/resources-data.ts diff --git a/src/models/collection.ts b/src/models/collection.ts index ca2b4b36..baa25c7a 100644 --- a/src/models/collection.ts +++ b/src/models/collection.ts @@ -21,6 +21,8 @@ export interface CollectionResource extends TrashableResource { version: number; preserveVersion: boolean; unsignedManifestText?: string; + fileCount: number; + fileSizeTotal: number; } export const getCollectionUrl = (uuid: string) => { diff --git a/src/services/collection-service/collection-service-files-response.ts b/src/services/collection-service/collection-service-files-response.ts index 619a4fc7..f3a2147e 100644 --- a/src/services/collection-service/collection-service-files-response.ts +++ b/src/services/collection-service/collection-service-files-response.ts @@ -2,16 +2,10 @@ // // SPDX-License-Identifier: AGPL-3.0 -import { createCollectionFilesTree, CollectionDirectory, CollectionFile, CollectionFileType, createCollectionDirectory, createCollectionFile } from "../../models/collection-file"; +import { CollectionDirectory, CollectionFile, CollectionFileType, createCollectionDirectory, createCollectionFile } from "../../models/collection-file"; import { getTagValue } from "~/common/xml"; import { getNodeChildren, Tree, mapTree } from '~/models/tree'; -export const parseFilesResponse = (document: Document) => { - const files = extractFilesData(document); - const tree = createCollectionFilesTree(files); - return sortFilesTree(tree); -}; - export const sortFilesTree = (tree: Tree) => { return mapTree(node => { const children = getNodeChildren(node.id)(tree); diff --git a/src/services/collection-service/collection-service.ts b/src/services/collection-service/collection-service.ts index c787cf7b..6eb9b5ba 100644 --- a/src/services/collection-service/collection-service.ts +++ b/src/services/collection-service/collection-service.ts @@ -7,8 +7,7 @@ import { AxiosInstance } from "axios"; import { CollectionFile, CollectionDirectory } from "~/models/collection-file"; import { WebDAV } from "~/common/webdav"; import { AuthService } from "../auth-service/auth-service"; -import { mapTreeValues } from "~/models/tree"; -import { parseFilesResponse } from "./collection-service-files-response"; +import { extractFilesData } from "./collection-service-files-response"; import { TrashableResourceService } from "~/services/common-service/trashable-resource-service"; import { ApiActions } from "~/services/api/api-actions"; @@ -22,8 +21,7 @@ export class CollectionService extends TrashableResourceService { + extendFileURL = (file: CollectionDirectory | CollectionFile) => { const baseUrl = this.webdavClient.defaults.baseURL.endsWith('/') ? this.webdavClient.defaults.baseURL.slice(0, -1) : this.webdavClient.defaults.baseURL; diff --git a/src/store/advanced-tab/advanced-tab.tsx b/src/store/advanced-tab/advanced-tab.tsx index c7bb677a..be82547b 100644 --- a/src/store/advanced-tab/advanced-tab.tsx +++ b/src/store/advanced-tab/advanced-tab.tsx @@ -435,7 +435,7 @@ const containerRequestApiResponse = (apiResponse: ContainerRequestResource) => { const collectionApiResponse = (apiResponse: CollectionResource) => { const { uuid, ownerUuid, createdAt, modifiedAt, modifiedByClientUuid, modifiedByUserUuid, name, description, properties, portableDataHash, replicationDesired, replicationConfirmedAt, replicationConfirmed, manifestText, deleteAt, trashAt, isTrashed, storageClassesDesired, - storageClassesConfirmed, storageClassesConfirmedAt, currentVersionUuid, version, preserveVersion } = apiResponse; + storageClassesConfirmed, storageClassesConfirmedAt, currentVersionUuid, version, preserveVersion, fileCount, fileSizeTotal } = apiResponse; const response = ` "uuid": "${uuid}", "owner_uuid": "${ownerUuid}", @@ -459,7 +459,9 @@ const collectionApiResponse = (apiResponse: CollectionResource) => { "storage_classes_confirmed_at": ${stringify(storageClassesConfirmedAt)}, "current_version_uuid": ${stringify(currentVersionUuid)}, "version": ${version}, -"preserve_version": ${preserveVersion}`; +"preserve_version": ${preserveVersion}, +"file_count": ${fileCount}, +"file_size_total": ${fileSizeTotal}`; return {'{'} {response} {'\n'} {'}'}; }; diff --git a/src/store/collection-panel/collection-panel-files/collection-panel-files-actions.ts b/src/store/collection-panel/collection-panel-files/collection-panel-files-actions.ts index c661d9f8..27e4d4e6 100644 --- a/src/store/collection-panel/collection-panel-files/collection-panel-files-actions.ts +++ b/src/store/collection-panel/collection-panel-files/collection-panel-files-actions.ts @@ -4,17 +4,16 @@ import { unionize, ofType, UnionOf } from "~/common/unionize"; import { Dispatch } from "redux"; -import { CollectionFilesTree, CollectionFileType } from "~/models/collection-file"; +import { CollectionFilesTree, CollectionFileType, createCollectionFilesTree } from "~/models/collection-file"; import { ServiceRepository } from "~/services/services"; import { RootState } from "../../store"; import { snackbarActions, SnackbarKind } from "../../snackbar/snackbar-actions"; import { dialogActions } from '../../dialog/dialog-actions'; -import { getNodeValue } from "~/models/tree"; +import { getNodeValue, mapTreeValues } from "~/models/tree"; import { filterCollectionFilesBySelection } from './collection-panel-files-state'; import { startSubmit, stopSubmit, initialize, FormErrors } from 'redux-form'; import { getDialog } from "~/store/dialog/dialog-reducer"; -import { getFileFullPath } from "~/services/collection-service/collection-service-files-response"; -import { resourcesDataActions } from "~/store/resources-data/resources-data-actions"; +import { getFileFullPath, sortFilesTree } from "~/services/collection-service/collection-service-files-response"; export const collectionPanelFilesAction = unionize({ SET_COLLECTION_FILES: ofType(), @@ -29,8 +28,10 @@ export type CollectionPanelFilesAction = UnionOf async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { const files = await services.collectionService.files(uuid); - dispatch(collectionPanelFilesAction.SET_COLLECTION_FILES(files)); - dispatch(resourcesDataActions.SET_FILES({ uuid, files })); + const tree = createCollectionFilesTree(files); + const sorted = sortFilesTree(tree); + const mapped = mapTreeValues(services.collectionService.extendFileURL)(sorted); + dispatch(collectionPanelFilesAction.SET_COLLECTION_FILES(mapped)); }; export const removeCollectionFiles = (filePaths: string[]) => diff --git a/src/store/project-panel/project-panel-middleware-service.ts b/src/store/project-panel/project-panel-middleware-service.ts index 361825d4..3a42d07e 100644 --- a/src/store/project-panel/project-panel-middleware-service.ts +++ b/src/store/project-panel/project-panel-middleware-service.ts @@ -28,9 +28,6 @@ import { DataExplorer, getDataExplorer } from '~/store/data-explorer/data-explor import { ListResults } from '~/services/common-service/common-service'; import { loadContainers } from '~/store/processes/processes-actions'; import { ResourceKind } from '~/models/resource'; -import { getResource } from "~/store/resources/resources"; -import { CollectionResource } from "~/models/collection"; -import { resourcesDataActions } from "~/store/resources-data/resources-data-actions"; import { getSortColumn } from "~/store/data-explorer/data-explorer-reducer"; import { serializeResourceTypeFilters } from '~/store/resource-type-filters/resource-type-filters'; import { updatePublicFavorites } from '~/store/public-favorites/public-favorites-actions'; @@ -58,7 +55,6 @@ export class ProjectPanelMiddlewareService extends DataExplorerMiddlewareService api.dispatch(updateFavorites(resourceUuids)); api.dispatch(updatePublicFavorites(resourceUuids)); api.dispatch(updateResources(response.items)); - api.dispatch(updateResourceData(resourceUuids)); await api.dispatch(loadMissingProcessesInformation(response.items)); api.dispatch(setItems(response)); } catch (e) { @@ -91,19 +87,6 @@ export const loadMissingProcessesInformation = (resources: GroupContentsResource } }; -export const updateResourceData = (resourceUuids: string[]) => - async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - resourceUuids.map(async uuid => { - const resource = getResource(uuid)(getState().resources); - if (resource && resource.kind === ResourceKind.COLLECTION) { - const files = await services.collectionService.files(uuid); - if (files) { - dispatch(resourcesDataActions.SET_FILES({ uuid, files })); - } - } - }); - }; - export const setItems = (listResults: ListResults) => projectPanelActions.SET_ITEMS({ ...listResultsToDataExplorerItemsMeta(listResults), diff --git a/src/store/resources-data/resources-data-actions.ts b/src/store/resources-data/resources-data-actions.ts deleted file mode 100644 index 7bc0e943..00000000 --- a/src/store/resources-data/resources-data-actions.ts +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (C) The Arvados Authors. All rights reserved. -// -// SPDX-License-Identifier: AGPL-3.0 - -import { unionize, ofType, UnionOf } from "~/common/unionize"; -import { CollectionDirectory, CollectionFile } from "~/models/collection-file"; -import { Tree } from "~/models/tree"; - -export const resourcesDataActions = unionize({ - SET_FILES: ofType<{uuid: string, files: Tree}>() -}); - -export type ResourcesDataActions = UnionOf; diff --git a/src/store/resources-data/resources-data-reducer.ts b/src/store/resources-data/resources-data-reducer.ts deleted file mode 100644 index 07a3a666..00000000 --- a/src/store/resources-data/resources-data-reducer.ts +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (C) The Arvados Authors. All rights reserved. -// -// SPDX-License-Identifier: AGPL-3.0 - -import { ResourcesDataActions, resourcesDataActions } from "~/store/resources-data/resources-data-actions"; -import { getNodeDescendantsIds, TREE_ROOT_ID } from "~/models/tree"; -import { CollectionFileType } from "~/models/collection-file"; - -export interface ResourceData { - fileCount: number; - fileSize: number; -} - -export type ResourcesDataState = { - [key: string]: ResourceData -}; - -export const resourcesDataReducer = (state: ResourcesDataState = {}, action: ResourcesDataActions) => - resourcesDataActions.match(action, { - SET_FILES: ({uuid, files}) => { - const flattenFiles = getNodeDescendantsIds(TREE_ROOT_ID)(files).map(id => files[id]); - const [fileSize, fileCount] = flattenFiles.reduce(([size, cnt], f) => - f && f.value.type === CollectionFileType.FILE - ? [size + f.value.size, cnt + 1] - : [size, cnt] - , [0, 0]); - return { - ...state, - [uuid]: { fileCount, fileSize } - }; - }, - default: () => state, - }); diff --git a/src/store/resources-data/resources-data.ts b/src/store/resources-data/resources-data.ts deleted file mode 100644 index 48c1e2b2..00000000 --- a/src/store/resources-data/resources-data.ts +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (C) The Arvados Authors. All rights reserved. -// -// SPDX-License-Identifier: AGPL-3.0 - -import { ResourceData, ResourcesDataState } from "~/store/resources-data/resources-data-reducer"; - -export const getResourceData = (id: string) => - (state: ResourcesDataState): ResourceData | undefined => state[id]; diff --git a/src/store/store.ts b/src/store/store.ts index 8a2ca240..2e27a56f 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -43,7 +43,6 @@ import { appInfoReducer } from '~/store/app-info/app-info-reducer'; import { searchBarReducer } from './search-bar/search-bar-reducer'; import { SEARCH_RESULTS_PANEL_ID } from '~/store/search-results-panel/search-results-panel-actions'; import { SearchResultsMiddlewareService } from './search-results-panel/search-results-middleware-service'; -import { resourcesDataReducer } from "~/store/resources-data/resources-data-reducer"; import { virtualMachinesReducer } from "~/store/virtual-machines/virtual-machines-reducer"; import { repositoriesReducer } from '~/store/repositories/repositories-reducer'; import { keepServicesReducer } from '~/store/keep-services/keep-services-reducer'; @@ -161,7 +160,6 @@ const createRootReducer = (services: ServiceRepository) => combineReducers({ processLogsPanel: processLogsPanelReducer, properties: propertiesReducer, resources: resourcesReducer, - resourcesData: resourcesDataReducer, router: routerReducer, snackbar: snackbarReducer, treePicker: treePickerReducer, diff --git a/src/store/tree-picker/tree-picker-actions.ts b/src/store/tree-picker/tree-picker-actions.ts index e4d6d933..1ec49141 100644 --- a/src/store/tree-picker/tree-picker-actions.ts +++ b/src/store/tree-picker/tree-picker-actions.ts @@ -4,6 +4,7 @@ import { unionize, ofType, UnionOf } from "~/common/unionize"; import { TreeNode, initTreeNode, getNodeDescendants, TreeNodeStatus, getNode, TreePickerId, Tree } from '~/models/tree'; +import { createCollectionFilesTree } from "~/models/collection-file"; import { Dispatch } from 'redux'; import { RootState } from '~/store/store'; import { ServiceRepository } from '~/services/services'; @@ -17,6 +18,8 @@ import { OrderBuilder } from '~/services/api/order-builder'; import { ProjectResource } from '~/models/project'; import { mapTree } from '../../models/tree'; import { LinkResource, LinkClass } from "~/models/link"; +import { mapTreeValues } from "~/models/tree"; +import { sortFilesTree } from "~/services/collection-service/collection-service-files-response"; export const treePickerActions = unionize({ LOAD_TREE_PICKER_NODE: ofType<{ id: string, pickerId: string }>(), @@ -139,7 +142,10 @@ export const loadCollection = (id: string, pickerId: string) => const node = getNode(id)(picker); if (node && 'kind' in node.value && node.value.kind === ResourceKind.COLLECTION) { - const filesTree = await services.collectionService.files(node.value.portableDataHash); + const files = await services.collectionService.files(node.value.portableDataHash); + const tree = createCollectionFilesTree(files); + const sorted = sortFilesTree(tree); + const filesTree = mapTreeValues(services.collectionService.extendFileURL)(sorted); dispatch( treePickerActions.APPEND_TREE_PICKER_NODE_SUBTREE({ diff --git a/src/views-components/data-explorer/renderers.tsx b/src/views-components/data-explorer/renderers.tsx index 32e14c5a..4179d60e 100644 --- a/src/views-components/data-explorer/renderers.tsx +++ b/src/views-components/data-explorer/renderers.tsx @@ -19,13 +19,13 @@ import { compose, Dispatch } from 'redux'; import { WorkflowResource } from '~/models/workflow'; import { ResourceStatus } from '~/views/workflow-panel/workflow-panel-view'; import { getUuidPrefix, openRunProcess } from '~/store/workflow-panel/workflow-panel-actions'; -import { getResourceData } from "~/store/resources-data/resources-data"; import { openSharingDialog } from '~/store/sharing-dialog/sharing-dialog-actions'; import { UserResource } from '~/models/user'; import { toggleIsActive, toggleIsAdmin } from '~/store/users/users-actions'; import { LinkResource } from '~/models/link'; import { navigateTo } from '~/store/navigation/navigation-action'; import { withResourceData } from '~/views-components/data-explorer/with-resources'; +import { CollectionResource } from '~/models/collection'; const renderName = (dispatch: Dispatch, item: { name: string; uuid: string, kind: string }) => @@ -398,8 +398,8 @@ export const renderFileSize = (fileSize?: number) => export const ResourceFileSize = connect( (state: RootState, props: { uuid: string }) => { - const resource = getResourceData(props.uuid)(state.resourcesData); - return { fileSize: resource ? resource.fileSize : 0 }; + const resource = getResource(props.uuid)(state.resources); + return { fileSize: resource ? resource.fileSizeTotal : 0 }; })((props: { fileSize?: number }) => renderFileSize(props.fileSize)); const renderOwner = (owner: string) => diff --git a/src/views-components/details-panel/collection-details.tsx b/src/views-components/details-panel/collection-details.tsx index a523d6fc..999d4c79 100644 --- a/src/views-components/details-panel/collection-details.tsx +++ b/src/views-components/details-panel/collection-details.tsx @@ -27,8 +27,8 @@ export class CollectionDetails extends DetailsData { {/* Missing attrs */} - - + + ; } } diff --git a/src/views-components/details-panel/details-data.tsx b/src/views-components/details-panel/details-data.tsx index 45afb02b..ca8e2cd7 100644 --- a/src/views-components/details-panel/details-data.tsx +++ b/src/views-components/details-panel/details-data.tsx @@ -4,10 +4,9 @@ import * as React from 'react'; import { DetailsResource } from "~/models/details"; -import { ResourceData } from "~/store/resources-data/resources-data-reducer"; export abstract class DetailsData { - constructor(protected item: T, protected data?: ResourceData) {} + constructor(protected item: T) { } getTitle(): string { return this.item.name || 'Projects'; @@ -17,6 +16,6 @@ export abstract class DetailsData { abstract getDetails(): React.ReactElement; getActivity(): React.ReactElement { - return
; + return
; } } diff --git a/src/views-components/details-panel/details-panel.tsx b/src/views-components/details-panel/details-panel.tsx index f4aaa843..8244e15f 100644 --- a/src/views-components/details-panel/details-panel.tsx +++ b/src/views-components/details-panel/details-panel.tsx @@ -21,8 +21,6 @@ import { EmptyDetails } from "./empty-details"; import { DetailsData } from "./details-data"; import { DetailsResource } from "~/models/details"; import { getResource } from '~/store/resources/resources'; -import { ResourceData } from "~/store/resources-data/resources-data-reducer"; -import { getResourceData } from "~/store/resources-data/resources-data"; import { toggleDetailsPanel, SLIDE_TIMEOUT } from '~/store/details-panel/details-panel-action'; import { FileDetails } from '~/views-components/details-panel/file-details'; import { getNode } from '~/models/tree'; @@ -62,13 +60,13 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ const EMPTY_RESOURCE: EmptyResource = { kind: undefined, name: 'Projects' }; -const getItem = (res: DetailsResource, resourceData?: ResourceData): DetailsData => { +const getItem = (res: DetailsResource): DetailsData => { if ('kind' in res) { switch (res.kind) { case ResourceKind.PROJECT: return new ProjectDetails(res); case ResourceKind.COLLECTION: - return new CollectionDetails(res, resourceData); + return new CollectionDetails(res); case ResourceKind.PROCESS: return new ProcessDetails(res); default: @@ -79,13 +77,12 @@ const getItem = (res: DetailsResource, resourceData?: ResourceData): DetailsData } }; -const mapStateToProps = ({ detailsPanel, resources, resourcesData, collectionPanelFiles }: RootState) => { +const mapStateToProps = ({ detailsPanel, resources, collectionPanelFiles }: RootState) => { const resource = getResource(detailsPanel.resourceUuid)(resources) as DetailsResource | undefined; const file = getNode(detailsPanel.resourceUuid)(collectionPanelFiles); - const resourceData = getResourceData(detailsPanel.resourceUuid)(resourcesData); return { isOpened: detailsPanel.isOpened, - item: getItem(resource || (file && file.value) || EMPTY_RESOURCE, resourceData), + item: getItem(resource || (file && file.value) || EMPTY_RESOURCE), }; }; diff --git a/src/views/collection-panel/collection-panel.tsx b/src/views/collection-panel/collection-panel.tsx index 28ae2f05..5c4b28df 100644 --- a/src/views/collection-panel/collection-panel.tsx +++ b/src/views/collection-panel/collection-panel.tsx @@ -21,8 +21,6 @@ import { getResource } from '~/store/resources/resources'; import { openContextMenu } from '~/store/context-menu/context-menu-actions'; import { ContextMenuKind } from '~/views-components/context-menu/context-menu'; import { formatFileSize } from "~/common/formatters"; -import { getResourceData } from "~/store/resources-data/resources-data"; -import { ResourceData } from "~/store/resources-data/resources-data-reducer"; import { openDetailsPanel } from '~/store/details-panel/details-panel-action'; import { snackbarActions, SnackbarKind } from '~/store/snackbar/snackbar-actions'; import { PropertyChipComponent } from '~/views-components/resource-properties-form/property-chip'; @@ -59,22 +57,21 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ interface CollectionPanelDataProps { item: CollectionResource; - data: ResourceData; } type CollectionPanelProps = CollectionPanelDataProps & DispatchProp & WithStyles & RouteComponentProps<{ id: string }>; export const CollectionPanel = withStyles(styles)( - connect((state: RootState, props: RouteComponentProps<{ id: string }>) => { - const item = getResource(props.match.params.id)(state.resources); - const data = getResourceData(props.match.params.id)(state.resourcesData); - return { item, data }; - })( + connect((state: RootState, props: RouteComponentProps<{ id: string }>) => { + const item = getResource(props.match.params.id)(state.resources); + const data = getResourceData(props.match.params.id)(state.resourcesData); + return { item, data }; + })( class extends React.Component { render() { - const { classes, item, data, dispatch } = this.props; + const { classes, item, dispatch } = this.props; return item ? <> @@ -107,9 +104,9 @@ export const CollectionPanel = withStyles(styles)( label='Portable data hash' linkToUuid={item && item.portableDataHash} /> + label='Number of files' value={item && item.fileCount} /> + label='Content size' value={item && formatFileSize(item.fileSizeTotal)} /> {(item.properties.container_request || item.properties.containerRequest) && -- 2.30.2