diff --git a/packages/my-joy-beta/src/components/instances/key-value.js b/packages/my-joy-beta/src/components/instances/key-value.js
index f1c95e45..3c2b745b 100644
--- a/packages/my-joy-beta/src/components/instances/key-value.js
+++ b/packages/my-joy-beta/src/components/instances/key-value.js
@@ -1,4 +1,6 @@
import React, { PureComponent } from 'react';
+import PropTypes from 'prop-types';
+import { withTheme } from 'styled-components';
import { Row, Col } from 'react-styled-flexboxgrid';
import Value from 'react-redux-values';
import { Field } from 'redux-form';
@@ -6,6 +8,7 @@ import styled from 'styled-components';
import remcalc from 'remcalc';
import titleCase from 'title-case';
import { Margin, Padding } from 'styled-components-spacing';
+import Flex, { FlexItem } from 'styled-flex-component';
import Editor from 'joyent-ui-toolkit/dist/es/editor';
import {
@@ -25,7 +28,9 @@ import {
FormMeta,
Button,
Textarea,
- Divider
+ Editor,
+ Divider,
+ DeleteIcon
} from 'joyent-ui-toolkit';
const CollapsedKeyValue = styled.span`
@@ -49,155 +54,180 @@ class ValueTextareaField extends PureComponent {
}
}
-const KeyValue = ({
- id,
- label = '',
- textarea,
- create,
- last,
- first,
- expanded,
- removing,
- pristine,
- error,
- submitting,
- onRemove,
- onToggleExpanded,
- handleSubmit,
- onClear
-}) => {
- const _error = error &&
- !submitting && (
-
- Ooops!
- {error}
-
+const TextareaKeyValue = ({ type, submitting }) => [
+
+
+
+ {titleCase(type)} key
+
+
+
+
+
+
,
+
+
+
+ {titleCase(type)} value
+
+
+
+
+
+
+];
+
+const InputKeyValue = ({ type, submitting }) => (
+
+
+
+ {titleCase(type)} key
+
+
+
+
+
+
+ {titleCase(type)} value
+
+
+
+
+
+);
+
+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 (
+
);
+ }
+);
- const _meta = expanded ? (
- {create ? `Create ${label}` : `Edit ${label}`}
- ) : (
-
- {`${input.value}: `}}
- />
- input.value} />
-
- );
-
- const chevronToggle = create ? null : (
-
-
-
- );
-
- const _valueField = textarea ? (
-
- ) : (
-
- );
-
- const _cancel = (
-
- );
-
- const _submit = (
-
- );
-
- return (
-
- );
+KeyValue.propTypes = {
+ input: PropTypes.oneOf(['input', 'textarea']).isRequired,
+ type: PropTypes.string.isRequired,
+ method: PropTypes.oneOf(['add', 'edit']).isRequired,
+ removing: PropTypes.bool.isRequired,
+ expanded: PropTypes.bool.isRequired,
+ onToggleExpanded: PropTypes.func,
+ onCancel: PropTypes.func,
+ onRemove: PropTypes.func
};
-export default ({ id, ...rest }) => (
-
- {({ value: removing }) => (
-
- )}
-
-);
+export default props => ;
diff --git a/packages/my-joy-beta/src/components/instances/metadata.js b/packages/my-joy-beta/src/components/instances/metadata.js
new file mode 100644
index 00000000..4d57054f
--- /dev/null
+++ b/packages/my-joy-beta/src/components/instances/metadata.js
@@ -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 }) => (
+
+);
+
+export const AddForm = props => (
+
+);
+
+export const EditForm = props => (
+
+);
diff --git a/packages/my-joy-beta/src/containers/instances/list.js b/packages/my-joy-beta/src/containers/instances/list.js
index b07816f1..0fce5920 100644
--- a/packages/my-joy-beta/src/containers/instances/list.js
+++ b/packages/my-joy-beta/src/containers/instances/list.js
@@ -30,6 +30,7 @@ import DisableInstanceFw from '@graphql/disable-instance-fw.gql';
import CreateSnapshot from '@graphql/create-snapshot.gql';
import StartSnapshot from '@graphql/start-from-snapshot.gql';
import Index from '@state/gen-index';
+import parseError from '@state/parse-error';
import {
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
const flipSubmitFalse = stopSubmit(TABLE_FORM_NAME, {
_error: err && parseError(err)
diff --git a/packages/my-joy-beta/src/containers/instances/metadata.js b/packages/my-joy-beta/src/containers/instances/metadata.js
index 04f61943..afc2c1a1 100644
--- a/packages/my-joy-beta/src/containers/instances/metadata.js
+++ b/packages/my-joy-beta/src/containers/instances/metadata.js
@@ -6,115 +6,121 @@ import { connect } from 'react-redux';
import { SubmissionError, reset, startSubmit, stopSubmit } from 'redux-form';
import ReduxForm from 'declarative-redux-form';
import find from 'lodash.find';
-import sortBy from 'lodash.sortby';
import get from 'lodash.get';
+import intercept from 'apr-intercept';
+import remcalc from 'remcalc';
import {
ViewContainer,
+ Title,
StatusLoader,
Message,
MessageDescription,
MessageTitle,
- Button
+ Button,
+ Divider,
+ H3
} from 'joyent-ui-toolkit';
import GetMetadata from '@graphql/list-metadata.gql';
import UpdateMetadata from '@graphql/update-metadata.gql';
import DeleteMetadata from '@graphql/delete-metadata.gql';
import { KeyValue } from '@components/instances';
+import parseError from '@state/parse-error';
-const METADATA_FORM_KEY = (name, field) => `instance-metadata-${name}-${field}`;
-const CREATE_METADATA_FORM_KEY = name => `instance-create-metadata-${name}`;
+import {
+ 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 = ({
instance,
- values = [],
+ metadata = [],
+ addOpen,
loading,
error,
- handleRemove,
- handleClear,
+ handleToggleAddOpen,
+ handleUpdateExpanded,
+ handleCancel,
+ handleCreate,
handleUpdate,
- handleCreate
+ handleRemove
}) => {
- const _loading = !(loading && !values.length) ? null : ;
+ const _loading = !(loading && !metadata.length) ? null : ;
+
+ const _add = addOpen ? (
+ handleToggleAddOpen(false)}
+ >
+ {MetadataAddForm}
+
+ ) : null;
+
+ const _line = !_loading
+ ? [
+ ,
+
+ ]
+ : null;
+
+ const _count = !_loading ? (
+
+ {metadata.length} key:value pair
+
+ ) : null;
- // metadata items forms
const _metadata =
!_loading &&
- values.map(({ form, initialValues }, i) => (
-
- {({ value: expanded, onValueChange }) => (
- handleUpdate(newValues, form)}
- destroyOnUnmount
- id={form}
- onClear={() => handleClear(form)}
- onToggleExpanded={() => onValueChange(!expanded)}
- onRemove={() => handleRemove(form)}
- label="metadata"
- last={values.length - 1 === i}
- first={i === 0}
- expanded={expanded}
- textarea
- >
- {KeyValue}
-
- )}
-
+ metadata.map(({ form, initialValues, expanded, removing }) => (
+ handleUpdateExpanded(form, !expanded)}
+ onCancel={() => handleCancel(form)}
+ onRemove={() => handleRemove(form)}
+ expanded={expanded}
+ removing={removing}
+ >
+ {MetadataEditForm}
+
));
- // create metadata form
- const _addKey = instance && CREATE_METADATA_FORM_KEY(instance.name);
- const _add = _metadata &&
- _addKey && (
-
- {({ value: expanded, onValueChange }) =>
- !expanded ? (
-
- ) : (
- handleClear(_addKey)}
- onToggleExpanded={() => onValueChange(!expanded)}
- onRemove={() => handleRemove(_addKey)}
- expanded={expanded}
- label="metadata"
- create
- textarea
- >
- {KeyValue}
-
- )}
-
- );
-
- // fetching error
const _error =
- error && !values.length && !_loading ? (
+ error && !_metadata.length && !_loading ? (
Ooops!
- An error occurred while loading your instance metadata
+ An error occurred while loading your metadata
) : null;
return (
-
- {_loading}
+
+ handleToggleAddOpen(!addOpen)}
+ >
+ {MetadataMenuForm}
+
+
+ {_line}
{_error}
- {_metadata}
+ {_loading}
{_add}
+ {_count}
+ {_metadata}
);
};
@@ -124,7 +130,7 @@ export default compose(
graphql(DeleteMetadata, { name: 'deleteMetadata' }),
graphql(GetMetadata, {
options: ({ match }) => ({
- pollInterval: 1000,
+ // pollInterval: 1000,
variables: {
name: get(match, 'params.instance')
}
@@ -133,23 +139,18 @@ export default compose(
const { name } = variables;
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 field = paramCase(name);
- const form = METADATA_FORM_KEY(name, field);
-
- return {
- form,
- initialValues: {
- name,
- value
- }
- };
- });
+ const metadata = values.map(({ name, value }) => ({
+ form: METADATA_FORM_KEY(name),
+ initialValues: {
+ name,
+ value
+ }
+ }));
return {
- values,
+ metadata,
instance,
loading,
error,
@@ -157,97 +158,136 @@ export default compose(
};
}
}),
- connect(null, (dispatch, ownProps) => {
- const {
- instance,
- values,
- refetch,
- updateMetadata,
- deleteMetadata
- } = ownProps;
+ connect(
+ ({ values }, { metadata, ownProps }) => ({
+ ...ownProps,
+ addOpen: get(values, 'add-metadata-open', false),
+ metadata: metadata.map(({ form, ...metadata }) => ({
+ ...metadata,
+ form,
+ expanded: get(values, `${form}-expanded`, false),
+ removing: get(values, `${form}-removing`, false)
+ }))
+ }),
+ (dispatch, ownProps) => {
+ const {
+ instance,
+ metadata,
+ updateMetadata,
+ deleteMetadata,
+ refetch
+ } = ownProps;
- return {
- // reset sets values to initialValues
- handleClear: form => dispatch(reset(form)),
- handleRemove: form =>
- Promise.resolve(
- // 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
+ return {
+ handleCancel: form =>
+ dispatch([
+ set({ name: `${form}-expanded`, value: false }),
+ dispatch(reset(form))
+ ]),
+ 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([
set({ name: `${form}-removing`, value: true }),
startSubmit(form)
- ])
- )
- .then(() =>
- // call mutation. get key from values' initialValues
+ ]);
+
+ // call mutation
+ const [err] = await intercept(
deleteMetadata({
variables: {
id: instance.id,
- name: get(find(values, ['form', form]), 'initialValues.name')
+ name: get(find(metadata, ['form', form]), 'initialValues.name')
}
})
- )
- // fetch metadata again
- .then(() => refetch())
- // we only flip removing and submitting when there is an 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 => {
+ );
+
+ if (err) {
+ // show mutation error
throw new SubmissionError({
- _error: error.graphQLErrors
- .map(({ message }) => message)
- .join('\n')
+ _error: parseError(err)
});
- }),
- handleCreate: ({ name, value }) =>
- // call mutation
- updateMetadata({
- variables: {
- id: instance.id,
- metadata: [{ name, value }]
}
- })
- // fetch metadata again
- .then(() => refetch())
- // reset create new metadata form
- .then(() => dispatch(reset(CREATE_METADATA_FORM_KEY(instance.name))))
- // submit is flipped once the promise is resolved
- .catch(error => {
- throw new SubmissionError({
- _error: error.graphQLErrors
- .map(({ message }) => message)
- .join('\n')
- });
- })
- };
- })
+
+ dispatch([
+ stopSubmit(form),
+ set({ name: `${form}-removing`, value: false })
+ ]);
+
+ // fetch metadata again (even though we are polling)
+ return refetch();
+ }
+ };
+ }
+ )
)(Metadata);
diff --git a/packages/ui-toolkit/src/form/group.js b/packages/ui-toolkit/src/form/group.js
index 815c35ab..f18affb8 100644
--- a/packages/ui-toolkit/src/form/group.js
+++ b/packages/ui-toolkit/src/form/group.js
@@ -28,7 +28,7 @@ class FormGroup extends Component {
}
renderGroup(inputProps) {
- const { className, style, children, ...rest } = this.props;
+ const { className, style, children, fluid = false, ...rest } = this.props;
const value = {
id: rndId(),
@@ -37,7 +37,7 @@ class FormGroup extends Component {
};
return (
-
+
{children}