refs #13967 Merge branch 'origin/13967-collection-files-stringifier' into 13856-uploa...
authorDaniel Kos <daniel.kos@contractors.roche.com>
Mon, 6 Aug 2018 19:44:43 +0000 (21:44 +0200)
committerDaniel Kos <daniel.kos@contractors.roche.com>
Mon, 6 Aug 2018 19:45:11 +0000 (21:45 +0200)
Arvados-DCO-1.1-Signed-off-by: Daniel Kos <daniel.kos@contractors.roche.com>

src/components/collection-panel-files/collection-panel-files.tsx
src/models/collection-file.ts
src/services/collection-files-service/collection-files-service.ts
src/services/collection-files-service/collection-manifest-mapper.test.ts
src/services/collection-files-service/collection-manifest-mapper.ts
src/services/collection-files-service/collection-manifest-parser.test.ts
src/services/collection-files-service/collection-manifest-parser.ts
src/store/collection-panel/collection-panel-files/collection-panel-files-reducer.test.ts

index 17bbe85e0d7f1748ee23ec692d7d501e13e7cafc..afe9e8517cc4027d5e90a4fca4d6007a560ea5d1 100644 (file)
@@ -8,6 +8,10 @@ import { FileTreeData } from '../file-tree/file-tree-data';
 import { FileTree } from '../file-tree/file-tree';
 import { IconButton, Grid, Typography, StyleRulesCallback, withStyles, WithStyles, CardHeader, CardContent, Card, Button } from '@material-ui/core';
 import { CustomizeTableIcon } from '../icon/icon';
+import { connect, DispatchProp } from "react-redux";
+import { Dispatch } from "redux";
+import { RootState } from "../../store/store";
+import { ServiceRepository } from "../../services/services";
 
 export interface CollectionPanelFilesProps {
     items: Array<TreeItem<FileTreeData>>;
@@ -36,14 +40,24 @@ const styles: StyleRulesCallback<CssRules> = theme => ({
     }
 });
 
