15685: Adds file path validation on rename file dialog.
authorLucas Di Pentima <lucas@di-pentima.com.ar>
Wed, 11 Nov 2020 21:10:28 +0000 (18:10 -0300)
committerLucas Di Pentima <lucas@di-pentima.com.ar>
Wed, 11 Nov 2020 21:10:28 +0000 (18:10 -0300)
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 <lucas@di-pentima.com.ar>

src/store/collection-panel/collection-panel-files/collection-panel-files-actions.ts
src/validators/valid-name.tsx
src/validators/validators.tsx
src/views-components/context-menu/action-sets/collection-files-item-action-set.ts
src/views-components/rename-file-dialog/rename-file-dialog.tsx

index 19f5a7eeffcfe4b83d30e55b0b22a972c32bbeda..e77798f8a16b0a3eadf2bae3b7cf05b4a6117f47 100644 (file)
@@ -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<RenameFileDialogData>(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<any>(loadCollectionFiles(currentCollection.uuid));
index da96712398e278f6a45de0cd57a6395a7691ad7b..000e27b004b6aa9922eff5de1d6f74d460dc1657 100644 (file)
@@ -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
index d9eca97f4c2a85b54a424497f3bce836e1380112..81fed2ccccdb4220822a342a38eb3af10a38a735 100644 (file)
@@ -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];
 
index 6ce62ca942c55e738f79b2b47a295bd7cc220d1a..bfbdec610e23eeab39fa094b7a198666573036d1 100644 (file)
@@ -29,7 +29,10 @@ export const collectionFilesItemActionSet: ContextMenuActionSet = readOnlyCollec
         name: "Rename",
         icon: RenameIcon,
         execute: (dispatch, resource) => {
-            dispatch<any>(openRenameFileDialog({ name: resource.name, id: resource.uuid }));
+            dispatch<any>(openRenameFileDialog({
+                name: resource.name,
+                id: resource.uuid,
+                path: resource.uuid.split('/').slice(1).join('/') }));
         }
     },
     {
index d05c110b7b3971e44e0aed7d700182e96d9b7fcb..9fbf6c9c8347c78fee27e263e7e0b2e37d1a104e 100644 (file)
@@ -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<any>(renameFile(data.name));
+        onSubmit: (data: { path: string }, dispatch) => {
+            dispatch<any>(renameFile(data.path));
         }
     })
-)((props: WithDialogProps<RenameFileDialogData> & InjectedFormProps<{ name: string }>) =>
+)((props: WithDialogProps<RenameFileDialogData> & InjectedFormProps<{ name: string, path: string }>) =>
     <FormDialog
         dialogTitle='Rename'
         formFields={RenameDialogFormFields}
@@ -33,9 +34,10 @@ const RenameDialogFormFields = (props: WithDialogProps<RenameFileDialogData>) =>
         {`Please, enter a new name for ${props.data.name}`}
     </DialogContentText>
     <Field
-        name='name'
+        name='path'
         component={TextField}
         autoFocus={true}
+        validate={RENAME_FILE_VALIDATION}
     />
     <WarningCollection text="Renaming a file will change the collection's content address." />
 </>;