From b527533a2d7ffa52b3f999f0ed293b83311fe59f Mon Sep 17 00:00:00 2001 From: Lucas Di Pentima Date: Wed, 11 Nov 2020 18:10:28 -0300 Subject: [PATCH] 15685: Adds file path validation on rename file dialog. Also, allows edition of the complete path instead of just the name, to mnatch workbench1 behavior, enabling the user to move the file to a directory inside the collection. Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima --- .../collection-panel-files-actions.ts | 5 +++-- src/validators/valid-name.tsx | 20 ++++++++++++++++--- src/validators/validators.tsx | 3 ++- .../collection-files-item-action-set.ts | 5 ++++- .../rename-file-dialog/rename-file-dialog.tsx | 10 ++++++---- 5 files changed, 32 insertions(+), 11 deletions(-) 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 19f5a7ee..e77798f8 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 @@ -121,6 +121,7 @@ export const RENAME_FILE_DIALOG = 'renameFileDialog'; export interface RenameFileDialogData { name: string; id: string; + path: string; } export const openRenameFileDialog = (data: RenameFileDialogData) => @@ -129,7 +130,7 @@ export const openRenameFileDialog = (data: RenameFileDialogData) => dispatch(dialogActions.OPEN_DIALOG({ id: RENAME_FILE_DIALOG, data })); }; -export const renameFile = (newName: string) => +export const renameFile = (newFullPath: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { const dialog = getDialog(getState().dialog, RENAME_FILE_DIALOG); const currentCollection = getState().collectionPanel.item; @@ -138,7 +139,7 @@ export const renameFile = (newName: string) => if (file) { dispatch(startSubmit(RENAME_FILE_DIALOG)); const oldPath = getFileFullPath(file); - const newPath = getFileFullPath({ ...file, name: newName }); + const newPath = newFullPath; try { await services.collectionService.moveFile(currentCollection.uuid, oldPath, newPath); dispatch(loadCollectionFiles(currentCollection.uuid)); diff --git a/src/validators/valid-name.tsx b/src/validators/valid-name.tsx index da967123..000e27b0 100644 --- a/src/validators/valid-name.tsx +++ b/src/validators/valid-name.tsx @@ -4,12 +4,12 @@ export const disallowDotName = /^\.{1,2}$/; export const disallowSlash = /\//; - -const ERROR_MESSAGE = "Name cannot be '.' or '..' or contain '/' characters"; +export const disallowLeadingWhitespaces = /^\s+/; +export const disallowTrailingWhitespaces = /\s+$/; export const validName = (value: string) => { return [disallowDotName, disallowSlash].find(aRule => value.match(aRule) !== null) - ? ERROR_MESSAGE + ? "Name cannot be '.' or '..' or contain '/' characters" : undefined; }; @@ -18,3 +18,17 @@ export const validNameAllowSlash = (value: string) => { ? "Name cannot be '.' or '..'" : undefined; }; + +export const validFileName = (value: string) => { + return [ + disallowLeadingWhitespaces, + disallowTrailingWhitespaces + ].find(aRule => value.match(aRule) !== null) + ? `Leading/trailing whitespaces not allowed on '${value}'` + : undefined; +}; + +export const validFilePath = (filePath: string) => { + const errors = filePath.split('/').map(pathPart => validFileName(pathPart)); + return errors.filter(e => e !== undefined)[0]; +}; \ No newline at end of file diff --git a/src/validators/validators.tsx b/src/validators/validators.tsx index d9eca97f..81fed2cc 100644 --- a/src/validators/validators.tsx +++ b/src/validators/validators.tsx @@ -6,7 +6,7 @@ import { require } from './require'; import { maxLength } from './max-length'; import { isRsaKey } from './is-rsa-key'; import { isRemoteHost } from "./is-remote-host"; -import { validName, validNameAllowSlash } from "./valid-name"; +import { validFilePath, validName, validNameAllowSlash } from "./valid-name"; export const TAG_KEY_VALIDATION = [require, maxLength(255)]; export const TAG_VALUE_VALIDATION = [require, maxLength(255)]; @@ -21,6 +21,7 @@ export const COLLECTION_PROJECT_VALIDATION = [require]; export const COPY_NAME_VALIDATION = [require, maxLength(255)]; export const COPY_FILE_VALIDATION = [require]; +export const RENAME_FILE_VALIDATION = [require, validFilePath]; export const MOVE_TO_VALIDATION = [require]; diff --git a/src/views-components/context-menu/action-sets/collection-files-item-action-set.ts b/src/views-components/context-menu/action-sets/collection-files-item-action-set.ts index 6ce62ca9..bfbdec61 100644 --- a/src/views-components/context-menu/action-sets/collection-files-item-action-set.ts +++ b/src/views-components/context-menu/action-sets/collection-files-item-action-set.ts @@ -29,7 +29,10 @@ export const collectionFilesItemActionSet: ContextMenuActionSet = readOnlyCollec name: "Rename", icon: RenameIcon, execute: (dispatch, resource) => { - dispatch(openRenameFileDialog({ name: resource.name, id: resource.uuid })); + dispatch(openRenameFileDialog({ + name: resource.name, + id: resource.uuid, + path: resource.uuid.split('/').slice(1).join('/') })); } }, { diff --git a/src/views-components/rename-file-dialog/rename-file-dialog.tsx b/src/views-components/rename-file-dialog/rename-file-dialog.tsx index d05c110b..9fbf6c9c 100644 --- a/src/views-components/rename-file-dialog/rename-file-dialog.tsx +++ b/src/views-components/rename-file-dialog/rename-file-dialog.tsx @@ -11,16 +11,17 @@ import { DialogContentText } from '@material-ui/core'; import { TextField } from '~/components/text-field/text-field'; import { RENAME_FILE_DIALOG, RenameFileDialogData, renameFile } from '~/store/collection-panel/collection-panel-files/collection-panel-files-actions'; import { WarningCollection } from '~/components/warning-collection/warning-collection'; +import { RENAME_FILE_VALIDATION } from '~/validators/validators'; export const RenameFileDialog = compose( withDialog(RENAME_FILE_DIALOG), reduxForm({ form: RENAME_FILE_DIALOG, - onSubmit: (data: { name: string }, dispatch) => { - dispatch(renameFile(data.name)); + onSubmit: (data: { path: string }, dispatch) => { + dispatch(renameFile(data.path)); } }) -)((props: WithDialogProps & InjectedFormProps<{ name: string }>) => +)((props: WithDialogProps & InjectedFormProps<{ name: string, path: string }>) => ) => {`Please, enter a new name for ${props.data.name}`} ; -- 2.30.2