feat(my-joy-beta): revise Metadata and KeyValue implementations
fixes #908
This commit is contained in:
parent
c184066f26
commit
2538453d98
@ -1,4 +1,6 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { withTheme } from 'styled-components';
|
||||||
import { Row, Col } from 'react-styled-flexboxgrid';
|
import { Row, Col } from 'react-styled-flexboxgrid';
|
||||||
import Value from 'react-redux-values';
|
import Value from 'react-redux-values';
|
||||||
import { Field } from 'redux-form';
|
import { Field } from 'redux-form';
|
||||||
@ -6,6 +8,7 @@ import styled from 'styled-components';
|
|||||||
import remcalc from 'remcalc';
|
import remcalc from 'remcalc';
|
||||||
import titleCase from 'title-case';
|
import titleCase from 'title-case';
|
||||||
import { Margin, Padding } from 'styled-components-spacing';
|
import { Margin, Padding } from 'styled-components-spacing';
|
||||||
|
import Flex, { FlexItem } from 'styled-flex-component';
|
||||||
import Editor from 'joyent-ui-toolkit/dist/es/editor';
|
import Editor from 'joyent-ui-toolkit/dist/es/editor';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -25,7 +28,9 @@ import {
|
|||||||
FormMeta,
|
FormMeta,
|
||||||
Button,
|
Button,
|
||||||
Textarea,
|
Textarea,
|
||||||
Divider
|
Editor,
|
||||||
|
Divider,
|
||||||
|
DeleteIcon
|
||||||
} from 'joyent-ui-toolkit';
|
} from 'joyent-ui-toolkit';
|
||||||
|
|
||||||
const CollapsedKeyValue = styled.span`
|
const CollapsedKeyValue = styled.span`
|
||||||
@ -49,155 +54,180 @@ class ValueTextareaField extends PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const KeyValue = ({
|
const TextareaKeyValue = ({ type, submitting }) => [
|
||||||
id,
|
<Row key="key">
|
||||||
label = '',
|
<Col xs={12}>
|
||||||
textarea,
|
<FormGroup name="name" reduxForm fluid>
|
||||||
create,
|
<FormLabel>{titleCase(type)} key</FormLabel>
|
||||||
last,
|
<Input type="text" disabled={submitting} />
|
||||||
first,
|
<FormMeta />
|
||||||
expanded,
|
</FormGroup>
|
||||||
removing,
|
<Divider height={remcalc(12)} transparent />
|
||||||
pristine,
|
</Col>
|
||||||
error,
|
</Row>,
|
||||||
submitting,
|
<Row key="value">
|
||||||
onRemove,
|
<Col xs={12}>
|
||||||
onToggleExpanded,
|
<FormGroup name="value" reduxForm fluid>
|
||||||
handleSubmit,
|
<FormLabel>{titleCase(type)} value</FormLabel>
|
||||||
onClear
|
<Field
|
||||||
}) => {
|
name="name"
|
||||||
const _error = error &&
|
fluid
|
||||||
!submitting && (
|
component={ValueTextareaField}
|
||||||
<Message error>
|
props={{ submitting }}
|
||||||
<MessageTitle>Ooops!</MessageTitle>
|
/>
|
||||||
<MessageDescription>{error}</MessageDescription>
|
<FormMeta />
|
||||||
</Message>
|
</FormGroup>
|
||||||
|
<Divider height={remcalc(12)} transparent />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
];
|
||||||
|
|
||||||
|
const InputKeyValue = ({ type, submitting }) => (
|
||||||
|
<Flex full justifyStart contentStretch>
|
||||||
|
<FlexItem basis="auto">
|
||||||
|
<FormGroup name="name" reduxForm fluid>
|
||||||
|
<FormLabel>{titleCase(type)} key</FormLabel>
|
||||||
|
<Input type="text" disabled={submitting} />
|
||||||
|
<FormMeta />
|
||||||
|
</FormGroup>
|
||||||
|
</FlexItem>
|
||||||
|
<FlexItem basis="auto">
|
||||||
|
<FormGroup name="value" reduxForm fluid>
|
||||||
|
<FormLabel>{titleCase(type)} value</FormLabel>
|
||||||
|
<Input disabled={submitting} />
|
||||||
|
<FormMeta />
|
||||||
|
</FormGroup>
|
||||||
|
</FlexItem>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
|
||||||
|
const KeyValue = withTheme(
|
||||||
|
({
|
||||||
|
input = 'input',
|
||||||
|
type = 'metadata',
|
||||||
|
method = 'add',
|
||||||
|
error = null,
|
||||||
|
expanded = true,
|
||||||
|
submitting = false,
|
||||||
|
pristine = true,
|
||||||
|
removing = false,
|
||||||
|
handleSubmit,
|
||||||
|
onToggleExpanded = () => null,
|
||||||
|
onCancel = () => null,
|
||||||
|
onRemove = () => null,
|
||||||
|
theme
|
||||||
|
}) => {
|
||||||
|
const handleHeaderClick = method === 'edit' && onToggleExpanded;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<Card collapsed={!expanded} actionable={!expanded} shadow>
|
||||||
|
<CardHeader
|
||||||
|
secondary={false}
|
||||||
|
transparent={false}
|
||||||
|
actionable={Boolean(handleHeaderClick)}
|
||||||
|
onClick={handleHeaderClick}
|
||||||
|
>
|
||||||
|
<CardHeaderMeta>
|
||||||
|
{method === 'add' ? (
|
||||||
|
<H4>{`${titleCase(method)} ${type}`}</H4>
|
||||||
|
) : (
|
||||||
|
<CollapsedKeyValue>
|
||||||
|
<Field
|
||||||
|
name="name"
|
||||||
|
type="text"
|
||||||
|
component={({ input }) =>
|
||||||
|
expanded ? (
|
||||||
|
`${input.value}: `
|
||||||
|
) : (
|
||||||
|
<b>{`${input.value}: `}</b>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Field
|
||||||
|
name="value"
|
||||||
|
type="text"
|
||||||
|
component={({ input }) => input.value}
|
||||||
|
/>
|
||||||
|
</CollapsedKeyValue>
|
||||||
|
)}
|
||||||
|
</CardHeaderMeta>
|
||||||
|
</CardHeader>
|
||||||
|
<CardOutlet>
|
||||||
|
<Padding all={1}>
|
||||||
|
{error && !submitting ? (
|
||||||
|
<Row>
|
||||||
|
<Col xs={12}>
|
||||||
|
<Message error>
|
||||||
|
<MessageTitle>Ooops!</MessageTitle>
|
||||||
|
<MessageDescription>{error}</MessageDescription>
|
||||||
|
</Message>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
) : null}
|
||||||
|
{input === 'input' ? (
|
||||||
|
<InputKeyValue type={type} submitting={submitting} />
|
||||||
|
) : (
|
||||||
|
<TextareaKeyValue type={type} submitting={submitting} />
|
||||||
|
)}
|
||||||
|
<Row between="xs" middle="xs">
|
||||||
|
<Col xs={method === 'add' ? 12 : 7}>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
onClick={onCancel}
|
||||||
|
disabled={submitting}
|
||||||
|
secondary
|
||||||
|
marginless
|
||||||
|
>
|
||||||
|
<span>Cancel</span>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={pristine}
|
||||||
|
loading={submitting && !removing}
|
||||||
|
marginless
|
||||||
|
>
|
||||||
|
<span>{method === 'add' ? 'Create' : 'Save'}</span>
|
||||||
|
</Button>
|
||||||
|
</Col>
|
||||||
|
<Col xs={method === 'add' ? false : 5}>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
onClick={onRemove}
|
||||||
|
disabled={submitting}
|
||||||
|
loading={removing}
|
||||||
|
secondary
|
||||||
|
right
|
||||||
|
icon
|
||||||
|
error
|
||||||
|
marginless
|
||||||
|
>
|
||||||
|
<DeleteIcon
|
||||||
|
disabled={submitting}
|
||||||
|
fill={submitting ? undefined : theme.red}
|
||||||
|
/>
|
||||||
|
<span>Delete</span>
|
||||||
|
</Button>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Padding>
|
||||||
|
</CardOutlet>
|
||||||
|
</Card>
|
||||||
|
<Divider height={remcalc(13)} transparent />
|
||||||
|
</form>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const _meta = expanded ? (
|
KeyValue.propTypes = {
|
||||||
<H4>{create ? `Create ${label}` : `Edit ${label}`}</H4>
|
input: PropTypes.oneOf(['input', 'textarea']).isRequired,
|
||||||
) : (
|
type: PropTypes.string.isRequired,
|
||||||
<CollapsedKeyValue>
|
method: PropTypes.oneOf(['add', 'edit']).isRequired,
|
||||||
<Field
|
removing: PropTypes.bool.isRequired,
|
||||||
name="name"
|
expanded: PropTypes.bool.isRequired,
|
||||||
type="text"
|
onToggleExpanded: PropTypes.func,
|
||||||
component={({ input }) => <b>{`${input.value}: `}</b>}
|
onCancel: PropTypes.func,
|
||||||
/>
|
onRemove: PropTypes.func
|
||||||
<Field name="value" type="text" component={({ input }) => input.value} />
|
|
||||||
</CollapsedKeyValue>
|
|
||||||
);
|
|
||||||
|
|
||||||
const chevronToggle = create ? null : (
|
|
||||||
<CardHeaderBox onClick={onToggleExpanded} actionable={expanded}>
|
|
||||||
<ChevronIcon />
|
|
||||||
</CardHeaderBox>
|
|
||||||
);
|
|
||||||
|
|
||||||
const _valueField = textarea ? (
|
|
||||||
<Field
|
|
||||||
name="name"
|
|
||||||
fluid
|
|
||||||
component={ValueTextareaField}
|
|
||||||
props={{ submitting }}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Input disabled={submitting} />
|
|
||||||
);
|
|
||||||
|
|
||||||
const _cancel = (
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
key="cancel"
|
|
||||||
bold
|
|
||||||
onClick={
|
|
||||||
create
|
|
||||||
? pristine ? onToggleExpanded : onClear
|
|
||||||
: pristine ? onRemove : onClear
|
|
||||||
}
|
|
||||||
disabled={submitting}
|
|
||||||
loading={submitting && removing}
|
|
||||||
secondary
|
|
||||||
marginless
|
|
||||||
>
|
|
||||||
{create ? (pristine ? 'Cancel' : 'Clear') : pristine ? 'Remove' : 'Clear'}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
|
|
||||||
const _submit = (
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
key="submit"
|
|
||||||
bold
|
|
||||||
disabled={pristine || submitting}
|
|
||||||
loading={submitting && !removing}
|
|
||||||
marginless
|
|
||||||
>
|
|
||||||
{create ? 'Create' : 'Update'}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<form onSubmit={handleSubmit}>
|
|
||||||
<Divider
|
|
||||||
transparent
|
|
||||||
marginBottom={!first && expanded ? remcalc(13) : 0}
|
|
||||||
/>
|
|
||||||
<Card
|
|
||||||
collapsed={!expanded}
|
|
||||||
actionable={!expanded}
|
|
||||||
bottomless={!last && !expanded}
|
|
||||||
>
|
|
||||||
<CardHeader
|
|
||||||
secondary={false}
|
|
||||||
transparent={false}
|
|
||||||
onClick={onToggleExpanded}
|
|
||||||
actionable
|
|
||||||
>
|
|
||||||
<CardHeaderMeta>
|
|
||||||
<Padding left={1}>{_meta}</Padding>
|
|
||||||
</CardHeaderMeta>
|
|
||||||
{chevronToggle}
|
|
||||||
</CardHeader>
|
|
||||||
<CardOutlet>
|
|
||||||
<Padding all={1}>
|
|
||||||
<Row>
|
|
||||||
<Col xs={12}>{_error}</Col>
|
|
||||||
</Row>
|
|
||||||
<Row>
|
|
||||||
<Col xs={6}>
|
|
||||||
<FormGroup name="name" field={Field} fluid>
|
|
||||||
<FormLabel>Enter {titleCase(label)} key</FormLabel>
|
|
||||||
<Input type="text" disabled={submitting} />
|
|
||||||
<FormMeta />
|
|
||||||
</FormGroup>
|
|
||||||
</Col>
|
|
||||||
<Col xs={6}>
|
|
||||||
<FormGroup name="value" field={Field} fluid>
|
|
||||||
<FormLabel>Enter {titleCase(label)} value</FormLabel>
|
|
||||||
{_valueField}
|
|
||||||
</FormGroup>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row>
|
|
||||||
<Col xs={12}>
|
|
||||||
<Margin top={2}>
|
|
||||||
{_cancel}
|
|
||||||
{_submit}
|
|
||||||
</Margin>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</Padding>
|
|
||||||
</CardOutlet>
|
|
||||||
</Card>
|
|
||||||
<Divider transparent marginBottom={last || expanded ? remcalc(13) : 0} />
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ({ id, ...rest }) => (
|
export default props => <KeyValue {...props} />;
|
||||||
<Value name={`${id}-removing`}>
|
|
||||||
{({ value: removing }) => (
|
|
||||||
<KeyValue {...rest} removing={removing} id={id} />
|
|
||||||
)}
|
|
||||||
</Value>
|
|
||||||
);
|
|
||||||
|
47
packages/my-joy-beta/src/components/instances/metadata.js
Normal file
47
packages/my-joy-beta/src/components/instances/metadata.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import KeyValue from './key-value';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
FormGroup,
|
||||||
|
Input,
|
||||||
|
FormLabel,
|
||||||
|
Button
|
||||||
|
} from 'joyent-ui-toolkit';
|
||||||
|
|
||||||
|
export const MenuForm = ({ searchable, onAdd }) => (
|
||||||
|
<form>
|
||||||
|
<Row>
|
||||||
|
<Col xs={7} sm={5}>
|
||||||
|
<FormGroup name="filter" fluid reduxForm>
|
||||||
|
<FormLabel>Filter</FormLabel>
|
||||||
|
<Input disabled={!searchable} fluid />
|
||||||
|
</FormGroup>
|
||||||
|
</Col>
|
||||||
|
<Col xs={5} sm={7}>
|
||||||
|
<FormGroup right>
|
||||||
|
<FormLabel>⁣</FormLabel>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
disabled={!searchable}
|
||||||
|
onClick={onAdd}
|
||||||
|
small
|
||||||
|
icon
|
||||||
|
fluid
|
||||||
|
>
|
||||||
|
Add metadata
|
||||||
|
</Button>
|
||||||
|
</FormGroup>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const AddForm = props => (
|
||||||
|
<KeyValue {...props} method="add" input="textarea" type="metadata" expanded />
|
||||||
|
);
|
||||||
|
|
||||||
|
export const EditForm = props => (
|
||||||
|
<KeyValue {...props} method="edit" input="textarea" type="metadata" />
|
||||||
|
);
|
@ -30,6 +30,7 @@ import DisableInstanceFw from '@graphql/disable-instance-fw.gql';
|
|||||||
import CreateSnapshot from '@graphql/create-snapshot.gql';
|
import CreateSnapshot from '@graphql/create-snapshot.gql';
|
||||||
import StartSnapshot from '@graphql/start-from-snapshot.gql';
|
import StartSnapshot from '@graphql/start-from-snapshot.gql';
|
||||||
import Index from '@state/gen-index';
|
import Index from '@state/gen-index';
|
||||||
|
import parseError from '@state/parse-error';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
default as InstanceList,
|
default as InstanceList,
|
||||||
@ -264,12 +265,6 @@ export default compose(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// parses the error to handle existance or not of graphQLErrors
|
|
||||||
const parseError = ({ graphQLErrors = [], message = '' }) =>
|
|
||||||
graphQLErrors.length
|
|
||||||
? graphQLErrors.map(({ message }) => message).join('\n')
|
|
||||||
: message;
|
|
||||||
|
|
||||||
// reverts submitting flag to false and propagates the error if it exists
|
// reverts submitting flag to false and propagates the error if it exists
|
||||||
const flipSubmitFalse = stopSubmit(TABLE_FORM_NAME, {
|
const flipSubmitFalse = stopSubmit(TABLE_FORM_NAME, {
|
||||||
_error: err && parseError(err)
|
_error: err && parseError(err)
|
||||||
|
@ -6,115 +6,121 @@ import { connect } from 'react-redux';
|
|||||||
import { SubmissionError, reset, startSubmit, stopSubmit } from 'redux-form';
|
import { SubmissionError, reset, startSubmit, stopSubmit } from 'redux-form';
|
||||||
import ReduxForm from 'declarative-redux-form';
|
import ReduxForm from 'declarative-redux-form';
|
||||||
import find from 'lodash.find';
|
import find from 'lodash.find';
|
||||||
import sortBy from 'lodash.sortby';
|
|
||||||
import get from 'lodash.get';
|
import get from 'lodash.get';
|
||||||
|
import intercept from 'apr-intercept';
|
||||||
|
import remcalc from 'remcalc';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ViewContainer,
|
ViewContainer,
|
||||||
|
Title,
|
||||||
StatusLoader,
|
StatusLoader,
|
||||||
Message,
|
Message,
|
||||||
MessageDescription,
|
MessageDescription,
|
||||||
MessageTitle,
|
MessageTitle,
|
||||||
Button
|
Button,
|
||||||
|
Divider,
|
||||||
|
H3
|
||||||
} from 'joyent-ui-toolkit';
|
} from 'joyent-ui-toolkit';
|
||||||
|
|
||||||
import GetMetadata from '@graphql/list-metadata.gql';
|
import GetMetadata from '@graphql/list-metadata.gql';
|
||||||
import UpdateMetadata from '@graphql/update-metadata.gql';
|
import UpdateMetadata from '@graphql/update-metadata.gql';
|
||||||
import DeleteMetadata from '@graphql/delete-metadata.gql';
|
import DeleteMetadata from '@graphql/delete-metadata.gql';
|
||||||
import { KeyValue } from '@components/instances';
|
import { KeyValue } from '@components/instances';
|
||||||
|
import parseError from '@state/parse-error';
|
||||||
|
|
||||||
const METADATA_FORM_KEY = (name, field) => `instance-metadata-${name}-${field}`;
|
import {
|
||||||
const CREATE_METADATA_FORM_KEY = name => `instance-create-metadata-${name}`;
|
MenuForm as MetadataMenuForm,
|
||||||
|
AddForm as MetadataAddForm,
|
||||||
|
EditForm as MetadataEditForm
|
||||||
|
} from '@components/instances/metadata';
|
||||||
|
|
||||||
|
const MENU_FORM_NAME = 'instance-metadata-list-menu';
|
||||||
|
const ADD_FORM_NAME = 'instance-metadata-add-new';
|
||||||
|
const METADATA_FORM_KEY = field => `instance-metadata-${paramCase(field)}`;
|
||||||
|
|
||||||
const Metadata = ({
|
const Metadata = ({
|
||||||
instance,
|
instance,
|
||||||
values = [],
|
metadata = [],
|
||||||
|
addOpen,
|
||||||
loading,
|
loading,
|
||||||
error,
|
error,
|
||||||
handleRemove,
|
handleToggleAddOpen,
|
||||||
handleClear,
|
handleUpdateExpanded,
|
||||||
|
handleCancel,
|
||||||
|
handleCreate,
|
||||||
handleUpdate,
|
handleUpdate,
|
||||||
handleCreate
|
handleRemove
|
||||||
}) => {
|
}) => {
|
||||||
const _loading = !(loading && !values.length) ? null : <StatusLoader />;
|
const _loading = !(loading && !metadata.length) ? null : <StatusLoader />;
|
||||||
|
|
||||||
|
const _add = addOpen ? (
|
||||||
|
<ReduxForm
|
||||||
|
form={ADD_FORM_NAME}
|
||||||
|
onSubmit={handleCreate}
|
||||||
|
onCancel={() => handleToggleAddOpen(false)}
|
||||||
|
>
|
||||||
|
{MetadataAddForm}
|
||||||
|
</ReduxForm>
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
const _line = !_loading
|
||||||
|
? [
|
||||||
|
<Divider key="line" height={remcalc(1)} />,
|
||||||
|
<Divider key="after-line-space" height={remcalc(24)} transparent />
|
||||||
|
]
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const _count = !_loading ? (
|
||||||
|
<H3 marginBottom={remcalc(24)} marginTop={addOpen && remcalc(24)}>
|
||||||
|
{metadata.length} key:value pair
|
||||||
|
</H3>
|
||||||
|
) : null;
|
||||||
|
|
||||||
// metadata items forms
|
|
||||||
const _metadata =
|
const _metadata =
|
||||||
!_loading &&
|
!_loading &&
|
||||||
values.map(({ form, initialValues }, i) => (
|
metadata.map(({ form, initialValues, expanded, removing }) => (
|
||||||
<Value name={`${form}-expanded`} key={form}>
|
<ReduxForm
|
||||||
{({ value: expanded, onValueChange }) => (
|
form={form}
|
||||||
<ReduxForm
|
key={form}
|
||||||
form={form}
|
initialValues={initialValues}
|
||||||
initialValues={initialValues}
|
destroyOnUnmount={false}
|
||||||
onSubmit={newValues => handleUpdate(newValues, form)}
|
onSubmit={handleUpdate}
|
||||||
destroyOnUnmount
|
onToggleExpanded={() => handleUpdateExpanded(form, !expanded)}
|
||||||
id={form}
|
onCancel={() => handleCancel(form)}
|
||||||
onClear={() => handleClear(form)}
|
onRemove={() => handleRemove(form)}
|
||||||
onToggleExpanded={() => onValueChange(!expanded)}
|
expanded={expanded}
|
||||||
onRemove={() => handleRemove(form)}
|
removing={removing}
|
||||||
label="metadata"
|
>
|
||||||
last={values.length - 1 === i}
|
{MetadataEditForm}
|
||||||
first={i === 0}
|
</ReduxForm>
|
||||||
expanded={expanded}
|
|
||||||
textarea
|
|
||||||
>
|
|
||||||
{KeyValue}
|
|
||||||
</ReduxForm>
|
|
||||||
)}
|
|
||||||
</Value>
|
|
||||||
));
|
));
|
||||||
|
|
||||||
// create metadata form
|
|
||||||
const _addKey = instance && CREATE_METADATA_FORM_KEY(instance.name);
|
|
||||||
const _add = _metadata &&
|
|
||||||
_addKey && (
|
|
||||||
<Value name={`${_addKey}-expanded`}>
|
|
||||||
{({ value: expanded, onValueChange }) =>
|
|
||||||
!expanded ? (
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
onClick={() => onValueChange(!expanded)}
|
|
||||||
secondary
|
|
||||||
>
|
|
||||||
Add metadata
|
|
||||||
</Button>
|
|
||||||
) : (
|
|
||||||
<ReduxForm
|
|
||||||
form={_addKey}
|
|
||||||
onSubmit={handleCreate}
|
|
||||||
id={_addKey}
|
|
||||||
onClear={() => handleClear(_addKey)}
|
|
||||||
onToggleExpanded={() => onValueChange(!expanded)}
|
|
||||||
onRemove={() => handleRemove(_addKey)}
|
|
||||||
expanded={expanded}
|
|
||||||
label="metadata"
|
|
||||||
create
|
|
||||||
textarea
|
|
||||||
>
|
|
||||||
{KeyValue}
|
|
||||||
</ReduxForm>
|
|
||||||
)}
|
|
||||||
</Value>
|
|
||||||
);
|
|
||||||
|
|
||||||
// fetching error
|
|
||||||
const _error =
|
const _error =
|
||||||
error && !values.length && !_loading ? (
|
error && !_metadata.length && !_loading ? (
|
||||||
<Message error>
|
<Message error>
|
||||||
<MessageTitle>Ooops!</MessageTitle>
|
<MessageTitle>Ooops!</MessageTitle>
|
||||||
<MessageDescription>
|
<MessageDescription>
|
||||||
An error occurred while loading your instance metadata
|
An error occurred while loading your metadata
|
||||||
</MessageDescription>
|
</MessageDescription>
|
||||||
</Message>
|
</Message>
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ViewContainer center={Boolean(_loading)} main>
|
<ViewContainer main>
|
||||||
{_loading}
|
<ReduxForm
|
||||||
|
form={MENU_FORM_NAME}
|
||||||
|
searchable={!_loading}
|
||||||
|
onAdd={() => handleToggleAddOpen(!addOpen)}
|
||||||
|
>
|
||||||
|
{MetadataMenuForm}
|
||||||
|
</ReduxForm>
|
||||||
|
<Divider height={remcalc(11)} transparent />
|
||||||
|
{_line}
|
||||||
{_error}
|
{_error}
|
||||||
{_metadata}
|
{_loading}
|
||||||
{_add}
|
{_add}
|
||||||
|
{_count}
|
||||||
|
{_metadata}
|
||||||
</ViewContainer>
|
</ViewContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -124,7 +130,7 @@ export default compose(
|
|||||||
graphql(DeleteMetadata, { name: 'deleteMetadata' }),
|
graphql(DeleteMetadata, { name: 'deleteMetadata' }),
|
||||||
graphql(GetMetadata, {
|
graphql(GetMetadata, {
|
||||||
options: ({ match }) => ({
|
options: ({ match }) => ({
|
||||||
pollInterval: 1000,
|
// pollInterval: 1000,
|
||||||
variables: {
|
variables: {
|
||||||
name: get(match, 'params.instance')
|
name: get(match, 'params.instance')
|
||||||
}
|
}
|
||||||
@ -133,23 +139,18 @@ export default compose(
|
|||||||
const { name } = variables;
|
const { name } = variables;
|
||||||
|
|
||||||
const instance = find(get(rest, 'machines', []), ['name', name]);
|
const instance = find(get(rest, 'machines', []), ['name', name]);
|
||||||
const metadata = get(instance, 'metadata', []);
|
const values = get(instance, 'metadata', []);
|
||||||
|
|
||||||
const values = sortBy(metadata, 'name').map(({ name, value }) => {
|
const metadata = values.map(({ name, value }) => ({
|
||||||
const field = paramCase(name);
|
form: METADATA_FORM_KEY(name),
|
||||||
const form = METADATA_FORM_KEY(name, field);
|
initialValues: {
|
||||||
|
name,
|
||||||
return {
|
value
|
||||||
form,
|
}
|
||||||
initialValues: {
|
}));
|
||||||
name,
|
|
||||||
value
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
values,
|
metadata,
|
||||||
instance,
|
instance,
|
||||||
loading,
|
loading,
|
||||||
error,
|
error,
|
||||||
@ -157,97 +158,136 @@ export default compose(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
connect(null, (dispatch, ownProps) => {
|
connect(
|
||||||
const {
|
({ values }, { metadata, ownProps }) => ({
|
||||||
instance,
|
...ownProps,
|
||||||
values,
|
addOpen: get(values, 'add-metadata-open', false),
|
||||||
refetch,
|
metadata: metadata.map(({ form, ...metadata }) => ({
|
||||||
updateMetadata,
|
...metadata,
|
||||||
deleteMetadata
|
form,
|
||||||
} = ownProps;
|
expanded: get(values, `${form}-expanded`, false),
|
||||||
|
removing: get(values, `${form}-removing`, false)
|
||||||
|
}))
|
||||||
|
}),
|
||||||
|
(dispatch, ownProps) => {
|
||||||
|
const {
|
||||||
|
instance,
|
||||||
|
metadata,
|
||||||
|
updateMetadata,
|
||||||
|
deleteMetadata,
|
||||||
|
refetch
|
||||||
|
} = ownProps;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// reset sets values to initialValues
|
handleCancel: form =>
|
||||||
handleClear: form => dispatch(reset(form)),
|
dispatch([
|
||||||
handleRemove: form =>
|
set({ name: `${form}-expanded`, value: false }),
|
||||||
Promise.resolve(
|
dispatch(reset(form))
|
||||||
// set removing=true (so that we can have a specific removing spinner)
|
]),
|
||||||
// because remove button is not a submit button, we have to manually flip that flag
|
handleToggleAddOpen: value =>
|
||||||
|
dispatch(set({ name: `add-metadata-open`, value })),
|
||||||
|
handleUpdateExpanded: (form, expanded) =>
|
||||||
|
dispatch(set({ name: `${form}-expanded`, value: expanded })),
|
||||||
|
handleCreate: async ({ name, value }) => {
|
||||||
|
// call mutation
|
||||||
|
const [err] = await intercept(
|
||||||
|
updateMetadata({
|
||||||
|
variables: {
|
||||||
|
id: instance.id,
|
||||||
|
metadata: [{ name, value }]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
// show mutation error
|
||||||
|
throw new SubmissionError({
|
||||||
|
_error: parseError(err)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch([
|
||||||
|
// reset create new metadata form
|
||||||
|
reset(ADD_FORM_NAME),
|
||||||
|
stopSubmit(ADD_FORM_NAME),
|
||||||
|
// close add form
|
||||||
|
set({ name: `add-metadata-open`, value: false })
|
||||||
|
]);
|
||||||
|
|
||||||
|
// fetch metadata again (even though we are polling)
|
||||||
|
return refetch();
|
||||||
|
},
|
||||||
|
handleUpdate: async ({ name, value }, _, { form }) => {
|
||||||
|
// call mutations
|
||||||
|
const [err] = await intercept(
|
||||||
|
Promise.all([
|
||||||
|
deleteMetadata({
|
||||||
|
variables: {
|
||||||
|
id: instance.id,
|
||||||
|
name: get(
|
||||||
|
find(metadata, ['form', form]),
|
||||||
|
'initialValues.name'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
updateMetadata({
|
||||||
|
variables: {
|
||||||
|
id: instance.id,
|
||||||
|
metadata: [{ name, value }]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
// show mutation error
|
||||||
|
throw new SubmissionError({
|
||||||
|
_error: parseError(err)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch([
|
||||||
|
// reset form
|
||||||
|
stopSubmit(form),
|
||||||
|
// close card
|
||||||
|
set({ name: `${form}-expanded`, value: false })
|
||||||
|
]);
|
||||||
|
|
||||||
|
// fetch metadata again (even though we are polling)
|
||||||
|
return refetch();
|
||||||
|
},
|
||||||
|
handleRemove: async form => {
|
||||||
dispatch([
|
dispatch([
|
||||||
set({ name: `${form}-removing`, value: true }),
|
set({ name: `${form}-removing`, value: true }),
|
||||||
startSubmit(form)
|
startSubmit(form)
|
||||||
])
|
]);
|
||||||
)
|
|
||||||
.then(() =>
|
// call mutation
|
||||||
// call mutation. get key from values' initialValues
|
const [err] = await intercept(
|
||||||
deleteMetadata({
|
deleteMetadata({
|
||||||
variables: {
|
variables: {
|
||||||
id: instance.id,
|
id: instance.id,
|
||||||
name: get(find(values, ['form', form]), 'initialValues.name')
|
name: get(find(metadata, ['form', form]), 'initialValues.name')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
);
|
||||||
// fetch metadata again
|
|
||||||
.then(() => refetch())
|
if (err) {
|
||||||
// we only flip removing and submitting when there is an error.
|
// show mutation error
|
||||||
// the reason for that is that metadata is updated asyncronously and
|
|
||||||
// it takes longer to have an efect than the mutation
|
|
||||||
.catch(error =>
|
|
||||||
dispatch([
|
|
||||||
set({ name: `${form}-removing`, value: false }),
|
|
||||||
stopSubmit(form, {
|
|
||||||
_error: error.graphQLErrors
|
|
||||||
.map(({ message }) => message)
|
|
||||||
.join('\n')
|
|
||||||
})
|
|
||||||
])
|
|
||||||
),
|
|
||||||
handleUpdate: ({ name, value }, form) =>
|
|
||||||
// call mutation. delete existing metadata, add new
|
|
||||||
Promise.all([
|
|
||||||
deleteMetadata({
|
|
||||||
variables: {
|
|
||||||
id: instance.id,
|
|
||||||
name: get(find(values, ['form', form]), 'initialValues.name')
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
updateMetadata({
|
|
||||||
variables: {
|
|
||||||
id: instance.id,
|
|
||||||
metadata: [{ name, value }]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
])
|
|
||||||
// fetch metadata again
|
|
||||||
.then(() => refetch())
|
|
||||||
// submit is flipped once the promise is resolved
|
|
||||||
.catch(error => {
|
|
||||||
throw new SubmissionError({
|
throw new SubmissionError({
|
||||||
_error: error.graphQLErrors
|
_error: parseError(err)
|
||||||
.map(({ message }) => message)
|
|
||||||
.join('\n')
|
|
||||||
});
|
});
|
||||||
}),
|
|
||||||
handleCreate: ({ name, value }) =>
|
|
||||||
// call mutation
|
|
||||||
updateMetadata({
|
|
||||||
variables: {
|
|
||||||
id: instance.id,
|
|
||||||
metadata: [{ name, value }]
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
// fetch metadata again
|
dispatch([
|
||||||
.then(() => refetch())
|
stopSubmit(form),
|
||||||
// reset create new metadata form
|
set({ name: `${form}-removing`, value: false })
|
||||||
.then(() => dispatch(reset(CREATE_METADATA_FORM_KEY(instance.name))))
|
]);
|
||||||
// submit is flipped once the promise is resolved
|
|
||||||
.catch(error => {
|
// fetch metadata again (even though we are polling)
|
||||||
throw new SubmissionError({
|
return refetch();
|
||||||
_error: error.graphQLErrors
|
}
|
||||||
.map(({ message }) => message)
|
};
|
||||||
.join('\n')
|
}
|
||||||
});
|
)
|
||||||
})
|
|
||||||
};
|
|
||||||
})
|
|
||||||
)(Metadata);
|
)(Metadata);
|
||||||
|
@ -28,7 +28,7 @@ class FormGroup extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderGroup(inputProps) {
|
renderGroup(inputProps) {
|
||||||
const { className, style, children, ...rest } = this.props;
|
const { className, style, children, fluid = false, ...rest } = this.props;
|
||||||
|
|
||||||
const value = {
|
const value = {
|
||||||
id: rndId(),
|
id: rndId(),
|
||||||
@ -37,7 +37,7 @@ class FormGroup extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper className={className} style={style} {...rest}>
|
<Wrapper className={className} style={style} fluid={fluid} {...rest}>
|
||||||
<Broadcast channel="input-group" value={value}>
|
<Broadcast channel="input-group" value={value}>
|
||||||
<Noop>{children}</Noop>
|
<Noop>{children}</Noop>
|
||||||
</Broadcast>
|
</Broadcast>
|
||||||
|
Loading…
Reference in New Issue
Block a user