Merge branch 'master' of git.curoverse.com:arvados-workbench2 into 14503_keep_service... 14503_keep_services_panel
authorJanicki Artur <artur.janicki@contractors.roche.com>
Mon, 3 Dec 2018 12:05:41 +0000 (13:05 +0100)
committerJanicki Artur <artur.janicki@contractors.roche.com>
Mon, 3 Dec 2018 12:05:41 +0000 (13:05 +0100)
refs #2
14503

Arvados-DCO-1.1-Signed-off-by: Janicki Artur <artur.janicki@contractors.roche.com>

README.md
public/vocabulary-example.json [new file with mode: 0644]
src/common/config.ts
src/store/auth/auth-actions.test.ts
src/views/run-process-panel/inputs/boolean-input.tsx
src/views/run-process-panel/inputs/directory-input.tsx
src/views/run-process-panel/inputs/enum-input.tsx
src/views/run-process-panel/inputs/file-input.tsx
src/views/run-process-panel/inputs/float-input.tsx
src/views/run-process-panel/inputs/int-input.tsx
src/views/run-process-panel/inputs/string-input.tsx

index ea9bc02fc1f0f038b016f5fd8f5ade2ab38ad2f9..e8d77701107179a6ef88fcfb68cd47d857a321a9 100644 (file)
--- a/README.md
+++ b/README.md
@@ -45,6 +45,10 @@ Currently this configuration schema is supported:
 }
 ```
 
+#### VOCABULARY_URL
+Local path, or any URL that allows cross-origin requests. See 
+[Vocabulary JSON file example](public/vocabulary-example.json).
+
 ### Licensing
 
 Arvados is Free Software. See COPYING for information about Arvados Free
diff --git a/public/vocabulary-example.json b/public/vocabulary-example.json
new file mode 100644 (file)
index 0000000..b227dc2
--- /dev/null
@@ -0,0 +1,32 @@
+{
+    "strict": false,
+    "tags": {
+        "fruit": {
+            "values": ["pineapple", "tomato", "orange", "banana", "advocado", "lemon", "apple", "peach", "strawberry"],
+            "strict": true
+        },
+        "animal": {
+            "values": ["human", "dog", "elephant", "eagle"],
+            "strict": false
+        },
+        "color": {
+            "values": ["yellow", "red", "magenta", "green"],
+            "strict": false
+        },
+        "text": {},
+        "category": {
+            "values": ["experimental", "development", "production"]
+        },
+        "comments": {},
+        "importance": {
+            "values": ["critical", "important", "low priority"]
+        },
+        "size": {
+            "values": ["x-small", "small", "medium", "large", "x-large"]
+        },
+        "country": {
+            "values": ["Afghanistan","Ă…land Islands","Albania","Algeria","American Samoa","AndorrA","Angola","Anguilla","Antarctica","Antigua and Barbuda","Argentina","Armenia","Aruba","Australia","Austria","Azerbaijan","Bahamas","Bahrain","Bangladesh","Barbados","Belarus","Belgium","Belize","Benin","Bermuda","Bhutan","Bolivia","Bosnia and Herzegovina","Botswana","Bouvet Island","Brazil","British Indian Ocean Territory","Brunei Darussalam","Bulgaria","Burkina Faso","Burundi","Cambodia","Cameroon","Canada","Cape Verde","Cayman Islands","Central African Republic","Chad","Chile","China","Christmas Island","Cocos (Keeling) Islands","Colombia","Comoros","Congo","Congo, The Democratic Republic of the","Cook Islands","Costa Rica","Cote D'Ivoire","Croatia","Cuba","Cyprus","Czech Republic","Denmark","Djibouti","Dominica","Dominican Republic","Ecuador","Egypt","El Salvador","Equatorial Guinea","Eritrea","Estonia","Ethiopia","Falkland Islands (Malvinas)","Faroe Islands","Fiji","Finland","France","French Guiana","French Polynesia","French Southern Territories","Gabon","Gambia","Georgia","Germany","Ghana","Gibraltar","Greece","Greenland","Grenada","Guadeloupe","Guam","Guatemala","Guernsey","Guinea","Guinea-Bissau","Guyana","Haiti","Heard Island and Mcdonald Islands","Holy See (Vatican City State)","Honduras","Hong Kong","Hungary","Iceland","India","Indonesia","Iran, Islamic Republic Of","Iraq","Ireland","Isle of Man","Israel","Italy","Jamaica","Japan","Jersey","Jordan","Kazakhstan","Kenya","Kiribati","Korea, Democratic People'S Republic of","Korea, Republic of","Kuwait","Kyrgyzstan","Lao People'S Democratic Republic","Latvia","Lebanon","Lesotho","Liberia","Libyan Arab Jamahiriya","Liechtenstein","Lithuania","Luxembourg","Macao","Macedonia, The Former Yugoslav Republic of","Madagascar","Malawi","Malaysia","Maldives","Mali","Malta","Marshall Islands","Martinique","Mauritania","Mauritius","Mayotte","Mexico","Micronesia, Federated States of","Moldova, Republic of","Monaco","Mongolia","Montserrat","Morocco","Mozambique","Myanmar","Namibia","Nauru","Nepal","Netherlands","Netherlands Antilles","New Caledonia","New Zealand","Nicaragua","Niger","Nigeria","Niue","Norfolk Island","Northern Mariana Islands","Norway","Oman","Pakistan","Palau","Palestinian Territory, Occupied","Panama","Papua New Guinea","Paraguay","Peru","Philippines","Pitcairn","Poland","Portugal","Puerto Rico","Qatar","Reunion","Romania","Russian Federation","RWANDA","Saint Helena","Saint Kitts and Nevis","Saint Lucia","Saint Pierre and Miquelon","Saint Vincent and the Grenadines","Samoa","San Marino","Sao Tome and Principe","Saudi Arabia","Senegal","Serbia and Montenegro","Seychelles","Sierra Leone","Singapore","Slovakia","Slovenia","Solomon Islands","Somalia","South Africa","South Georgia and the South Sandwich Islands","Spain","Sri Lanka","Sudan","Suriname","Svalbard and Jan Mayen","Swaziland","Sweden","Switzerland","Syrian Arab Republic","Taiwan, Province of China","Tajikistan","Tanzania, United Republic of","Thailand","Timor-Leste","Togo","Tokelau","Tonga","Trinidad and Tobago","Tunisia","Turkey","Turkmenistan","Turks and Caicos Islands","Tuvalu","Uganda","Ukraine","United Arab Emirates","United Kingdom","United States","United States Minor Outlying Islands","Uruguay","Uzbekistan","Vanuatu","Venezuela","Viet Nam","Virgin Islands, British","Virgin Islands, U.S.","Wallis and Futuna","Western Sahara","Yemen","Zambia","Zimbabwe"],
+            "strict": true
+        }
+    }
+}
\ No newline at end of file
index c74277e42cc1ff78a9499e688f9f3bc171ca2835..b7b89bd9e4930100188725b7f117f67abf076587 100644 (file)
@@ -60,7 +60,8 @@ export const fetchConfig = () => {
         .then(config => Axios
             .get<Config>(getDiscoveryURL(config.API_HOST))
             .then(response => ({ 
-                config: {...response.data, vocabularyUrl: config.VOCABULARY_URL }, 
+                // TODO: After tests delete `|| '/vocabulary-example.json'`
+                config: {...response.data, vocabularyUrl: config.VOCABULARY_URL || '/vocabulary-example.json' }, 
                 apiHost: config.API_HOST, 
             })));
 
index 3d6913a67d9a3f4514725af7fc371739028e8911..c54438b170c5b1a27c945444644c6f17bfb066fb 100644 (file)
@@ -43,7 +43,7 @@ describe('auth-actions', () => {
         localStorage.setItem(USER_LAST_NAME_KEY, "Doe");
         localStorage.setItem(USER_UUID_KEY, "uuid");
         localStorage.setItem(USER_OWNER_UUID_KEY, "ownerUuid");
-        localStorage.setItem(USER_IS_ADMIN, JSON.stringify(false));
+        localStorage.setItem(USER_IS_ADMIN, JSON.stringify("false"));
 
         store.dispatch(initAuth());
 
@@ -56,7 +56,7 @@ describe('auth-actions', () => {
                 lastName: "Doe",
                 uuid: "uuid",
                 ownerUuid: "ownerUuid",
-                isAdmin: false
+                isAdmin: true
             }
         });
     });
index 5da547423bb94155cf825645f52d057ec0c93449..6a214e9dd846aa0a6907bbefbdfd6cc47e7680e2 100644 (file)
@@ -3,6 +3,7 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
+import { memoize } from 'lodash/fp';
 import { BooleanCommandInputParameter } from '~/models/workflow';
 import { Field } from 'redux-form';
 import { Switch } from '@material-ui/core';
@@ -16,17 +17,23 @@ export const BooleanInput = ({ input }: BooleanInputProps) =>
         name={input.id}
         commandInput={input}
         component={BooleanInputComponent}
-        normalize={(value, prevValue) => !prevValue}
+        normalize={normalize}
     />;
 
+const normalize = (_: any, prevValue: boolean) => !prevValue;
+
 const BooleanInputComponent = (props: GenericInputProps) =>
     <GenericInput
         component={Input}
         {...props} />;
 
-const Input = (props: GenericInputProps) =>
+const Input = ({ input, commandInput }: GenericInputProps) =>
     <Switch
         color='primary'
-        checked={props.input.value}
-        onChange={() => props.input.onChange(props.input.value)} 
-        disabled={props.commandInput.disabled} />;
\ No newline at end of file
+        checked={input.value}
+        onChange={handleChange(input.onChange, input.value)}
+        disabled={commandInput.disabled} />;
+
+const handleChange = memoize(
+    (onChange: (value: string) => void, value: string) => () => onChange(value)
+);
index aa25fefc0bfbe9b45213ce220e8a855c8f22b68f..29ccd6e0ddf7d544855840fd2420fcba78e90135 100644 (file)
@@ -3,23 +3,24 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
+import { connect, DispatchProp } from 'react-redux';
+import { memoize } from 'lodash/fp';
+import { Field } from 'redux-form';
+import { Input, Dialog, DialogTitle, DialogContent, DialogActions, Button } from '@material-ui/core';
 import {
     isRequiredInput,
     DirectoryCommandInputParameter,
     CWLType,
     Directory
 } from '~/models/workflow';
-import { Field } from 'redux-form';
-import { Input, Dialog, DialogTitle, DialogContent, DialogActions, Button } from '@material-ui/core';
 import { GenericInputProps, GenericInput } from './generic-input';
 import { ProjectsTreePicker } from '~/views-components/projects-tree-picker/projects-tree-picker';
-import { connect, DispatchProp } from 'react-redux';
 import { initProjectsTreePicker } from '~/store/tree-picker/tree-picker-actions';
 import { TreeItem } from '~/components/tree/tree';
 import { ProjectsTreePickerItem } from '~/views-components/projects-tree-picker/generic-projects-tree-picker';
 import { CollectionResource } from '~/models/collection';
 import { ResourceKind } from '~/models/resource';
-import { ERROR_MESSAGE } from '../../../validators/require';
+import { ERROR_MESSAGE } from '~/validators/require';
 
 export interface DirectoryInputProps {
     input: DirectoryCommandInputParameter;
@@ -29,18 +30,25 @@ export const DirectoryInput = ({ input }: DirectoryInputProps) =>
         name={input.id}
         commandInput={input}
         component={DirectoryInputComponent}
-        format={(value?: Directory) => value ? value.basename : ''}
-        parse={(directory: CollectionResource): Directory => ({
-            class: CWLType.DIRECTORY,
-            location: `keep:${directory.portableDataHash}`,
-            basename: directory.name,
-        })}
-        validate={[
-            isRequiredInput(input)
-                ? (directory?: Directory) => directory ? undefined : ERROR_MESSAGE
-                : () => undefined,
-        ]} />;
-
+        format={format}
+        parse={parse}
+        validate={getValidation(input)} />;
+
+const format = (value?: Directory) => value ? value.basename : '';
+
+const parse = (directory: CollectionResource): Directory => ({
+    class: CWLType.DIRECTORY,
+    location: `keep:${directory.portableDataHash}`,
+    basename: directory.name,
+});
+
+const getValidation = memoize(
+    (input: DirectoryCommandInputParameter) => ([
+        isRequiredInput(input)
+            ? (directory?: Directory) => directory ? undefined : ERROR_MESSAGE
+            : () => undefined,
+    ])
+);
 
 interface DirectoryInputComponentState {
     open: boolean;
@@ -78,7 +86,7 @@ const DirectoryInputComponent = connect()(
             this.props.input.onChange(this.state.directory);
         }
 
-        setDirectory = (event: React.MouseEvent<HTMLElement>, { data }: TreeItem<ProjectsTreePickerItem>, pickerId: string) => {
+        setDirectory = (_: {}, { data }: TreeItem<ProjectsTreePickerItem>) => {
             if ('kind' in data && data.kind === ResourceKind.COLLECTION) {
                 this.setState({ directory: data });
             } else {
index 86ff6fb14c2e733b21ed792e5e2ef8fb0dd9f64d..3b0289e79f4a8fee08ecde6546f99f7cf853b5d0 100644 (file)
@@ -3,9 +3,9 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
-import { EnumCommandInputParameter, CommandInputEnumSchema } from '~/models/workflow';
 import { Field } from 'redux-form';
 import { Select, MenuItem } from '@material-ui/core';
+import { EnumCommandInputParameter, CommandInputEnumSchema } from '~/models/workflow';
 import { GenericInputProps, GenericInput } from './generic-input';
 
 export interface EnumInputProps {
@@ -30,8 +30,20 @@ const Input = (props: GenericInputProps) => {
         onChange={props.input.onChange}
         disabled={props.commandInput.disabled} >
         {type.symbols.map(symbol =>
-            <MenuItem key={symbol} value={symbol.split('/').pop()}>
-                {symbol.split('/').pop()}
+            <MenuItem key={symbol} value={extractValue(symbol)}>
+                {extractValue(symbol)}
             </MenuItem>)}
     </Select>;
-};
\ No newline at end of file
+};
+
+/**
+ * Values in workflow definition have an absolute form, for example: 
+ * 
+ * ```#input_collector.cwl/enum_type/Pathway table```
+ * 
+ * We want a value that is in form accepted by backend.
+ * According to the example above, the correct value is:
+ * 
+ * ```Pathway table```
+ */
+const extractValue = (symbol: string) => symbol.split('/').pop();
index 7e0925e8e9e175481887c1c9988be0eee329f82e..0611100722a2e30512a675dc770b933d34cd88b6 100644 (file)
@@ -3,6 +3,7 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
+import { memoize } from 'lodash/fp';
 import {
     isRequiredInput,
     FileCommandInputParameter,
@@ -28,18 +29,24 @@ export const FileInput = ({ input }: FileInputProps) =>
         name={input.id}
         commandInput={input}
         component={FileInputComponent}
-        format={(value?: File) => value ? value.basename : ''}
-        parse={(file: CollectionFile): File => ({
-            class: CWLType.FILE,
-            location: `keep:${file.id}`,
-            basename: file.name,
-        })}
-        validate={[
-            isRequiredInput(input)
-                ? (file?: File) => file ? undefined : ERROR_MESSAGE
-                : () => undefined,
-        ]} />;
+        format={format}
+        parse={parse}
+        validate={getValidation(input)} />;
 
+const format = (value?: File) => value ? value.basename : '';
+
+const parse = (file: CollectionFile): File => ({
+    class: CWLType.FILE,
+    location: `keep:${file.id}`,
+    basename: file.name,
+});
+
+const getValidation = memoize(
+    (input: FileCommandInputParameter) => ([
+        isRequiredInput(input)
+            ? (file?: File) => file ? undefined : ERROR_MESSAGE
+            : () => undefined,
+    ]));
 
 interface FileInputComponentState {
     open: boolean;
@@ -77,7 +84,7 @@ const FileInputComponent = connect()(
             this.props.input.onChange(this.state.file);
         }
 
-        setFile = (event: React.MouseEvent<HTMLElement>, { data }: TreeItem<ProjectsTreePickerItem>, pickerId: string) => {
+        setFile = (_: {}, { data }: TreeItem<ProjectsTreePickerItem>) => {
             if ('type' in data && data.type === CollectionFileType.FILE) {
                 this.setState({ file: data });
             } else {
index 56a58012b260bc2b6492671bc760d12f0479e4eb..a5905dc586d16bb684bfa3b9c443062c5a4e0ebc 100644 (file)
@@ -3,6 +3,7 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
+import { memoize } from 'lodash/fp';
 import { FloatCommandInputParameter, isRequiredInput } from '~/models/workflow';
 import { Field } from 'redux-form';
 import { isNumber } from '~/validators/is-number';
@@ -17,11 +18,17 @@ export const FloatInput = ({ input }: FloatInputProps) =>
         commandInput={input}
         component={Input}
         parse={parseFloat}
-        format={value => isNaN(value) ? '' : JSON.stringify(value)}
-        validate={[
-            isRequiredInput(input)
-                ? isNumber
-                : () => undefined,]} />;
+        format={format}
+        validate={getValidation(input)} />;
+
+const format = (value: any) => isNaN(value) ? '' : JSON.stringify(value);
+
+const getValidation = memoize(
+    (input: FloatCommandInputParameter) => ([
+        isRequiredInput(input)
+            ? isNumber
+            : () => undefined,])
+);
 
 const Input = (props: GenericInputProps) =>
     <GenericInput
index 413ee49c7abc0eee00961fd66f941e2b71ef19ad..32ebeb75c27bc8ee6be8ed186b5051a5dcff0ab0 100644 (file)
@@ -3,6 +3,7 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
+import { memoize } from 'lodash/fp';
 import { IntCommandInputParameter, isRequiredInput } from '~/models/workflow';
 import { Field } from 'redux-form';
 import { isInteger } from '~/validators/is-integer';
@@ -17,13 +18,20 @@ export const IntInput = ({ input }: IntInputProps) =>
         name={input.id}
         commandInput={input}
         component={InputComponent}
-        parse={value => parseInt(value, 10)}
-        format={value => isNaN(value) ? '' : JSON.stringify(value)}
-        validate={[
-            isRequiredInput(input)
-                ? isInteger
-                : () => undefined,
-        ]} />;
+        parse={parse}
+        format={format}
+        validate={getValidation(input)} />;
+
+const parse = (value: any) => parseInt(value, 10);
+
+const format = (value: any) => isNaN(value) ? '' : JSON.stringify(value);
+
+const getValidation = memoize(
+    (input: IntCommandInputParameter) => ([
+        isRequiredInput(input)
+            ? isInteger
+            : () => undefined,
+    ]));
 
 const InputComponent = (props: GenericInputProps) =>
     <GenericInput
index f6b50a7c47dd5bb417e75e503520f0be6ee691d9..7fc74315e98d6f3739381c10da74c3ea300438eb 100644 (file)
@@ -3,6 +3,7 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
+import { memoize } from 'lodash/fp';
 import { isRequiredInput, StringCommandInputParameter } from '~/models/workflow';
 import { Field } from 'redux-form';
 import { require } from '~/validators/require';
@@ -17,11 +18,14 @@ export const StringInput = ({ input }: StringInputProps) =>
         name={input.id}
         commandInput={input}
         component={StringInputComponent}
-        validate={[
-            isRequiredInput(input)
-                ? require
-                : () => undefined,
-        ]} />;
+        validate={getValidation(input)} />;
+
+const getValidation = memoize(
+    (input: StringCommandInputParameter) => ([
+        isRequiredInput(input)
+            ? require
+            : () => undefined,
+    ]));
 
 const StringInputComponent = (props: GenericInputProps) =>
     <GenericInput