mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
feat: UI for view, create and edit context fields (#204)
* feat: UI for view, create and edit context fields * fix: lint
This commit is contained in:
parent
03b4ec9751
commit
d7f9b892a3
@ -74,9 +74,8 @@ export const FormButtons = ({ submitText = 'Create', onCancel }) => (
|
|||||||
{submitText}
|
{submitText}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button type="cancel" ripple raised onClick={onCancel} style={{ float: 'right' }}>
|
<Button type="cancel" onClick={onCancel}>
|
||||||
<Icon name="cancel" />
|
Cancel
|
||||||
Cancel
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -12,3 +12,11 @@ export const formatFullDateTimeWithLocale = (v, locale, tz) => {
|
|||||||
}
|
}
|
||||||
return new Date(v).toLocaleString(locale, dateTimeOptions);
|
return new Date(v).toLocaleString(locale, dateTimeOptions);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const trim = value => {
|
||||||
|
if (value && value.trim) {
|
||||||
|
return value.trim();
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
25
frontend/src/component/context/create-context-container.js
Normal file
25
frontend/src/component/context/create-context-container.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { connect } from 'react-redux';
|
||||||
|
import ContextComponent from './form-context-component';
|
||||||
|
import { createContextField, validateName } from './../../store/context/actions';
|
||||||
|
|
||||||
|
const mapStateToProps = (state, props) => {
|
||||||
|
let contextField = { name: '', description: '', legalValues: [] };
|
||||||
|
if (props.contextFieldName) {
|
||||||
|
contextField = state.context.toJS().find(n => n.name === props.contextFieldName);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
contextField,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
validateName,
|
||||||
|
submit: contextField => createContextField(contextField)(dispatch),
|
||||||
|
});
|
||||||
|
|
||||||
|
const FormAddContainer = connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(ContextComponent);
|
||||||
|
|
||||||
|
export default FormAddContainer;
|
26
frontend/src/component/context/edit-context-container.js
Normal file
26
frontend/src/component/context/edit-context-container.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { connect } from 'react-redux';
|
||||||
|
import ContextComponent from './form-context-component';
|
||||||
|
import { updateContextField, validateName } from './../../store/context/actions';
|
||||||
|
|
||||||
|
const mapStateToProps = (state, props) => {
|
||||||
|
const contextFieldBase = { name: '', description: '', legalValues: [] };
|
||||||
|
const field = state.context.toJS().find(n => n.name === props.contextFieldName);
|
||||||
|
const contextField = Object.assign(contextFieldBase, field);
|
||||||
|
|
||||||
|
return {
|
||||||
|
contextField,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
validateName,
|
||||||
|
submit: contextField => updateContextField(contextField)(dispatch),
|
||||||
|
editMode: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const FormAddContainer = connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(ContextComponent);
|
||||||
|
|
||||||
|
export default FormAddContainer;
|
171
frontend/src/component/context/form-context-component.jsx
Normal file
171
frontend/src/component/context/form-context-component.jsx
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Button, Chip, Textfield, Card, CardTitle, CardText, CardActions } from 'react-mdl';
|
||||||
|
|
||||||
|
import { FormButtons, styles as commonStyles } from '../common';
|
||||||
|
import { trim } from '../common/util';
|
||||||
|
|
||||||
|
class AddContextComponent extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
contextField: props.contextField,
|
||||||
|
errors: {},
|
||||||
|
currentLegalValue: '',
|
||||||
|
dirty: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDerivedStateFromProps(props, state) {
|
||||||
|
if (!state.contextField.name && props.contextField.name) {
|
||||||
|
return { contextField: props.contextField };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setValue = (field, value) => {
|
||||||
|
const { contextField } = this.state;
|
||||||
|
contextField[field] = value;
|
||||||
|
this.setState({ contextField, dirty: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
validateContextName = async name => {
|
||||||
|
const { errors } = this.state;
|
||||||
|
const { validateName } = this.props;
|
||||||
|
try {
|
||||||
|
await validateName(name);
|
||||||
|
errors.name = undefined;
|
||||||
|
} catch (err) {
|
||||||
|
errors.name = err.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ errors });
|
||||||
|
};
|
||||||
|
|
||||||
|
onCancel = evt => {
|
||||||
|
evt.preventDefault();
|
||||||
|
this.props.history.push('/context');
|
||||||
|
};
|
||||||
|
|
||||||
|
onSubmit = evt => {
|
||||||
|
evt.preventDefault();
|
||||||
|
const { contextField } = this.state;
|
||||||
|
this.props.submit(contextField).then(() => this.props.history.push('/context'));
|
||||||
|
};
|
||||||
|
|
||||||
|
updateCurrentLegalValue = evt => {
|
||||||
|
this.setState({ currentLegalValue: trim(evt.target.value) });
|
||||||
|
};
|
||||||
|
|
||||||
|
addLegalValue = evt => {
|
||||||
|
evt.preventDefault();
|
||||||
|
const { contextField, currentLegalValue, errors } = this.state;
|
||||||
|
|
||||||
|
if (contextField.legalValues.indexOf(currentLegalValue) !== -1) {
|
||||||
|
errors.currentLegalValue = 'Duplicate legal value';
|
||||||
|
this.setState({ errors });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const legalValues = contextField.legalValues.concat(trim(currentLegalValue));
|
||||||
|
contextField.legalValues = legalValues;
|
||||||
|
this.setState({
|
||||||
|
contextField,
|
||||||
|
currentLegalValue: '',
|
||||||
|
errors: {},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
removeLegalValue = index => {
|
||||||
|
const { contextField } = this.state;
|
||||||
|
const legalValues = contextField.legalValues.filter((_, i) => i !== index);
|
||||||
|
contextField.legalValues = legalValues;
|
||||||
|
this.setState({ contextField });
|
||||||
|
};
|
||||||
|
|
||||||
|
renderLegalValue = (value, index) => (
|
||||||
|
<Chip
|
||||||
|
key={`${value}:${index}`}
|
||||||
|
className="mdl-color--blue-grey-100"
|
||||||
|
style={{ marginRight: '4px' }}
|
||||||
|
onClose={() => this.removeLegalValue(index)}
|
||||||
|
>
|
||||||
|
{value}
|
||||||
|
</Chip>
|
||||||
|
);
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { contextField, errors } = this.state;
|
||||||
|
const { editMode } = this.props;
|
||||||
|
const submitText = editMode ? 'Update' : 'Create';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card shadow={0} className={commonStyles.fullwidth} style={{ overflow: 'visible' }}>
|
||||||
|
<CardTitle style={{ paddingTop: '24px', paddingBottom: '0', wordBreak: 'break-all' }}>
|
||||||
|
Create context field
|
||||||
|
</CardTitle>
|
||||||
|
<CardText>
|
||||||
|
Context fields are a basic building block used in Unleash to control roll-out. They can be used
|
||||||
|
together with strategy constraints as part of the activation strategy evaluation.
|
||||||
|
</CardText>
|
||||||
|
<form onSubmit={this.onSubmit}>
|
||||||
|
<section style={{ padding: '16px' }}>
|
||||||
|
<Textfield
|
||||||
|
floatingLabel
|
||||||
|
label="Name"
|
||||||
|
name="name"
|
||||||
|
value={contextField.name}
|
||||||
|
error={errors.name}
|
||||||
|
disabled={editMode}
|
||||||
|
onBlur={v => this.validateContextName(v.target.value)}
|
||||||
|
onChange={v => this.setValue('name', trim(v.target.value))}
|
||||||
|
/>
|
||||||
|
<Textfield
|
||||||
|
floatingLabel
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
rows={1}
|
||||||
|
label="Description"
|
||||||
|
error={errors.description}
|
||||||
|
value={contextField.description}
|
||||||
|
onChange={v => this.setValue('description', v.target.value)}
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<section style={{ padding: '16px', background: '#fafafa' }}>
|
||||||
|
<h6 style={{ marginTop: '0' }}>Legal values</h6>
|
||||||
|
<p style={{ color: 'rgba(0,0,0,.54)' }}>
|
||||||
|
By defining the legal values the Unleash Admin UI will validate the user input. A
|
||||||
|
concrete example would be that we know all values for our “environment” (local,
|
||||||
|
development, stage, production).
|
||||||
|
</p>
|
||||||
|
<Textfield
|
||||||
|
floatingLabel
|
||||||
|
label="Value"
|
||||||
|
name="value"
|
||||||
|
style={{ width: '130px' }}
|
||||||
|
value={this.state.currentLegalValue}
|
||||||
|
error={errors.currentLegalValue}
|
||||||
|
onChange={this.updateCurrentLegalValue}
|
||||||
|
/>
|
||||||
|
<Button onClick={this.addLegalValue}>Add</Button>
|
||||||
|
<div>{contextField.legalValues.map(this.renderLegalValue)}</div>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
<CardActions>
|
||||||
|
<FormButtons submitText={submitText} onCancel={this.onCancel} />
|
||||||
|
</CardActions>
|
||||||
|
</form>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AddContextComponent.propTypes = {
|
||||||
|
contextField: PropTypes.object.isRequired,
|
||||||
|
validateName: PropTypes.func.isRequired,
|
||||||
|
fetchContext: PropTypes.func.isRequired,
|
||||||
|
submit: PropTypes.func.isRequired,
|
||||||
|
history: PropTypes.object.isRequired,
|
||||||
|
editMode: PropTypes.bool.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddContextComponent;
|
80
frontend/src/component/context/list-component.jsx
Normal file
80
frontend/src/component/context/list-component.jsx
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { List, ListItem, ListItemAction, ListItemContent, IconButton, Card } from 'react-mdl';
|
||||||
|
import { HeaderTitle, styles as commonStyles } from '../common';
|
||||||
|
import { CREATE_CONTEXT_FIELD, DELETE_CONTEXT_FIELD } from '../../permissions';
|
||||||
|
|
||||||
|
class ContextFieldListComponent extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
contextFields: PropTypes.array.isRequired,
|
||||||
|
fetchContext: PropTypes.func.isRequired,
|
||||||
|
removeContextField: PropTypes.func.isRequired,
|
||||||
|
history: PropTypes.object.isRequired,
|
||||||
|
hasPermission: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
// this.props.fetchContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
removeContextField = (contextField, evt) => {
|
||||||
|
evt.preventDefault();
|
||||||
|
this.props.removeContextField(contextField);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { contextFields, hasPermission } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card shadow={0} className={commonStyles.fullwidth} style={{ overflow: 'visible' }}>
|
||||||
|
<HeaderTitle
|
||||||
|
title="Context Fields"
|
||||||
|
actions={
|
||||||
|
hasPermission(CREATE_CONTEXT_FIELD) ? (
|
||||||
|
<IconButton
|
||||||
|
raised
|
||||||
|
colored
|
||||||
|
accent
|
||||||
|
name="add"
|
||||||
|
onClick={() => this.props.history.push('/context/create')}
|
||||||
|
title="Add new context field"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<List>
|
||||||
|
{contextFields.length > 0 ? (
|
||||||
|
contextFields.map((field, i) => (
|
||||||
|
<ListItem key={i} twoLine>
|
||||||
|
<ListItemContent icon="album" subtitle={field.description}>
|
||||||
|
<Link to={`/context/edit/${field.name}`}>
|
||||||
|
<strong>{field.name}</strong>
|
||||||
|
</Link>
|
||||||
|
</ListItemContent>
|
||||||
|
<ListItemAction>
|
||||||
|
{hasPermission(DELETE_CONTEXT_FIELD) ? (
|
||||||
|
<IconButton
|
||||||
|
name="delete"
|
||||||
|
title="Remove contextField"
|
||||||
|
onClick={this.removeContextField.bind(this, field)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
</ListItemAction>
|
||||||
|
</ListItem>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<ListItem>No context fields defined</ListItem>
|
||||||
|
)}
|
||||||
|
</List>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ContextFieldListComponent;
|
30
frontend/src/component/context/list-container.jsx
Normal file
30
frontend/src/component/context/list-container.jsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { connect } from 'react-redux';
|
||||||
|
import ContextFieldListComponent from './list-component.jsx';
|
||||||
|
import { fetchContext, removeContextField } from './../../store/context/actions';
|
||||||
|
import { hasPermission } from '../../permissions';
|
||||||
|
|
||||||
|
const mapStateToProps = state => {
|
||||||
|
const list = state.context.toJS();
|
||||||
|
|
||||||
|
return {
|
||||||
|
contextFields: list,
|
||||||
|
hasPermission: hasPermission.bind(null, state.user.get('profile')),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
removeContextField: contextField => {
|
||||||
|
// eslint-disable-next-line no-alert
|
||||||
|
if (window.confirm('Are you sure you want to remove this context field?')) {
|
||||||
|
removeContextField(contextField)(dispatch);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fetchContext: () => fetchContext()(dispatch),
|
||||||
|
});
|
||||||
|
|
||||||
|
const ContextFieldListContainer = connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(ContextFieldListComponent);
|
||||||
|
|
||||||
|
export default ContextFieldListContainer;
|
@ -145,19 +145,9 @@ exports[`renders correctly with with variants 1`] = `
|
|||||||
|
|
||||||
<react-mdl-Button
|
<react-mdl-Button
|
||||||
onClick={[MockFunction]}
|
onClick={[MockFunction]}
|
||||||
raised={true}
|
|
||||||
ripple={true}
|
|
||||||
style={
|
|
||||||
Object {
|
|
||||||
"float": "right",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
type="cancel"
|
type="cancel"
|
||||||
>
|
>
|
||||||
<react-mdl-Icon
|
Cancel
|
||||||
name="cancel"
|
|
||||||
/>
|
|
||||||
Cancel
|
|
||||||
</react-mdl-Button>
|
</react-mdl-Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@ -225,19 +215,9 @@ exports[`renders correctly with without variants 1`] = `
|
|||||||
|
|
||||||
<react-mdl-Button
|
<react-mdl-Button
|
||||||
onClick={[MockFunction]}
|
onClick={[MockFunction]}
|
||||||
raised={true}
|
|
||||||
ripple={true}
|
|
||||||
style={
|
|
||||||
Object {
|
|
||||||
"float": "right",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
type="cancel"
|
type="cancel"
|
||||||
>
|
>
|
||||||
<react-mdl-Icon
|
Cancel
|
||||||
name="cancel"
|
|
||||||
/>
|
|
||||||
Cancel
|
|
||||||
</react-mdl-Button>
|
</react-mdl-Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -121,6 +121,25 @@ Array [
|
|||||||
"path": "/applications",
|
"path": "/applications",
|
||||||
"title": "Applications",
|
"title": "Applications",
|
||||||
},
|
},
|
||||||
|
Object {
|
||||||
|
"component": [Function],
|
||||||
|
"parent": "/context",
|
||||||
|
"path": "/context/create",
|
||||||
|
"title": "Create",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"component": [Function],
|
||||||
|
"parent": "/context",
|
||||||
|
"path": "/context/edit/:name",
|
||||||
|
"title": ":name",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"component": [Function],
|
||||||
|
"hidden": true,
|
||||||
|
"icon": "apps",
|
||||||
|
"path": "/context",
|
||||||
|
"title": "Context Fields",
|
||||||
|
},
|
||||||
Object {
|
Object {
|
||||||
"component": [Function],
|
"component": [Function],
|
||||||
"icon": "exit_to_app",
|
"icon": "exit_to_app",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { routes, baseRoutes, getRoute } from '../routes';
|
import { routes, baseRoutes, getRoute } from '../routes';
|
||||||
|
|
||||||
test('returns all defined routes', () => {
|
test('returns all defined routes', () => {
|
||||||
expect(routes.length).toEqual(14);
|
expect(routes.length).toEqual(17);
|
||||||
expect(routes).toMatchSnapshot();
|
expect(routes).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -11,6 +11,9 @@ import ShowArchive from '../../page/archive/show';
|
|||||||
import Archive from '../../page/archive';
|
import Archive from '../../page/archive';
|
||||||
import Applications from '../../page/applications';
|
import Applications from '../../page/applications';
|
||||||
import ApplicationView from '../../page/applications/view';
|
import ApplicationView from '../../page/applications/view';
|
||||||
|
import ContextFields from '../../page/context';
|
||||||
|
import CreateContextField from '../../page/context/create';
|
||||||
|
import EditContextField from '../../page/context/edit';
|
||||||
import LogoutFeatures from '../../page/user/logout';
|
import LogoutFeatures from '../../page/user/logout';
|
||||||
|
|
||||||
export const routes = [
|
export const routes = [
|
||||||
@ -42,9 +45,14 @@ export const routes = [
|
|||||||
{ path: '/applications/:name', title: ':name', parent: '/applications', component: ApplicationView },
|
{ path: '/applications/:name', title: ':name', parent: '/applications', component: ApplicationView },
|
||||||
{ path: '/applications', title: 'Applications', icon: 'apps', component: Applications },
|
{ path: '/applications', title: 'Applications', icon: 'apps', component: Applications },
|
||||||
|
|
||||||
|
// Context
|
||||||
|
{ path: '/context/create', parent: '/context', title: 'Create', component: CreateContextField },
|
||||||
|
{ path: '/context/edit/:name', parent: '/context', title: ':name', component: EditContextField },
|
||||||
|
{ path: '/context', title: 'Context Fields', icon: 'apps', component: ContextFields, hidden: true },
|
||||||
|
|
||||||
{ path: '/logout', title: 'Sign out', icon: 'exit_to_app', component: LogoutFeatures },
|
{ path: '/logout', title: 'Sign out', icon: 'exit_to_app', component: LogoutFeatures },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const getRoute = path => routes.find(route => route.path === path);
|
export const getRoute = path => routes.find(route => route.path === path);
|
||||||
|
|
||||||
export const baseRoutes = routes.filter(route => !route.parent);
|
export const baseRoutes = routes.filter(route => !route.hidden).filter(route => !route.parent);
|
||||||
|
@ -1,13 +1,52 @@
|
|||||||
import { throwIfNotSuccess } from './helper';
|
import { throwIfNotSuccess, headers } from './helper';
|
||||||
|
|
||||||
const URI = 'api/admin/context';
|
const URI = 'api/admin/context';
|
||||||
|
|
||||||
function fetchContext() {
|
function fetchAll() {
|
||||||
return fetch(URI, { credentials: 'include' })
|
return fetch(URI, { credentials: 'include' })
|
||||||
.then(throwIfNotSuccess)
|
.then(throwIfNotSuccess)
|
||||||
.then(response => response.json());
|
.then(response => response.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function create(contextField) {
|
||||||
|
return fetch(URI, {
|
||||||
|
method: 'POST',
|
||||||
|
headers,
|
||||||
|
body: JSON.stringify(contextField),
|
||||||
|
credentials: 'include',
|
||||||
|
}).then(throwIfNotSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
function update(contextField) {
|
||||||
|
return fetch(`${URI}/${contextField.name}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers,
|
||||||
|
body: JSON.stringify(contextField),
|
||||||
|
credentials: 'include',
|
||||||
|
}).then(throwIfNotSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove(contextField) {
|
||||||
|
return fetch(`${URI}/${contextField.name}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers,
|
||||||
|
credentials: 'include',
|
||||||
|
}).then(throwIfNotSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
function validate(name) {
|
||||||
|
return fetch(`${URI}/validate`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers,
|
||||||
|
credentials: 'include',
|
||||||
|
body: JSON.stringify(name),
|
||||||
|
}).then(throwIfNotSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
fetchContext,
|
fetchAll,
|
||||||
|
create,
|
||||||
|
update,
|
||||||
|
remove,
|
||||||
|
validate,
|
||||||
};
|
};
|
||||||
|
11
frontend/src/page/context/create.js
Normal file
11
frontend/src/page/context/create.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import CreateContextField from '../../component/context/create-context-container';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
const render = ({ history }) => <CreateContextField title="Create context field" history={history} />;
|
||||||
|
|
||||||
|
render.propTypes = {
|
||||||
|
history: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default render;
|
14
frontend/src/page/context/edit.js
Normal file
14
frontend/src/page/context/edit.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import CreateContextField from '../../component/context/edit-context-container';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
const render = ({ match: { params }, history }) => (
|
||||||
|
<CreateContextField contextFieldName={params.name} title="Edit context field" history={history} />
|
||||||
|
);
|
||||||
|
|
||||||
|
render.propTypes = {
|
||||||
|
match: PropTypes.object.isRequired,
|
||||||
|
history: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default render;
|
11
frontend/src/page/context/index.js
Normal file
11
frontend/src/page/context/index.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ContextFields from '../../component/context/list-container';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
const render = ({ history }) => <ContextFields history={history} />;
|
||||||
|
|
||||||
|
render.propTypes = {
|
||||||
|
history: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default render;
|
@ -6,6 +6,9 @@ export const CREATE_STRATEGY = 'CREATE_STRATEGY';
|
|||||||
export const UPDATE_STRATEGY = 'UPDATE_STRATEGY';
|
export const UPDATE_STRATEGY = 'UPDATE_STRATEGY';
|
||||||
export const DELETE_STRATEGY = 'DELETE_STRATEGY';
|
export const DELETE_STRATEGY = 'DELETE_STRATEGY';
|
||||||
export const UPDATE_APPLICATION = 'UPDATE_APPLICATION';
|
export const UPDATE_APPLICATION = 'UPDATE_APPLICATION';
|
||||||
|
export const CREATE_CONTEXT_FIELD = 'CREATE_CONTEXT_FIELD';
|
||||||
|
export const UPDATE_CONTEXT_FIELD = 'UPDATE_CONTEXT_FIELD';
|
||||||
|
export const DELETE_CONTEXT_FIELD = 'DELETE_CONTEXT_FIELD';
|
||||||
|
|
||||||
export function hasPermission(user, permission) {
|
export function hasPermission(user, permission) {
|
||||||
return (
|
return (
|
||||||
|
@ -3,16 +3,50 @@ import { dispatchAndThrow } from '../util';
|
|||||||
|
|
||||||
export const RECEIVE_CONTEXT = 'RECEIVE_CONTEXT';
|
export const RECEIVE_CONTEXT = 'RECEIVE_CONTEXT';
|
||||||
export const ERROR_RECEIVE_CONTEXT = 'ERROR_RECEIVE_CONTEXT';
|
export const ERROR_RECEIVE_CONTEXT = 'ERROR_RECEIVE_CONTEXT';
|
||||||
|
export const REMOVE_CONTEXT = 'REMOVE_CONTEXT';
|
||||||
|
export const ERROR_REMOVING_CONTEXT = 'ERROR_REMOVING_CONTEXT';
|
||||||
|
export const ADD_CONTEXT_FIELD = 'ADD_CONTEXT_FIELD';
|
||||||
|
export const ERROR_ADD_CONTEXT_FIELD = 'ERROR_ADD_CONTEXT_FIELD';
|
||||||
|
export const UPDATE_CONTEXT_FIELD = 'UPDATE_CONTEXT_FIELD';
|
||||||
|
export const ERROR_UPDATE_CONTEXT_FIELD = 'ERROR_UPDATE_CONTEXT_FIELD';
|
||||||
|
|
||||||
export const receiveContext = json => ({
|
const receiveContext = value => ({ type: RECEIVE_CONTEXT, value });
|
||||||
type: RECEIVE_CONTEXT,
|
const addContextField = context => ({ type: ADD_CONTEXT_FIELD, context });
|
||||||
value: json,
|
const upContextField = context => ({ type: UPDATE_CONTEXT_FIELD, context });
|
||||||
});
|
const createRemoveContext = context => ({ type: REMOVE_CONTEXT, context });
|
||||||
|
|
||||||
export function fetchContext() {
|
export function fetchContext() {
|
||||||
return dispatch =>
|
return dispatch =>
|
||||||
api
|
api
|
||||||
.fetchContext()
|
.fetchAll()
|
||||||
.then(json => dispatch(receiveContext(json)))
|
.then(json => dispatch(receiveContext(json)))
|
||||||
.catch(dispatchAndThrow(dispatch, ERROR_RECEIVE_CONTEXT));
|
.catch(dispatchAndThrow(dispatch, ERROR_RECEIVE_CONTEXT));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function removeContextField(context) {
|
||||||
|
return dispatch =>
|
||||||
|
api
|
||||||
|
.remove(context)
|
||||||
|
.then(() => dispatch(createRemoveContext(context)))
|
||||||
|
.catch(dispatchAndThrow(dispatch, ERROR_REMOVING_CONTEXT));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createContextField(context) {
|
||||||
|
return dispatch =>
|
||||||
|
api
|
||||||
|
.create(context)
|
||||||
|
.then(() => dispatch(addContextField(context)))
|
||||||
|
.catch(dispatchAndThrow(dispatch, ERROR_ADD_CONTEXT_FIELD));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateContextField(context) {
|
||||||
|
return dispatch =>
|
||||||
|
api
|
||||||
|
.update(context)
|
||||||
|
.then(() => dispatch(upContextField(context)))
|
||||||
|
.catch(dispatchAndThrow(dispatch, ERROR_UPDATE_CONTEXT_FIELD));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateName(name) {
|
||||||
|
return api.validate({ name });
|
||||||
|
}
|
||||||
|
@ -1,15 +1,24 @@
|
|||||||
import { RECEIVE_CONTEXT } from './actions';
|
import { List } from 'immutable';
|
||||||
|
import { RECEIVE_CONTEXT, REMOVE_CONTEXT, ADD_CONTEXT_FIELD, UPDATE_CONTEXT_FIELD } from './actions';
|
||||||
|
|
||||||
const DEFAULT_CONTEXT_FIELDS = [{ name: 'environment' }, { name: 'userId' }, { name: 'appName' }];
|
const DEFAULT_CONTEXT_FIELDS = [{ name: 'environment' }, { name: 'userId' }, { name: 'appName' }];
|
||||||
|
|
||||||
function getInitState() {
|
function getInitState() {
|
||||||
return DEFAULT_CONTEXT_FIELDS;
|
return new List(DEFAULT_CONTEXT_FIELDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
const strategies = (state = getInitState(), action) => {
|
const strategies = (state = getInitState(), action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case RECEIVE_CONTEXT:
|
case RECEIVE_CONTEXT:
|
||||||
return action.value;
|
return new List(action.value);
|
||||||
|
case REMOVE_CONTEXT:
|
||||||
|
return state.remove(state.indexOf(action.context));
|
||||||
|
case ADD_CONTEXT_FIELD:
|
||||||
|
return state.push(action.context);
|
||||||
|
case UPDATE_CONTEXT_FIELD: {
|
||||||
|
const index = state.findIndex(item => item.name === action.context.name);
|
||||||
|
return state.set(index, action.context);
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,8 @@ import {
|
|||||||
|
|
||||||
import { ERROR_UPDATING_STRATEGY, ERROR_CREATING_STRATEGY, ERROR_RECEIVE_STRATEGIES } from './strategy/actions';
|
import { ERROR_UPDATING_STRATEGY, ERROR_CREATING_STRATEGY, ERROR_RECEIVE_STRATEGIES } from './strategy/actions';
|
||||||
|
|
||||||
|
import { ERROR_ADD_CONTEXT_FIELD, ERROR_UPDATE_CONTEXT_FIELD } from './context/actions';
|
||||||
|
|
||||||
import { FORBIDDEN } from './util';
|
import { FORBIDDEN } from './util';
|
||||||
|
|
||||||
const debug = require('debug')('unleash:error-store');
|
const debug = require('debug')('unleash:error-store');
|
||||||
@ -37,6 +39,8 @@ const strategies = (state = getInitState(), action) => {
|
|||||||
case ERROR_UPDATING_STRATEGY:
|
case ERROR_UPDATING_STRATEGY:
|
||||||
case ERROR_CREATING_STRATEGY:
|
case ERROR_CREATING_STRATEGY:
|
||||||
case ERROR_RECEIVE_STRATEGIES:
|
case ERROR_RECEIVE_STRATEGIES:
|
||||||
|
case ERROR_ADD_CONTEXT_FIELD:
|
||||||
|
case ERROR_UPDATE_CONTEXT_FIELD:
|
||||||
return addErrorIfNotAlreadyInList(state, action.error.message);
|
return addErrorIfNotAlreadyInList(state, action.error.message);
|
||||||
case FORBIDDEN:
|
case FORBIDDEN:
|
||||||
return addErrorIfNotAlreadyInList(state, action.error.message || '403 Forbidden');
|
return addErrorIfNotAlreadyInList(state, action.error.message || '403 Forbidden');
|
||||||
|
@ -20,10 +20,7 @@ const updatedStrategy = strategy => ({ type: UPDATE_STRATEGY, strategy });
|
|||||||
|
|
||||||
const startRequest = () => ({ type: REQUEST_STRATEGIES });
|
const startRequest = () => ({ type: REQUEST_STRATEGIES });
|
||||||
|
|
||||||
const receiveStrategies = json => ({
|
const receiveStrategies = json => ({ type: RECEIVE_STRATEGIES, value: json.strategies });
|
||||||
type: RECEIVE_STRATEGIES,
|
|
||||||
value: json.strategies,
|
|
||||||
});
|
|
||||||
|
|
||||||
const startCreate = () => ({ type: START_CREATE_STRATEGY });
|
const startCreate = () => ({ type: START_CREATE_STRATEGY });
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user