From 3f51c120dab4eafb820f9c2a5e4f836cc9b04982 Mon Sep 17 00:00:00 2001 From: Daniel Kos Date: Mon, 22 Oct 2018 21:53:46 +0200 Subject: [PATCH] Remove resource extending, add separate resource data store Feature #14349 Arvados-DCO-1.1-Signed-off-by: Daniel Kos --- src/models/collection.ts | 2 -- .../collection-panel-files-actions.ts | 2 ++ .../project-panel-middleware-service.ts | 30 +++++------------ .../resources-data/resources-data-actions.ts | 14 ++++++++ .../resources-data/resources-data-reducer.ts | 33 +++++++++++++++++++ src/store/resources-data/resources-data.ts | 8 +++++ src/store/resources/resources.ts | 2 +- src/store/store.ts | 2 ++ .../data-explorer/renderers.tsx | 3 +- .../details-panel/collection-details.tsx | 4 +-- .../details-panel/details-data.tsx | 3 +- .../details-panel/details-panel.tsx | 11 ++++--- .../collection-panel/collection-panel.tsx | 16 +++++---- 13 files changed, 91 insertions(+), 39 deletions(-) create mode 100644 src/store/resources-data/resources-data-actions.ts create mode 100644 src/store/resources-data/resources-data-reducer.ts create mode 100644 src/store/resources-data/resources-data.ts diff --git a/src/models/collection.ts b/src/models/collection.ts index 735ffa83..f8e38f9a 100644 --- a/src/models/collection.ts +++ b/src/models/collection.ts @@ -14,8 +14,6 @@ export interface CollectionResource extends TrashableResource { replicationDesired: number; replicationConfirmed: number; replicationConfirmedAt: string; - fileSize: number; - fileCount: number; } export const getCollectionUrl = (uuid: string) => { 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 99ab6829..9a1d645b 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 @@ -14,6 +14,7 @@ import { filterCollectionFilesBySelection } from './collection-panel-files-state import { startSubmit, stopSubmit, reset } 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"; export const collectionPanelFilesAction = unionize({ SET_COLLECTION_FILES: ofType(), @@ -29,6 +30,7 @@ export const loadCollectionFiles = (uuid: string) => 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 })); }; 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 27efcd03..0da669a2 100644 --- a/src/store/project-panel/project-panel-middleware-service.ts +++ b/src/store/project-panel/project-panel-middleware-service.ts @@ -20,18 +20,17 @@ import { updateFavorites } from "../favorites/favorites-actions"; import { PROJECT_PANEL_CURRENT_UUID, projectPanelActions } from './project-panel-action'; import { Dispatch, MiddlewareAPI } from "redux"; import { ProjectResource } from "~/models/project"; -import { resourcesActions, updateResources } from "~/store/resources/resources-actions"; +import { updateResources } from "~/store/resources/resources-actions"; import { getProperty } from "~/store/properties/properties"; import { snackbarActions, SnackbarKind } from '../snackbar/snackbar-actions'; import { progressIndicatorActions } from '~/store/progress-indicator/progress-indicator-actions.ts'; import { DataExplorer, getDataExplorer } from '../data-explorer/data-explorer-reducer'; import { ListResults } from '~/services/common-service/common-resource-service'; import { loadContainers } from '../processes/processes-actions'; -import { Resource, ResourceKind } from '~/models/resource'; +import { ResourceKind } from '~/models/resource'; import { getResource } from "~/store/resources/resources"; import { CollectionResource } from "~/models/collection"; -import { getNode, getNodeDescendantsIds, TreeNode } from "~/models/tree"; -import { CollectionDirectory, CollectionFile, CollectionFileType } from "~/models/collection-file"; +import { resourcesDataActions } from "~/store/resources-data/resources-data-actions"; export class ProjectPanelMiddlewareService extends DataExplorerMiddlewareService { constructor(private services: ServiceRepository, id: string) { @@ -54,7 +53,7 @@ export class ProjectPanelMiddlewareService extends DataExplorerMiddlewareService const resourceUuids = response.items.map(item => item.uuid); api.dispatch(updateFavorites(resourceUuids)); api.dispatch(updateResources(response.items)); - api.dispatch(updateFilesInfo(resourceUuids)); + api.dispatch(updateResourceData(resourceUuids)); await api.dispatch(loadMissingProcessesInformation(response.items)); api.dispatch(setItems(response)); } catch (e) { @@ -87,28 +86,17 @@ export const loadMissingProcessesInformation = (resources: GroupContentsResource } }; -export const updateFilesInfo = (resourceUuids: string[]) => +export const updateResourceData = (resourceUuids: string[]) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - const resources = await Promise.all(resourceUuids.map(async uuid => { + resourceUuids.map(async uuid => { const resource = getResource(uuid)(getState().resources); if (resource && resource.kind === ResourceKind.COLLECTION) { const files = await services.collectionService.files(uuid); - const flattenFiles: (TreeNode | undefined)[] = getNodeDescendantsIds('')(files).map(id => getNode(id)(files)); - let fileSize = 0; - let fileCount = 0; - if (flattenFiles) { - fileCount = flattenFiles.length; - fileSize = flattenFiles.reduce((acc, f) => { - return acc + (f && f.value.type === CollectionFileType.FILE ? f.value.size : 0); - }, 0); + if (files) { + dispatch(resourcesDataActions.SET_FILES({ uuid, files })); } - - resource.fileCount = fileCount; - resource.fileSize = fileSize; } - return resource; - })); - dispatch(resourcesActions.SET_RESOURCES(resources.filter(res => res) as Resource[])); + }); }; export const setItems = (listResults: ListResults) => diff --git a/src/store/resources-data/resources-data-actions.ts b/src/store/resources-data/resources-data-actions.ts new file mode 100644 index 00000000..660699c3 --- /dev/null +++ b/src/store/resources-data/resources-data-actions.ts @@ -0,0 +1,14 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { unionize } from "src/common/unionize"; +import { ofType, UnionOf } from "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 new file mode 100644 index 00000000..07a3a666 --- /dev/null +++ b/src/store/resources-data/resources-data-reducer.ts @@ -0,0 +1,33 @@ +// 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 new file mode 100644 index 00000000..48c1e2b2 --- /dev/null +++ b/src/store/resources-data/resources-data.ts @@ -0,0 +1,8 @@ +// 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/resources/resources.ts b/src/store/resources/resources.ts index 10c82ffe..e7153dec 100644 --- a/src/store/resources/resources.ts +++ b/src/store/resources/resources.ts @@ -3,7 +3,7 @@ // SPDX-License-Identifier: AGPL-3.0 import { Resource } from "~/models/resource"; -import { ResourceKind } from '../../models/resource'; +import { ResourceKind } from '~/models/resource'; export type ResourcesState = { [key: string]: Resource }; diff --git a/src/store/store.ts b/src/store/store.ts index 7a0d58f8..fa2a5be9 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -42,6 +42,7 @@ 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"; const composeEnhancers = (process.env.NODE_ENV === 'development' && @@ -101,6 +102,7 @@ const createRootReducer = (services: ServiceRepository) => combineReducers({ processLogsPanel: processLogsPanelReducer, properties: propertiesReducer, resources: resourcesReducer, + resourcesData: resourcesDataReducer, router: routerReducer, snackbar: snackbarReducer, treePicker: treePickerReducer, diff --git a/src/views-components/data-explorer/renderers.tsx b/src/views-components/data-explorer/renderers.tsx index e76600ed..6e25508d 100644 --- a/src/views-components/data-explorer/renderers.tsx +++ b/src/views-components/data-explorer/renderers.tsx @@ -20,6 +20,7 @@ import { WorkflowResource } from '~/models/workflow'; import { ResourceStatus } from '~/views/workflow-panel/workflow-panel-view'; import { getUuidPrefix } from '~/store/workflow-panel/workflow-panel-actions'; import { CollectionResource } from "~/models/collection"; +import { getResourceData } from "~/store/resources-data/resources-data"; export const renderName = (item: { name: string; uuid: string, kind: string }) => @@ -150,7 +151,7 @@ export const renderFileSize = (fileSize?: number) => export const ResourceFileSize = connect( (state: RootState, props: { uuid: string }) => { - const resource = getResource(props.uuid)(state.resources); + const resource = getResourceData(props.uuid)(state.resourcesData); return { fileSize: resource ? resource.fileSize : 0 }; })((props: { fileSize?: number }) => renderFileSize(props.fileSize)); diff --git a/src/views-components/details-panel/collection-details.tsx b/src/views-components/details-panel/collection-details.tsx index aec99110..98fa3886 100644 --- a/src/views-components/details-panel/collection-details.tsx +++ b/src/views-components/details-panel/collection-details.tsx @@ -28,8 +28,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 b5ebc36e..45afb02b 100644 --- a/src/views-components/details-panel/details-data.tsx +++ b/src/views-components/details-panel/details-data.tsx @@ -4,9 +4,10 @@ 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) {} + constructor(protected item: T, protected data?: ResourceData) {} getTitle(): string { return this.item.name || 'Projects'; diff --git a/src/views-components/details-panel/details-panel.tsx b/src/views-components/details-panel/details-panel.tsx index f0075558..5e5ccefc 100644 --- a/src/views-components/details-panel/details-panel.tsx +++ b/src/views-components/details-panel/details-panel.tsx @@ -22,6 +22,8 @@ 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"; type CssRules = 'root' | 'container' | 'opened' | 'headerContainer' | 'headerIcon' | 'tabContainer'; @@ -57,13 +59,13 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ }, }); -const getItem = (resource: DetailsResource): DetailsData => { +const getItem = (resource: DetailsResource, resourceData?: ResourceData): DetailsData => { const res = resource || { kind: undefined, name: 'Projects' }; switch (res.kind) { case ResourceKind.PROJECT: return new ProjectDetails(res); case ResourceKind.COLLECTION: - return new CollectionDetails(res); + return new CollectionDetails(res, resourceData); case ResourceKind.PROCESS: return new ProcessDetails(res); default: @@ -71,11 +73,12 @@ const getItem = (resource: DetailsResource): DetailsData => { } }; -const mapStateToProps = ({ detailsPanel, resources }: RootState) => { +const mapStateToProps = ({ detailsPanel, resources, resourcesData }: RootState) => { const resource = getResource(detailsPanel.resourceUuid)(resources) as DetailsResource; + const resourceData = getResourceData(detailsPanel.resourceUuid)(resourcesData); return { isOpened: detailsPanel.isOpened, - item: getItem(resource) + item: getItem(resource, resourceData) }; }; diff --git a/src/views/collection-panel/collection-panel.tsx b/src/views/collection-panel/collection-panel.tsx index 7defc072..42f7787c 100644 --- a/src/views/collection-panel/collection-panel.tsx +++ b/src/views/collection-panel/collection-panel.tsx @@ -23,6 +23,8 @@ 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"; type CssRules = 'card' | 'iconHeader' | 'tag' | 'copyIcon' | 'label' | 'value'; @@ -55,6 +57,7 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ interface CollectionPanelDataProps { item: CollectionResource; + data: ResourceData; } type CollectionPanelProps = CollectionPanelDataProps & DispatchProp @@ -63,14 +66,13 @@ type CollectionPanelProps = CollectionPanelDataProps & DispatchProp export const CollectionPanel = withStyles(styles)( connect((state: RootState, props: RouteComponentProps<{ id: string }>) => { - const collection = getResource(props.match.params.id)(state.resources); - return { - item: collection - }; + 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 } = this.props; + const { classes, item, data } = this.props; return item ? <> @@ -100,9 +102,9 @@ export const CollectionPanel = withStyles(styles)( + label='Number of files' value={data && data.fileCount} /> + label='Content size' value={data && formatFileSize(data.fileSize)} /> -- 2.30.2