[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