Skip to content

Commit

Permalink
use two-step form for provisioning service instances (#373)
Browse files Browse the repository at this point in the history
* use two-step form for provisioning service instances

A two step form allows us to cleanly separate input that Kubeapps needs
from the generalised input from the Service Broker's schema. The first
form simply asks for the name of the Service Instance, and the second
form renders the parameters using the broker-provided instanceCreateParameterSchema.

* review

* render smaller modal for name form
  • Loading branch information
prydonius authored Jun 25, 2018
1 parent 85838b6 commit 04dbb9d
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 27 deletions.
88 changes: 64 additions & 24 deletions dashboard/src/components/ProvisionButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ interface IProvisionButtonProps {
interface IProvisionButtonState {
isProvisioning: boolean;
modalIsOpen: boolean;
name: string;
displayNameForm: boolean;
}

const RequiredRBACRoles: IRBACRole[] = [
Expand All @@ -39,35 +41,35 @@ const RequiredRBACRoles: IRBACRole[] = [
},
];

const NameProperty: JSONSchema6 = {
description: "Name for ServiceInstance",
type: "string",
const smallModalStyle = {
content: {
bottom: "auto",
left: "50%",
marginRight: "-50%",
right: "auto",
top: "50%",
transform: "translate(-50%, -50%)",
},
};

class ProvisionButton extends React.Component<IProvisionButtonProps, IProvisionButtonState> {
public state: IProvisionButtonState = {
displayNameForm: true,
isProvisioning: false,
modalIsOpen: false,
name: "",
};

public render() {
const { selectedPlan } = this.props;
let schema = selectedPlan.spec.instanceCreateParameterSchema;
if (schema) {
schema.properties = {
name: NameProperty,
...(schema.properties || {}),
};
schema.required = [...(schema.required || []), "name"];
} else {
// If the Service Broker does not define a schema, default to a raw
// parameters JSON object
if (!schema) {
schema = {
properties: {
kubeappsRawParameters: {
title: "Parameters",
type: "object",
},
name: NameProperty,
},
type: "object",
};
Expand All @@ -87,18 +89,32 @@ class ProvisionButton extends React.Component<IProvisionButtonProps, IProvisionB
isOpen={this.state.modalIsOpen}
onRequestClose={this.closeModal}
contentLabel="Modal"
style={this.state.displayNameForm ? smallModalStyle : {}}
>
{this.props.error && <div className="margin-b-big">{this.renderError()}</div>}
<SchemaForm schema={schema} onSubmit={this.handleProvision}>
<div>
<button className="button button-primary" type="submit">
Submit
</button>
<button className="button" onClick={this.closeModal}>
Cancel
</button>
</div>
</SchemaForm>
{this.state.displayNameForm ? (
<SchemaForm schema={this.nameSchema()} onSubmit={this.handleNameChange}>
<div>
<button className="button button-primary" type="submit">
Continue
</button>
<button className="button" onClick={this.closeModal}>
Cancel
</button>
</div>
</SchemaForm>
) : (
<SchemaForm schema={schema} onSubmit={this.handleProvision}>
<div>
<button className="button button-primary" type="submit">
Submit
</button>
<button className="button" onClick={this.handleBackButton}>
Back
</button>
</div>
</SchemaForm>
)}
</Modal>
</div>
);
Expand All @@ -116,13 +132,23 @@ class ProvisionButton extends React.Component<IProvisionButtonProps, IProvisionB
});
};

public handleBackButton = (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
this.setState({ displayNameForm: true });
};

public handleNameChange = ({ formData }: ISubmitEvent<{ Name: string }>) => {
this.setState({ name: formData.Name, displayNameForm: false });
};

public handleProvision = async ({
formData,
}: ISubmitEvent<{ name: string; kubeappsRawParameters: {} }>) => {
const { namespace, provision, push, selectedClass, selectedPlan } = this.props;
const { name } = this.state;
this.setState({ isProvisioning: true });

const { name, kubeappsRawParameters, ...rest } = formData;
const { kubeappsRawParameters, ...rest } = formData;
if (selectedClass && selectedPlan) {
const provisioned = await provision(
name,
Expand All @@ -143,6 +169,20 @@ class ProvisionButton extends React.Component<IProvisionButtonProps, IProvisionB
}
};

private nameSchema(): JSONSchema6 {
return {
properties: {
Name: {
default: this.state.name,
description: "Name for ServiceInstance",
type: "string",
},
},
required: ["Name"],
type: "object",
};
}

private renderError() {
const { error, namespace } = this.props;
switch (error && error.constructor) {
Expand Down
3 changes: 2 additions & 1 deletion dashboard/src/components/SchemaForm/CustomObjectField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ class CustomObjectField extends React.Component<FieldProps, ICustomObjectFieldSt
if (schema.properties) {
return <ObjectField {...this.props} />;
}
const label = schema.title || name;
return (
<div>
<label htmlFor={name}>{name} (JSON)</label>
<label htmlFor={label}>{label} (JSON)</label>
<AceEditor
className="margin-b-big"
mode="json"
Expand Down
9 changes: 7 additions & 2 deletions dashboard/src/components/SchemaForm/FieldTemplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@ import { FieldTemplateProps } from "react-jsonschema-form";

// adapted from https://jsfiddle.net/hdp1kgn6/1/
const FieldTemplate: React.SFC<FieldTemplateProps> = props => {
const { id, classNames, label, children, displayLabel } = props;
const { id, classNames, label, children, displayLabel, required } = props;
return (
<div className={classNames}>
{displayLabel && <label htmlFor={id}>{label}</label>}
{displayLabel && (
<label htmlFor={id}>
{label}
{required && " (required)"}
</label>
)}
{children}
</div>
);
Expand Down

0 comments on commit 04dbb9d

Please sign in to comment.