[ARVADOS-WORKBENCH2] created: 2.1.0-356-g43c92c8e

Git user git at public.arvados.org
Tue May 4 21:25:23 UTC 2021


        at  43c92c8e2518642fa1033d66a0f72fcb026c0308 (commit)


commit 43c92c8e2518642fa1033d66a0f72fcb026c0308
Author: Daniel Kutyła <daniel.kutyla at contractors.roche.com>
Date:   Tue May 4 23:18:41 2021 +0200

    17595: Fixed multiselect, added tests
    
    Arvados-DCO-1.1-Signed-off-by: Daniel Kutyła <daniel.kutyla at contractors.roche.com>

diff --git a/cypress/fixtures/workflow_with_array_fields.yaml b/cypress/fixtures/workflow_with_array_fields.yaml
new file mode 100644
index 00000000..c5d5f66f
--- /dev/null
+++ b/cypress/fixtures/workflow_with_array_fields.yaml
@@ -0,0 +1,122 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
+---
+"$graph":
+- "$namespaces":
+    arv: http://arvados.org/cwl#
+  class: ExpressionTool
+  doc: get the gct and vcf files, add other files
+  expression: |
+    $\{
+      var fileArray = [];
+
+      // get files from collection
+      for (var j = 0; j < inputs.collectionArray.length; j++) {
+        for (var i = 0; i < inputs.collectionArray[j].listing.length; i++) {
+          var matchedName = inputs.collectionArray[j].listing[i].basename.match(/^(.+)(.gct|.vcf|.vcf.gz|.gct.tsv|.vcf.tsv)$/);
+          if (matchedName) {
+            var nameString = inputs.collectionArray[j].listing[i].basename.split(".")[0]
+            fileArray.push(inputs.collectionArray[j].listing[i])
+          }
+        }
+      }
+
+      // get any other files from the input file array
+      for (var i = 0; i < inputs.additionalFileArray.length; i++) {
+        fileArray.push(inputs.additionalFileArray[i])
+      }
+
+
+      return {
+        "collectedArray": fileArray,
+        "nameString": nameString
+      }
+
+    }
+  id: "#collectFiles.cwl"
+  inputs:
+  - id: "#collectFiles.cwl/additionalFileArray"
+    type:
+      items: File
+      type: array
+  - id: "#collectFiles.cwl/collectionArray"
+    type:
+      items: Directory
+      type: array
+  outputs:
+  - id: "#collectFiles.cwl/collectedArray"
+    type:
+      items: File
+      type: array
+  - id: "#collectFiles.cwl/nameString"
+    type: string
+  requirements:
+  - class: InlineJavascriptRequirement
+- class: Workflow
+  doc: A workflow to collect .gct and .vcf files files and store them together with
+    specified metainformation files. Mostly used to create a collection for genestack
+    upload. Can only be run on the WB2.
+  hints:
+  - class: ResourceRequirement
+    coresMin: 1
+    ramMin: 85000
+  id: "#main"
+  inputs:
+  - default:
+    - basename: metainfo.txt
+      class: File
+      location: keep:1cd1dbc27dc10fa8aaaf8fa19efd3bb8+237/metainfo.txt
+      nameext: ".txt"
+      nameroot: metainfo
+      size: 0
+    - basename: test11.txt
+      class: File
+      location: keep:1cd1dbc27dc10fa8aaaf8fa19efd3bb8+237/test11.txt
+      nameext: ".txt"
+      nameroot: test11
+      size: 0
+    doc: This input allows you to add any number of additional files to be integrated
+      in the output collection.
+    id: "#main/additionalFileArray"
+    label: Files
+    type:
+      items: File
+      type: array
+  - default:
+    - basename: collection1
+      class: Directory
+      location: keep:1cd1dbc27dc10fa8aaaf8fa19efd3bb8+237/collection1
+    - basename: collection2
+      class: Directory
+      location: keep:1cd1dbc27dc10fa8aaaf8fa19efd3bb8+237/collection2
+    doc: This input allows you to specify collections to integrate. From these collections,
+      all files that end with .gct or .vcf will be extracted.
+    id: "#main/collectionArray"
+    label: Collections
+    type:
+      items: Directory
+      type: array
+  outputs:
+  - id: "#main/genestackArray"
+    outputSource: "#main/collectFiles/collectedArray"
+    type:
+      items: File
+      type: array
+  requirements:
+  - class: SubworkflowFeatureRequirement
+  - class: ScatterFeatureRequirement
+  - class: StepInputExpressionRequirement
+  - class: InlineJavascriptRequirement
+  steps:
+  - id: "#main/collectFiles"
+    in:
+    - id: "#main/collectFiles/additionalFileArray"
+      source: "#main/additionalFileArray"
+    - id: "#main/collectFiles/collectionArray"
+      source: "#main/collectionArray"
+    out:
+    - "#main/collectFiles/collectedArray"
+    run: "#collectFiles.cwl"
+cwlVersion: v1.0
\ No newline at end of file
diff --git a/cypress/integration/favorites.spec.js b/cypress/integration/favorites.spec.js
index 22514beb..e48adebf 100644
--- a/cypress/integration/favorites.spec.js
+++ b/cypress/integration/favorites.spec.js
@@ -204,4 +204,101 @@ describe('Favorites tests', function () {
                     });
             });
     });
