From c2842c81e6afce0d85ed9840830b48edad8bc8e8 Mon Sep 17 00:00:00 2001 From: Youssef Khedher Date: Fri, 11 Feb 2022 00:08:55 +0100 Subject: [PATCH] Refactor/strategies (#668) * feat: add useStrategiesApi hook * refactor: remove redux from strategies component * refactor: CreateStrategy Component * fix: remove ts errors * refactor: change strategy-detail to functional component * refactor: get strategy name from params * refactor: use features hook and refactor toggle list link * refactor: StrategiesList * refactor: fix delete strategy function * fix: ts errors * refactor: CreateStrategy to StrategyForm * feat: add toast for StrategyForm * refactor: add StrategyView and delete old component * refactor: StrategyDetails and clean unused files * fix: cleanup unused code * fix: add await * fix: remove unused stores Co-authored-by: Fredrik Oseberg --- frontend/src/component/menu/routes.js | 10 +- .../CreateStrategy/CreateStrategy.jsx | 129 ---------- .../strategies/CreateStrategy/index.js | 154 ----------- .../{styles.js => StrategiesList.styles.ts} | 0 ...{StrategiesList.jsx => StrategiesList.tsx} | 146 ++++++----- .../strategies/StrategiesList/index.jsx | 36 --- .../strategies/StrategyForm/StrategyForm.tsx | 198 ++++++++++++++ .../StrategyParameter/StrategyParameter.jsx | 0 .../StrategyParameters/StrategyParameters.jsx | 2 +- .../StrategyDetails/StrategyDetails.tsx | 97 +++++++ .../strategies/StrategyView/StrategyView.tsx | 78 ++++++ .../TogglesLinkList/TogglesLinkList.tsx | 47 ++++ .../list-component-test.jsx.snap | 12 +- .../strategy-details-component-test.jsx.snap | 241 +----------------- .../__tests__/list-component-test.jsx | 52 ++-- .../strategy-details-component-test.jsx | 4 +- .../strategies/show-strategy-component.js | 100 -------- .../strategies/strategy-details-component.jsx | 103 -------- .../strategies/strategy-details-container.js | 33 --- .../strategies/toggles-link-list.jsx | 42 --- .../useStrategiesApi/useStrategiesApi.ts | 98 +++++++ .../useApplications/useApplications.ts | 2 +- .../getters/useStrategies/useStrategies.ts | 4 +- frontend/src/page/strategies/create.js | 11 - frontend/src/page/strategies/index.js | 11 - frontend/src/page/strategies/show.js | 14 - frontend/src/store/application/actions.js | 58 ----- frontend/src/store/application/api.js | 52 ---- frontend/src/store/application/index.js | 26 -- frontend/src/store/error/index.js | 14 - frontend/src/store/index.js | 4 - frontend/src/store/strategy/actions.js | 138 ---------- frontend/src/store/strategy/api.js | 61 ----- frontend/src/store/strategy/index.js | 67 ----- frontend/src/store/ui-bootstrap/actions.js | 5 +- 35 files changed, 654 insertions(+), 1395 deletions(-) delete mode 100644 frontend/src/component/strategies/CreateStrategy/CreateStrategy.jsx delete mode 100644 frontend/src/component/strategies/CreateStrategy/index.js rename frontend/src/component/strategies/StrategiesList/{styles.js => StrategiesList.styles.ts} (100%) rename frontend/src/component/strategies/StrategiesList/{StrategiesList.jsx => StrategiesList.tsx} (66%) delete mode 100644 frontend/src/component/strategies/StrategiesList/index.jsx create mode 100644 frontend/src/component/strategies/StrategyForm/StrategyForm.tsx rename frontend/src/component/strategies/{CreateStrategy => StrategyForm}/StrategyParameters/StrategyParameter/StrategyParameter.jsx (100%) rename frontend/src/component/strategies/{CreateStrategy => StrategyForm}/StrategyParameters/StrategyParameters.jsx (92%) create mode 100644 frontend/src/component/strategies/StrategyView/StrategyDetails/StrategyDetails.tsx create mode 100644 frontend/src/component/strategies/StrategyView/StrategyView.tsx create mode 100644 frontend/src/component/strategies/TogglesLinkList/TogglesLinkList.tsx delete mode 100644 frontend/src/component/strategies/show-strategy-component.js delete mode 100644 frontend/src/component/strategies/strategy-details-component.jsx delete mode 100644 frontend/src/component/strategies/strategy-details-container.js delete mode 100644 frontend/src/component/strategies/toggles-link-list.jsx create mode 100644 frontend/src/hooks/api/actions/useStrategiesApi/useStrategiesApi.ts delete mode 100644 frontend/src/page/strategies/create.js delete mode 100644 frontend/src/page/strategies/index.js delete mode 100644 frontend/src/page/strategies/show.js delete mode 100644 frontend/src/store/application/actions.js delete mode 100644 frontend/src/store/application/api.js delete mode 100644 frontend/src/store/application/index.js delete mode 100644 frontend/src/store/strategy/actions.js delete mode 100644 frontend/src/store/strategy/api.js delete mode 100644 frontend/src/store/strategy/index.js diff --git a/frontend/src/component/menu/routes.js b/frontend/src/component/menu/routes.js index 564708142a..8809dae5a7 100644 --- a/frontend/src/component/menu/routes.js +++ b/frontend/src/component/menu/routes.js @@ -1,8 +1,8 @@ import CopyFeatureToggle from '../../page/features/copy'; import { FeatureToggleListContainer } from '../feature/FeatureToggleList/FeatureToggleListContainer'; -import CreateStrategies from '../../page/strategies/create'; -import StrategyView from '../../page/strategies/show'; -import Strategies from '../../page/strategies'; +import { StrategyForm } from '../strategies/StrategyForm/StrategyForm'; +import { StrategyView } from '../../component/strategies/StrategyView/StrategyView'; +import { StrategiesList } from '../strategies/StrategiesList/StrategiesList'; import HistoryPage from '../../page/history'; import HistoryTogglePage from '../../page/history/toggle'; import { ArchiveListContainer } from '../archive/ArchiveListContainer'; @@ -243,7 +243,7 @@ export const routes = [ path: '/strategies/create', title: 'Create', parent: '/strategies', - component: CreateStrategies, + component: StrategyForm, type: 'protected', layout: 'main', menu: {}, @@ -260,7 +260,7 @@ export const routes = [ { path: '/strategies', title: 'Strategies', - component: Strategies, + component: StrategiesList, type: 'protected', layout: 'main', menu: { mobile: true, advanced: true }, diff --git a/frontend/src/component/strategies/CreateStrategy/CreateStrategy.jsx b/frontend/src/component/strategies/CreateStrategy/CreateStrategy.jsx deleted file mode 100644 index 10ea940c41..0000000000 --- a/frontend/src/component/strategies/CreateStrategy/CreateStrategy.jsx +++ /dev/null @@ -1,129 +0,0 @@ -import PropTypes from 'prop-types'; - -import { Typography, TextField, Button } from '@material-ui/core'; -import { Add } from '@material-ui/icons'; - -import PageContent from '../../common/PageContent/PageContent'; -import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender'; - -import { styles as commonStyles, FormButtons } from '../../common'; -import { trim } from '../../common/util'; -import StrategyParameters from './StrategyParameters/StrategyParameters'; - -const CreateStrategy = ({ - input, - setValue, - appParameter, - onCancel, - editMode = false, - errors, - onSubmit, - clearErrors, - updateParameter, -}) => { - const getHeaderTitle = () => { - if (editMode) return 'Edit strategy'; - return 'Create a new strategy'; - }; - - return ( - - - Be careful! Changing a strategy definition might also - require changes to the implementation in the clients. - - } - /> - -
- { - clearErrors(); - setValue('name', trim(target.value)); - }} - value={input.name} - variant="outlined" - size="small" - /> - - - setValue('description', target.value) - } - value={input.description} - variant="outlined" - size="small" - /> - - - - - - Update - - } - elseShow={ - - } - /> - -
- ); -}; - -CreateStrategy.propTypes = { - input: PropTypes.object, - setValue: PropTypes.func, - appParameter: PropTypes.func, - updateParameter: PropTypes.func, - clear: PropTypes.func, - onCancel: PropTypes.func, - onSubmit: PropTypes.func, - errors: PropTypes.object, - editMode: PropTypes.bool, - clearErrors: PropTypes.func, -}; - -export default CreateStrategy; diff --git a/frontend/src/component/strategies/CreateStrategy/index.js b/frontend/src/component/strategies/CreateStrategy/index.js deleted file mode 100644 index ae7747e27f..0000000000 --- a/frontend/src/component/strategies/CreateStrategy/index.js +++ /dev/null @@ -1,154 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; - -import { connect } from 'react-redux'; - -import { - createStrategy, - updateStrategy, -} from '../../../store/strategy/actions'; - -import CreateStrategy from './CreateStrategy'; -import { loadNameFromUrl } from '../../common/util'; - -const STRATEGY_EXIST_ERROR = 'Error: Strategy with name'; - -class WrapperComponent extends Component { - constructor(props) { - super(props); - this.state = { - strategy: this.props.strategy, - errors: {}, - dirty: false, - }; - } - - clearErrors = () => { - this.setState({ errors: {} }); - }; - - appParameter = () => { - const { strategy } = this.state; - strategy.parameters = [...strategy.parameters, {}]; - this.setState({ strategy, dirty: true }); - }; - - updateParameter = (index, updated) => { - const { strategy } = this.state; - - // 1. Make a shallow copy of the items - let parameters = [...strategy.parameters]; - // 2. Make a shallow copy of the item you want to mutate - let item = { ...parameters[index] }; - // 3. Replace the property you're intested in - // 4. Put it back into our array. N.B. we *are* mutating the array here, but that's why we made a copy first - parameters[index] = Object.assign({}, item, updated); - // 5. Set the state to our new copy - strategy.parameters = parameters; - this.setState({ strategy }); - }; - - setValue = (field, value) => { - const { strategy } = this.state; - strategy[field] = value; - this.setState({ strategy, dirty: true }); - }; - - onSubmit = async evt => { - evt.preventDefault(); - const { createStrategy, updateStrategy, history, editMode } = - this.props; - const { strategy } = this.state; - - const parameters = (strategy.parameters || []) - .filter(({ name }) => !!name) - .map( - ({ - name, - type = 'string', - description = '', - required = false, - }) => ({ - name, - type, - description, - required, - }) - ); - - strategy.parameters = parameters; - - if (editMode) { - await updateStrategy(strategy); - - history.push(`/strategies/view/${strategy.name}`); - } else { - try { - await createStrategy(strategy); - history.push(`/strategies`); - } catch (e) { - if (e.toString().includes(STRATEGY_EXIST_ERROR)) { - this.setState({ - errors: { - name: 'A strategy with this name already exists ', - }, - }); - } - } - } - }; - - onCancel = evt => { - evt.preventDefault(); - const { history, editMode } = this.props; - const { strategy } = this.state; - - if (editMode) { - history.push(`/strategies/view/${strategy.name}`); - } else { - history.push('/strategies'); - } - }; - - render() { - return ( - - ); - } -} -WrapperComponent.propTypes = { - history: PropTypes.object.isRequired, - createStrategy: PropTypes.func.isRequired, - updateStrategy: PropTypes.func.isRequired, - strategy: PropTypes.object, - editMode: PropTypes.bool, -}; - -const mapDispatchToProps = { createStrategy, updateStrategy }; - -const mapStateToProps = (state, props) => { - const { strategy, editMode } = props; - return { - strategy: strategy - ? strategy - : { name: loadNameFromUrl(), description: '', parameters: [] }, - editMode, - }; -}; - -const FormAddContainer = connect( - mapStateToProps, - mapDispatchToProps -)(WrapperComponent); - -export default FormAddContainer; diff --git a/frontend/src/component/strategies/StrategiesList/styles.js b/frontend/src/component/strategies/StrategiesList/StrategiesList.styles.ts similarity index 100% rename from frontend/src/component/strategies/StrategiesList/styles.js rename to frontend/src/component/strategies/StrategiesList/StrategiesList.styles.ts diff --git a/frontend/src/component/strategies/StrategiesList/StrategiesList.jsx b/frontend/src/component/strategies/StrategiesList/StrategiesList.tsx similarity index 66% rename from frontend/src/component/strategies/StrategiesList/StrategiesList.jsx rename to frontend/src/component/strategies/StrategiesList/StrategiesList.tsx index c3ad193d65..8439435d58 100644 --- a/frontend/src/component/strategies/StrategiesList/StrategiesList.jsx +++ b/frontend/src/component/strategies/StrategiesList/StrategiesList.tsx @@ -1,9 +1,7 @@ -import { useContext, useEffect, useState } from 'react'; -import PropTypes from 'prop-types'; +import { useContext, useState } from 'react'; import classnames from 'classnames'; import { Link, useHistory } from 'react-router-dom'; import useMediaQuery from '@material-ui/core/useMediaQuery'; - import { IconButton, List, @@ -19,42 +17,44 @@ import { Visibility, VisibilityOff, } from '@material-ui/icons'; - import { CREATE_STRATEGY, DELETE_STRATEGY, UPDATE_STRATEGY, } from '../../providers/AccessProvider/permissions'; - import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender'; import PageContent from '../../common/PageContent/PageContent'; import HeaderTitle from '../../common/HeaderTitle'; - -import { useStyles } from './styles'; +import { useStyles } from './StrategiesList.styles'; import AccessContext from '../../../contexts/AccessContext'; import Dialogue from '../../common/Dialogue'; import { ADD_NEW_STRATEGY_ID } from '../../../testIds'; import PermissionIconButton from '../../common/PermissionIconButton/PermissionIconButton'; import PermissionButton from '../../common/PermissionButton/PermissionButton'; import { getHumanReadableStrategyName } from '../../../utils/strategy-names'; +import useStrategies from '../../../hooks/api/getters/useStrategies/useStrategies'; +import useStrategiesApi from '../../../hooks/api/actions/useStrategiesApi/useStrategiesApi'; +import useToast from '../../../hooks/useToast'; +import { IStrategy } from '../../../interfaces/strategy'; -const StrategiesList = ({ - strategies, - fetchStrategies, - removeStrategy, - deprecateStrategy, - reactivateStrategy, -}) => { +interface IDialogueMetaData { + show: boolean; + title: string; + onConfirm: () => void; +} + +export const StrategiesList = () => { const history = useHistory(); const styles = useStyles(); const smallScreen = useMediaQuery('(max-width:700px)'); const { hasAccess } = useContext(AccessContext); - const [dialogueMetaData, setDialogueMetaData] = useState({ show: false }); - - useEffect(() => { - fetchStrategies(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + const [dialogueMetaData, setDialogueMetaData] = useState( + { show: false, title: '', onConfirm: () => {} } + ); + const { strategies, refetchStrategies } = useStrategies(); + const { removeStrategy, deprecateStrategy, reactivateStrategy } = + useStrategiesApi(); + const { setToastData, setToastApiError } = useToast(); const headerButton = () => ( history.push('/strategies/create')} color="primary" permission={CREATE_STRATEGY} - variant="contained" data-test={ADD_NEW_STRATEGY_ID} tooltip={'Add new strategy'} > @@ -99,16 +98,70 @@ const StrategiesList = ({ ); - const reactivateButton = strategy => ( + const onReactivateStrategy = (strategy: IStrategy) => { + setDialogueMetaData({ + show: true, + title: 'Really reactivate strategy?', + onConfirm: async () => { + try { + await reactivateStrategy(strategy); + refetchStrategies(); + setToastData({ + type: 'success', + title: 'Success', + text: 'Strategy reactivated successfully', + }); + } catch (e: any) { + setToastApiError(e.toString()); + } + }, + }); + }; + + const onDeprecateStrategy = (strategy: IStrategy) => { + setDialogueMetaData({ + show: true, + title: 'Really deprecate strategy?', + onConfirm: async () => { + try { + await deprecateStrategy(strategy); + refetchStrategies(); + setToastData({ + type: 'success', + title: 'Success', + text: 'Strategy deprecated successfully', + }); + } catch (e: any) { + setToastApiError(e.toString()); + } + }, + }); + }; + + const onDeleteStrategy = (strategy: IStrategy) => { + setDialogueMetaData({ + show: true, + title: 'Really delete strategy?', + onConfirm: async () => { + try { + await removeStrategy(strategy); + refetchStrategies(); + setToastData({ + type: 'success', + title: 'Success', + text: 'Strategy deleted successfully', + }); + } catch (e: any) { + setToastApiError(e.toString()); + } + }, + }); + }; + + const reactivateButton = (strategy: IStrategy) => ( - setDialogueMetaData({ - show: true, - title: 'Really reactivate strategy?', - onConfirm: () => reactivateStrategy(strategy), - }) - } + onClick={() => onReactivateStrategy(strategy)} permission={UPDATE_STRATEGY} tooltip={'Reactivate activation strategy'} > @@ -117,7 +170,7 @@ const StrategiesList = ({ ); - const deprecateButton = strategy => ( + const deprecateButton = (strategy: IStrategy) => ( - setDialogueMetaData({ - show: true, - title: 'Really deprecate strategy?', - onConfirm: () => deprecateStrategy(strategy), - }) - } + onClick={() => onDeprecateStrategy(strategy)} permission={UPDATE_STRATEGY} tooltip={'Deprecate activation strategy'} > @@ -149,18 +196,12 @@ const StrategiesList = ({ /> ); - const deleteButton = strategy => ( + const deleteButton = (strategy: IStrategy) => ( - setDialogueMetaData({ - show: true, - title: 'Really delete strategy?', - onConfirm: () => removeStrategy(strategy), - }) - } + onClick={() => onDeleteStrategy(strategy)} permission={DELETE_STRATEGY} tooltip={'Delete strategy'} > @@ -230,23 +271,10 @@ const StrategiesList = ({ 0} - show={strategyList()} + show={<>{strategyList()}} elseShow={No strategies found} /> ); }; - -StrategiesList.propTypes = { - strategies: PropTypes.array.isRequired, - fetchStrategies: PropTypes.func.isRequired, - removeStrategy: PropTypes.func.isRequired, - deprecateStrategy: PropTypes.func.isRequired, - reactivateStrategy: PropTypes.func.isRequired, - history: PropTypes.object.isRequired, - name: PropTypes.string, - deprecated: PropTypes.bool, -}; - -export default StrategiesList; diff --git a/frontend/src/component/strategies/StrategiesList/index.jsx b/frontend/src/component/strategies/StrategiesList/index.jsx deleted file mode 100644 index 9703490dc9..0000000000 --- a/frontend/src/component/strategies/StrategiesList/index.jsx +++ /dev/null @@ -1,36 +0,0 @@ -import { connect } from 'react-redux'; -import StrategiesList from './StrategiesList.jsx'; -import { - fetchStrategies, - removeStrategy, - deprecateStrategy, - reactivateStrategy, -} from '../../../store/strategy/actions'; - -const mapStateToProps = state => { - const list = state.strategies.get('list').toArray(); - - return { - strategies: list, - }; -}; - -const mapDispatchToProps = dispatch => ({ - removeStrategy: strategy => { - removeStrategy(strategy)(dispatch); - }, - deprecateStrategy: strategy => { - deprecateStrategy(strategy)(dispatch); - }, - reactivateStrategy: strategy => { - reactivateStrategy(strategy)(dispatch); - }, - fetchStrategies: () => fetchStrategies()(dispatch), -}); - -const StrategiesListContainer = connect( - mapStateToProps, - mapDispatchToProps -)(StrategiesList); - -export default StrategiesListContainer; diff --git a/frontend/src/component/strategies/StrategyForm/StrategyForm.tsx b/frontend/src/component/strategies/StrategyForm/StrategyForm.tsx new file mode 100644 index 0000000000..369116939e --- /dev/null +++ b/frontend/src/component/strategies/StrategyForm/StrategyForm.tsx @@ -0,0 +1,198 @@ +import React, { useState } from 'react'; +import { Typography, TextField, Button } from '@material-ui/core'; +import { Add } from '@material-ui/icons'; +import PageContent from '../../common/PageContent/PageContent'; +import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender'; +import { styles as commonStyles, FormButtons } from '../../common'; +import { trim } from '../../common/util'; +import StrategyParameters from './StrategyParameters/StrategyParameters'; +import { useHistory } from 'react-router-dom'; +import useStrategiesApi from '../../../hooks/api/actions/useStrategiesApi/useStrategiesApi'; +import { IStrategy } from '../../../interfaces/strategy'; +import useToast from '../../../hooks/useToast'; +import useStrategies from '../../../hooks/api/getters/useStrategies/useStrategies'; + +interface ICustomStrategyParams { + name?: string; + type?: string; + description?: string; + required?: boolean; +} + +interface ICustomStrategyErrors { + name?: string; +} + +interface IStrategyFormProps { + editMode: boolean; + strategy: IStrategy; +} +export const StrategyForm = ({ editMode, strategy }: IStrategyFormProps) => { + const history = useHistory(); + const [name, setName] = useState(strategy?.name || ''); + const [description, setDescription] = useState(strategy?.description || ''); + const [params, setParams] = useState( + strategy?.parameters || [] + ); + const [errors, setErrors] = useState({}); + const { createStrategy, updateStrategy } = useStrategiesApi(); + const { refetchStrategies } = useStrategies(); + const { setToastData, setToastApiError } = useToast(); + + const clearErrors = () => { + setErrors({}); + }; + + const getHeaderTitle = () => { + if (editMode) return 'Edit strategy'; + return 'Create a new strategy'; + }; + + const appParameter = () => { + setParams(prev => [...prev, {}]); + }; + + const updateParameter = (index: number, updated: object) => { + let item = { ...params[index] }; + params[index] = Object.assign({}, item, updated); + setParams(prev => [...prev]); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + const parameters = (params || []) + .filter(({ name }) => !!name) + .map( + ({ + name, + type = 'string', + description = '', + required = false, + }) => ({ + name, + type, + description, + required, + }) + ); + setParams(prev => [...parameters]); + if (editMode) { + try { + await updateStrategy({ name, description, parameters: params }); + history.push(`/strategies/view/${name}`); + setToastData({ + type: 'success', + title: 'Success', + text: 'Successfully updated strategy', + }); + refetchStrategies(); + } catch (e: any) { + setToastApiError(e.toString()); + } + } else { + try { + await createStrategy({ name, description, parameters: params }); + history.push(`/strategies`); + setToastData({ + type: 'success', + title: 'Success', + text: 'Successfully created new strategy', + }); + refetchStrategies(); + } catch (e: any) { + const STRATEGY_EXIST_ERROR = 'Error: Strategy with name'; + if (e.toString().includes(STRATEGY_EXIST_ERROR)) { + setErrors({ + name: 'A strategy with this name already exists', + }); + } + } + } + }; + + const handleCancel = () => history.goBack(); + + return ( + + + Be careful! Changing a strategy definition might also + require changes to the implementation in the clients. + + } + /> + +
+ { + clearErrors(); + setName(trim(e.target.value)); + }} + value={name} + variant="outlined" + size="small" + /> + + setDescription(e.target.value)} + value={description} + variant="outlined" + size="small" + /> + + + + + + Update + + } + elseShow={ + + } + /> + +
+ ); +}; diff --git a/frontend/src/component/strategies/CreateStrategy/StrategyParameters/StrategyParameter/StrategyParameter.jsx b/frontend/src/component/strategies/StrategyForm/StrategyParameters/StrategyParameter/StrategyParameter.jsx similarity index 100% rename from frontend/src/component/strategies/CreateStrategy/StrategyParameters/StrategyParameter/StrategyParameter.jsx rename to frontend/src/component/strategies/StrategyForm/StrategyParameters/StrategyParameter/StrategyParameter.jsx diff --git a/frontend/src/component/strategies/CreateStrategy/StrategyParameters/StrategyParameters.jsx b/frontend/src/component/strategies/StrategyForm/StrategyParameters/StrategyParameters.jsx similarity index 92% rename from frontend/src/component/strategies/CreateStrategy/StrategyParameters/StrategyParameters.jsx rename to frontend/src/component/strategies/StrategyForm/StrategyParameters/StrategyParameters.jsx index e99325e59b..7ce319ab65 100644 --- a/frontend/src/component/strategies/CreateStrategy/StrategyParameters/StrategyParameters.jsx +++ b/frontend/src/component/strategies/StrategyForm/StrategyParameters/StrategyParameters.jsx @@ -10,7 +10,7 @@ const StrategyParameters = ({ input = [], count = 0, updateParameter }) => ( {gerArrayWithEntries(count).map((v, i) => ( updateParameter(i, v, true)} + set={v => updateParameter(i, v)} index={i} input={input[i]} /> diff --git a/frontend/src/component/strategies/StrategyView/StrategyDetails/StrategyDetails.tsx b/frontend/src/component/strategies/StrategyView/StrategyDetails/StrategyDetails.tsx new file mode 100644 index 0000000000..8016dd31a1 --- /dev/null +++ b/frontend/src/component/strategies/StrategyView/StrategyDetails/StrategyDetails.tsx @@ -0,0 +1,97 @@ +import { + Grid, + List, + ListItem, + ListItemAvatar, + ListItemText, + Tooltip, +} from '@material-ui/core'; +import { Add, RadioButtonChecked } from '@material-ui/icons'; +import { AppsLinkList } from '../../../common'; +import ConditionallyRender from '../../../common/ConditionallyRender'; +import styles from '../../strategies.module.scss'; +import { TogglesLinkList } from '../../TogglesLinkList/TogglesLinkList'; +import { IParameter, IStrategy } from '../../../../interfaces/strategy'; +import { IApplication } from '../../../../interfaces/application'; +import { IFeatureToggle } from '../../../../interfaces/featureToggle'; + +interface IStrategyDetailsProps { + strategy: IStrategy; + applications: IApplication[]; + toggles: IFeatureToggle[]; +} + +export const StrategyDetails = ({ + strategy, + applications, + toggles, +}: IStrategyDetailsProps) => { + const { parameters = [] } = strategy; + const renderParameters = (params: IParameter[]) => { + if (params) { + return params.map(({ name, type, description, required }, i) => ( + + + + + + + } + elseShow={ + + + + + + } + /> + + {name} ({type}) + + } + secondary={description} + /> + + )); + } else { + return (no params); + } + }; + + return ( +
+ + +
Deprecated
+
+ } + /> + +
Parameters
+
+ {renderParameters(parameters)} +
+ + +
Applications using this strategy
+
+ +
+ + +
Toggles using this strategy
+
+ +
+ +
+ ); +}; diff --git a/frontend/src/component/strategies/StrategyView/StrategyView.tsx b/frontend/src/component/strategies/StrategyView/StrategyView.tsx new file mode 100644 index 0000000000..e9db415695 --- /dev/null +++ b/frontend/src/component/strategies/StrategyView/StrategyView.tsx @@ -0,0 +1,78 @@ +import { useContext } from 'react'; +import { Grid, Typography } from '@material-ui/core'; +import { StrategyForm } from '../StrategyForm/StrategyForm'; +import { UPDATE_STRATEGY } from '../../providers/AccessProvider/permissions'; +import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender'; +import TabNav from '../../common/TabNav/TabNav'; +import PageContent from '../../common/PageContent/PageContent'; +import AccessContext from '../../../contexts/AccessContext'; +import useStrategies from '../../../hooks/api/getters/useStrategies/useStrategies'; +import { useParams } from 'react-router-dom'; +import { useFeatures } from '../../../hooks/api/getters/useFeatures/useFeatures'; +import useApplications from '../../../hooks/api/getters/useApplications/useApplications'; +import { StrategyDetails } from './StrategyDetails/StrategyDetails'; + +export const StrategyView = () => { + const { hasAccess } = useContext(AccessContext); + const { strategyName } = useParams<{ strategyName: string }>(); + const { strategies } = useStrategies(); + const { features } = useFeatures(); + const { applications } = useApplications(); + + const toggles = features.filter(toggle => { + return toggle?.strategies.findIndex(s => s.name === strategyName) > -1; + }); + + const strategy = strategies.find(n => n.name === strategyName); + + const tabData = [ + { + label: 'Details', + component: ( + + ), + }, + { + label: 'Edit', + component: , + }, + ]; + + if (!strategy) return null; + return ( + + + + + {strategy.description} + + + + + } + elseShow={ +
+
+ +
+
+ } + /> +
+
+
+ ); +}; diff --git a/frontend/src/component/strategies/TogglesLinkList/TogglesLinkList.tsx b/frontend/src/component/strategies/TogglesLinkList/TogglesLinkList.tsx new file mode 100644 index 0000000000..2310ec4ef3 --- /dev/null +++ b/frontend/src/component/strategies/TogglesLinkList/TogglesLinkList.tsx @@ -0,0 +1,47 @@ +import { + List, + ListItem, + ListItemAvatar, + ListItemText, + Tooltip, +} from '@material-ui/core'; +import { PlayArrow, Pause } from '@material-ui/icons'; +import styles from '../../common/common.module.scss'; +import { Link } from 'react-router-dom'; +import ConditionallyRender from '../../common/ConditionallyRender'; + +interface ITogglesLinkListProps { + toggles: []; +} + +export const TogglesLinkList = ({ toggles }: ITogglesLinkListProps) => ( + + 0} + show={ + <> + {toggles.map(({ name, description = '-', enabled }) => ( + + + + {enabled ? : } + + + + {name} + + } + secondary={description} + /> + + ))} + + } + /> + +); diff --git a/frontend/src/component/strategies/__tests__/__snapshots__/list-component-test.jsx.snap b/frontend/src/component/strategies/__tests__/__snapshots__/list-component-test.jsx.snap index b2ef224934..af59195c0d 100644 --- a/frontend/src/component/strategies/__tests__/__snapshots__/list-component-test.jsx.snap +++ b/frontend/src/component/strategies/__tests__/__snapshots__/list-component-test.jsx.snap @@ -67,18 +67,18 @@ exports[`renders correctly with one strategy 1`] = ` className="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock" > - Another + Gradual rollout

- another's description + Roll out to a percentage of your userbase, and ensure that the experience is the same for the user on each visit.

@@ -201,18 +201,18 @@ exports[`renders correctly with one strategy without permissions 1`] = ` className="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock" > - Another + Gradual rollout

