feat(cp-frontend): improve review stage of deployment group creation

This commit is contained in:
Sérgio Ramos 2017-06-22 23:01:21 +01:00 committed by Judit Greskovits
parent 627761e98d
commit 2ea33c2a07
4 changed files with 135 additions and 29 deletions

View File

@ -1,9 +1,49 @@
import React from 'react'; import React from 'react';
import { FormGroup, FormMeta, Input, Button } from 'joyent-ui-toolkit';
import { Field } from 'redux-form'; import { Field } from 'redux-form';
import styled from 'styled-components';
import { Row, Col } from 'react-styled-flexboxgrid'; import { Row, Col } from 'react-styled-flexboxgrid';
import { Dots2 } from 'styled-text-spinners'; import { Dots2 } from 'styled-text-spinners';
import Bundle from 'react-bundle'; import Bundle from 'react-bundle';
import remcalc from 'remcalc';
import forceArray from 'force-array';
import {
FormGroup,
FormMeta,
Input,
Button,
Card,
H3,
typography
} from 'joyent-ui-toolkit';
const Dl = styled.dl`
margin: ${remcalc(13)} ${remcalc(19)};
`;
const ServiceName = H3.extend`
margin-top: 0;
margin-bottom: 0;
line-height: 1.6;
font-weight: 600;
`;
const ServiceCard = Card.extend`
min-height: ${remcalc(72)};
`;
const ImageTitle = ServiceName.extend`
display: inline-block;
`;
const Image = styled.span`
${typography.fontFamily};
`;
const ButtonsRow = Row.extend`
margin-top: ${remcalc(29)};
margin-bottom: ${remcalc(60)};
`;
const Editor = ManifestEditor => ({ input }) => const Editor = ManifestEditor => ({ input }) =>
<ManifestEditor mode="yaml" {...input} />; <ManifestEditor mode="yaml" {...input} />;
@ -18,13 +58,13 @@ export const Name = ({ handleSubmit, onCancel, dirty }) =>
</FormGroup> </FormGroup>
</Col> </Col>
</Row> </Row>
<Row> <ButtonsRow>
<Button onClick={onCancel} secondary>Cancel</Button> <Button onClick={onCancel} secondary>Cancel</Button>
<Button type="submit" disabled={!dirty}>Next</Button> <Button type="submit" disabled={!dirty}>Next</Button>
</Row> </ButtonsRow>
</form>; </form>;
export const Manifest = ({ handleSubmit, onCancel, dirty, mode }) => export const Manifest = ({ handleSubmit, onCancel, dirty, mode, loading }) =>
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<Bundle load={() => import('joyent-manifest-editor')}> <Bundle load={() => import('joyent-manifest-editor')}>
{ManifestEditor => {ManifestEditor =>
@ -32,22 +72,35 @@ export const Manifest = ({ handleSubmit, onCancel, dirty, mode }) =>
? <Field name="manifest" component={Editor(ManifestEditor)} /> ? <Field name="manifest" component={Editor(ManifestEditor)} />
: <Dots2 />} : <Dots2 />}
</Bundle> </Bundle>
<Row> <ButtonsRow>
<Button onClick={onCancel} secondary>Cancel</Button> <Button onClick={onCancel} secondary>Cancel</Button>
<Button type="submit" disabled={!dirty}>Review</Button> <Button disabled={loading || !dirty} type="submit">
</Row> {loading ? <Dots2 /> : 'Review'}
</Button>
</ButtonsRow>
</form>; </form>;
export const Review = ({ handleSubmit, onCancel, dirty, ...state }) => export const Review = ({ handleSubmit, onCancel, dirty, ...state }) => {
<form onSubmit={handleSubmit}> const serviceList = forceArray(state.services).map(({ name, image }) =>
<pre>{state.deploymentGroupName}</pre> <ServiceCard>
<pre>{state.manifest}</pre> <Dl>
<Row> <dt><ServiceName>{name}</ServiceName></dt>
<Button onClick={onCancel} disabled={state.loading} secondary> <dt><ImageTitle>Image:</ImageTitle> <Image>{image}</Image></dt>
Cancel </Dl>
</Button> </ServiceCard>
<Button disabled={state.loading} type="submit"> );
{state.loading ? <Dots2 /> : 'Provision'}
</Button> return (
</Row> <form onSubmit={handleSubmit}>
</form>; {serviceList}
<ButtonsRow>
<Button onClick={onCancel} disabled={state.loading} secondary>
Cancel
</Button>
<Button disabled={state.loading} type="submit">
{state.loading ? <Dots2 /> : 'Confirm and Deploy'}
</Button>
</ButtonsRow>
</form>
);
};

View File