+
+    it.only('can select multi files when creating workflow', () => {
+        cy.createProject({
+            owningUser: activeUser,
+            projectName: 'myProject1',
+            addToFavorites: true
+        });
+
+        cy.createCollection(adminUser.token, {
+            name: `Test collection ${Math.floor(Math.random() * 999999)}`,
+            owner_uuid: activeUser.user.uuid,
+            manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n. 37b51d194a7513e45b56f6524f2d51f2+3 0:3:baz\n"
+        })
+            .as('testCollection');
+
+        cy.createCollection(adminUser.token, {
+            name: `Test collection ${Math.floor(Math.random() * 999999)}`,
+            owner_uuid: activeUser.user.uuid,
+            manifest_text: `. 37b51d194a7513e45b56f6524f2d51f2+3 0:3:buz\n`
+        })
+            .as('testCollection2');
+
+        cy.getAll('@myProject1', '@testCollection', '@testCollection2')
+            .then(function ([myProject1, testCollection, testCollection2]) {
+                cy.readFile('cypress/fixtures/workflow_with_array_fields.yaml').then(workflow => {
+                    cy.createWorkflow(adminUser.token, {
+                        name: `TestWorkflow${Math.floor(Math.random() * 999999)}.cwl`,
+                        definition: workflow,
+                        owner_uuid: myProject1.uuid,
+                    })
+                        .as('testWorkflow');
+                });
+
+                cy.loginAs(activeUser);
+
+                cy.get('main').contains(myProject1.name).click();
+
+                cy.get('[data-cy=side-panel-button]').click();
+
+                cy.get('#aside-menu-list').contains('Run a process').click();
+
+                cy.get('@testWorkflow')
+                    .then((testWorkflow) => {
+                        cy.get('main').contains(testWorkflow.name).click();
+                        cy.get('[data-cy=run-process-next-button]').click();
+
+                        cy.get('label').contains('Files').parent('div').find('input').click();
+                        cy.get('div[role=dialog]')
+                            .within(() => {
+                                cy.get('p').contains('Projects').closest('div[role=button]')
+                                    .within(() => {
+                                        cy.get('svg[role=presentation]')
+                                            .click({ multiple: true });
+                                    });
+
+                                cy.get(`[data-id=${testCollection.uuid}]`)
+                                    .find('i').click();
+
+                                cy.contains('bar').closest('[data-action=TOGGLE_ACTIVE]').parent().find('input[type=checkbox]').click();
+
+                                cy.contains('baz').closest('[data-action=TOGGLE_ACTIVE]').parent().find('input[type=checkbox]').click();
+
+                                cy.get('[data-cy=ok-button]').click();
+                            });
+
+                        cy.get('label').contains('Collections').parent('div').find('input').click();
+                        cy.get('div[role=dialog]')
+                            .within(() => {
+                                cy.get('p').contains('Projects').closest('div[role=button]')
+                                    .within(() => {
+                                        cy.get('svg[role=presentation]')
+                                            .click({ multiple: true });
+                                    });
+
+                                cy.get(`[data-id=${testCollection.uuid}]`)
+                                    .find('input[type=checkbox]').click();
+
+                                cy.get(`[data-id=${testCollection2.uuid}]`)
+                                    .find('input[type=checkbox]').click();
+
+                                cy.get('[data-cy=ok-button]').click();
+                            });
+                    });
+
+                cy.get('label').contains('Files').parent('div')
+                    .within(() => {
+                        cy.contains('baz');
+                        cy.contains('bar');
+                    });
+
+                cy.get('label').contains('Collections').parent('div')
+                    .within(() => {
+                        cy.contains(testCollection.name);
+                        cy.contains(testCollection2.name);
+                    });
+            });
+    });
 });
