From 4c34de655fc7f8839b205b48f460a168f302dd63 Mon Sep 17 00:00:00 2001 From: Stephen Smith Date: Thu, 21 Sep 2023 15:02:10 -0400 Subject: [PATCH] 20225: Add subdirectory selection support to directory input Converts getFileOperationLocation into a dispatchable helper so it can access the store to augment FileOperationLocations with a PDH Converts DirectoryTreePickerField into a connected class component in order to use getFileOperationLocation Change DirectoryInputComponent to include directories in the picker and use getFileOperationLocation to pass FileOperationLocation instead of Collection objects so that the subpath within the collection can be embedded in the Directory object Arvados-DCO-1.1-Signed-off-by: Stephen Smith --- .../collection-partial-copy-actions.ts | 8 ++- .../collection-partial-move-actions.ts | 2 +- src/store/tree-picker/tree-picker-actions.ts | 46 ++++++++------ .../form-fields/collection-form-fields.tsx | 2 +- .../tree-picker-field.tsx | 60 ++++++++++++------- .../inputs/directory-input.tsx | 38 +++++++----- 6 files changed, 99 insertions(+), 57 deletions(-) diff --git a/src/store/collections/collection-partial-copy-actions.ts b/src/store/collections/collection-partial-copy-actions.ts index 4b3af2a2..a0933c64 100644 --- a/src/store/collections/collection-partial-copy-actions.ts +++ b/src/store/collections/collection-partial-copy-actions.ts @@ -156,7 +156,13 @@ export const copyCollectionPartialToExistingCollection = (fileSelection: Collect dispatch(progressIndicatorActions.START_WORKING(COLLECTION_PARTIAL_COPY_TO_SELECTED_COLLECTION)); // Copy files - const updatedCollection = await services.collectionService.copyFiles(fileSelection.collection.portableDataHash, fileSelection.selectedPaths, {uuid: formData.destination.uuid}, formData.destination.path || '/', false); + const updatedCollection = await services.collectionService.copyFiles( + fileSelection.collection.portableDataHash, + fileSelection.selectedPaths, + {uuid: formData.destination.uuid}, + formData.destination.subpath || '/', + false + ); dispatch(updateResources([updatedCollection])); dispatch(dialogActions.CLOSE_DIALOG({ id: COLLECTION_PARTIAL_COPY_TO_SELECTED_COLLECTION })); diff --git a/src/store/collections/collection-partial-move-actions.ts b/src/store/collections/collection-partial-move-actions.ts index 92e20981..56f7302d 100644 --- a/src/store/collections/collection-partial-move-actions.ts +++ b/src/store/collections/collection-partial-move-actions.ts @@ -158,7 +158,7 @@ export const moveCollectionPartialToExistingCollection = (fileSelection: Collect fileSelection.collection.portableDataHash, fileSelection.selectedPaths, {uuid: formData.destination.uuid}, - formData.destination.path || '/', false + formData.destination.subpath || '/', false ); dispatch(updateResources([updatedCollection])); diff --git a/src/store/tree-picker/tree-picker-actions.ts b/src/store/tree-picker/tree-picker-actions.ts index 72d1cb65..18385e31 100644 --- a/src/store/tree-picker/tree-picker-actions.ts +++ b/src/store/tree-picker/tree-picker-actions.ts @@ -23,6 +23,8 @@ import { mapTreeValues } from "models/tree"; import { sortFilesTree } from "services/collection-service/collection-service-files-response"; import { GroupClass, GroupResource } from "models/group"; import { CollectionResource } from "models/collection"; +import { getResource } from "store/resources/resources"; +import { updateResources } from "store/resources/resources-actions"; export const treePickerActions = unionize({ LOAD_TREE_PICKER_NODE: ofType<{ id: string, pickerId: string }>(), @@ -167,6 +169,7 @@ export const loadProject = (params: LoadProjectParamsWithId) => const itemLimit = 200; const { items, itemsAvailable } = await services.groupsService.contents((loadShared || searchProjects) ? '' : id, { filters, excludeHomeProject: loadShared || undefined, limit: itemLimit }); + dispatch(updateResources(items)); if (itemsAvailable > itemLimit) { items.push({ @@ -523,29 +526,38 @@ const buildParams = (ownerUuid: string) => { * if the item represents a valid target/destination location */ export type FileOperationLocation = { + name: string; uuid: string; - path: string; + pdh?: string; + subpath: string; } -export const getFileOperationLocation = (item: ProjectsTreePickerItem): FileOperationLocation | undefined => { - if ('kind' in item && item.kind === ResourceKind.COLLECTION) { - return { - uuid: item.uuid, - path: '/' - }; - } else if ('type' in item && item.type === CollectionFileType.DIRECTORY) { - const uuid = getCollectionResourceCollectionUuid(item.id); - if (uuid) { +export const getFileOperationLocation = (item: ProjectsTreePickerItem) => + async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise => { + if ('kind' in item && item.kind === ResourceKind.COLLECTION) { return { - uuid, - path: [item.path, item.name].join('/') + name: item.name, + uuid: item.uuid, + pdh: item.portableDataHash, + subpath: '/', }; - } else { - return undefined; + } else if ('type' in item && item.type === CollectionFileType.DIRECTORY) { + const uuid = getCollectionResourceCollectionUuid(item.id); + if (uuid) { + const collection = getResource(uuid)(getState().resources); + if (collection) { + const itemPath = [item.path, item.name].join('/'); + + return { + name: item.name, + uuid, + pdh: collection.portableDataHash, + subpath: itemPath, + }; + } + } } - } else { return undefined; - } -}; + }; /** * Create an expanded tree picker subtree from array of nested projects/collection diff --git a/src/views-components/form-fields/collection-form-fields.tsx b/src/views-components/form-fields/collection-form-fields.tsx index 23a44965..7d5fcf80 100644 --- a/src/views-components/form-fields/collection-form-fields.tsx +++ b/src/views-components/form-fields/collection-form-fields.tsx @@ -65,7 +65,7 @@ export const DirectoryPickerField = (props: PickerIdProp) => ; interface StorageClassesProps { diff --git a/src/views-components/projects-tree-picker/tree-picker-field.tsx b/src/views-components/projects-tree-picker/tree-picker-field.tsx index 17417bf5..793eeaa3 100644 --- a/src/views-components/projects-tree-picker/tree-picker-field.tsx +++ b/src/views-components/projects-tree-picker/tree-picker-field.tsx @@ -9,7 +9,9 @@ import { WrappedFieldProps } from 'redux-form'; import { ProjectsTreePicker } from 'views-components/projects-tree-picker/projects-tree-picker'; import { ProjectsTreePickerItem } from 'store/tree-picker/tree-picker-middleware'; import { PickerIdProp } from 'store/tree-picker/picker-id'; -import { getFileOperationLocation } from "store/tree-picker/tree-picker-actions"; +import { FileOperationLocation, getFileOperationLocation } from "store/tree-picker/tree-picker-actions"; +import { connect } from "react-redux"; +import { Dispatch } from "redux"; export const ProjectTreePickerField = (props: WrappedFieldProps & PickerIdProp) =>
@@ -44,24 +46,40 @@ export const CollectionTreePickerField = (props: WrappedFieldProps & PickerIdPro
; -const handleDirectoryChange = (props: WrappedFieldProps) => - (_: any, { data }: TreeItem) => { - props.input.onChange(getFileOperationLocation(data) || ''); - } +type ProjectsTreePickerActionProps = { + getFileOperationLocation: (item: ProjectsTreePickerItem) => Promise; +} -export const DirectoryTreePickerField = (props: WrappedFieldProps & PickerIdProp) => -
-
- - {props.meta.dirty && props.meta.error && - - {props.meta.error} - } -
-
; +const projectsTreePickerMapDispatchToProps = (dispatch: Dispatch): ProjectsTreePickerActionProps => ({ + getFileOperationLocation: (item: ProjectsTreePickerItem) => dispatch(getFileOperationLocation(item)), +}); + +type ProjectsTreePickerCombinedProps = ProjectsTreePickerActionProps & WrappedFieldProps & PickerIdProp; + +export const DirectoryTreePickerField = connect(null, projectsTreePickerMapDispatchToProps)( + class DirectoryTreePickerFieldComponent extends React.Component { + + handleDirectoryChange = (props: WrappedFieldProps) => + async (_: any, { data }: TreeItem) => { + const location = await this.props.getFileOperationLocation(data); + props.input.onChange(location || ''); + } + + render() { + return
+
+ + {this.props.meta.dirty && this.props.meta.error && + + {this.props.meta.error} + } +
+
; + } + }); diff --git a/src/views/run-process-panel/inputs/directory-input.tsx b/src/views/run-process-panel/inputs/directory-input.tsx index 5348cc2b..bd9dc67e 100644 --- a/src/views/run-process-panel/inputs/directory-input.tsx +++ b/src/views/run-process-panel/inputs/directory-input.tsx @@ -15,12 +15,11 @@ import { } from 'models/workflow'; import { GenericInputProps, GenericInput } from './generic-input'; import { ProjectsTreePicker } from 'views-components/projects-tree-picker/projects-tree-picker'; -import { initProjectsTreePicker } from 'store/tree-picker/tree-picker-actions'; +import { FileOperationLocation, getFileOperationLocation, initProjectsTreePicker } from 'store/tree-picker/tree-picker-actions'; import { TreeItem } from 'components/tree/tree'; import { ProjectsTreePickerItem } from 'store/tree-picker/tree-picker-middleware'; -import { CollectionResource } from 'models/collection'; -import { ResourceKind } from 'models/resource'; import { ERROR_MESSAGE } from 'validators/require'; +import { Dispatch } from 'redux'; export interface DirectoryInputProps { input: DirectoryCommandInputParameter; @@ -43,9 +42,9 @@ export const DirectoryInput = ({ input, options }: DirectoryInputProps) => const format = (value?: Directory) => value ? value.basename : ''; -const parse = (directory: CollectionResource): Directory => ({ +const parse = (directory: FileOperationLocation): Directory => ({ class: CWLType.DIRECTORY, - location: `keep:${directory.portableDataHash}`, + location: `keep:${directory.pdh}${directory.subpath}`, basename: directory.name, }); @@ -59,11 +58,21 @@ const getValidation = memoize( interface DirectoryInputComponentState { open: boolean; - directory?: CollectionResource; + directory?: FileOperationLocation; } -const DirectoryInputComponent = connect()( - class FileInputComponent extends React.Component void; + getFileOperationLocation: (item: ProjectsTreePickerItem) => Promise; +} + +const mapDispatchToProps = (dispatch: Dispatch): DirectoryInputActionProps => ({ + initProjectsTreePicker: (pickerId: string) => dispatch(initProjectsTreePicker(pickerId)), + getFileOperationLocation: (item: ProjectsTreePickerItem) => dispatch(getFileOperationLocation(item)), +}); + +const DirectoryInputComponent = connect(null, mapDispatchToProps)( + class FileInputComponent extends React.Component { state: DirectoryInputComponentState = { @@ -71,8 +80,7 @@ const DirectoryInputComponent = connect()( }; componentDidMount() { - this.props.dispatch( - initProjectsTreePicker(this.props.commandInput.id)); + this.props.initProjectsTreePicker(this.props.commandInput.id); } render() { @@ -95,12 +103,9 @@ const DirectoryInputComponent = connect()( this.props.input.onChange(this.state.directory); } - setDirectory = (_: {}, { data }: TreeItem) => { - if ('kind' in data && data.kind === ResourceKind.COLLECTION) { - this.setState({ directory: data }); - } else { - this.setState({ directory: undefined }); - } + setDirectory = async (_: {}, { data: item }: TreeItem) => { + const location = await this.props.getFileOperationLocation(item); + this.setState({ directory: location }); } renderInput() { @@ -143,6 +148,7 @@ const DirectoryInputComponent = connect()( -- 2.30.2