mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-20 00:08:02 +01:00
Fix/feedback on create (#292)
* fix: copy feature toggle instead of setting newVariants on the reference * fix: remove console log * fix: update messages * fix: give feedback on strategy actions * fix: do not allow feature toggle to be created with empty name * fix: disable delete if only one strategy is applied * fix: archive view * fix: set name field on add variant required * fix: set required on feature toggle name
This commit is contained in:
parent
06d7f9b609
commit
2f1848f6fd
@ -23,7 +23,6 @@ Now you should be able to review rendering information in the console. If you do
|
|||||||
|
|
||||||
You need to first start the unleash-api on port 4242
|
You need to first start the unleash-api on port 4242
|
||||||
before you can start working on unleash-frontend.
|
before you can start working on unleash-frontend.
|
||||||
|
|
||||||
Start webpack-dev-server with hot-reload:
|
Start webpack-dev-server with hot-reload:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
@ -26,8 +26,6 @@ const ContextList = ({ removeContextField, history, contextFields }) => {
|
|||||||
const [showDelDialogue, setShowDelDialogue] = useState(false);
|
const [showDelDialogue, setShowDelDialogue] = useState(false);
|
||||||
const [name, setName] = useState();
|
const [name, setName] = useState();
|
||||||
|
|
||||||
console.log(contextFields);
|
|
||||||
|
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
const contextList = () =>
|
const contextList = () =>
|
||||||
contextFields.map(field => (
|
contextFields.map(field => (
|
||||||
|
@ -36,6 +36,7 @@ const FeatureToggleList = ({
|
|||||||
updateSetting,
|
updateSetting,
|
||||||
featureMetrics,
|
featureMetrics,
|
||||||
toggleFeature,
|
toggleFeature,
|
||||||
|
archive,
|
||||||
loading,
|
loading,
|
||||||
}) => {
|
}) => {
|
||||||
const { hasAccess } = useContext(AccessContext);
|
const { hasAccess } = useContext(AccessContext);
|
||||||
@ -93,12 +94,24 @@ const FeatureToggleList = ({
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
elseShow={
|
elseShow={
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={archive}
|
||||||
|
show={
|
||||||
<ListItem className={styles.emptyStateListItem}>
|
<ListItem className={styles.emptyStateListItem}>
|
||||||
No features available. Get started by adding a new
|
No archived features.
|
||||||
feature toggle.
|
|
||||||
<Link to="/features/create">Add your first toggle</Link>
|
|
||||||
</ListItem>
|
</ListItem>
|
||||||
}
|
}
|
||||||
|
elseShow={
|
||||||
|
<ListItem className={styles.emptyStateListItem}>
|
||||||
|
No features available. Get started by adding a
|
||||||
|
new feature toggle.
|
||||||
|
<Link to="/features/create">
|
||||||
|
Add your first toggle
|
||||||
|
</Link>
|
||||||
|
</ListItem>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -134,7 +147,9 @@ const FeatureToggleList = ({
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={!archive}
|
||||||
|
show={
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={smallScreen}
|
condition={smallScreen}
|
||||||
show={
|
show={
|
||||||
@ -175,6 +190,8 @@ const FeatureToggleList = ({
|
|||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { toggleFeature, fetchFeatureToggles } from '../../../store/feature-toggle/actions';
|
import {
|
||||||
|
toggleFeature,
|
||||||
|
fetchFeatureToggles,
|
||||||
|
} from '../../../store/feature-toggle/actions';
|
||||||
import { updateSettingForGroup } from '../../../store/settings/actions';
|
import { updateSettingForGroup } from '../../../store/settings/actions';
|
||||||
import FeatureToggleList from './FeatureToggleList';
|
import FeatureToggleList from './FeatureToggleList';
|
||||||
|
|
||||||
@ -11,18 +14,23 @@ function checkConstraints(strategy, regex) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function resolveCurrentProjectId(settings) {
|
function resolveCurrentProjectId(settings) {
|
||||||
if(!settings.currentProjectId || settings.currentProjectId === '*') {
|
if (!settings.currentProjectId || settings.currentProjectId === '*') {
|
||||||
return 'default';
|
return 'default';
|
||||||
} return settings.currentProjectId;
|
}
|
||||||
|
return settings.currentProjectId;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mapStateToPropsConfigurable = isFeature => state => {
|
export const mapStateToPropsConfigurable = isFeature => state => {
|
||||||
const featureMetrics = state.featureMetrics.toJS();
|
const featureMetrics = state.featureMetrics.toJS();
|
||||||
const settings = state.settings.toJS().feature || {};
|
const settings = state.settings.toJS().feature || {};
|
||||||
let features = isFeature ? state.features.toJS() : state.archive.get('list').toArray();
|
let features = isFeature
|
||||||
|
? state.features.toJS()
|
||||||
|
: state.archive.get('list').toArray();
|
||||||
|
|
||||||
if (settings.currentProjectId && settings.currentProjectId !== '*') {
|
if (settings.currentProjectId && settings.currentProjectId !== '*') {
|
||||||
features = features.filter(f => f.project === settings.currentProjectId);
|
features = features.filter(
|
||||||
|
f => f.project === settings.currentProjectId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.filter) {
|
if (settings.filter) {
|
||||||
@ -33,8 +41,11 @@ export const mapStateToPropsConfigurable = isFeature => state => {
|
|||||||
feature.strategies.some(s => checkConstraints(s, regex)) ||
|
feature.strategies.some(s => checkConstraints(s, regex)) ||
|
||||||
regex.test(feature.name) ||
|
regex.test(feature.name) ||
|
||||||
regex.test(feature.description) ||
|
regex.test(feature.description) ||
|
||||||
feature.strategies.some(s => s && s.name && regex.test(s.name)) ||
|
feature.strategies.some(
|
||||||
(settings.filter.length > 1 && regex.test(JSON.stringify(feature)))
|
s => s && s.name && regex.test(s.name)
|
||||||
|
) ||
|
||||||
|
(settings.filter.length > 1 &&
|
||||||
|
regex.test(JSON.stringify(feature)))
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Invalid filter regex
|
// Invalid filter regex
|
||||||
@ -56,9 +67,13 @@ export const mapStateToPropsConfigurable = isFeature => state => {
|
|||||||
a.stale === b.stale ? 0 : a.stale ? -1 : 1
|
a.stale === b.stale ? 0 : a.stale ? -1 : 1
|
||||||
);
|
);
|
||||||
} else if (settings.sort === 'created') {
|
} else if (settings.sort === 'created') {
|
||||||
features = features.sort((a, b) => (new Date(a.createdAt) > new Date(b.createdAt) ? -1 : 1));
|
features = features.sort((a, b) =>
|
||||||
|
new Date(a.createdAt) > new Date(b.createdAt) ? -1 : 1
|
||||||
|
);
|
||||||
} else if (settings.sort === 'Last seen') {
|
} else if (settings.sort === 'Last seen') {
|
||||||
features = features.sort((a, b) => (new Date(a.lastSeenAt) > new Date(b.lastSeenAt) ? -1 : 1));
|
features = features.sort((a, b) =>
|
||||||
|
new Date(a.lastSeenAt) > new Date(b.lastSeenAt) ? -1 : 1
|
||||||
|
);
|
||||||
} else if (settings.sort === 'name') {
|
} else if (settings.sort === 'name') {
|
||||||
features = features.sort((a, b) => {
|
features = features.sort((a, b) => {
|
||||||
if (a.name < b.name) {
|
if (a.name < b.name) {
|
||||||
@ -70,7 +85,9 @@ export const mapStateToPropsConfigurable = isFeature => state => {
|
|||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
} else if (settings.sort === 'strategies') {
|
} else if (settings.sort === 'strategies') {
|
||||||
features = features.sort((a, b) => (a.strategies.length > b.strategies.length ? -1 : 1));
|
features = features.sort((a, b) =>
|
||||||
|
a.strategies.length > b.strategies.length ? -1 : 1
|
||||||
|
);
|
||||||
} else if (settings.sort === 'type') {
|
} else if (settings.sort === 'type') {
|
||||||
features = features.sort((a, b) => {
|
features = features.sort((a, b) => {
|
||||||
if (a.type < b.type) {
|
if (a.type < b.type) {
|
||||||
@ -82,7 +99,9 @@ export const mapStateToPropsConfigurable = isFeature => state => {
|
|||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
} else if (settings.sort === 'metrics') {
|
} else if (settings.sort === 'metrics') {
|
||||||
const target = settings.showLastHour ? featureMetrics.lastHour : featureMetrics.lastMinute;
|
const target = settings.showLastHour
|
||||||
|
? featureMetrics.lastHour
|
||||||
|
: featureMetrics.lastMinute;
|
||||||
|
|
||||||
features = features.sort((a, b) => {
|
features = features.sort((a, b) => {
|
||||||
if (!target[a.name]) {
|
if (!target[a.name]) {
|
||||||
@ -102,6 +121,7 @@ export const mapStateToPropsConfigurable = isFeature => state => {
|
|||||||
features,
|
features,
|
||||||
currentProjectId: resolveCurrentProjectId(settings),
|
currentProjectId: resolveCurrentProjectId(settings),
|
||||||
featureMetrics,
|
featureMetrics,
|
||||||
|
archive: !isFeature,
|
||||||
settings,
|
settings,
|
||||||
loading: state.apiCalls.fetchTogglesState.loading,
|
loading: state.apiCalls.fetchTogglesState.loading,
|
||||||
};
|
};
|
||||||
@ -113,6 +133,9 @@ const mapDispatchToProps = {
|
|||||||
updateSetting: updateSettingForGroup('feature'),
|
updateSetting: updateSettingForGroup('feature'),
|
||||||
};
|
};
|
||||||
|
|
||||||
const FeatureToggleListContainer = connect(mapStateToProps, mapDispatchToProps)(FeatureToggleList);
|
const FeatureToggleListContainer = connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(FeatureToggleList);
|
||||||
|
|
||||||
export default FeatureToggleListContainer;
|
export default FeatureToggleListContainer;
|
||||||
|
@ -45,6 +45,7 @@ const CreateFeature = ({
|
|||||||
size="small"
|
size="small"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
label="Name"
|
label="Name"
|
||||||
|
required
|
||||||
placeholder="Unique-name"
|
placeholder="Unique-name"
|
||||||
className={styles.nameInput}
|
className={styles.nameInput}
|
||||||
name="name"
|
name="name"
|
||||||
|
@ -50,7 +50,7 @@ class WrapperComponent extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
validateName = async featureToggleName => {
|
validateName = async featureToggleName => {
|
||||||
const { errors } = {...this.state};
|
const { errors } = { ...this.state };
|
||||||
try {
|
try {
|
||||||
await validateName(featureToggleName);
|
await validateName(featureToggleName);
|
||||||
errors.name = undefined;
|
errors.name = undefined;
|
||||||
@ -61,7 +61,7 @@ class WrapperComponent extends Component {
|
|||||||
this.setState({ errors });
|
this.setState({ errors });
|
||||||
};
|
};
|
||||||
|
|
||||||
onSubmit = evt => {
|
onSubmit = async evt => {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
const { createFeatureToggles, history } = this.props;
|
const { createFeatureToggles, history } = this.props;
|
||||||
const { featureToggle } = this.state;
|
const { featureToggle } = this.state;
|
||||||
@ -76,10 +76,17 @@ class WrapperComponent extends Component {
|
|||||||
featureToggle.strategies = [defaultStrategy];
|
featureToggle.strategies = [defaultStrategy];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
createFeatureToggles(featureToggle).then(() =>
|
await createFeatureToggles(featureToggle).then(() =>
|
||||||
history.push(`/features/strategies/${featureToggle.name}`)
|
history.push(`/features/strategies/${featureToggle.name}`)
|
||||||
);
|
);
|
||||||
|
} catch (e) {
|
||||||
|
if (e.toString().includes('not allowed to be empty')) {
|
||||||
|
this.setState({
|
||||||
|
errors: { name: 'Name is not allowed to be empty' },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onCancel = evt => {
|
onCancel = evt => {
|
||||||
|
@ -16,6 +16,8 @@ import { styles as commonStyles } from '../../common';
|
|||||||
import styles from './copy-feature-component.module.scss';
|
import styles from './copy-feature-component.module.scss';
|
||||||
|
|
||||||
import { trim } from '../../common/util';
|
import { trim } from '../../common/util';
|
||||||
|
import ConditionallyRender from '../../common/ConditionallyRender';
|
||||||
|
import { Alert } from '@material-ui/lab';
|
||||||
|
|
||||||
class CopyFeatureComponent extends Component {
|
class CopyFeatureComponent extends Component {
|
||||||
// static displayName = `AddFeatureComponent-${getDisplayName(Component)}`;
|
// static displayName = `AddFeatureComponent-${getDisplayName(Component)}`;
|
||||||
@ -62,7 +64,7 @@ class CopyFeatureComponent extends Component {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onSubmit = evt => {
|
onSubmit = async evt => {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
|
|
||||||
const { nameError, newToggleName, replaceGroupId } = this.state;
|
const { nameError, newToggleName, replaceGroupId } = this.state;
|
||||||
@ -82,11 +84,15 @@ class CopyFeatureComponent extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
this.props
|
this.props
|
||||||
.createFeatureToggle(copyToggle)
|
.createFeatureToggle(copyToggle)
|
||||||
.then(() =>
|
.then(() =>
|
||||||
history.push(`/features/strategies/${copyToggle.name}`)
|
history.push(`/features/strategies/${copyToggle.name}`)
|
||||||
);
|
);
|
||||||
|
} catch (e) {
|
||||||
|
this.setState({ apiError: e });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -104,7 +110,10 @@ class CopyFeatureComponent extends Component {
|
|||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
<h1>Copy {copyToggle.name}</h1>
|
<h1>Copy {copyToggle.name}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={this.state.apiError}
|
||||||
|
show={<Alert severity="error">{this.state.apiError}</Alert>}
|
||||||
|
/>
|
||||||
<section className={styles.content}>
|
<section className={styles.content}>
|
||||||
<p className={styles.text}>
|
<p className={styles.text}>
|
||||||
You are about to create a new feature toggle by cloning
|
You are about to create a new feature toggle by cloning
|
||||||
|
@ -14,6 +14,7 @@ const StrategyCard = ({
|
|||||||
connectDragPreview,
|
connectDragPreview,
|
||||||
connectDragSource,
|
connectDragSource,
|
||||||
removeStrategy,
|
removeStrategy,
|
||||||
|
disableDelete,
|
||||||
editStrategy,
|
editStrategy,
|
||||||
connectDropTarget,
|
connectDropTarget,
|
||||||
}) => {
|
}) => {
|
||||||
@ -28,9 +29,13 @@ const StrategyCard = ({
|
|||||||
connectDragSource={connectDragSource}
|
connectDragSource={connectDragSource}
|
||||||
removeStrategy={removeStrategy}
|
removeStrategy={removeStrategy}
|
||||||
editStrategy={editStrategy}
|
editStrategy={editStrategy}
|
||||||
|
disableDelete={disableDelete}
|
||||||
/>
|
/>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<StrategyCardContent strategy={strategy} strategyDefinition={strategyDefinition} />
|
<StrategyCardContent
|
||||||
|
strategy={strategy}
|
||||||
|
strategyDefinition={strategyDefinition}
|
||||||
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</span>
|
</span>
|
||||||
|
@ -11,12 +11,14 @@ import {
|
|||||||
|
|
||||||
import { useStyles } from './StrategyCardHeader.styles.js';
|
import { useStyles } from './StrategyCardHeader.styles.js';
|
||||||
import { ReactComponent as ReorderIcon } from '../../../../../assets/icons/reorder.svg';
|
import { ReactComponent as ReorderIcon } from '../../../../../assets/icons/reorder.svg';
|
||||||
|
import ConditionallyRender from '../../../../common/ConditionallyRender/ConditionallyRender';
|
||||||
|
|
||||||
const StrategyCardHeader = ({
|
const StrategyCardHeader = ({
|
||||||
name,
|
name,
|
||||||
connectDragSource,
|
connectDragSource,
|
||||||
removeStrategy,
|
removeStrategy,
|
||||||
editStrategy,
|
editStrategy,
|
||||||
|
disableDelete,
|
||||||
}) => {
|
}) => {
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
|
|
||||||
@ -51,13 +53,40 @@ const StrategyCardHeader = ({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={disableDelete}
|
||||||
|
show={
|
||||||
|
<Tooltip title="One strategy must always be applied. You can not delete this strategy.">
|
||||||
|
<span>
|
||||||
|
<IconButton
|
||||||
|
onClick={removeStrategy}
|
||||||
|
disabled={disableDelete}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
className={
|
||||||
|
styles.strateyCardHeaderIcon
|
||||||
|
}
|
||||||
|
>
|
||||||
|
delete
|
||||||
|
</Icon>
|
||||||
|
</IconButton>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
elseShow={
|
||||||
<Tooltip title="Delete strategy">
|
<Tooltip title="Delete strategy">
|
||||||
<IconButton onClick={removeStrategy}>
|
<IconButton onClick={removeStrategy}>
|
||||||
<Icon className={styles.strateyCardHeaderIcon}>
|
<Icon
|
||||||
|
className={
|
||||||
|
styles.strateyCardHeaderIcon
|
||||||
|
}
|
||||||
|
>
|
||||||
delete
|
delete
|
||||||
</Icon>
|
</Icon>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
@ -124,6 +124,8 @@ const StrategiesList = props => {
|
|||||||
return strategies.find(s => s.name === strategyName);
|
return strategies.find(s => s.name === strategyName);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const disableDelete = editableStrategies.length === 1;
|
||||||
|
|
||||||
const cards = editableStrategies.map((strategy, i) => (
|
const cards = editableStrategies.map((strategy, i) => (
|
||||||
<StrategyCard
|
<StrategyCard
|
||||||
key={i}
|
key={i}
|
||||||
@ -133,6 +135,7 @@ const StrategiesList = props => {
|
|||||||
moveStrategy={moveStrategy}
|
moveStrategy={moveStrategy}
|
||||||
editStrategy={() => setEditStrategyIndex(i)}
|
editStrategy={() => setEditStrategyIndex(i)}
|
||||||
index={i}
|
index={i}
|
||||||
|
disableDelete={disableDelete}
|
||||||
movable
|
movable
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
|
@ -86,10 +86,10 @@ const AddVariant = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const submit = async e => {
|
const submit = async e => {
|
||||||
|
setError({});
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const validationError = validateName(data.name);
|
const validationError = validateName(data.name);
|
||||||
|
|
||||||
if (validationError) {
|
if (validationError) {
|
||||||
setError(validationError);
|
setError(validationError);
|
||||||
return;
|
return;
|
||||||
@ -112,9 +112,13 @@ const AddVariant = ({
|
|||||||
clear();
|
clear();
|
||||||
closeDialog();
|
closeDialog();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error.message.includes('duplicate value')) {
|
||||||
|
setError({ name: 'A variant with that name already exists.' });
|
||||||
|
} else {
|
||||||
const msg = error.message || 'Could not add variant';
|
const msg = error.message || 'Could not add variant';
|
||||||
setError({ general: msg });
|
setError({ general: msg });
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPayload = e => {
|
const onPayload = e => {
|
||||||
@ -188,9 +192,11 @@ const AddVariant = ({
|
|||||||
name="name"
|
name="name"
|
||||||
placeholder=""
|
placeholder=""
|
||||||
className={commonStyles.fullWidth}
|
className={commonStyles.fullWidth}
|
||||||
|
helperText={error.name}
|
||||||
value={data.name || ''}
|
value={data.name || ''}
|
||||||
error={error.name}
|
error={Boolean(error.name)}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
|
required
|
||||||
size="small"
|
size="small"
|
||||||
type="name"
|
type="name"
|
||||||
onChange={setVariantValue}
|
onChange={setVariantValue}
|
||||||
@ -214,7 +220,7 @@ const AddVariant = ({
|
|||||||
}}
|
}}
|
||||||
style={{ marginRight: '0.8rem' }}
|
style={{ marginRight: '0.8rem' }}
|
||||||
value={data.weight || ''}
|
value={data.weight || ''}
|
||||||
error={error.weight}
|
error={Boolean(error.weight)}
|
||||||
type="number"
|
type="number"
|
||||||
disabled={!isFixWeight}
|
disabled={!isFixWeight}
|
||||||
onChange={setVariantValue}
|
onChange={setVariantValue}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { Component } from 'react';
|
import { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
|
||||||
@ -63,12 +63,16 @@ class UpdateVariantComponent extends Component {
|
|||||||
|
|
||||||
onRemoveVariant = (e, index) => {
|
onRemoveVariant = (e, index) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
try {
|
||||||
this.props.removeVariant(index);
|
this.props.removeVariant(index);
|
||||||
|
} catch (e) {
|
||||||
|
console.log('An exception was caught.');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
renderVariant = (variant, index) => (
|
renderVariant = (variant, index) => (
|
||||||
<VariantViewComponent
|
<VariantViewComponent
|
||||||
key={index}
|
key={variant.name}
|
||||||
variant={variant}
|
variant={variant}
|
||||||
editVariant={e => this.openEditVariant(e, index, variant)}
|
editVariant={e => this.openEditVariant(e, index, variant)}
|
||||||
removeVariant={e => this.onRemoveVariant(e, index)}
|
removeVariant={e => this.onRemoveVariant(e, index)}
|
||||||
|
@ -6,7 +6,10 @@ import { updateWeight } from '../../common/util';
|
|||||||
|
|
||||||
const mapStateToProps = (state, ownProps) => ({
|
const mapStateToProps = (state, ownProps) => ({
|
||||||
variants: ownProps.featureToggle.variants || [],
|
variants: ownProps.featureToggle.variants || [],
|
||||||
stickinessOptions: ['default', ...state.context.filter(c => c.stickiness).map(c => c.name)],
|
stickinessOptions: [
|
||||||
|
'default',
|
||||||
|
...state.context.filter(c => c.stickiness).map(c => c.name),
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch, ownProps) => ({
|
const mapDispatchToProps = (dispatch, ownProps) => ({
|
||||||
@ -15,11 +18,15 @@ const mapDispatchToProps = (dispatch, ownProps) => ({
|
|||||||
const currentVariants = featureToggle.variants || [];
|
const currentVariants = featureToggle.variants || [];
|
||||||
const variants = [...currentVariants, variant];
|
const variants = [...currentVariants, variant];
|
||||||
updateWeight(variants, 1000);
|
updateWeight(variants, 1000);
|
||||||
return requestUpdateFeatureToggleVariants(featureToggle, variants)(dispatch);
|
return requestUpdateFeatureToggleVariants(
|
||||||
|
featureToggle,
|
||||||
|
variants
|
||||||
|
)(dispatch);
|
||||||
},
|
},
|
||||||
removeVariant: index => {
|
removeVariant: index => {
|
||||||
const { featureToggle } = ownProps;
|
const { featureToggle } = ownProps;
|
||||||
const currentVariants = featureToggle.variants || [];
|
const currentVariants = featureToggle.variants || [];
|
||||||
|
|
||||||
const variants = currentVariants.filter((v, i) => i !== index);
|
const variants = currentVariants.filter((v, i) => i !== index);
|
||||||
if (variants.length > 0) {
|
if (variants.length > 0) {
|
||||||
updateWeight(variants, 1000);
|
updateWeight(variants, 1000);
|
||||||
@ -29,7 +36,9 @@ const mapDispatchToProps = (dispatch, ownProps) => ({
|
|||||||
updateVariant: (index, variant) => {
|
updateVariant: (index, variant) => {
|
||||||
const { featureToggle } = ownProps;
|
const { featureToggle } = ownProps;
|
||||||
const currentVariants = featureToggle.variants || [];
|
const currentVariants = featureToggle.variants || [];
|
||||||
const variants = currentVariants.map((v, i) => (i === index ? variant : v));
|
const variants = currentVariants.map((v, i) =>
|
||||||
|
i === index ? variant : v
|
||||||
|
);
|
||||||
updateWeight(variants, 1000);
|
updateWeight(variants, 1000);
|
||||||
requestUpdateFeatureToggleVariants(featureToggle, variants)(dispatch);
|
requestUpdateFeatureToggleVariants(featureToggle, variants)(dispatch);
|
||||||
},
|
},
|
||||||
@ -41,4 +50,7 @@ const mapDispatchToProps = (dispatch, ownProps) => ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(UpdateFeatureToggleComponent);
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(UpdateFeatureToggleComponent);
|
||||||
|
@ -88,10 +88,25 @@ const CreateStrategy = ({
|
|||||||
Add parameter
|
Add parameter
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={editMode}
|
||||||
|
show={
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
style={{ display: 'block' }}
|
||||||
|
>
|
||||||
|
Update
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
elseShow={
|
||||||
<FormButtons
|
<FormButtons
|
||||||
submitText={editMode ? 'Update' : 'Create'}
|
submitText={'Create'}
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
/>
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
</form>
|
</form>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
);
|
);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useContext, useEffect } from 'react';
|
import { useContext, useEffect, useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { Link, useHistory } from 'react-router-dom';
|
import { Link, useHistory } from 'react-router-dom';
|
||||||
@ -25,6 +25,7 @@ import HeaderTitle from '../../common/HeaderTitle';
|
|||||||
|
|
||||||
import { useStyles } from './styles';
|
import { useStyles } from './styles';
|
||||||
import AccessContext from '../../../contexts/AccessContext';
|
import AccessContext from '../../../contexts/AccessContext';
|
||||||
|
import Dialogue from '../../common/Dialogue';
|
||||||
|
|
||||||
const StrategiesList = ({
|
const StrategiesList = ({
|
||||||
strategies,
|
strategies,
|
||||||
@ -37,6 +38,7 @@ const StrategiesList = ({
|
|||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
const smallScreen = useMediaQuery('(max-width:700px)');
|
const smallScreen = useMediaQuery('(max-width:700px)');
|
||||||
const { hasAccess } = useContext(AccessContext);
|
const { hasAccess } = useContext(AccessContext);
|
||||||
|
const [dialogueMetaData, setDialogueMetaData] = useState({ show: false });
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchStrategies();
|
fetchStrategies();
|
||||||
@ -86,7 +88,15 @@ const StrategiesList = ({
|
|||||||
|
|
||||||
const reactivateButton = strategy => (
|
const reactivateButton = strategy => (
|
||||||
<Tooltip title="Reactivate activation strategy">
|
<Tooltip title="Reactivate activation strategy">
|
||||||
<IconButton onClick={() => reactivateStrategy(strategy)}>
|
<IconButton
|
||||||
|
onClick={() =>
|
||||||
|
setDialogueMetaData({
|
||||||
|
show: true,
|
||||||
|
title: 'Really reactivate strategy?',
|
||||||
|
onConfirm: () => reactivateStrategy(strategy),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
<Icon>visibility</Icon>
|
<Icon>visibility</Icon>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@ -107,7 +117,16 @@ const StrategiesList = ({
|
|||||||
elseShow={
|
elseShow={
|
||||||
<Tooltip title="Deprecate activation strategy">
|
<Tooltip title="Deprecate activation strategy">
|
||||||
<div>
|
<div>
|
||||||
<IconButton onClick={() => deprecateStrategy(strategy)}>
|
<IconButton
|
||||||
|
onClick={() =>
|
||||||
|
setDialogueMetaData({
|
||||||
|
show: true,
|
||||||
|
title: 'Really deprecate strategy?',
|
||||||
|
onConfirm: () =>
|
||||||
|
deprecateStrategy(strategy),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
<Icon>visibility_off</Icon>
|
<Icon>visibility_off</Icon>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
@ -121,7 +140,15 @@ const StrategiesList = ({
|
|||||||
condition={strategy.editable}
|
condition={strategy.editable}
|
||||||
show={
|
show={
|
||||||
<Tooltip title="Delete strategy">
|
<Tooltip title="Delete strategy">
|
||||||
<IconButton onClick={() => removeStrategy(strategy)}>
|
<IconButton
|
||||||
|
onClick={() =>
|
||||||
|
setDialogueMetaData({
|
||||||
|
show: true,
|
||||||
|
title: 'Really delete strategy?',
|
||||||
|
onConfirm: () => removeStrategy(strategy),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
<Icon>delete</Icon>
|
<Icon>delete</Icon>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@ -167,12 +194,25 @@ const StrategiesList = ({
|
|||||||
</ListItem>
|
</ListItem>
|
||||||
));
|
));
|
||||||
|
|
||||||
|
const onDialogConfirm = () => {
|
||||||
|
dialogueMetaData?.onConfirm();
|
||||||
|
setDialogueMetaData(prev => ({ ...prev, show: false }));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContent
|
<PageContent
|
||||||
headerContent={
|
headerContent={
|
||||||
<HeaderTitle title="Strategies" actions={headerButton()} />
|
<HeaderTitle title="Strategies" actions={headerButton()} />
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
<Dialogue
|
||||||
|
open={dialogueMetaData.show}
|
||||||
|
onClick={onDialogConfirm}
|
||||||
|
title={dialogueMetaData?.title}
|
||||||
|
onClose={() =>
|
||||||
|
setDialogueMetaData(prev => ({ ...prev, show: false }))
|
||||||
|
}
|
||||||
|
/>
|
||||||
<List>
|
<List>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={strategies.length > 0}
|
condition={strategies.length > 0}
|
||||||
|
@ -17,26 +17,20 @@ const mapStateToProps = state => {
|
|||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
removeStrategy: strategy => {
|
removeStrategy: strategy => {
|
||||||
// eslint-disable-next-line no-alert
|
|
||||||
if (window.confirm('Are you sure you want to remove this strategy?')) {
|
|
||||||
removeStrategy(strategy)(dispatch);
|
removeStrategy(strategy)(dispatch);
|
||||||
}
|
|
||||||
},
|
},
|
||||||
deprecateStrategy: strategy => {
|
deprecateStrategy: strategy => {
|
||||||
// eslint-disable-next-line no-alert
|
|
||||||
if (window.confirm('Are you sure you want to deprecate this strategy?')) {
|
|
||||||
deprecateStrategy(strategy)(dispatch);
|
deprecateStrategy(strategy)(dispatch);
|
||||||
}
|
|
||||||
},
|
},
|
||||||
reactivateStrategy: strategy => {
|
reactivateStrategy: strategy => {
|
||||||
// eslint-disable-next-line no-alert
|
|
||||||
if (window.confirm('Are you sure you want to reactivate this strategy?')) {
|
|
||||||
reactivateStrategy(strategy)(dispatch);
|
reactivateStrategy(strategy)(dispatch);
|
||||||
}
|
|
||||||
},
|
},
|
||||||
fetchStrategies: () => fetchStrategies()(dispatch),
|
fetchStrategies: () => fetchStrategies()(dispatch),
|
||||||
});
|
});
|
||||||
|
|
||||||
const StrategiesListContainer = connect(mapStateToProps, mapDispatchToProps)(StrategiesList);
|
const StrategiesListContainer = connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(StrategiesList);
|
||||||
|
|
||||||
export default StrategiesListContainer;
|
export default StrategiesListContainer;
|
||||||
|
@ -14,9 +14,7 @@ class AuthenticationCustomComponent extends React.Component {
|
|||||||
<p>{authDetails.message}</p>
|
<p>{authDetails.message}</p>
|
||||||
<CardActions style={{ textAlign: 'center' }}>
|
<CardActions style={{ textAlign: 'center' }}>
|
||||||
<a href={authDetails.path}>
|
<a href={authDetails.path}>
|
||||||
<Button raised colored>
|
<Button>Sign In</Button>
|
||||||
Sign In
|
|
||||||
</Button>
|
|
||||||
</a>
|
</a>
|
||||||
</CardActions>
|
</CardActions>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
const defaultErrorMessage = 'Unexpected exception when talking to unleash-api';
|
const defaultErrorMessage = 'Unexpected exception when talking to unleash-api';
|
||||||
|
|
||||||
function extractJoiMsg(body) {
|
function extractJoiMsg(body) {
|
||||||
return body.details.length > 0 ? body.details[0].message : defaultErrorMessage;
|
return body.details.length > 0
|
||||||
|
? body.details[0].message
|
||||||
|
: defaultErrorMessage;
|
||||||
}
|
}
|
||||||
function extractLegacyMsg(body) {
|
function extractLegacyMsg(body) {
|
||||||
return body && body.length > 0 ? body[0].msg : defaultErrorMessage;
|
return body && body.length > 0 ? body[0].msg : defaultErrorMessage;
|
||||||
@ -35,7 +37,9 @@ export class ForbiddenError extends Error {
|
|||||||
|
|
||||||
export class NotFoundError extends Error {
|
export class NotFoundError extends Error {
|
||||||
constructor(statusCode) {
|
constructor(statusCode) {
|
||||||
super('The requested resource could not be found but may be available in the future');
|
super(
|
||||||
|
'The requested resource could not be found but may be available in the future'
|
||||||
|
);
|
||||||
this.name = 'NotFoundError';
|
this.name = 'NotFoundError';
|
||||||
this.statusCode = statusCode;
|
this.statusCode = statusCode;
|
||||||
}
|
}
|
||||||
@ -45,11 +49,19 @@ export function throwIfNotSuccess(response) {
|
|||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
if (response.status === 401) {
|
if (response.status === 401) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
response.json().then(body => reject(new AuthenticationError(response.status, body)));
|
response
|
||||||
|
.json()
|
||||||
|
.then(body =>
|
||||||
|
reject(new AuthenticationError(response.status, body))
|
||||||
|
);
|
||||||
});
|
});
|
||||||
} else if (response.status === 403) {
|
} else if (response.status === 403) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
response.json().then(body => reject(new ForbiddenError(response.status, body)));
|
response
|
||||||
|
.json()
|
||||||
|
.then(body =>
|
||||||
|
reject(new ForbiddenError(response.status, body))
|
||||||
|
);
|
||||||
});
|
});
|
||||||
} else if (response.status === 404) {
|
} else if (response.status === 404) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@ -57,12 +69,18 @@ export function throwIfNotSuccess(response) {
|
|||||||
});
|
});
|
||||||
} else if (response.status > 399 && response.status < 501) {
|
} else if (response.status > 399 && response.status < 501) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
response.json().then(body => {
|
response
|
||||||
const errorMsg = body && body.isJoi ? extractJoiMsg(body) : extractLegacyMsg(body);
|
.json()
|
||||||
|
.then(body => {
|
||||||
|
const errorMsg =
|
||||||
|
body && body.isJoi
|
||||||
|
? extractJoiMsg(body)
|
||||||
|
: extractLegacyMsg(body);
|
||||||
let error = new Error(errorMsg);
|
let error = new Error(errorMsg);
|
||||||
error.statusCode = response.status;
|
error.statusCode = response.status;
|
||||||
reject(error);
|
reject(error);
|
||||||
}).catch(() => reject(new Error(defaultErrorMessage)))
|
})
|
||||||
|
.catch(() => reject(new Error(defaultErrorMessage)));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return Promise.reject(new ServiceError(response.status));
|
return Promise.reject(new ServiceError(response.status));
|
||||||
|
@ -13,6 +13,7 @@ import {
|
|||||||
ERROR_UPDATING_STRATEGY,
|
ERROR_UPDATING_STRATEGY,
|
||||||
ERROR_CREATING_STRATEGY,
|
ERROR_CREATING_STRATEGY,
|
||||||
ERROR_RECEIVE_STRATEGIES,
|
ERROR_RECEIVE_STRATEGIES,
|
||||||
|
UPDATE_STRATEGY_SUCCESS,
|
||||||
} from '../strategy/actions';
|
} from '../strategy/actions';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -79,9 +80,13 @@ const strategies = (state = getInitState(), action) => {
|
|||||||
return state.update('list', list =>
|
return state.update('list', list =>
|
||||||
list.remove(list.indexOf(action.error))
|
list.remove(list.indexOf(action.error))
|
||||||
);
|
);
|
||||||
|
// This reducer controls not only errors, but general information and success
|
||||||
|
// messages. This can be a little misleading, given it's naming. We should
|
||||||
|
// revise how this works in a future update.
|
||||||
case UPDATE_FEATURE_TOGGLE:
|
case UPDATE_FEATURE_TOGGLE:
|
||||||
case UPDATE_FEATURE_TOGGLE_STRATEGIES:
|
case UPDATE_FEATURE_TOGGLE_STRATEGIES:
|
||||||
case UPDATE_APPLICATION_FIELD:
|
case UPDATE_APPLICATION_FIELD:
|
||||||
|
case UPDATE_STRATEGY_SUCCESS:
|
||||||
return addErrorIfNotAlreadyInList(state, action.info);
|
return addErrorIfNotAlreadyInList(state, action.info);
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
|
@ -110,7 +110,10 @@ export function createFeatureToggles(featureToggle) {
|
|||||||
featureToggle: createdFeature,
|
featureToggle: createdFeature,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(dispatchError(dispatch, ERROR_CREATING_FEATURE_TOGGLE));
|
.catch(e => {
|
||||||
|
dispatchError(dispatch, ERROR_CREATING_FEATURE_TOGGLE);
|
||||||
|
throw e;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,24 +192,28 @@ export function requestUpdateFeatureToggleStrategies(
|
|||||||
|
|
||||||
export function requestUpdateFeatureToggleVariants(featureToggle, newVariants) {
|
export function requestUpdateFeatureToggleVariants(featureToggle, newVariants) {
|
||||||
return dispatch => {
|
return dispatch => {
|
||||||
featureToggle.variants = newVariants;
|
const newFeature = { ...featureToggle };
|
||||||
|
newFeature.variants = newVariants;
|
||||||
dispatch({ type: START_UPDATE_FEATURE_TOGGLE });
|
dispatch({ type: START_UPDATE_FEATURE_TOGGLE });
|
||||||
|
|
||||||
return api
|
return api
|
||||||
.update(featureToggle)
|
.update(newFeature)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
const info = `${featureToggle.name} successfully updated!`;
|
const info = `${newFeature.name} successfully updated!`;
|
||||||
setTimeout(
|
setTimeout(
|
||||||
() => dispatch({ type: MUTE_ERROR, error: info }),
|
() => dispatch({ type: MUTE_ERROR, error: info }),
|
||||||
1000
|
1000
|
||||||
);
|
);
|
||||||
return dispatch({
|
return dispatch({
|
||||||
type: UPDATE_FEATURE_TOGGLE_STRATEGIES,
|
type: UPDATE_FEATURE_TOGGLE_STRATEGIES,
|
||||||
featureToggle,
|
featureToggle: newFeature,
|
||||||
info,
|
info,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(dispatchError(dispatch, ERROR_UPDATE_FEATURE_TOGGLE));
|
.catch(e => {
|
||||||
|
dispatchError(dispatch, ERROR_UPDATE_FEATURE_TOGGLE);
|
||||||
|
throw e;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,14 +50,14 @@ function validate(featureToggle) {
|
|||||||
|
|
||||||
function update(featureToggle) {
|
function update(featureToggle) {
|
||||||
return validateToggle(featureToggle)
|
return validateToggle(featureToggle)
|
||||||
.then(() =>
|
.then(() => {
|
||||||
fetch(`${URI}/${featureToggle.name}`, {
|
return fetch(`${URI}/${featureToggle.name}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers,
|
headers,
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
body: JSON.stringify(featureToggle),
|
body: JSON.stringify(featureToggle),
|
||||||
|
});
|
||||||
})
|
})
|
||||||
)
|
|
||||||
.then(throwIfNotSuccess);
|
.then(throwIfNotSuccess);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import api from './api';
|
import api from './api';
|
||||||
import applicationApi from '../application/api';
|
import applicationApi from '../application/api';
|
||||||
import { dispatchError } from '../util';
|
import { dispatchError } from '../util';
|
||||||
|
import { MUTE_ERROR } from '../error/actions';
|
||||||
|
|
||||||
export const ADD_STRATEGY = 'ADD_STRATEGY';
|
export const ADD_STRATEGY = 'ADD_STRATEGY';
|
||||||
export const UPDATE_STRATEGY = 'UPDATE_STRATEGY';
|
export const UPDATE_STRATEGY = 'UPDATE_STRATEGY';
|
||||||
@ -19,6 +20,7 @@ export const ERROR_UPDATING_STRATEGY = 'ERROR_UPDATING_STRATEGY';
|
|||||||
export const ERROR_REMOVING_STRATEGY = 'ERROR_REMOVING_STRATEGY';
|
export const ERROR_REMOVING_STRATEGY = 'ERROR_REMOVING_STRATEGY';
|
||||||
export const ERROR_DEPRECATING_STRATEGY = 'ERROR_DEPRECATING_STRATEGY';
|
export const ERROR_DEPRECATING_STRATEGY = 'ERROR_DEPRECATING_STRATEGY';
|
||||||
export const ERROR_REACTIVATING_STRATEGY = 'ERROR_REACTIVATING_STRATEGY';
|
export const ERROR_REACTIVATING_STRATEGY = 'ERROR_REACTIVATING_STRATEGY';
|
||||||
|
export const UPDATE_STRATEGY_SUCCESS = 'UPDATE_STRATEGY_SUCCESS';
|
||||||
|
|
||||||
export const receiveStrategies = json => ({
|
export const receiveStrategies = json => ({
|
||||||
type: RECEIVE_STRATEGIES,
|
type: RECEIVE_STRATEGIES,
|
||||||
@ -46,6 +48,14 @@ const reactivateStrategyEvent = strategy => ({
|
|||||||
strategy,
|
strategy,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const setInfoMessage = (info, dispatch) => {
|
||||||
|
dispatch({
|
||||||
|
type: UPDATE_STRATEGY_SUCCESS,
|
||||||
|
info: info,
|
||||||
|
});
|
||||||
|
setTimeout(() => dispatch({ type: MUTE_ERROR, error: info }), 1500);
|
||||||
|
};
|
||||||
|
|
||||||
export function fetchStrategies() {
|
export function fetchStrategies() {
|
||||||
return dispatch => {
|
return dispatch => {
|
||||||
dispatch(startRequest());
|
dispatch(startRequest());
|
||||||
@ -64,6 +74,9 @@ export function createStrategy(strategy) {
|
|||||||
return api
|
return api
|
||||||
.create(strategy)
|
.create(strategy)
|
||||||
.then(() => dispatch(addStrategy(strategy)))
|
.then(() => dispatch(addStrategy(strategy)))
|
||||||
|
.then(() => {
|
||||||
|
setInfoMessage('Strategy successfully created.', dispatch);
|
||||||
|
})
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
dispatchError(dispatch, ERROR_CREATING_STRATEGY);
|
dispatchError(dispatch, ERROR_CREATING_STRATEGY);
|
||||||
throw e;
|
throw e;
|
||||||
@ -78,6 +91,9 @@ export function updateStrategy(strategy) {
|
|||||||
return api
|
return api
|
||||||
.update(strategy)
|
.update(strategy)
|
||||||
.then(() => dispatch(updatedStrategy(strategy)))
|
.then(() => dispatch(updatedStrategy(strategy)))
|
||||||
|
.then(() => {
|
||||||
|
setInfoMessage('Strategy successfully updated.', dispatch);
|
||||||
|
})
|
||||||
.catch(dispatchError(dispatch, ERROR_UPDATING_STRATEGY));
|
.catch(dispatchError(dispatch, ERROR_UPDATING_STRATEGY));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -87,6 +103,9 @@ export function removeStrategy(strategy) {
|
|||||||
api
|
api
|
||||||
.remove(strategy)
|
.remove(strategy)
|
||||||
.then(() => dispatch(createRemoveStrategy(strategy)))
|
.then(() => dispatch(createRemoveStrategy(strategy)))
|
||||||
|
.then(() => {
|
||||||
|
setInfoMessage('Strategy successfully deleted.', dispatch);
|
||||||
|
})
|
||||||
.catch(dispatchError(dispatch, ERROR_REMOVING_STRATEGY));
|
.catch(dispatchError(dispatch, ERROR_REMOVING_STRATEGY));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,6 +118,9 @@ export function deprecateStrategy(strategy) {
|
|||||||
dispatch(startDeprecate());
|
dispatch(startDeprecate());
|
||||||
api.deprecate(strategy)
|
api.deprecate(strategy)
|
||||||
.then(() => dispatch(deprecateStrategyEvent(strategy)))
|
.then(() => dispatch(deprecateStrategyEvent(strategy)))
|
||||||
|
.then(() =>
|
||||||
|
setInfoMessage('Strategy successfully deprecated', dispatch)
|
||||||
|
)
|
||||||
.catch(dispatchError(dispatch, ERROR_DEPRECATING_STRATEGY));
|
.catch(dispatchError(dispatch, ERROR_DEPRECATING_STRATEGY));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -108,6 +130,9 @@ export function reactivateStrategy(strategy) {
|
|||||||
dispatch(startReactivate());
|
dispatch(startReactivate());
|
||||||
api.reactivate(strategy)
|
api.reactivate(strategy)
|
||||||
.then(() => dispatch(reactivateStrategyEvent(strategy)))
|
.then(() => dispatch(reactivateStrategyEvent(strategy)))
|
||||||
|
.then(() =>
|
||||||
|
setInfoMessage('Strategy successfully reactivated', dispatch)
|
||||||
|
)
|
||||||
.catch(dispatchError(dispatch, ERROR_REACTIVATING_STRATEGY));
|
.catch(dispatchError(dispatch, ERROR_REACTIVATING_STRATEGY));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user