\ No newline at end of file
diff --git a/src/components/tree/tree.tsx b/src/components/tree/tree.tsx
index cf4d708d..e84e0f3a 100644
--- a/src/components/tree/tree.tsx
+++ b/src/components/tree/tree.tsx
@@ -155,6 +155,9 @@ interface FlatTreeProps {
     getProperArrowAnimation: Function;
     itemsMap?: Map<string, TreeItem<any>>;
     classes: any;
+    showSelection: any;
+    useRadioButtons?: boolean;
+    handleCheckboxChange: Function;
 }
 
 const FLAT_TREE_ACTIONS = {
@@ -163,7 +166,7 @@ const FLAT_TREE_ACTIONS = {
     toggleActive: 'TOGGLE_ACTIVE',
 };
 
-const ItemIcon = React.memo(({type, kind, active, groupClass, classes}: any) => {
+const ItemIcon = React.memo(({ type, kind, active, groupClass, classes }: any) => {
     let Icon = ProjectIcon;
 
     if (groupClass === GroupClass.FILTER) {
@@ -184,7 +187,7 @@ const ItemIcon = React.memo(({type, kind, active, groupClass, classes}: any) =>
     }
 
     if (kind) {
-        switch(kind) {
+        switch (kind) {
             case ResourceKind.COLLECTION:
                 Icon = CollectionIcon;
                 break;
@@ -231,6 +234,17 @@ const FlatTree = (props: FlatTreeProps) =>
                             {props.getProperArrowAnimation(item.status, item.items!)}
                         </ListItemIcon>
                     </i>
+                    {props.showSelection(item) && !props.useRadioButtons &&
+                        <Checkbox
+                            checked={item.selected}
+                            className={props.classes.checkbox}
+                            color="primary"
+                            onClick={props.handleCheckboxChange(item)} />}
+                    {props.showSelection(item) && props.useRadioButtons &&
+                        <Radio
+                            checked={item.selected}
+                            className={props.classes.checkbox}
+                            color="primary" />}
                     <div data-action={FLAT_TREE_ACTIONS.toggleActive} className={props.classes.renderContainer}>
                         <span style={{ display: 'flex', alignItems: 'center' }}>
                             <ItemIcon type={item.data.type} active={item.active} kind={item.data.kind} groupClass={item.data.kind === ResourceKind.GROUP ? item.data.groupClass : ''} classes={props.classes} />
@@ -293,29 +307,32 @@ export const Tree = withStyles(styles)(
                         {
                             it.open && it.items && it.items.length > 0 &&
                                 it.flatTree ?
-                                    <FlatTree
-                                        it={it}
-                                        itemsMap={itemsMap}
-                                        classes={this.props.classes}
-                                        levelIndentation={levelIndentation}
+                                <FlatTree
+                                    it={it}
+                                    itemsMap={itemsMap}
+                                    showSelection={showSelection}
+                                    classes={this.props.classes}
+                                    useRadioButtons={useRadioButtons}
+                                    levelIndentation={levelIndentation}
+                                    handleCheckboxChange={this.handleCheckboxChange}
+                                    onContextMenu={this.props.onContextMenu}
+                                    handleToggleItemOpen={this.handleToggleItemOpen}
+                                    toggleItemActive={this.props.toggleItemActive}
+                                    getToggableIconClassNames={this.getToggableIconClassNames}
+                                    getProperArrowAnimation={this.getProperArrowAnimation}
+                                /> :
+                                <Collapse in={it.open} timeout="auto" unmountOnExit>
+                                    <Tree
+                                        showSelection={this.props.showSelection}
+                                        items={it.items}
+                                        render={render}
+                                        disableRipple={disableRipple}
+                                        toggleItemOpen={toggleItemOpen}
+                                        toggleItemActive={toggleItemActive}
+                                        level={level + 1}
                                         onContextMenu={this.props.onContextMenu}
-                                        handleToggleItemOpen={this.handleToggleItemOpen}
-                                        toggleItemActive={this.props.toggleItemActive}
-                                        getToggableIconClassNames={this.getToggableIconClassNames}
-                                        getProperArrowAnimation={this.getProperArrowAnimation}
-                                    /> :
-                                    <Collapse in={it.open} timeout="auto" unmountOnExit>
-                                        <Tree
-                                            showSelection={this.props.showSelection}
-                                            items={it.items}
-                                            render={render}
-                                            disableRipple={disableRipple}
-                                            toggleItemOpen={toggleItemOpen}
-                                            toggleItemActive={toggleItemActive}
-                                            level={level + 1}
-                                            onContextMenu={this.props.onContextMenu}
-                                            toggleItemSelection={this.props.toggleItemSelection} />
-                                    </Collapse>
+                                        toggleItemSelection={this.props.toggleItemSelection} />
+                                </Collapse>
                         }
                     </div>)}
             </List>;
diff --git a/src/views/run-process-panel/inputs/directory-array-input.tsx b/src/views/run-process-panel/inputs/directory-array-input.tsx
index 8b03a123..2b4826c0 100644
--- a/src/views/run-process-panel/inputs/directory-array-input.tsx
+++ b/src/views/run-process-panel/inputs/directory-array-input.tsx
@@ -30,6 +30,7 @@ import { ResourceKind } from '~/models/resource';
 
 export interface DirectoryArrayInputProps {
     input: DirectoryArrayCommandInputParameter;
+    options?: { showOnlyOwned: boolean, showOnlyWritable: boolean };
 }
 
 export const DirectoryArrayInput = ({ input }: DirectoryArrayInputProps) =>
@@ -93,7 +94,9 @@ const mapStateToProps = createStructuredSelector({
 });
 
 const DirectoryArrayInputComponent = connect(mapStateToProps)(
-    class DirectoryArrayInputComponent extends React.Component<DirectoryArrayInputComponentProps & GenericInputProps & DispatchProp, DirectoryArrayInputComponentState> {
+    class DirectoryArrayInputComponent extends React.Component<DirectoryArrayInputComponentProps & GenericInputProps & DispatchProp & {
+        options?: { showOnlyOwned: boolean, showOnlyWritable: boolean };
+    }, DirectoryArrayInputComponentState> {
         state: DirectoryArrayInputComponentState = {
             open: false,
             directories: [],
@@ -241,6 +244,7 @@ const DirectoryArrayInputComponent = connect(mapStateToProps)(
                 <DialogActions>
                     <Button onClick={this.closeDialog}>Cancel</Button>
                     <Button
+                        data-cy='ok-button'
                         variant='contained'
                         color='primary'
                         onClick={this.submit}>Ok</Button>
@@ -276,6 +280,7 @@ const DirectoryArrayInputComponent = connect(mapStateToProps)(
                             pickerId={this.props.commandInput.id}
                             includeCollections
                             showSelection
+                            options={this.props.options}
                             toggleItemSelection={this.refreshDirectories} />
                     </div>
                     <Divider />
diff --git a/src/views/run-process-panel/inputs/file-array-input.tsx b/src/views/run-process-panel/inputs/file-array-input.tsx
index 14d16824..173e6b5e 100644
--- a/src/views/run-process-panel/inputs/file-array-input.tsx
+++ b/src/views/run-process-panel/inputs/file-array-input.tsx
@@ -29,6 +29,7 @@ import withStyles, { StyleRulesCallback } from '@material-ui/core/styles/withSty
 
 export interface FileArrayInputProps {
     input: FileArrayCommandInputParameter;
+    options?: { showOnlyOwned: boolean, showOnlyWritable: boolean };
 }
 export const FileArrayInput = ({ input }: FileArrayInputProps) =>
     <Field
@@ -92,7 +93,9 @@ const mapStateToProps = createStructuredSelector({
 });
 
 const FileArrayInputComponent = connect(mapStateToProps)(
-    class FileArrayInputComponent extends React.Component<FileArrayInputComponentProps & GenericInputProps & DispatchProp, FileArrayInputComponentState> {
+    class FileArrayInputComponent extends React.Component<FileArrayInputComponentProps & GenericInputProps & DispatchProp & {
+        options?: { showOnlyOwned: boolean, showOnlyWritable: boolean };
+    }, FileArrayInputComponentState> {
         state: FileArrayInputComponentState = {
             open: false,
             files: [],
@@ -223,6 +226,7 @@ const FileArrayInputComponent = connect(mapStateToProps)(
                 <DialogActions>
                     <Button onClick={this.closeDialog}>Cancel</Button>
                     <Button
+                        data-cy='ok-button'
                         variant='contained'
                         color='primary'
                         onClick={this.submit}>Ok</Button>
@@ -259,6 +263,7 @@ const FileArrayInputComponent = connect(mapStateToProps)(
                             includeCollections
                             includeFiles
                             showSelection
+                            options={this.props.options}
                             toggleItemSelection={this.refreshFiles} />
                     </div>
                     <Divider />
diff --git a/src/views/run-process-panel/run-process-inputs-form.tsx b/src/views/run-process-panel/run-process-inputs-form.tsx
index e6a504db..196655e0 100644
--- a/src/views/run-process-panel/run-process-inputs-form.tsx
+++ b/src/views/run-process-panel/run-process-inputs-form.tsx
@@ -111,10 +111,10 @@ const getInputComponent = (input: CommandInputParameter) => {
             return <FloatArrayInput input={input as FloatArrayCommandInputParameter} />;
 
         case isArrayOfType(input, CWLType.FILE):
-            return <FileArrayInput input={input as FileArrayCommandInputParameter} />;
+            return <FileArrayInput options={{ showOnlyOwned: false, showOnlyWritable: false }} input={input as FileArrayCommandInputParameter} />;
 
         case isArrayOfType(input, CWLType.DIRECTORY):
-            return <DirectoryArrayInput input={input as DirectoryArrayCommandInputParameter} />;
+            return <DirectoryArrayInput options={{ showOnlyOwned: false, showOnlyWritable: false }} input={input as DirectoryArrayCommandInputParameter} />;
 
         default:
             return null;

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list