@ -8,6 +8,7 @@ import paramCase from 'param-case';
import DeploymentGroupBySlug from '@graphql/DeploymentGroupBySlug.gql'; import DeploymentGroupBySlug from '@graphql/DeploymentGroupBySlug.gql';
import DeploymentGroupCreateMutation from '@graphql/DeploymentGroupCreate.gql'; import DeploymentGroupCreateMutation from '@graphql/DeploymentGroupCreate.gql';
import DeploymentGroupProvisionMutation from '@graphql/DeploymentGroupProvision.gql'; import DeploymentGroupProvisionMutation from '@graphql/DeploymentGroupProvision.gql';
import DeploymentGroupConfigMutation from '@graphql/DeploymentGroupConfig.gql';
import { client } from '@state/store'; import { client } from '@state/store';
import { LayoutContainer } from '@components/layout'; import { LayoutContainer } from '@components/layout';
@ -52,8 +53,9 @@ const ReviewForm = reduxForm({
// state between refreshes // state between refreshes
class DeploymentGroupCreate extends Component { class DeploymentGroupCreate extends Component {
state = { state = {
deploymentGroupName: '', name: '',
manifest: '', manifest: '',
services: [],
loading: false, loading: false,
error: null error: null
}; };
@ -74,15 +76,40 @@ class DeploymentGroupCreate extends Component {
} }
handleNameSubmit({ name = '' }) { handleNameSubmit({ name = '' }) {
this.setState({ deploymentGroupName: name }, () => this.setState({ name }, () =>
this.redirect({ stage: 'manifest', prog: true }) this.redirect({ stage: 'manifest', prog: true })
); );
} }
handleManifestSubmit({ manifest = '' }) { handleManifestSubmit({ manifest = '' }) {
this.setState({ manifest }, () => const { config } = this.props;
this.redirect({ stage: 'review', prog: true }) const { name } = this.state;
);
const getConfig = async () => {
const [err, conf] = await intercept(
config({
deploymentGroupName: name,
type: 'COMPOSE',
format: 'YAML',
raw: manifest
})
);
if (err) {
return this.setState({
error: err.message
});
}
const { data } = conf;
const { config: services } = data;
this.setState({ loading: false, services }, () => {
this.redirect({ stage: 'review', prog: true });
});
};
this.setState({ manifest, loading: true }, getConfig);
} }
handleReviewSubmit() { handleReviewSubmit() {
@ -108,12 +135,10 @@ class DeploymentGroupCreate extends Component {
} }
createDeploymentGroup = async () => { createDeploymentGroup = async () => {
const { deploymentGroupName } = this.state; const { name } = this.state;
const { createDeploymentGroup } = this.props; const { createDeploymentGroup } = this.props;
const [err, res] = await intercept( const [err, res] = await intercept(createDeploymentGroup({ name }));
createDeploymentGroup({ name: deploymentGroupName })
);
if (err) { if (err) {
this.setState({ this.setState({
@ -157,6 +182,7 @@ class DeploymentGroupCreate extends Component {
<ManifestForm <ManifestForm
onSubmit={this.handleManifestSubmit} onSubmit={this.handleManifestSubmit}
onCancel={this.handleCancel} onCancel={this.handleCancel}
loading={this.state.loading}
/> />
); );
} }
@ -191,6 +217,7 @@ class DeploymentGroupCreate extends Component {
} }
render() { render() {
const { err } = this.state;
const { match } = this.props; const { match } = this.props;
const stage = match.params.stage; const stage = match.params.stage;
@ -202,11 +229,22 @@ class DeploymentGroupCreate extends Component {
return this.redirect({ stage: 'name' }); return this.redirect({ stage: 'name' });
} }
if (stage !== 'name' && !this.state.name) {
return this.redirect({ stage: 'name' });
}
if (stage === 'review' && !this.state.manifest) {
return this.redirect({ stage: 'manifest' });
}
const view = this.stages[stage](); const view = this.stages[stage]();
const error = err ? <span>{err}</span> : null;
return ( return (
<LayoutContainer> <LayoutContainer>
<H2>Creating deployment group</H2> <H2>Creating deployment group</H2>
{error}
{view} {view}
</LayoutContainer> </LayoutContainer>
); );
@ -223,5 +261,10 @@ export default compose(
props: ({ mutate }) => ({ props: ({ mutate }) => ({
provisionManifest: variables => mutate({ variables }) provisionManifest: variables => mutate({ variables })
}) })
}),
graphql(DeploymentGroupConfigMutation, {
props: ({ mutate }) => ({
config: variables => mutate({ variables })
})
}) })
)(DeploymentGroupCreate); )(DeploymentGroupCreate);

View File

@ -53,7 +53,9 @@ const InstanceListGql = graphql(InstancesQuery, {
const params = props.match.params; const params = props.match.params;
const deploymentGroupSlug = params.deploymentGroup; const deploymentGroupSlug = params.deploymentGroup;
const serviceSlug = params.service; const serviceSlug = params.service;
return { return {
pollInterval: 1000,
variables: { variables: {
deploymentGroupSlug, deploymentGroupSlug,
serviceSlug serviceSlug

View File

@ -0,0 +1,8 @@
#import "./ServiceInfo.gql"
mutation config($deploymentGroupName: String!, $type: ManifestType!, $format: ManifestFormat!, $raw: String!) {
config(deploymentGroupName: $deploymentGroupName, type: $type, format: $format, raw: $raw) {
image
...ServiceInfo
}
}