- another's description + Roll out to a percentage of your userbase, and ensure that the experience is the same for the user on each visit.

diff --git a/frontend/src/component/strategies/__tests__/__snapshots__/strategy-details-component-test.jsx.snap b/frontend/src/component/strategies/__tests__/__snapshots__/strategy-details-component-test.jsx.snap index 7cc163a816..3d1274c249 100644 --- a/frontend/src/component/strategies/__tests__/__snapshots__/strategy-details-component-test.jsx.snap +++ b/frontend/src/component/strategies/__tests__/__snapshots__/strategy-details-component-test.jsx.snap @@ -1,242 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`renders correctly with one strategy 1`] = ` -
-
-
-
-

- Another -

-
-
-
-
-
-
-
- another's description -
-
-
-
-
-
-
- Parameters -
-
-
    -
  • -
    - - - -
    -
    - -
    - customParam - - - ( - list - ) - -
    -
    -

    - customList -

    -
    -
  • -
-
-
-
- Applications using this strategy -
-
-
    -
  • -
    -
    - - - -
    -
    -
    - - - appA - - -

    - app description -

    -
    -
  • -
-
-
-
- Toggles using this strategy -
-
- -
-
-
-
-
-
-
-
-
-`; +exports[`renders correctly with one strategy 1`] = `null`; diff --git a/frontend/src/component/strategies/__tests__/list-component-test.jsx b/frontend/src/component/strategies/__tests__/list-component-test.jsx index ce3afed807..801eb528b3 100644 --- a/frontend/src/component/strategies/__tests__/list-component-test.jsx +++ b/frontend/src/component/strategies/__tests__/list-component-test.jsx @@ -1,10 +1,12 @@ -import React from 'react'; import { MemoryRouter } from 'react-router-dom'; import { ThemeProvider } from '@material-ui/core'; -import StrategiesListComponent from '../StrategiesList/StrategiesList'; +import { StrategiesList } from '../StrategiesList/StrategiesList'; import renderer from 'react-test-renderer'; import theme from '../../../themes/main-theme'; import AccessProvider from '../../providers/AccessProvider/AccessProvider'; +import { createFakeStore } from '../../../accessStoreFake'; +import { ADMIN } from '../../providers/AccessProvider/permissions'; +import UIProvider from '../../providers/UIProvider/UIProvider'; test('renders correctly with one strategy', () => { const strategy = { @@ -14,16 +16,18 @@ test('renders correctly with one strategy', () => { const tree = renderer.create( - - - + + + + + ); @@ -39,16 +43,20 @@ test('renders correctly with one strategy without permissions', () => { const tree = renderer.create( - - - + + + + + ); diff --git a/frontend/src/component/strategies/__tests__/strategy-details-component-test.jsx b/frontend/src/component/strategies/__tests__/strategy-details-component-test.jsx index f75033ac45..d16d440b57 100644 --- a/frontend/src/component/strategies/__tests__/strategy-details-component-test.jsx +++ b/frontend/src/component/strategies/__tests__/strategy-details-component-test.jsx @@ -1,6 +1,6 @@ import React from 'react'; import { ThemeProvider } from '@material-ui/core'; -import StrategyDetails from '../strategy-details-component'; +import { StrategyView } from '../StrategyView/StrategyView'; import renderer from 'react-test-renderer'; import { MemoryRouter } from 'react-router-dom'; import theme from '../../../themes/main-theme'; @@ -36,7 +36,7 @@ test('renders correctly with one strategy', () => { - ( - - - - - - - } - elseShow={ - - - - - - } - /> - - {name} ({type}) -
- } - secondary={description} - /> - - )); - } else { - return (no params); - } - } - - render() { - const { strategy, applications, toggles } = this.props; - - const { parameters = [] } = strategy; - - return ( -
- - -
Deprecated
-
- } - /> - -
Parameters
-
- {this.renderParameters(parameters)} -
- - -
Applications using this strategy
-
- -
- - -
Toggles using this strategy
-
- -
- -
- ); - } -} - -export default ShowStrategyComponent; diff --git a/frontend/src/component/strategies/strategy-details-component.jsx b/frontend/src/component/strategies/strategy-details-component.jsx deleted file mode 100644 index df23ced4df..0000000000 --- a/frontend/src/component/strategies/strategy-details-component.jsx +++ /dev/null @@ -1,103 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { Grid, Typography } from '@material-ui/core'; -import ShowStrategy from './show-strategy-component'; -import EditStrategy from './CreateStrategy'; -import { UPDATE_STRATEGY } from '../providers/AccessProvider/permissions'; -import ConditionallyRender from '../common/ConditionallyRender/ConditionallyRender'; -import TabNav from '../common/TabNav/TabNav'; -import PageContent from '../common/PageContent/PageContent'; -import AccessContext from '../../contexts/AccessContext'; - -export default class StrategyDetails extends Component { - static contextType = AccessContext; - - static propTypes = { - strategyName: PropTypes.string.isRequired, - toggles: PropTypes.array, - applications: PropTypes.array, - activeTab: PropTypes.string.isRequired, - strategy: PropTypes.object, - fetchStrategies: PropTypes.func.isRequired, - fetchApplications: PropTypes.func.isRequired, - fetchFeatureToggles: PropTypes.func.isRequired, - history: PropTypes.object.isRequired, - }; - - componentDidMount() { - if (!this.props.strategy) { - this.props.fetchStrategies(); - } - if (!this.props.applications || this.props.applications.length === 0) { - this.props.fetchApplications(); - } - if (!this.props.toggles || this.props.toggles.length === 0) { - this.props.fetchFeatureToggles(); - } - } - - render() { - const strategy = this.props.strategy; - if (!strategy) return null; - - const tabData = [ - { - label: 'Details', - component: ( - - ), - }, - { - label: 'Edit', - component: ( - - ), - }, - ]; - - const { hasAccess } = this.context; - - return ( - - - - - {strategy.description} - - - - - } - elseShow={ -
-
- -
-
- } - /> -
-
-
- ); - } -} diff --git a/frontend/src/component/strategies/strategy-details-container.js b/frontend/src/component/strategies/strategy-details-container.js deleted file mode 100644 index 00a6b4611d..0000000000 --- a/frontend/src/component/strategies/strategy-details-container.js +++ /dev/null @@ -1,33 +0,0 @@ -import { connect } from 'react-redux'; -import ShowStrategy from './strategy-details-component'; -import { fetchStrategies } from './../../store/strategy/actions'; -import { fetchAll } from './../../store/application/actions'; -import { fetchFeatureToggles } from './../../store/feature-toggle/actions'; - -const mapStateToProps = (state, props) => { - let strategy = state.strategies.get('list').find(n => n.name === props.strategyName); - - const applications = state.applications - .get('list') - .filter(app => app.strategies && app.strategies.includes(props.strategyName)); - - const toggles = state.features.filter( - toggle => toggle.get('strategies').findIndex(s => s.name === props.strategyName) > -1 - ); - - return { - strategy, - strategyName: props.strategyName, - applications: applications && applications.toJS(), - toggles: toggles && toggles.toJS(), - activeTab: props.activeTab, - }; -}; - -const Constainer = connect(mapStateToProps, { - fetchStrategies, - fetchApplications: fetchAll, - fetchFeatureToggles, -})(ShowStrategy); - -export default Constainer; diff --git a/frontend/src/component/strategies/toggles-link-list.jsx b/frontend/src/component/strategies/toggles-link-list.jsx deleted file mode 100644 index c3fc70222b..0000000000 --- a/frontend/src/component/strategies/toggles-link-list.jsx +++ /dev/null @@ -1,42 +0,0 @@ -import { - List, - ListItem, - ListItemAvatar, - ListItemText, - Tooltip, -} from '@material-ui/core'; -import { PlayArrow, Pause } from '@material-ui/icons'; - -import styles from '../common/common.module.scss'; -import { Link } from 'react-router-dom'; -import PropTypes from 'prop-types'; -import React from 'react'; -import ConditionallyRender from '../common/ConditionallyRender/ConditionallyRender'; - -export const TogglesLinkList = ({ toggles }) => ( - - 0} - show={toggles.map(({ name, description = '-', enabled }) => ( - - - - {enabled ? : } - - - - {name} - - } - secondary={description} - /> - - ))} - /> - -); -TogglesLinkList.propTypes = { - toggles: PropTypes.array, -}; diff --git a/frontend/src/hooks/api/actions/useStrategiesApi/useStrategiesApi.ts b/frontend/src/hooks/api/actions/useStrategiesApi/useStrategiesApi.ts new file mode 100644 index 0000000000..867e97fe42 --- /dev/null +++ b/frontend/src/hooks/api/actions/useStrategiesApi/useStrategiesApi.ts @@ -0,0 +1,98 @@ +import useAPI from '../useApi/useApi'; + +export interface ICustomStrategyPayload { + name: string; + description: string; + parameters: object[]; +} + +const useStrategiesApi = () => { + const { makeRequest, createRequest, errors, loading } = useAPI({ + propagateErrors: true, + }); + const URI = 'api/admin/strategies'; + + const createStrategy = async (strategy: ICustomStrategyPayload) => { + const req = createRequest(URI, { + method: 'POST', + body: JSON.stringify(strategy), + }); + + try { + const res = await makeRequest(req.caller, req.id); + + return res; + } catch (e) { + throw e; + } + }; + + const updateStrategy = async (strategy: ICustomStrategyPayload) => { + const path = `${URI}/${strategy.name}`; + const req = createRequest(path, { + method: 'PUT', + body: JSON.stringify(strategy), + }); + + try { + const res = await makeRequest(req.caller, req.id); + + return res; + } catch (e) { + throw e; + } + }; + + const removeStrategy = async (strategy: ICustomStrategyPayload) => { + const path = `${URI}/${strategy.name}`; + const req = createRequest(path, { method: 'DELETE' }); + + try { + const res = await makeRequest(req.caller, req.id); + + return res; + } catch (e) { + throw e; + } + }; + + const deprecateStrategy = async (strategy: ICustomStrategyPayload) => { + const path = `${URI}/${strategy.name}/deprecate`; + const req = createRequest(path, { + method: 'POST', + }); + + try { + const res = await makeRequest(req.caller, req.id); + + return res; + } catch (e) { + throw e; + } + }; + + const reactivateStrategy = async (strategy: ICustomStrategyPayload) => { + const path = `${URI}/${strategy.name}/reactivate`; + const req = createRequest(path, { method: 'POST' }); + + try { + const res = await makeRequest(req.caller, req.id); + + return res; + } catch (e) { + throw e; + } + }; + + return { + createStrategy, + updateStrategy, + removeStrategy, + deprecateStrategy, + reactivateStrategy, + errors, + loading, + }; +}; + +export default useStrategiesApi; diff --git a/frontend/src/hooks/api/getters/useApplications/useApplications.ts b/frontend/src/hooks/api/getters/useApplications/useApplications.ts index c6d15753d7..8be4aae85e 100644 --- a/frontend/src/hooks/api/getters/useApplications/useApplications.ts +++ b/frontend/src/hooks/api/getters/useApplications/useApplications.ts @@ -42,7 +42,7 @@ const useApplications = ( }, [data, error]); return { - applications: data?.applications || {}, + applications: data?.applications || [], error, loading, refetchApplications, diff --git a/frontend/src/hooks/api/getters/useStrategies/useStrategies.ts b/frontend/src/hooks/api/getters/useStrategies/useStrategies.ts index 25e88b9952..df21ca34a4 100644 --- a/frontend/src/hooks/api/getters/useStrategies/useStrategies.ts +++ b/frontend/src/hooks/api/getters/useStrategies/useStrategies.ts @@ -49,7 +49,7 @@ const useStrategies = (options: SWRConfiguration = {}) => { ); const [loading, setLoading] = useState(!error && !data); - const refetch = () => { + const refetchStrategies = () => { mutate(STRATEGIES_CACHE_KEY); }; @@ -61,7 +61,7 @@ const useStrategies = (options: SWRConfiguration = {}) => { strategies: data?.strategies || [flexibleRolloutStrategy], error, loading, - refetch, + refetchStrategies, }; }; diff --git a/frontend/src/page/strategies/create.js b/frontend/src/page/strategies/create.js deleted file mode 100644 index 52da77a4e8..0000000000 --- a/frontend/src/page/strategies/create.js +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import AddStrategies from '../../component/strategies/CreateStrategy'; -import PropTypes from 'prop-types'; - -const render = ({ history }) => ; - -render.propTypes = { - history: PropTypes.object.isRequired, -}; - -export default render; diff --git a/frontend/src/page/strategies/index.js b/frontend/src/page/strategies/index.js deleted file mode 100644 index a95f01a5f0..0000000000 --- a/frontend/src/page/strategies/index.js +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import Strategies from '../../component/strategies/StrategiesList'; -import PropTypes from 'prop-types'; - -const render = ({ history }) => ; - -render.propTypes = { - history: PropTypes.object.isRequired, -}; - -export default render; diff --git a/frontend/src/page/strategies/show.js b/frontend/src/page/strategies/show.js deleted file mode 100644 index a349f1165e..0000000000 --- a/frontend/src/page/strategies/show.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import ShowStrategy from '../../component/strategies/strategy-details-container'; - -const render = ({ match: { params }, history }) => ( - -); - -render.propTypes = { - match: PropTypes.object.isRequired, - history: PropTypes.object.isRequired, -}; - -export default render; diff --git a/frontend/src/store/application/actions.js b/frontend/src/store/application/actions.js deleted file mode 100644 index 0a2b62423b..0000000000 --- a/frontend/src/store/application/actions.js +++ /dev/null @@ -1,58 +0,0 @@ -import api from './api'; -import { dispatchError } from '../util'; -import { MUTE_ERROR } from '../error/actions'; - -export const RECEIVE_ALL_APPLICATIONS = 'RECEIVE_ALL_APPLICATIONS'; -export const ERROR_RECEIVE_ALL_APPLICATIONS = 'ERROR_RECEIVE_ALL_APPLICATIONS'; -export const ERROR_UPDATING_APPLICATION_DATA = 'ERROR_UPDATING_APPLICATION_DATA'; - -export const RECEIVE_APPLICATION = 'RECEIVE_APPLICATION'; -export const UPDATE_APPLICATION_FIELD = 'UPDATE_APPLICATION_FIELD'; -export const DELETE_APPLICATION = 'DELETE_APPLICATION'; -export const ERROR_DELETE_APPLICATION = 'ERROR_DELETE_APPLICATION'; - -const recieveAllApplications = json => ({ - type: RECEIVE_ALL_APPLICATIONS, - value: json, -}); - -const recieveApplication = json => ({ - type: RECEIVE_APPLICATION, - value: json, -}); - -export function fetchAll() { - return dispatch => - api - .fetchAll() - .then(json => dispatch(recieveAllApplications(json))) - .catch(dispatchError(dispatch, ERROR_RECEIVE_ALL_APPLICATIONS)); -} - -export function storeApplicationMetaData(appName, key, value) { - return dispatch => - api - .storeApplicationMetaData(appName, key, value) - .then(() => { - const info = `${appName} successfully updated!`; - setTimeout(() => dispatch({ type: MUTE_ERROR, error: info }), 1000); - dispatch({ type: UPDATE_APPLICATION_FIELD, appName, key, value, info }); - }) - .catch(dispatchError(dispatch, ERROR_UPDATING_APPLICATION_DATA)); -} - -export function fetchApplication(appName) { - return dispatch => - api - .fetchApplication(appName) - .then(json => dispatch(recieveApplication(json))) - .catch(dispatchError(dispatch, ERROR_RECEIVE_ALL_APPLICATIONS)); -} - -export function deleteApplication(appName) { - return dispatch => - api - .deleteApplication(appName) - .then(() => dispatch({ type: DELETE_APPLICATION, appName })) - .catch(dispatchError(dispatch, ERROR_DELETE_APPLICATION)); -} diff --git a/frontend/src/store/application/api.js b/frontend/src/store/application/api.js deleted file mode 100644 index 5a941d27f4..0000000000 --- a/frontend/src/store/application/api.js +++ /dev/null @@ -1,52 +0,0 @@ -import { formatApiPath } from '../../utils/format-path'; -import { throwIfNotSuccess, headers } from '../api-helper'; - -const URI = formatApiPath('api/admin/metrics/applications'); - -function fetchAll() { - return fetch(URI, { headers, credentials: 'include' }) - .then(throwIfNotSuccess) - .then(response => response.json()); -} - -function fetchApplication(appName) { - return fetch(`${URI}/${appName}`, { headers, credentials: 'include' }) - .then(throwIfNotSuccess) - .then(response => response.json()); -} - -function fetchApplicationsWithStrategyName(strategyName) { - return fetch(`${URI}?strategyName=${strategyName}`, { - headers, - credentials: 'include', - }) - .then(throwIfNotSuccess) - .then(response => response.json()); -} - -function storeApplicationMetaData(appName, key, value) { - const data = {}; - data[key] = value; - return fetch(`${URI}/${appName}`, { - method: 'POST', - headers, - body: JSON.stringify(data), - credentials: 'include', - }).then(throwIfNotSuccess); -} - -function deleteApplication(appName) { - return fetch(`${URI}/${appName}`, { - method: 'DELETE', - headers, - credentials: 'include', - }).then(throwIfNotSuccess); -} - -export default { - fetchApplication, - fetchAll, - fetchApplicationsWithStrategyName, - storeApplicationMetaData, - deleteApplication, -}; diff --git a/frontend/src/store/application/index.js b/frontend/src/store/application/index.js deleted file mode 100644 index a5f57e2afa..0000000000 --- a/frontend/src/store/application/index.js +++ /dev/null @@ -1,26 +0,0 @@ -import { fromJS, List, Map } from 'immutable'; -import { RECEIVE_ALL_APPLICATIONS, RECEIVE_APPLICATION, UPDATE_APPLICATION_FIELD, DELETE_APPLICATION } from './actions'; - -function getInitState() { - return fromJS({ list: [], apps: {} }); -} - -const store = (state = getInitState(), action) => { - switch (action.type) { - case RECEIVE_APPLICATION: - return state.setIn(['apps', action.value.appName], new Map(action.value)); - case RECEIVE_ALL_APPLICATIONS: - return state.set('list', new List(action.value.applications)); - case UPDATE_APPLICATION_FIELD: - return state.setIn(['apps', action.appName, action.key], action.value); - case DELETE_APPLICATION: { - const index = state.get('list').findIndex(item => item.appName === action.appName); - const result = state.removeIn(['list', index]); - return result.removeIn(['apps', action.appName]); - } - default: - return state; - } -}; - -export default store; diff --git a/frontend/src/store/error/index.js b/frontend/src/store/error/index.js index ef8d1ba4d2..ec0cc63798 100644 --- a/frontend/src/store/error/index.js +++ b/frontend/src/store/error/index.js @@ -9,15 +9,6 @@ import { UPDATE_FEATURE_TOGGLE, } from '../feature-toggle/actions'; -import { - ERROR_UPDATING_STRATEGY, - ERROR_CREATING_STRATEGY, - ERROR_RECEIVE_STRATEGIES, - UPDATE_STRATEGY_SUCCESS, -} from '../strategy/actions'; - -import { UPDATE_APPLICATION_FIELD } from '../application/actions'; - import { FORBIDDEN } from '../util'; const debug = require('debug')('unleash:error-store'); @@ -42,9 +33,6 @@ const strategies = (state = getInitState(), action) => { case ERROR_REMOVE_FEATURE_TOGGLE: case ERROR_FETCH_FEATURE_TOGGLES: case ERROR_UPDATE_FEATURE_TOGGLE: - case ERROR_UPDATING_STRATEGY: - case ERROR_CREATING_STRATEGY: - case ERROR_RECEIVE_STRATEGIES: return addErrorIfNotAlreadyInList(state, action.error.message); case FORBIDDEN: return addErrorIfNotAlreadyInList( @@ -60,8 +48,6 @@ const strategies = (state = getInitState(), action) => { // revise how this works in a future update. case UPDATE_FEATURE_TOGGLE: case UPDATE_FEATURE_TOGGLE_STRATEGIES: - case UPDATE_APPLICATION_FIELD: - case UPDATE_STRATEGY_SUCCESS: return addErrorIfNotAlreadyInList(state, action.info); default: return state; diff --git a/frontend/src/store/index.js b/frontend/src/store/index.js index 597a1553da..812a7762cd 100644 --- a/frontend/src/store/index.js +++ b/frontend/src/store/index.js @@ -1,15 +1,11 @@ import { combineReducers } from 'redux'; import features from './feature-toggle'; -import strategies from './strategy'; import error from './error'; -import applications from './application'; import apiCalls from './api-calls'; const unleashStore = combineReducers({ features, - strategies, error, - applications, apiCalls, }); diff --git a/frontend/src/store/strategy/actions.js b/frontend/src/store/strategy/actions.js deleted file mode 100644 index b56a010294..0000000000 --- a/frontend/src/store/strategy/actions.js +++ /dev/null @@ -1,138 +0,0 @@ -import api from './api'; -import applicationApi from '../application/api'; -import { dispatchError } from '../util'; -import { MUTE_ERROR } from '../error/actions'; - -export const ADD_STRATEGY = 'ADD_STRATEGY'; -export const UPDATE_STRATEGY = 'UPDATE_STRATEGY'; -export const REMOVE_STRATEGY = 'REMOVE_STRATEGY'; -export const DEPRECATE_STRATEGY = 'DEPRECATE_STRATEGY'; -export const REACTIVATE_STRATEGY = 'REACTIVATE_STRATEGY'; -export const REQUEST_STRATEGIES = 'REQUEST_STRATEGIES'; -export const START_CREATE_STRATEGY = 'START_CREATE_STRATEGY'; -export const START_UPDATE_STRATEGY = 'START_UPDATE_STRATEGY'; -export const START_DEPRECATE_STRATEGY = 'START_DEPRECATE_STRATEGY'; -export const START_REACTIVATE_STRATEGY = 'START_REACTIVATE_STRATEGY'; -export const RECEIVE_STRATEGIES = 'RECEIVE_STRATEGIES'; -export const ERROR_RECEIVE_STRATEGIES = 'ERROR_RECEIVE_STRATEGIES'; -export const ERROR_CREATING_STRATEGY = 'ERROR_CREATING_STRATEGY'; -export const ERROR_UPDATING_STRATEGY = 'ERROR_UPDATING_STRATEGY'; -export const ERROR_REMOVING_STRATEGY = 'ERROR_REMOVING_STRATEGY'; -export const ERROR_DEPRECATING_STRATEGY = 'ERROR_DEPRECATING_STRATEGY'; -export const ERROR_REACTIVATING_STRATEGY = 'ERROR_REACTIVATING_STRATEGY'; -export const UPDATE_STRATEGY_SUCCESS = 'UPDATE_STRATEGY_SUCCESS'; - -export const receiveStrategies = json => ({ - type: RECEIVE_STRATEGIES, - value: json, -}); - -const addStrategy = strategy => ({ type: ADD_STRATEGY, strategy }); -const createRemoveStrategy = strategy => ({ type: REMOVE_STRATEGY, strategy }); -const updatedStrategy = strategy => ({ type: UPDATE_STRATEGY, strategy }); - -const startRequest = () => ({ type: REQUEST_STRATEGIES }); - -const startCreate = () => ({ type: START_CREATE_STRATEGY }); - -const startUpdate = () => ({ type: START_UPDATE_STRATEGY }); - -const startDeprecate = () => ({ type: START_DEPRECATE_STRATEGY }); -const deprecateStrategyEvent = strategy => ({ - type: DEPRECATE_STRATEGY, - strategy, -}); -const startReactivate = () => ({ type: START_REACTIVATE_STRATEGY }); -const reactivateStrategyEvent = strategy => ({ - type: REACTIVATE_STRATEGY, - strategy, -}); - -const setInfoMessage = (info, dispatch) => { - dispatch({ - type: UPDATE_STRATEGY_SUCCESS, - info: info, - }); - setTimeout(() => dispatch({ type: MUTE_ERROR, error: info }), 1500); -}; - -export function fetchStrategies() { - return dispatch => { - dispatch(startRequest()); - - return api - .fetchAll() - .then(json => dispatch(receiveStrategies(json.strategies))) - .catch(dispatchError(dispatch, ERROR_RECEIVE_STRATEGIES)); - }; -} - -export function createStrategy(strategy) { - return dispatch => { - dispatch(startCreate()); - - return api - .create(strategy) - .then(() => dispatch(addStrategy(strategy))) - .then(() => { - setInfoMessage('Strategy successfully created.', dispatch); - }) - .catch(e => { - dispatchError(dispatch, ERROR_CREATING_STRATEGY); - throw e; - }); - }; -} - -export function updateStrategy(strategy) { - return dispatch => { - dispatch(startUpdate()); - - return api - .update(strategy) - .then(() => dispatch(updatedStrategy(strategy))) - .then(() => { - setInfoMessage('Strategy successfully updated.', dispatch); - }) - .catch(dispatchError(dispatch, ERROR_UPDATING_STRATEGY)); - }; -} - -export function removeStrategy(strategy) { - return dispatch => - api - .remove(strategy) - .then(() => dispatch(createRemoveStrategy(strategy))) - .then(() => { - setInfoMessage('Strategy successfully deleted.', dispatch); - }) - .catch(dispatchError(dispatch, ERROR_REMOVING_STRATEGY)); -} - -export function getApplicationsWithStrategy(strategyName) { - return applicationApi.fetchApplicationsWithStrategyName(strategyName); -} - -export function deprecateStrategy(strategy) { - return dispatch => { - dispatch(startDeprecate()); - api.deprecate(strategy) - .then(() => dispatch(deprecateStrategyEvent(strategy))) - .then(() => - setInfoMessage('Strategy successfully deprecated', dispatch) - ) - .catch(dispatchError(dispatch, ERROR_DEPRECATING_STRATEGY)); - }; -} - -export function reactivateStrategy(strategy) { - return dispatch => { - dispatch(startReactivate()); - api.reactivate(strategy) - .then(() => dispatch(reactivateStrategyEvent(strategy))) - .then(() => - setInfoMessage('Strategy successfully reactivated', dispatch) - ) - .catch(dispatchError(dispatch, ERROR_REACTIVATING_STRATEGY)); - }; -} diff --git a/frontend/src/store/strategy/api.js b/frontend/src/store/strategy/api.js deleted file mode 100644 index dccff368c6..0000000000 --- a/frontend/src/store/strategy/api.js +++ /dev/null @@ -1,61 +0,0 @@ -import { formatApiPath } from '../../utils/format-path'; -import { throwIfNotSuccess, headers } from '../api-helper'; - -const URI = formatApiPath('api/admin/strategies'); - -function fetchAll() { - return fetch(URI, { credentials: 'include' }) - .then(throwIfNotSuccess) - .then(response => response.json()); -} - -function create(strategy) { - return fetch(URI, { - method: 'POST', - headers, - body: JSON.stringify(strategy), - credentials: 'include', - }).then(throwIfNotSuccess); -} - -function update(strategy) { - return fetch(`${URI}/${strategy.name}`, { - method: 'put', - headers, - body: JSON.stringify(strategy), - credentials: 'include', - }).then(throwIfNotSuccess); -} - -function remove(strategy) { - return fetch(`${URI}/${strategy.name}`, { - method: 'DELETE', - headers, - credentials: 'include', - }).then(throwIfNotSuccess); -} - -function deprecate(strategy) { - return fetch(`${URI}/${strategy.name}/deprecate`, { - method: 'POST', - headers, - credentials: 'include', - }).then(throwIfNotSuccess); -} - -function reactivate(strategy) { - return fetch(`${URI}/${strategy.name}/reactivate`, { - method: 'POST', - headers, - credentials: 'include', - }).then(throwIfNotSuccess); -} - -export default { - fetchAll, - create, - update, - remove, - deprecate, - reactivate, -}; diff --git a/frontend/src/store/strategy/index.js b/frontend/src/store/strategy/index.js deleted file mode 100644 index 900ae3259c..0000000000 --- a/frontend/src/store/strategy/index.js +++ /dev/null @@ -1,67 +0,0 @@ -import { List, Map as $Map } from 'immutable'; -import { - RECEIVE_STRATEGIES, - REMOVE_STRATEGY, - ADD_STRATEGY, - UPDATE_STRATEGY, - DEPRECATE_STRATEGY, - REACTIVATE_STRATEGY, -} from './actions'; - -function getInitState() { - return new $Map({ list: new List() }); -} - -function removeStrategy(state, action) { - const indexToRemove = state.get('list').indexOf(action.strategy); - if (indexToRemove !== -1) { - return state.update('list', list => list.remove(indexToRemove)); - } - return state; -} - -function updateStrategy(state, action) { - return state.update('list', list => - list.map(strategy => { - if (strategy.name === action.strategy.name) { - return action.strategy; - } else { - return strategy; - } - }) - ); -} - -function setDeprecationStatus(state, action, status) { - return state.update('list', list => - list.map(strategy => { - if (strategy.name === action.strategy.name) { - action.strategy.deprecated = status; - return action.strategy; - } else { - return strategy; - } - }) - ); -} - -const strategies = (state = getInitState(), action) => { - switch (action.type) { - case RECEIVE_STRATEGIES: - return state.set('list', new List(action.value)); - case REMOVE_STRATEGY: - return removeStrategy(state, action); - case ADD_STRATEGY: - return state.update('list', list => list.push(action.strategy)); - case UPDATE_STRATEGY: - return updateStrategy(state, action); - case DEPRECATE_STRATEGY: - return setDeprecationStatus(state, action, true); - case REACTIVATE_STRATEGY: - return setDeprecationStatus(state, action, false); - default: - return state; - } -}; - -export default strategies; diff --git a/frontend/src/store/ui-bootstrap/actions.js b/frontend/src/store/ui-bootstrap/actions.js index 2b48741dd3..317536c4e9 100644 --- a/frontend/src/store/ui-bootstrap/actions.js +++ b/frontend/src/store/ui-bootstrap/actions.js @@ -1,6 +1,5 @@ import api from './api'; import { dispatchError } from '../util'; -import { receiveStrategies } from '../strategy/actions'; export const RECEIVE_BOOTSTRAP = 'RECEIVE_CONFIG'; export const ERROR_RECEIVE_BOOTSTRAP = 'ERROR_RECEIVE_CONFIG'; @@ -9,8 +8,6 @@ export function fetchUiBootstrap() { return dispatch => api .fetchUIBootstrap() - .then(json => { - dispatch(receiveStrategies(json.strategies)); - }) + .then(json => {}) .catch(dispatchError(dispatch, ERROR_RECEIVE_BOOTSTRAP)); }