mirror of
https://github.com/Unleash/unleash.git
synced 2025-06-04 01:18:20 +02:00
Fix/v4 corrections (#287)
* fix: remove project display check * fix: refetch bootstrap on user change * fix: remove console log * fix: update test * fix: do not allow submit if errors exists * fix: do not allow strategies to redirect home when name is taken
This commit is contained in:
parent
549be832bf
commit
9b1a07c5ab
@ -20,7 +20,7 @@ const App = ({ location, user, fetchUiBootstrap }: IAppProps) => {
|
||||
useEffect(() => {
|
||||
fetchUiBootstrap();
|
||||
/* eslint-disable-next-line */
|
||||
}, []);
|
||||
}, [user.authDetails?.type]);
|
||||
|
||||
const renderMainLayoutRoutes = () => {
|
||||
return routes.filter(route => route.layout === 'main').map(renderRoute);
|
||||
|
@ -9,16 +9,13 @@ const ProjectSelect = ({
|
||||
projects,
|
||||
currentProjectId,
|
||||
updateCurrentProject,
|
||||
...rest
|
||||
}) => {
|
||||
const setProject = v => {
|
||||
const id = typeof v === 'string' ? v.trim() : '';
|
||||
updateCurrentProject(id);
|
||||
};
|
||||
|
||||
if (!projects || projects.length === 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO fixme
|
||||
let curentProject = projects.find(i => i.id === currentProjectId);
|
||||
if (!curentProject) {
|
||||
@ -57,6 +54,8 @@ const ProjectSelect = ({
|
||||
];
|
||||
};
|
||||
|
||||
const { updateSetting, fetchProjects, ...passDown } = rest;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<DropdownMenu
|
||||
@ -66,6 +65,7 @@ const ProjectSelect = ({
|
||||
callback={handleChangeProject}
|
||||
renderOptions={renderProjectOptions}
|
||||
className=""
|
||||
{...passDown}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
@ -6,7 +6,7 @@ import { MenuItemWithIcon } from '../../../common';
|
||||
import DropdownMenu from '../../../common/DropdownMenu/DropdownMenu';
|
||||
import ProjectSelect from '../../../common/ProjectSelect';
|
||||
import { useStyles } from './styles';
|
||||
import classnames from 'classnames';
|
||||
import useLoading from '../../../../hooks/useLoading';
|
||||
|
||||
const sortingOptions = [
|
||||
{ type: 'name', displayName: 'Name' },
|
||||
@ -27,6 +27,7 @@ const FeatureToggleListActions = ({
|
||||
loading,
|
||||
}) => {
|
||||
const styles = useStyles();
|
||||
const ref = useLoading(loading);
|
||||
|
||||
const handleSort = e => {
|
||||
const target = e.target.getAttribute('data-target');
|
||||
@ -64,14 +65,15 @@ const FeatureToggleListActions = ({
|
||||
];
|
||||
|
||||
return (
|
||||
<div className={styles.actions}>
|
||||
<div className={styles.actions} ref={ref}>
|
||||
<DropdownMenu
|
||||
id={'metric'}
|
||||
label={`Last ${settings.showLastHour ? 'hour' : 'minute'}`}
|
||||
title="Metric interval"
|
||||
callback={toggleMetrics}
|
||||
renderOptions={renderMetricsOptions}
|
||||
className={classnames({ skeleton: loading })}
|
||||
className=""
|
||||
data-loading
|
||||
/>
|
||||
<DropdownMenu
|
||||
id={'sorting'}
|
||||
@ -79,9 +81,14 @@ const FeatureToggleListActions = ({
|
||||
callback={handleSort}
|
||||
renderOptions={renderSortingOptions}
|
||||
title="Sort by"
|
||||
className={classnames({ skeleton: loading })}
|
||||
className=""
|
||||
data-loading
|
||||
/>
|
||||
<ProjectSelect
|
||||
settings={settings}
|
||||
updateSetting={updateSetting}
|
||||
data-loading
|
||||
/>
|
||||
<ProjectSelect settings={settings} updateSetting={updateSetting} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -70,6 +70,7 @@ exports[`renders correctly with one feature 1`] = `
|
||||
aria-controls="metric"
|
||||
aria-haspopup="true"
|
||||
className="MuiButtonBase-root MuiButton-root MuiButton-text"
|
||||
data-loading={true}
|
||||
disabled={false}
|
||||
id="metric"
|
||||
onBlur={[Function]}
|
||||
@ -108,6 +109,7 @@ exports[`renders correctly with one feature 1`] = `
|
||||
aria-controls="sorting"
|
||||
aria-haspopup="true"
|
||||
className="MuiButtonBase-root MuiButton-root MuiButton-text"
|
||||
data-loading={true}
|
||||
disabled={false}
|
||||
id="sorting"
|
||||
onBlur={[Function]}
|
||||
@ -270,6 +272,7 @@ exports[`renders correctly with one feature without permissions 1`] = `
|
||||
aria-controls="metric"
|
||||
aria-haspopup="true"
|
||||
className="MuiButtonBase-root MuiButton-root MuiButton-text"
|
||||
data-loading={true}
|
||||
disabled={false}
|
||||
id="metric"
|
||||
onBlur={[Function]}
|
||||
@ -311,6 +314,7 @@ exports[`renders correctly with one feature without permissions 1`] = `
|
||||
aria-controls="sorting"
|
||||
aria-haspopup="true"
|
||||
className="MuiButtonBase-root MuiButton-root MuiButton-text"
|
||||
data-loading={true}
|
||||
disabled={false}
|
||||
id="sorting"
|
||||
onBlur={[Function]}
|
||||
|
@ -18,6 +18,7 @@ import FeatureTypeSelect from '../feature-type-select-container';
|
||||
import ProjectSelect from '../project-select-container';
|
||||
import UpdateDescriptionComponent from '../view/update-description-component';
|
||||
import {
|
||||
CREATE_FEATURE,
|
||||
DELETE_FEATURE,
|
||||
UPDATE_FEATURE,
|
||||
} from '../../AccessProvider/permissions';
|
||||
@ -35,6 +36,7 @@ import ConfirmDialogue from '../../common/Dialogue';
|
||||
|
||||
import { useCommonStyles } from '../../../common.styles';
|
||||
import AccessContext from '../../../contexts/AccessContext';
|
||||
import { projectFilterGenerator } from '../../../utils/project-filter-generator';
|
||||
|
||||
const FeatureView = ({
|
||||
activeTab,
|
||||
@ -53,6 +55,7 @@ const FeatureView = ({
|
||||
featureTags,
|
||||
fetchTags,
|
||||
tagTypes,
|
||||
user,
|
||||
}) => {
|
||||
const isFeatureView = !!fetchFeatureToggles;
|
||||
const [delDialog, setDelDialog] = useState(false);
|
||||
@ -215,6 +218,7 @@ const FeatureView = ({
|
||||
|
||||
const findActiveTab = activeTab =>
|
||||
tabs.findIndex(tab => tab.name === activeTab);
|
||||
|
||||
return (
|
||||
<Paper
|
||||
className={commonStyles.fullwidth}
|
||||
@ -253,6 +257,10 @@ const FeatureView = ({
|
||||
label="Project"
|
||||
filled
|
||||
editable={editable}
|
||||
filter={projectFilterGenerator(
|
||||
user,
|
||||
CREATE_FEATURE
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<FeatureTagComponent
|
||||
|
@ -10,15 +10,22 @@ import {
|
||||
} from '../../../store/feature-toggle/actions';
|
||||
|
||||
import FeatureView from './FeatureView';
|
||||
import { fetchTags, tagFeature, untagFeature } from '../../../store/feature-tags/actions';
|
||||
import {
|
||||
fetchTags,
|
||||
tagFeature,
|
||||
untagFeature,
|
||||
} from '../../../store/feature-tags/actions';
|
||||
|
||||
export default connect(
|
||||
(state, props) => ({
|
||||
features: state.features.toJS(),
|
||||
featureToggle: state.features.toJS().find(toggle => toggle.name === props.featureToggleName),
|
||||
featureToggle: state.features
|
||||
.toJS()
|
||||
.find(toggle => toggle.name === props.featureToggleName),
|
||||
featureTags: state.featureTags.toJS(),
|
||||
tagTypes: state.tagTypes.toJS(),
|
||||
activeTab: props.activeTab,
|
||||
user: state.user.toJS(),
|
||||
}),
|
||||
{
|
||||
fetchFeatureToggles,
|
||||
|
@ -0,0 +1,140 @@
|
||||
import { useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { CardActions, Switch, TextField } from '@material-ui/core';
|
||||
import FeatureTypeSelect from '../../feature-type-select-container';
|
||||
import ProjectSelect from '../../project-select-container';
|
||||
import StrategiesList from '../../strategy/strategies-list-container';
|
||||
import PageContent from '../../../common/PageContent/PageContent';
|
||||
|
||||
import { FormButtons, styles as commonStyles } from '../../../common';
|
||||
import { trim } from '../../../common/util';
|
||||
|
||||
import styles from '../add-feature-component.module.scss';
|
||||
import {
|
||||
CF_CREATE_BTN_ID,
|
||||
CF_DESC_ID,
|
||||
CF_NAME_ID,
|
||||
CF_TYPE_ID,
|
||||
} from '../../../../testIds';
|
||||
import { CREATE_FEATURE } from '../../../AccessProvider/permissions';
|
||||
import { projectFilterGenerator } from '../../../../utils/project-filter-generator';
|
||||
|
||||
const CreateFeature = ({
|
||||
input,
|
||||
errors,
|
||||
setValue,
|
||||
validateName,
|
||||
onSubmit,
|
||||
onCancel,
|
||||
user,
|
||||
}) => {
|
||||
useEffect(() => {
|
||||
window.onbeforeunload = () =>
|
||||
'Data will be lost if you leave the page, are you sure?';
|
||||
|
||||
return () => {
|
||||
window.onbeforeunload = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<PageContent headerContent="Create new feature toggle">
|
||||
<form onSubmit={onSubmit}>
|
||||
<div className={styles.formContainer}>
|
||||
<TextField
|
||||
size="small"
|
||||
variant="outlined"
|
||||
label="Name"
|
||||
placeholder="Unique-name"
|
||||
className={styles.nameInput}
|
||||
name="name"
|
||||
inputProps={{
|
||||
'data-test': CF_NAME_ID,
|
||||
}}
|
||||
value={input.name}
|
||||
error={errors.name !== undefined}
|
||||
helperText={errors.name}
|
||||
onBlur={v => validateName(v.target.value)}
|
||||
onChange={v => setValue('name', trim(v.target.value))}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.formContainer}>
|
||||
<FeatureTypeSelect
|
||||
value={input.type}
|
||||
onChange={v => setValue('type', v.target.value)}
|
||||
label={'Toggle type'}
|
||||
id="feature-type-select"
|
||||
editable
|
||||
inputProps={{
|
||||
'data-test': CF_TYPE_ID,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<section className={styles.formContainer}>
|
||||
<ProjectSelect
|
||||
value={input.project}
|
||||
onChange={v => setValue('project', v.target.value)}
|
||||
filter={projectFilterGenerator(user, CREATE_FEATURE)}
|
||||
/>
|
||||
</section>
|
||||
<section className={styles.formContainer}>
|
||||
<TextField
|
||||
size="small"
|
||||
variant="outlined"
|
||||
className={commonStyles.fullwidth}
|
||||
multiline
|
||||
rows={4}
|
||||
label="Description"
|
||||
placeholder="A short description of the feature toggle"
|
||||
error={errors.description !== undefined}
|
||||
helperText={errors.description}
|
||||
value={input.description}
|
||||
inputProps={{
|
||||
'data-test': CF_DESC_ID,
|
||||
}}
|
||||
onChange={v => setValue('description', v.target.value)}
|
||||
/>
|
||||
</section>
|
||||
<section className={styles.toggleContainer}>
|
||||
<Switch
|
||||
checked={input.enabled}
|
||||
onChange={() => {
|
||||
setValue('enabled', !input.enabled);
|
||||
}}
|
||||
/>
|
||||
<p className={styles.toggleText}>
|
||||
{input.enabled ? 'Enabled' : 'Disabled'} feature toggle
|
||||
</p>
|
||||
</section>
|
||||
<section className={styles.strategiesContainer}>
|
||||
<StrategiesList
|
||||
configuredStrategies={input.strategies}
|
||||
featureToggleName={input.name}
|
||||
saveStrategies={s => setValue('strategies', s)}
|
||||
editable
|
||||
/>
|
||||
</section>
|
||||
<CardActions>
|
||||
<FormButtons
|
||||
submitText={'Create'}
|
||||
primaryButtonTestId={CF_CREATE_BTN_ID}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
</CardActions>
|
||||
</form>
|
||||
</PageContent>
|
||||
);
|
||||
};
|
||||
|
||||
CreateFeature.propTypes = {
|
||||
input: PropTypes.object,
|
||||
errors: PropTypes.object,
|
||||
setValue: PropTypes.func.isRequired,
|
||||
onSubmit: PropTypes.func.isRequired,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
validateName: PropTypes.func.isRequired,
|
||||
initCallRequired: PropTypes.bool,
|
||||
init: PropTypes.func,
|
||||
};
|
||||
|
||||
export default CreateFeature;
|
@ -1,9 +1,12 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { createFeatureToggles, validateName } from '../../../store/feature-toggle/actions';
|
||||
import AddFeatureComponent from './add-feature-component';
|
||||
import { loadNameFromHash } from '../../common/util';
|
||||
import {
|
||||
createFeatureToggles,
|
||||
validateName,
|
||||
} from '../../../../store/feature-toggle/actions';
|
||||
import CreateFeature from './CreateFeature';
|
||||
import { loadNameFromHash } from '../../../common/util';
|
||||
|
||||
const defaultStrategy = {
|
||||
name: 'default',
|
||||
@ -63,11 +66,17 @@ class WrapperComponent extends Component {
|
||||
const { createFeatureToggles, history } = this.props;
|
||||
const { featureToggle } = this.state;
|
||||
|
||||
if (Object.keys(this.state.errors)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (featureToggle.strategies < 1) {
|
||||
featureToggle.strategies = [defaultStrategy];
|
||||
}
|
||||
|
||||
createFeatureToggles(featureToggle).then(() => history.push(`/features/strategies/${featureToggle.name}`));
|
||||
createFeatureToggles(featureToggle).then(() =>
|
||||
history.push(`/features/strategies/${featureToggle.name}`)
|
||||
);
|
||||
};
|
||||
|
||||
onCancel = evt => {
|
||||
@ -77,7 +86,7 @@ class WrapperComponent extends Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<AddFeatureComponent
|
||||
<CreateFeature
|
||||
onSubmit={this.onSubmit}
|
||||
onCancel={this.onCancel}
|
||||
validateName={this.validateName}
|
||||
@ -85,6 +94,7 @@ class WrapperComponent extends Component {
|
||||
setStrategies={this.setStrategies}
|
||||
input={this.state.featureToggle}
|
||||
errors={this.state.errors}
|
||||
user={this.props.user}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -97,16 +107,20 @@ WrapperComponent.propTypes = {
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
validateName: name => validateName(name)(dispatch),
|
||||
createFeatureToggles: featureToggle => createFeatureToggles(featureToggle)(dispatch),
|
||||
createFeatureToggles: featureToggle =>
|
||||
createFeatureToggles(featureToggle)(dispatch),
|
||||
});
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const settings = state.settings.toJS().feature || {};
|
||||
const currentProjectId = resolveCurrentProjectId(settings);
|
||||
|
||||
return { currentProjectId };
|
||||
return { currentProjectId, user: state.user.toJS() };
|
||||
};
|
||||
|
||||
const FormAddContainer = connect(mapStateToProps, mapDispatchToProps)(WrapperComponent);
|
||||
const FormAddContainer = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(WrapperComponent);
|
||||
|
||||
export default FormAddContainer;
|
@ -1,120 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { CardActions, Switch, TextField } from '@material-ui/core';
|
||||
import FeatureTypeSelect from '../feature-type-select-container';
|
||||
import ProjectSelect from '../project-select-container';
|
||||
import StrategiesList from '../strategy/strategies-list-container';
|
||||
import PageContent from '../../common/PageContent/PageContent';
|
||||
|
||||
import { FormButtons, styles as commonStyles } from '../../common';
|
||||
import { trim } from '../../common/util';
|
||||
|
||||
import styles from './add-feature-component.module.scss';
|
||||
import { CF_CREATE_BTN_ID, CF_DESC_ID, CF_NAME_ID, CF_TYPE_ID } from '../../../testIds';
|
||||
|
||||
class AddFeatureComponent extends Component {
|
||||
// static displayName = `AddFeatureComponent-${getDisplayName(Component)}`;
|
||||
componentDidMount() {
|
||||
window.onbeforeunload = () => 'Data will be lost if you leave the page, are you sure?';
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.onbeforeunload = false;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { input, errors, setValue, validateName, onSubmit, onCancel } = this.props;
|
||||
|
||||
return (
|
||||
<PageContent headerContent="Create new feature toggle">
|
||||
<form onSubmit={onSubmit}>
|
||||
<div className={styles.formContainer}>
|
||||
<TextField
|
||||
size="small"
|
||||
variant="outlined"
|
||||
label="Name"
|
||||
placeholder="Unique-name"
|
||||
className={styles.nameInput}
|
||||
name="name"
|
||||
inputProps={{
|
||||
'data-test': CF_NAME_ID,
|
||||
}}
|
||||
value={input.name}
|
||||
error={errors.name !== undefined}
|
||||
helperText={errors.name}
|
||||
onBlur={v => validateName(v.target.value)}
|
||||
onChange={v => setValue('name', trim(v.target.value))}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.formContainer}>
|
||||
<FeatureTypeSelect
|
||||
value={input.type}
|
||||
onChange={v => setValue('type', v.target.value)}
|
||||
label={'Toggle type'}
|
||||
id="feature-type-select"
|
||||
editable
|
||||
inputProps={{
|
||||
'data-test': CF_TYPE_ID,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<section className={styles.formContainer}>
|
||||
<ProjectSelect value={input.project} onChange={v => setValue('project', v.target.value)} />
|
||||
</section>
|
||||
<section className={styles.formContainer}>
|
||||
<TextField
|
||||
size="small"
|
||||
variant="outlined"
|
||||
className={commonStyles.fullwidth}
|
||||
multiline
|
||||
rows={4}
|
||||
label="Description"
|
||||
placeholder="A short description of the feature toggle"
|
||||
error={errors.description !== undefined}
|
||||
helperText={errors.description}
|
||||
value={input.description}
|
||||
inputProps={{
|
||||
'data-test': CF_DESC_ID,
|
||||
}}
|
||||
onChange={v => setValue('description', v.target.value)}
|
||||
/>
|
||||
</section>
|
||||
<section className={styles.toggleContainer}>
|
||||
<Switch
|
||||
checked={input.enabled}
|
||||
onChange={() => {
|
||||
setValue('enabled', !input.enabled);
|
||||
}}
|
||||
/>
|
||||
<p className={styles.toggleText}>{input.enabled ? 'Enabled' : 'Disabled'} feature toggle</p>
|
||||
</section>
|
||||
<section className={styles.strategiesContainer}>
|
||||
<StrategiesList
|
||||
configuredStrategies={input.strategies}
|
||||
featureToggleName={input.name}
|
||||
saveStrategies={s => setValue('strategies', s)}
|
||||
editable
|
||||
/>
|
||||
</section>
|
||||
<CardActions>
|
||||
<FormButtons submitText={'Create'} primaryButtonTestId={CF_CREATE_BTN_ID} onCancel={onCancel} />
|
||||
</CardActions>
|
||||
</form>
|
||||
</PageContent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AddFeatureComponent.propTypes = {
|
||||
input: PropTypes.object,
|
||||
errors: PropTypes.object,
|
||||
setValue: PropTypes.func.isRequired,
|
||||
onSubmit: PropTypes.func.isRequired,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
validateName: PropTypes.func.isRequired,
|
||||
initCallRequired: PropTypes.bool,
|
||||
init: PropTypes.func,
|
||||
};
|
||||
|
||||
export default AddFeatureComponent;
|
@ -9,6 +9,7 @@
|
||||
|
||||
.nameInput {
|
||||
margin-right: 1.5rem;
|
||||
min-width: 250px;
|
||||
}
|
||||
|
||||
.formContainer {
|
||||
|
@ -11,23 +11,43 @@ class ProjectSelectComponent extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { value, projects, onChange, enabled } = this.props;
|
||||
const { value, projects, onChange, enabled, filter } = this.props;
|
||||
|
||||
if (!enabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const options = projects.map(t => ({
|
||||
key: t.id,
|
||||
label: t.name,
|
||||
title: t.description,
|
||||
}));
|
||||
const formatOption = project => {
|
||||
return {
|
||||
key: project.id,
|
||||
label: project.name,
|
||||
title: project.description,
|
||||
};
|
||||
};
|
||||
|
||||
let options;
|
||||
if (filter) {
|
||||
options = projects
|
||||
.filter(project => {
|
||||
return filter(project);
|
||||
})
|
||||
.map(formatOption);
|
||||
} else {
|
||||
options = projects.map(formatOption);
|
||||
}
|
||||
|
||||
if (value && !options.find(o => o.key === value)) {
|
||||
options.push({ key: value, label: value });
|
||||
}
|
||||
|
||||
return <MySelect label="Project" options={options} value={value} onChange={onChange} />;
|
||||
return (
|
||||
<MySelect
|
||||
label="Project"
|
||||
options={options}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -188,7 +188,7 @@ const AddVariant = ({
|
||||
name="name"
|
||||
placeholder=""
|
||||
className={commonStyles.fullWidth}
|
||||
value={data.name}
|
||||
value={data.name || ''}
|
||||
error={error.name}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
@ -213,7 +213,7 @@ const AddVariant = ({
|
||||
),
|
||||
}}
|
||||
style={{ marginRight: '0.8rem' }}
|
||||
value={data.weight}
|
||||
value={data.weight || ''}
|
||||
error={error.weight}
|
||||
type="number"
|
||||
disabled={!isFixWeight}
|
||||
|
@ -77,6 +77,7 @@ test('renders correctly with one feature', () => {
|
||||
featureToggle={feature}
|
||||
fetchFeatureToggles={jest.fn()}
|
||||
history={{}}
|
||||
user={{ permissions: [] }}
|
||||
featureTags={[]}
|
||||
fetchTags={jest.fn()}
|
||||
untagFeature={jest.fn()}
|
||||
|
@ -0,0 +1,113 @@
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Typography, TextField, Button, Icon } from '@material-ui/core';
|
||||
|
||||
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 (
|
||||
<PageContent headerContent={getHeaderTitle()}>
|
||||
<ConditionallyRender
|
||||
condition={editMode}
|
||||
show={
|
||||
<Typography variant="p">
|
||||
Be careful! Changing a strategy definition might also
|
||||
require changes to the implementation in the clients.
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
|
||||
<form
|
||||
onSubmit={onSubmit}
|
||||
className={commonStyles.contentSpacing}
|
||||
style={{ maxWidth: '400px' }}
|
||||
>
|
||||
<TextField
|
||||
label="Strategy name"
|
||||
name="name"
|
||||
placeholder=""
|
||||
disabled={editMode}
|
||||
error={Boolean(errors.name)}
|
||||
helperText={errors.name}
|
||||
onChange={({ target }) => {
|
||||
clearErrors();
|
||||
setValue('name', trim(target.value));
|
||||
}}
|
||||
value={input.name}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
/>
|
||||
|
||||
<TextField
|
||||
className={commonStyles.fullwidth}
|
||||
multiline
|
||||
rows={2}
|
||||
label="Description"
|
||||
name="description"
|
||||
placeholder=""
|
||||
onChange={({ target }) =>
|
||||
setValue('description', target.value)
|
||||
}
|
||||
value={input.description}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
/>
|
||||
|
||||
<StrategyParameters
|
||||
input={input.parameters}
|
||||
count={input.parameters.length}
|
||||
updateParameter={updateParameter}
|
||||
/>
|
||||
<Button
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
appParameter();
|
||||
}}
|
||||
startIcon={<Icon>add</Icon>}
|
||||
>
|
||||
Add parameter
|
||||
</Button>
|
||||
|
||||
<FormButtons
|
||||
submitText={editMode ? 'Update' : 'Create'}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
</form>
|
||||
</PageContent>
|
||||
);
|
||||
};
|
||||
|
||||
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;
|
@ -0,0 +1,66 @@
|
||||
import { TextField, Checkbox, FormControlLabel } from '@material-ui/core';
|
||||
import PropTypes from 'prop-types';
|
||||
import MySelect from '../../../../common/select';
|
||||
import { styles as commonStyles } from '../../../../common';
|
||||
|
||||
const paramTypesOptions = [
|
||||
{ key: 'string', label: 'string' },
|
||||
{ key: 'percentage', label: 'percentage' },
|
||||
{ key: 'list', label: 'list' },
|
||||
{ key: 'number', label: 'number' },
|
||||
{ key: 'boolean', label: 'boolean' },
|
||||
];
|
||||
|
||||
const StrategyParameter = ({ set, input = {}, index }) => {
|
||||
const handleTypeChange = event => {
|
||||
set({ type: event.target.value });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={commonStyles.contentSpacing}>
|
||||
<TextField
|
||||
style={{ width: '50%', marginRight: '5px' }}
|
||||
label={`Parameter name ${index + 1}`}
|
||||
onChange={({ target }) => set({ name: target.value }, true)}
|
||||
value={input.name || ''}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
/>
|
||||
<MySelect
|
||||
label="Type"
|
||||
options={paramTypesOptions}
|
||||
value={input.type || 'string'}
|
||||
onChange={handleTypeChange}
|
||||
id={`prop-type-${index}-select`}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
style={{ width: '100%' }}
|
||||
rows={2}
|
||||
multiline
|
||||
label={`Parameter name ${index + 1} description`}
|
||||
onChange={({ target }) => set({ description: target.value })}
|
||||
value={input.description || ''}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={!!input.required}
|
||||
onChange={() => set({ required: !input.required })}
|
||||
/>
|
||||
}
|
||||
label="Required"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
StrategyParameter.propTypes = {
|
||||
input: PropTypes.object,
|
||||
set: PropTypes.func,
|
||||
index: PropTypes.number,
|
||||
};
|
||||
|
||||
export default StrategyParameter;
|
@ -0,0 +1,27 @@
|
||||
import StrategyParameter from './StrategyParameter/StrategyParameter';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
function gerArrayWithEntries(num) {
|
||||
return Array.from(Array(num));
|
||||
}
|
||||
|
||||
const StrategyParameters = ({ input = [], count = 0, updateParameter }) => (
|
||||
<div>
|
||||
{gerArrayWithEntries(count).map((v, i) => (
|
||||
<StrategyParameter
|
||||
key={i}
|
||||
set={v => updateParameter(i, v, true)}
|
||||
index={i}
|
||||
input={input[i]}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
StrategyParameters.propTypes = {
|
||||
input: PropTypes.array,
|
||||
updateParameter: PropTypes.func.isRequired,
|
||||
count: PropTypes.number,
|
||||
};
|
||||
|
||||
export default StrategyParameters;
|
@ -3,10 +3,15 @@ import PropTypes from 'prop-types';
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { createStrategy, updateStrategy } from '../../store/strategy/actions';
|
||||
import {
|
||||
createStrategy,
|
||||
updateStrategy,
|
||||
} from '../../../store/strategy/actions';
|
||||
|
||||
import AddStrategy from './form-strategy';
|
||||
import { loadNameFromHash } from '../common/util';
|
||||
import CreateStrategy from './CreateStrategy';
|
||||
import { loadNameFromHash } from '../../common/util';
|
||||
|
||||
const STRATEGY_EXIST_ERROR = 'Error: Strategy with name';
|
||||
|
||||
class WrapperComponent extends Component {
|
||||
constructor(props) {
|
||||
@ -18,6 +23,10 @@ class WrapperComponent extends Component {
|
||||
};
|
||||
}
|
||||
|
||||
clearErrors = () => {
|
||||
this.setState({ errors: {} });
|
||||
};
|
||||
|
||||
appParameter = () => {
|
||||
const { strategy } = this.state;
|
||||
strategy.parameters = [...strategy.parameters, {}];
|
||||
@ -47,26 +56,49 @@ class WrapperComponent extends Component {
|
||||
|
||||
onSubmit = async evt => {
|
||||
evt.preventDefault();
|
||||
const { createStrategy, updateStrategy, history, editMode } = this.props;
|
||||
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,
|
||||
}));
|
||||
.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 {
|
||||
await createStrategy(strategy);
|
||||
history.push(`/strategies`);
|
||||
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 ',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -84,7 +116,7 @@ class WrapperComponent extends Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<AddStrategy
|
||||
<CreateStrategy
|
||||
onSubmit={this.onSubmit}
|
||||
onCancel={this.onCancel}
|
||||
setValue={this.setValue}
|
||||
@ -93,6 +125,7 @@ class WrapperComponent extends Component {
|
||||
input={this.state.strategy}
|
||||
errors={this.state.errors}
|
||||
editMode={this.props.editMode}
|
||||
clearErrors={this.clearErrors}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -110,11 +143,16 @@ const mapDispatchToProps = { createStrategy, updateStrategy };
|
||||
const mapStateToProps = (state, props) => {
|
||||
const { strategy, editMode } = props;
|
||||
return {
|
||||
strategy: strategy ? strategy : { name: loadNameFromHash(), description: '', parameters: [] },
|
||||
strategy: strategy
|
||||
? strategy
|
||||
: { name: loadNameFromHash(), description: '', parameters: [] },
|
||||
editMode,
|
||||
};
|
||||
};
|
||||
|
||||
const FormAddContainer = connect(mapStateToProps, mapDispatchToProps)(WrapperComponent);
|
||||
const FormAddContainer = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(WrapperComponent);
|
||||
|
||||
export default FormAddContainer;
|
@ -1,165 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Typography, TextField, FormControlLabel, Checkbox, Button, Icon } from '@material-ui/core';
|
||||
|
||||
import MySelect from '../common/select';
|
||||
import PageContent from '../common/PageContent/PageContent';
|
||||
import ConditionallyRender from '../common/ConditionallyRender/ConditionallyRender';
|
||||
|
||||
import { styles as commonStyles, FormButtons } from '../common';
|
||||
import { trim } from '../common/util';
|
||||
|
||||
function gerArrayWithEntries(num) {
|
||||
return Array.from(Array(num));
|
||||
}
|
||||
|
||||
const paramTypesOptions = [
|
||||
{ key: 'string', label: 'string' },
|
||||
{ key: 'percentage', label: 'percentage' },
|
||||
{ key: 'list', label: 'list' },
|
||||
{ key: 'number', label: 'number' },
|
||||
{ key: 'boolean', label: 'boolean' },
|
||||
];
|
||||
|
||||
const Parameter = ({ set, input = {}, index }) => {
|
||||
const handleTypeChange = event => {
|
||||
set({ type: event.target.value });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={commonStyles.contentSpacing}>
|
||||
<TextField
|
||||
style={{ width: '50%', marginRight: '5px' }}
|
||||
label={`Parameter name ${index + 1}`}
|
||||
onChange={({ target }) => set({ name: target.value }, true)}
|
||||
value={input.name || ''}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
/>
|
||||
<MySelect
|
||||
label="Type"
|
||||
options={paramTypesOptions}
|
||||
value={input.type || 'string'}
|
||||
onChange={handleTypeChange}
|
||||
id={`prop-type-${index}-select`}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
style={{ width: '100%' }}
|
||||
rows={2}
|
||||
multiline
|
||||
label={`Parameter name ${index + 1} description`}
|
||||
onChange={({ target }) => set({ description: target.value })}
|
||||
value={input.description || ''}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={<Checkbox checked={!!input.required} onChange={() => set({ required: !input.required })} />}
|
||||
label="Required"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Parameter.propTypes = {
|
||||
input: PropTypes.object,
|
||||
set: PropTypes.func,
|
||||
index: PropTypes.number,
|
||||
};
|
||||
|
||||
const Parameters = ({ input = [], count = 0, updateParameter }) => (
|
||||
<div>
|
||||
{gerArrayWithEntries(count).map((v, i) => (
|
||||
<Parameter key={i} set={v => updateParameter(i, v, true)} index={i} input={input[i]} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
Parameters.propTypes = {
|
||||
input: PropTypes.array,
|
||||
updateParameter: PropTypes.func.isRequired,
|
||||
count: PropTypes.number,
|
||||
};
|
||||
|
||||
class AddStrategy extends Component {
|
||||
static propTypes = {
|
||||
input: PropTypes.object,
|
||||
setValue: PropTypes.func,
|
||||
appParameter: PropTypes.func,
|
||||
updateParameter: PropTypes.func,
|
||||
clear: PropTypes.func,
|
||||
onCancel: PropTypes.func,
|
||||
onSubmit: PropTypes.func,
|
||||
editMode: PropTypes.bool,
|
||||
};
|
||||
|
||||
getHeaderTitle = () => {
|
||||
const { editMode } = this.props;
|
||||
if (editMode) return 'Edit strategy';
|
||||
return 'Create a new strategy';
|
||||
};
|
||||
|
||||
render() {
|
||||
const { input, setValue, appParameter, onCancel, editMode = false, onSubmit, updateParameter } = this.props;
|
||||
|
||||
return (
|
||||
<PageContent headerContent={this.getHeaderTitle()}>
|
||||
<ConditionallyRender
|
||||
condition={editMode}
|
||||
show={
|
||||
<Typography variant="p">
|
||||
Be careful! Changing a strategy definition might also require changes to the implementation
|
||||
in the clients.
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
|
||||
<form onSubmit={onSubmit} className={commonStyles.contentSpacing} style={{ maxWidth: '400px' }}>
|
||||
<TextField
|
||||
label="Strategy name"
|
||||
name="name"
|
||||
placeholder=""
|
||||
disabled={editMode}
|
||||
onChange={({ target }) => setValue('name', trim(target.value))}
|
||||
value={input.name}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
/>
|
||||
|
||||
<TextField
|
||||
className={commonStyles.fullwidth}
|
||||
multiline
|
||||
rows={2}
|
||||
label="Description"
|
||||
name="description"
|
||||
placeholder=""
|
||||
onChange={({ target }) => setValue('description', target.value)}
|
||||
value={input.description}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
/>
|
||||
|
||||
<Parameters
|
||||
input={input.parameters}
|
||||
count={input.parameters.length}
|
||||
updateParameter={updateParameter}
|
||||
/>
|
||||
<Button
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
appParameter();
|
||||
}}
|
||||
startIcon={<Icon>add</Icon>}
|
||||
>
|
||||
Add parameter
|
||||
</Button>
|
||||
|
||||
<FormButtons submitText={editMode ? 'Update' : 'Create'} onCancel={onCancel} />
|
||||
</form>
|
||||
</PageContent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AddStrategy;
|
@ -2,7 +2,7 @@ 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 './form-container';
|
||||
import EditStrategy from './CreateStrategy';
|
||||
import { UPDATE_STRATEGY } from '../AccessProvider/permissions';
|
||||
import ConditionallyRender from '../common/ConditionallyRender/ConditionallyRender';
|
||||
import TabNav from '../common/TabNav/TabNav';
|
||||
|
@ -1,7 +1,13 @@
|
||||
interface IAuthStatus {
|
||||
export interface IAuthStatus {
|
||||
authDetails: IAuthDetails;
|
||||
showDialog: boolean;
|
||||
profile?: IUser;
|
||||
permissions: IPermission[];
|
||||
}
|
||||
|
||||
export interface IPermission {
|
||||
permission: string;
|
||||
project: string;
|
||||
}
|
||||
|
||||
interface IAuthDetails {
|
||||
|
@ -1,8 +1,10 @@
|
||||
import React from 'react';
|
||||
import AddFeatureToggleForm from '../../component/feature/create/add-feature-container';
|
||||
import CreateFeature from '../../component/feature/create/CreateFeature';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const render = ({ history }) => <AddFeatureToggleForm title="Create feature toggle" history={history} />;
|
||||
const render = ({ history }) => (
|
||||
<CreateFeature title="Create feature toggle" history={history} />
|
||||
);
|
||||
|
||||
render.propTypes = {
|
||||
history: PropTypes.object.isRequired,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import AddStrategies from '../../component/strategies/form-container';
|
||||
import AddStrategies from '../../component/strategies/CreateStrategy';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const render = ({ history }) => <AddStrategies history={history} />;
|
||||
|
@ -9,13 +9,28 @@ import {
|
||||
UPDATE_FEATURE_TOGGLE,
|
||||
} from '../feature-toggle/actions';
|
||||
|
||||
import { ERROR_UPDATING_STRATEGY, ERROR_CREATING_STRATEGY, ERROR_RECEIVE_STRATEGIES } from '../strategy/actions';
|
||||
import {
|
||||
ERROR_UPDATING_STRATEGY,
|
||||
ERROR_CREATING_STRATEGY,
|
||||
ERROR_RECEIVE_STRATEGIES,
|
||||
} from '../strategy/actions';
|
||||
|
||||
import { ERROR_ADD_CONTEXT_FIELD, ERROR_UPDATE_CONTEXT_FIELD } from '../context/actions';
|
||||
import {
|
||||
ERROR_ADD_CONTEXT_FIELD,
|
||||
ERROR_UPDATE_CONTEXT_FIELD,
|
||||
} from '../context/actions';
|
||||
|
||||
import { ERROR_REMOVING_PROJECT, ERROR_ADD_PROJECT, ERROR_UPDATE_PROJECT } from '../project/actions';
|
||||
import {
|
||||
ERROR_REMOVING_PROJECT,
|
||||
ERROR_ADD_PROJECT,
|
||||
ERROR_UPDATE_PROJECT,
|
||||
} from '../project/actions';
|
||||
|
||||
import { ERROR_ADD_ADDON_CONFIG, ERROR_UPDATE_ADDON_CONFIG, ERROR_REMOVING_ADDON_CONFIG } from '../addons/actions'
|
||||
import {
|
||||
ERROR_ADD_ADDON_CONFIG,
|
||||
ERROR_UPDATE_ADDON_CONFIG,
|
||||
ERROR_REMOVING_ADDON_CONFIG,
|
||||
} from '../addons/actions';
|
||||
|
||||
import { UPDATE_APPLICATION_FIELD } from '../application/actions';
|
||||
|
||||
@ -54,12 +69,16 @@ const strategies = (state = getInitState(), action) => {
|
||||
case ERROR_UPDATE_ADDON_CONFIG:
|
||||
case ERROR_REMOVING_ADDON_CONFIG:
|
||||
case ERROR_ADD_PROJECT:
|
||||
console.log(action);
|
||||
return addErrorIfNotAlreadyInList(state, action.error.message);
|
||||
case FORBIDDEN:
|
||||
return addErrorIfNotAlreadyInList(state, action.error.message || '403 Forbidden');
|
||||
return addErrorIfNotAlreadyInList(
|
||||
state,
|
||||
action.error.message || '403 Forbidden'
|
||||
);
|
||||
case MUTE_ERROR:
|
||||
return state.update('list', list => list.remove(list.indexOf(action.error)));
|
||||
return state.update('list', list =>
|
||||
list.remove(list.indexOf(action.error))
|
||||
);
|
||||
case UPDATE_FEATURE_TOGGLE:
|
||||
case UPDATE_FEATURE_TOGGLE_STRATEGIES:
|
||||
case UPDATE_APPLICATION_FIELD:
|
||||
|
@ -20,7 +20,9 @@ const features = (state = new List([]), action) => {
|
||||
return state.push(new $Map(action.featureToggle));
|
||||
case REMOVE_FEATURE_TOGGLE:
|
||||
debug(REMOVE_FEATURE_TOGGLE, action);
|
||||
return state.filter(toggle => toggle.get('name') !== action.featureToggleName);
|
||||
return state.filter(
|
||||
toggle => toggle.get('name') !== action.featureToggleName
|
||||
);
|
||||
case TOGGLE_FEATURE_TOGGLE:
|
||||
debug(TOGGLE_FEATURE_TOGGLE, action);
|
||||
return state.map(toggle => {
|
||||
@ -62,7 +64,6 @@ const features = (state = new List([]), action) => {
|
||||
return new List(action.featureToggles.map($Map));
|
||||
case USER_LOGIN:
|
||||
case USER_LOGOUT:
|
||||
console.log('clear toggle store');
|
||||
debug(USER_LOGOUT, action);
|
||||
return new List([]);
|
||||
default:
|
||||
|
@ -64,7 +64,10 @@ export function createStrategy(strategy) {
|
||||
return api
|
||||
.create(strategy)
|
||||
.then(() => dispatch(addStrategy(strategy)))
|
||||
.catch(dispatchError(dispatch, ERROR_CREATING_STRATEGY));
|
||||
.catch(e => {
|
||||
dispatchError(dispatch, ERROR_CREATING_STRATEGY);
|
||||
throw e;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
36
frontend/src/utils/project-filter-generator.ts
Normal file
36
frontend/src/utils/project-filter-generator.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { ADMIN } from '../component/AccessProvider/permissions';
|
||||
import IAuthStatus, { IPermission } from '../interfaces/user';
|
||||
|
||||
type objectIdx = {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
export const projectFilterGenerator = (
|
||||
user: IAuthStatus,
|
||||
matcherPermission: string
|
||||
) => {
|
||||
let admin = false;
|
||||
const permissionMap: objectIdx = user.permissions.reduce(
|
||||
(acc: objectIdx, current: IPermission) => {
|
||||
if (current.permission === ADMIN) {
|
||||
admin = true;
|
||||
}
|
||||
|
||||
if (current.permission === matcherPermission) {
|
||||
acc[current.project] = matcherPermission;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
return (project: string) => {
|
||||
if (admin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (permissionMap[project]) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
};
|
Loading…
Reference in New Issue
Block a user