-export const CollectionPanelFiles = withStyles(styles)(
-    ({ onItemMenuOpen, onOptionsMenuOpen, classes, ...treeProps }: CollectionPanelFilesProps & WithStyles<CssRules>) =>
+const renameFile = () => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+  services.collectionFilesService.renameTest();
+};
+
+
+export const CollectionPanelFiles =
+    connect()(
+    withStyles(styles)(
+    ({ onItemMenuOpen, onOptionsMenuOpen, classes, dispatch, ...treeProps }: CollectionPanelFilesProps & DispatchProp & WithStyles<CssRules>) =>
         <Card className={classes.root}>
             <CardHeader
                 title="Files"
                 action={
-                    <Button 
-                        variant='raised' 
+                    <Button onClick={
+                        () => {
+                            dispatch<any>(renameFile());
+                        }}
+                        variant='raised'
                         color='primary'
                         size='small'>
                         Upload data
@@ -65,4 +79,5 @@ export const CollectionPanelFiles = withStyles(styles)(
                     </Typography>
             </Grid>
             <FileTree onMenuOpen={onItemMenuOpen} {...treeProps} />
-        </Card>);
+        </Card>)
+);
index a1400806c4affb2ed2e5120b6b2f218faf9f4de8..7bf6df74412d54de3d7f013da6f5f9fb8b15dc76 100644 (file)
@@ -12,14 +12,14 @@ export enum CollectionFileType {
 }
 
 export interface CollectionDirectory {
-    parentId: string;
+    path: string;
     id: string;
     name: string;
     type: CollectionFileType.DIRECTORY;
 }
 
 export interface CollectionFile {
-    parentId: string;
+    path: string;
     id: string;
     name: string;
     size: number;
@@ -29,7 +29,7 @@ export interface CollectionFile {
 export const createCollectionDirectory = (data: Partial<CollectionDirectory>): CollectionDirectory => ({
     id: '',
     name: '',
-    parentId: '',
+    path: '',
     type: CollectionFileType.DIRECTORY,
     ...data
 });
@@ -37,7 +37,7 @@ export const createCollectionDirectory = (data: Partial<CollectionDirectory>): C
 export const createCollectionFile = (data: Partial<CollectionFile>): CollectionFile => ({
     id: '',
     name: '',
-    parentId: '',
+    path: '',
     size: 0,
     type: CollectionFileType.FILE,
     ...data
index 96c9e9905820e927efed0e856d8a27cee3e66703..dfeed0b7032de2fec5e7b654345113292b4ce39d 100644 (file)
@@ -3,11 +3,14 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { CollectionService } from "../collection-service/collection-service";
-import { parseKeepManifestText } from "./collection-manifest-parser";
+import { parseKeepManifestText, stringifyKeepManifest } from "./collection-manifest-parser";
 import { mapManifestToCollectionFilesTree } from "./collection-manifest-mapper";
+import { CollectionFile } from "../../models/collection-file";
+import { CommonResourceService } from "../../common/api/common-resource-service";
+import * as _ from "lodash";
 
 export class CollectionFilesService {
-    
+
     constructor(private collectionService: CollectionService) { }
 
     getFiles(collectionUuid: string) {
@@ -22,4 +25,45 @@ export class CollectionFilesService {
             );
     }
 
-}
\ No newline at end of file
+    async renameFile(collectionUuid: string, file: { name: string, path: string }, newName: string) {
+        const collection = await this.collectionService.get(collectionUuid);
+        const manifest = parseKeepManifestText(collection.manifestText);
+        const updatedManifest = manifest.map(
+            stream => stream.name === file.path
+                ? {
+                    ...stream,
+                    files: stream.files.map(
+                        f => f.name === file.name
+                            ? { ...f, name: newName }
+                            : f
+                    )
+                }
+                : stream
+        );
+        const manifestText = stringifyKeepManifest(updatedManifest);
+        const data = { ...collection, manifestText };
+        return this.collectionService.update(collectionUuid, CommonResourceService.mapKeys(_.snakeCase)(data));
+    }
+
+    async deleteFile(collectionUuid: string, file: { name: string, path: string }) {
+        const collection = await this.collectionService.get(collectionUuid);
+        const manifest = parseKeepManifestText(collection.manifestText);
+        const updatedManifest = manifest.map(stream =>
+            stream.name === file.path
+                ? {
+                    ...stream,
+                    files: stream.files.filter(f => f.name !== file.name)
+                }
+                : stream
+        );
+        const manifestText = stringifyKeepManifest(updatedManifest);
+        return this.collectionService.update(collectionUuid, { manifestText });
+    }
+
+    renameTest() {
+        const u = this.renameFile('qr1hi-4zz18-n0sx074erl4p0ph', {
+            name: 'extracted2.txt.png',
+            path: ''
+        }, 'extracted-new.txt.png');
+    }
+}
index ad8f8727eba264747c30325af94be8cd3cad17c7..f08ea7bd1dc6a30e5a51895848efedf211492f9a 100644 (file)
@@ -3,32 +3,32 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { parseKeepManifestText } from "./collection-manifest-parser";
-import { mapManifestToFiles, mapManifestToDirectories } from "./collection-manifest-mapper";
+import { mapManifestToFiles, mapManifestToDirectories, mapManifestToCollectionFilesTree, mapCollectionFilesTreeToManifest } from "./collection-manifest-mapper";
 
 test('mapManifestToFiles', () => {
     const manifestText = `. 930625b054ce894ac40596c3f5a0d947+33 0:0:a 0:0:b 0:33:output.txt\n./c d41d8cd98f00b204e9800998ecf8427e+0 0:0:d`;
     const manifest = parseKeepManifestText(manifestText);
     const files = mapManifestToFiles(manifest);
     expect(files).toEqual([{
-        parentId: '',
+        path: '',
         id: '/a',
         name: 'a',
         size: 0,
         type: 'file'
     }, {
-        parentId: '',
+        path: '',
         id: '/b',
         name: 'b',
         size: 0,
         type: 'file'
     }, {
-        parentId: '',
+        path: '',
         id: '/output.txt',
         name: 'output.txt',
         size: 33,
         type: 'file'
     }, {
-        parentId: '/c',
+        path: '/c',
         id: '/c/d',
         name: 'd',
         size: 0,
@@ -41,19 +41,51 @@ test('mapManifestToDirectories', () => {
     const manifest = parseKeepManifestText(manifestText);
     const directories = mapManifestToDirectories(manifest);
     expect(directories).toEqual([{
-        parentId: "",
+        path: "",
         id: '/c',
         name: 'c',
         type: 'directory'
     }, {
-        parentId: '/c',
+        path: '/c',
         id: '/c/user',
         name: 'user',
         type: 'directory'
     }, {
-        parentId: '/c/user',
+        path: '/c/user',
         id: '/c/user/results',
         name: 'results',
         type: 'directory'
     },]);
+});
+
+test('mapCollectionFilesTreeToManifest', () => {
+    const manifestText = `. 930625b054ce894ac40596c3f5a0d947+33 0:22:test.txt\n./c/user/results 930625b054ce894ac40596c3f5a0d947+33 0:0:a 0:0:b 0:33:output.txt\n`;
+    const tree = mapManifestToCollectionFilesTree(parseKeepManifestText(manifestText));
+    const manifest = mapCollectionFilesTreeToManifest(tree);
+    expect(manifest).toEqual([{
+        name: '',
+        locators: [],
+        files: [{
+            name: 'test.txt',
+            position: '',
+            size: 22
+        },],
+    }, {
+        name: '/c/user/results',
+        locators: [],
+        files: [{
+            name: 'a',
+            position: '',
+            size: 0
+        }, {
+            name: 'b',
+            position: '',
+            size: 0
+        }, {
+            name: 'output.txt',
+            position: '',
+            size: 33
+        },],
+    },]);
+
 });
\ No newline at end of file
index c2a8ae8e138050789cfde7fb6af258699cb8e3dc..860308179c87927ade63dc67aff8386321225b0a 100644 (file)
@@ -2,10 +2,23 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { uniqBy } from 'lodash';
+import { uniqBy, groupBy } from 'lodash';
 import { KeepManifestStream, KeepManifestStreamFile, KeepManifest } from "../../models/keep-manifest";
-import { TreeNode, setNode, createTree } from '../../models/tree';
-import { CollectionFilesTree, CollectionFile, CollectionDirectory, createCollectionDirectory, createCollectionFile } from '../../models/collection-file';
+import { TreeNode, setNode, createTree, getNodeDescendants, getNodeValue } from '../../models/tree';
+import { CollectionFilesTree, CollectionFile, CollectionDirectory, createCollectionDirectory, createCollectionFile, CollectionFileType } from '../../models/collection-file';
+
+export const mapCollectionFilesTreeToManifest = (tree: CollectionFilesTree): KeepManifest => {
+    const values = getNodeDescendants('')(tree).map(id => getNodeValue(id)(tree));
+    const files = values.filter(value => value && value.type === CollectionFileType.FILE) as CollectionFile[];
+    const fileGroups = groupBy(files, file => file.path);
+    return Object
+        .keys(fileGroups)
+        .map(dirName => ({
+            name: dirName,
+            locators: [],
+            files: fileGroups[dirName].map(mapCollectionFile)
+        }));
+};
 
 export const mapManifestToCollectionFilesTree = (manifest: KeepManifest): CollectionFilesTree =>
     manifestToCollectionFiles(manifest)
@@ -16,7 +29,7 @@ export const mapManifestToCollectionFilesTree = (manifest: KeepManifest): Collec
 export const mapCollectionFileToTreeNode = (file: CollectionFile): TreeNode<CollectionFile> => ({
     children: [],
     id: file.id,
-    parent: file.parentId,
+    parent: file.path,
     value: file
 });
 
@@ -47,7 +60,7 @@ const splitDirectory = (directory: CollectionDirectory): CollectionDirectory[] =
 
 const mapPathComponentToDirectory = (component: string, index: number, components: string[]): CollectionDirectory =>
     createCollectionDirectory({
-        parentId: index === 0 ? '' : joinPathComponents(components, index),
+        path: index === 0 ? '' : joinPathComponents(components, index),
         id: joinPathComponents(components, index + 1),
         name: component,
     });
@@ -55,9 +68,15 @@ const mapPathComponentToDirectory = (component: string, index: number, component
 const joinPathComponents = (components: string[], index: number) =>
     `/${components.slice(0, index).join('/')}`;
 
+const mapCollectionFile = (file: CollectionFile): KeepManifestStreamFile => ({
+    name: file.name,
+    position: '',
+    size: file.size
+});
+
 const mapStreamDirectory = (stream: KeepManifestStream): CollectionDirectory =>
     createCollectionDirectory({
-        parentId: '',
+        path: '',
         id: stream.name,
         name: stream.name,
     });
@@ -65,7 +84,7 @@ const mapStreamDirectory = (stream: KeepManifestStream): CollectionDirectory =>
 const mapStreamFile = (stream: KeepManifestStream) =>
     (file: KeepManifestStreamFile): CollectionFile =>
         createCollectionFile({
-            parentId: stream.name,
+            path: stream.name,
             id: `${stream.name}/${file.name}`,
             name: file.name,
             size: file.size,
index eddc9c622607f86c31a2f48f5422a278f31e0fc1..09525d860cdc38e552cc4d377297ac5d3e2b201b 100644 (file)
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { parseKeepManifestText, parseKeepManifestStream } from "./collection-manifest-parser";
+import { parseKeepManifestText, parseKeepManifestStream, stringifyKeepManifest } from "./collection-manifest-parser";
 
 describe('parseKeepManifestText', () => {
     it('should parse text into streams', () => {
@@ -26,9 +26,15 @@ describe('parseKeepManifestStream', () => {
     });
     it('should parse stream files', () => {
         expect(stream.files).toEqual([
-            {name: 'a', position: '0', size: 0},
-            {name: 'b', position: '0', size: 0},
-            {name: 'output.txt', position: '0', size: 33},
+            { name: 'a', position: '0', size: 0 },
+            { name: 'b', position: '0', size: 0 },
+            { name: 'output.txt', position: '0', size: 33 },
         ]);
     });
+});
+
+test('stringifyKeepManifest', () => {
+    const manifestText = `. 930625b054ce894ac40596c3f5a0d947+33 0:22:test.txt\n./c/user/results 930625b054ce894ac40596c3f5a0d947+33 0:0:a 0:0:b 0:33:output.txt\n`;
+    const manifest = parseKeepManifestText(manifestText);
+    expect(stringifyKeepManifest(manifest)).toEqual(`. 930625b054ce894ac40596c3f5a0d947+33 0:22:test.txt\n./c/user/results 930625b054ce894ac40596c3f5a0d947+33 0:0:a 0:0:b 0:33:output.txt\n`);
 });
\ No newline at end of file
index df334d49fe846c09734b8b7310161ed86ebd3c66..bed26783ef741f0ddcc1106ee313b770ffcf3354 100644 (file)
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { KeepManifestStream, KeepManifestStreamFile } from "../../models/keep-manifest";
+import { KeepManifestStream, KeepManifestStreamFile, KeepManifest } from "../../models/keep-manifest";
 
 /**
  * Documentation [http://doc.arvados.org/api/storage.html](http://doc.arvados.org/api/storage.html)
@@ -25,6 +25,12 @@ export const parseKeepManifestStream = (stream: string): KeepManifestStream => {
     };
 };
 
+export const stringifyKeepManifest = (manifest: KeepManifest) =>
+    manifest.map(stringifyKeepManifestStream).join('');
+
+export const stringifyKeepManifestStream = (stream: KeepManifestStream) =>
+    `.${stream.name} ${stream.locators.join(' ')} ${stream.files.map(stringifyFile).join(' ')}\n`;
+
 const FILE_LOCATOR_REGEXP = /^([0-9a-f]{32})\+([0-9]+)(\+[A-Z][-A-Za-z0-9@_]*)*$/;
 
 const FILE_REGEXP = /([0-9]+):([0-9]+):(.*)/;
@@ -43,4 +49,7 @@ const parseFile = (token: string): KeepManifestStreamFile => {
     const match = FILE_REGEXP.exec(token);
     const [position, size, name] = match!.slice(1);
     return { name, position, size: parseInt(size, 10) };
-};
\ No newline at end of file
+};
+
+const stringifyFile = (file: KeepManifestStreamFile) =>
+    `${file.position}:${file.size}:${file.name}`;
\ No newline at end of file
index 1a6bb7d7964447873e389f8e51567bd1c4f83e42..94b71ffb249d729751b9b8e9486d9ce19ca75b8f 100644 (file)
@@ -11,21 +11,21 @@ import { CollectionPanelFile, CollectionPanelDirectory } from "./collection-pane
 describe('CollectionPanelFilesReducer', () => {
 
     const files: Array<CollectionFile | CollectionDirectory> = [
-        createCollectionDirectory({ id: 'Directory 1', name: 'Directory 1', parentId: '' }),
-        createCollectionDirectory({ id: 'Directory 2', name: 'Directory 2', parentId: 'Directory 1' }),
-        createCollectionDirectory({ id: 'Directory 3', name: 'Directory 3', parentId: '' }),
-        createCollectionDirectory({ id: 'Directory 4', name: 'Directory 4', parentId: 'Directory 3' }),
-        createCollectionFile({ id: 'file1.txt', name: 'file1.txt', parentId: 'Directory 2' }),
-        createCollectionFile({ id: 'file2.txt', name: 'file2.txt', parentId: 'Directory 2' }),
-        createCollectionFile({ id: 'file3.txt', name: 'file3.txt', parentId: 'Directory 3' }),
-        createCollectionFile({ id: 'file4.txt', name: 'file4.txt', parentId: 'Directory 3' }),
-        createCollectionFile({ id: 'file5.txt', name: 'file5.txt', parentId: 'Directory 4' }),
+        createCollectionDirectory({ id: 'Directory 1', name: 'Directory 1', path: '' }),
+        createCollectionDirectory({ id: 'Directory 2', name: 'Directory 2', path: 'Directory 1' }),
+        createCollectionDirectory({ id: 'Directory 3', name: 'Directory 3', path: '' }),
+        createCollectionDirectory({ id: 'Directory 4', name: 'Directory 4', path: 'Directory 3' }),
+        createCollectionFile({ id: 'file1.txt', name: 'file1.txt', path: 'Directory 2' }),
+        createCollectionFile({ id: 'file2.txt', name: 'file2.txt', path: 'Directory 2' }),
+        createCollectionFile({ id: 'file3.txt', name: 'file3.txt', path: 'Directory 3' }),
+        createCollectionFile({ id: 'file4.txt', name: 'file4.txt', path: 'Directory 3' }),
+        createCollectionFile({ id: 'file5.txt', name: 'file5.txt', path: 'Directory 4' }),
     ];
 
     const collectionFilesTree = files.reduce((tree, file) => setNode({
         children: [],
         id: file.id,
-        parent: file.parentId,
+        parent: file.path,
         value: file
     })(tree), createTree<CollectionFile | CollectionDirectory>());
 
@@ -35,7 +35,7 @@ describe('CollectionPanelFilesReducer', () => {
 
     it('SET_COLLECTION_FILES', () => {
         expect(getNodeValue('Directory 1')(collectionPanelFilesTree)).toEqual({
-            ...createCollectionDirectory({ id: 'Directory 1', name: 'Directory 1', parentId: '' }),
+            ...createCollectionDirectory({ id: 'Directory 1', name: 'Directory 1', path: '' }),
             collapsed: true,
             selected: false
         });