diff --git a/client/src/project/new/ProjectNew.present.js b/client/src/project/new/ProjectNew.present.js
index bdd7a845fd..537fbda38f 100644
--- a/client/src/project/new/ProjectNew.present.js
+++ b/client/src/project/new/ProjectNew.present.js
@@ -34,7 +34,7 @@ import {
} from "reactstrap";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
- faExclamationTriangle, faInfoCircle, faLink, faQuestionCircle, faSyncAlt
+ faExclamationTriangle, faInfoCircle, faLink, faQuestionCircle, faSyncAlt, faUndo
} from "@fortawesome/free-solid-svg-icons";
import {
@@ -974,6 +974,32 @@ function TemplateGalleryRow(props) {
);
}
+/**
+ * Create a "restore default" button.
+ *
+ * @param {function} restore - function to invoke
+ * @param {string} tip - message to display in the tooltip
+ * @param {boolean} disabled - whether it's disabled or not
+ */
+function RestoreButton(props) {
+ const { restore, name, disabled } = props;
+
+ const id = `restore_${name}`;
+ const tip = disabled ?
+ "Default value already selected" :
+ "Restore default value";
+
+ return (
+
+
+ {tip}
+
+ );
+}
+
class Variables extends Component {
render() {
const { input, handlers } = this.props;
@@ -987,19 +1013,111 @@ class Variables extends Component {
const template = templates.all.filter(t => t.id === input.template)[0];
if (!template || !template.variables || !Object.keys(template.variables).length)
return null;
- const variables = Object.keys(template.variables).map(variable => (
-
-
- handlers.setVariable(variable, e.target.value)} />
- {capitalize(template.variables[variable])}
-
- ));
+ const variables = Object.keys(template.variables).map(variable => {
+ const data = template.variables[variable];
+
+ // fallback to avoid breaking old variable structure
+ if (typeof data !== "object") {
+ return (
+
+
+ handlers.setVariable(variable, e.target.value)} />
+ {capitalize(template.variables[variable])}
+
+ );
+ }
+
+ // expected `data` properties: default_value, description, enum, type.
+ // changing enum to enumValues to avoid using js reserved word
+ return (
+
+ );
+ });
return variables;
}
}
+function Variable(props) {
+ const { default_value, description, enumValues, handlers, input, name, type } = props;
+ const id = `parameter-${name}`;
+
+ const descriptionOutput = description ?
+ ({capitalize(description)}) :
+ null;
+
+ const defaultOutput = default_value != null ?
+ `Default: ${default_value}` :
+ null;
+
+ const restoreButton = default_value != null ?
+ (
+ handlers.setVariable(name, default_value)}
+ />
+ ) :
+ null;
+
+ let inputElement = null;
+ if (type === "boolean") {
+ inputElement = (
+
+ handlers.setVariable(name, e.target.checked)}
+ className="form-check-input rounded-pill" />
+
+ {restoreButton}
+
+ );
+ // inputElement = null;
+ }
+ else if (type === "enum") {
+ const enumObjects = enumValues.map(enumObject => {
+ const enumId = `enum-${id}-${enumObject.toString()}`;
+ return (
+
+ );
+ });
+ inputElement = (
+
+ {restoreButton}
+ handlers.setVariable(name, e.target.value)}>
+ {enumObjects}
+
+ {descriptionOutput}
+
+ );
+ }
+ else {
+ const inputType = type === "number" ?
+ "number" :
+ "text";
+ inputElement = (
+
+ {restoreButton}
+ handlers.setVariable(name, e.target.value)}
+ placeholder={defaultOutput} />
+ {descriptionOutput}
+
+ );
+ }
+
+ return inputElement;
+}
+
class Create extends Component {
constructor(props) {
super(props);
@@ -1109,6 +1227,25 @@ class Create extends Component {
"based on " + (templates.all.find(t => t.id === input.template).name) :
"";
+ const errorFields = meta.validation.errors ?
+ Object.keys(meta.validation.errors)
+ .filter(field => !input[`${field}Pristine`]) // don't consider pristine fields
+ .map(field => capitalize(field)) :
+ [];
+ const plural = errorFields.length > 1 ?
+ "s" :
+ "";
+ const errorMessage = errorFields.length ?
+ (
+
+
+ To create a new project, please first fix problems with the following field{plural}:{" "}
+ {errorFields.join(", ")}
+
+
+ ) :
+ null;
+
return (
{alert}
@@ -1124,6 +1261,7 @@ class Create extends Component {
meta={meta}
createUrl={this.props.handlers.createEncodedUrl}
/>
+ {errorMessage}
);
}
@@ -1204,7 +1342,7 @@ function ShareLinkModal(props) {
if (include.variables) {
let variablesObject = {};
for (let variable of Object.keys(input.variables)) {
- if (input.variables[variable])
+ if (input.variables[variable] != null)
variablesObject[variable] = input.variables[variable];
}
dataObject.variables = variablesObject;
diff --git a/client/src/project/new/ProjectNew.state.js b/client/src/project/new/ProjectNew.state.js
index b5d171c23a..9c4432e1ca 100644
--- a/client/src/project/new/ProjectNew.state.js
+++ b/client/src/project/new/ProjectNew.state.js
@@ -91,17 +91,32 @@ class NewProjectCoordinator {
Object.keys(template.variables) :
[];
- // preserve already set values
+ // preserve already set values or set default values when available
const oldValues = currentInput.template ?
currentInput.variables :
{};
const oldVariables = Object.keys(oldValues);
const values = variables.reduce((values, variable) => {
- const text = oldVariables.includes(variable) ?
- oldValues[variable] :
- "";
- return { ...values, [variable]: text };
+ let value = "";
+
+ const variableData = template.variables[variable];
+ if (typeof variableData === "object") {
+ // set first value for enum, and "false" for boolean
+ if (variableData["type"] === "enum" && variableData["enum"] && variableData["enum"].length)
+ value = variableData["enum"][0];
+
+ // set default, if any
+ if (typeof variableData === "object" && variableData["default_value"] != null)
+ value = variableData["default_value"];
+ }
+
+ // set older value, if any
+ if (oldVariables.includes(variable))
+ value = oldValues[variable];
+
+ return { ...values, [variable]: value };
}, {});
+
return values;
}
@@ -535,10 +550,10 @@ class NewProjectCoordinator {
newProjectData.ref = userTemplates.ref;
}
- // add variables
+ // add variables after converting to string (renku core accept string only)
let parameters = [];
for (let variable of Object.keys(input.variables))
- parameters.push({ key: variable, value: input.variables[variable] });
+ parameters.push({ key: variable, value: input.variables[variable].toString() });
newProjectData.parameters = parameters;
// reset all previous creation progresses and invoke the project creation API