1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-06-18 01:18:23 +02:00

feature: Add support for permission system in unleash frontend

refactored PermissionComponent to pure component
fixed feature-list-item-component
This commit is contained in:
Benjamin Ludewig 2019-01-02 10:21:06 +01:00
parent 1eb8fc0464
commit aad612d3d6
6 changed files with 113 additions and 94 deletions

View File

@ -1,47 +1,21 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { ADMIN } from '../../permissions'; import { ADMIN } from '../../permissions';
class PermissionComponent extends Component { const PermissionComponent = ({ user, permission, component, otherwise }) => {
static propTypes = { if (
user: PropTypes.object, user &&
component: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), (!user.permissions || user.permissions.indexOf(ADMIN) !== -1 || user.permissions.indexOf(permission) !== -1)
others: PropTypes.object, ) {
denied: PropTypes.object, return component;
granted: PropTypes.object,
otherwise: PropTypes.node,
permission: PropTypes.string,
children: PropTypes.node,
};
render() {
const { user, otherwise, component: Component, permission, granted, denied, children, ...others } = this.props;
let grantedComponent = Component;
let deniedCompoinent = otherwise || '';
if (granted || denied) {
grantedComponent = (
<Component {...others} {...granted || {}}>
{children}
</Component>
);
deniedCompoinent = (
<Component {...others} {...denied || {}}>
{children}
</Component>
);
}
if (!user) return deniedCompoinent;
if (
!user.permissions ||
user.permissions.indexOf(ADMIN) !== -1 ||
user.permissions.indexOf(permission) !== -1
) {
return grantedComponent;
}
return deniedCompoinent;
} }
} return otherwise || '';
};
PermissionComponent.propTypes = {
user: PropTypes.object,
component: PropTypes.node,
otherwise: PropTypes.node,
permission: PropTypes.string,
};
export default PermissionComponent; export default PermissionComponent;

View File

@ -22,7 +22,7 @@ exports[`renders correctly with one feature 1`] = `
> >
<react-mdl-Switch <react-mdl-Switch
checked={false} checked={false}
disabled={true} disabled={false}
onChange={[Function]} onChange={[Function]}
title="Toggle Another" title="Toggle Another"
/> />

View File

@ -1,8 +1,12 @@
import React from 'react'; import React from 'react';
import { MemoryRouter } from 'react-router-dom'; import { MemoryRouter } from 'react-router-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import { Map as $Map } from 'immutable';
import Feature from './../feature-list-item-component'; import Feature from './../feature-list-item-component';
import renderer from 'react-test-renderer'; import renderer from 'react-test-renderer';
import { UPDATE_FEATURE } from '../../../permissions';
jest.mock('react-mdl'); jest.mock('react-mdl');
@ -21,19 +25,22 @@ test('renders correctly with one feature', () => {
], ],
createdAt: '2018-02-04T20:27:52.127Z', createdAt: '2018-02-04T20:27:52.127Z',
}; };
const store = { user: new $Map({ profile: { permissions: [UPDATE_FEATURE] } }) };
const featureMetrics = { lastHour: {}, lastMinute: {}, seenApps: {} }; const featureMetrics = { lastHour: {}, lastMinute: {}, seenApps: {} };
const settings = { sort: 'name' }; const settings = { sort: 'name' };
const tree = renderer.create( const tree = renderer.create(
<MemoryRouter> <Provider store={createStore(state => state, store)}>
<Feature <MemoryRouter>
key={0} <Feature
settings={settings} key={0}
metricsLastHour={featureMetrics.lastHour[feature.name]} settings={settings}
metricsLastMinute={featureMetrics.lastMinute[feature.name]} metricsLastHour={featureMetrics.lastHour[feature.name]}
feature={feature} metricsLastMinute={featureMetrics.lastMinute[feature.name]}
toggleFeature={jest.fn()} feature={feature}
/> toggleFeature={jest.fn()}
</MemoryRouter> />
</MemoryRouter>
</Provider>
); );
expect(tree).toMatchSnapshot(); expect(tree).toMatchSnapshot();

View File

@ -3,6 +3,8 @@ import PropTypes from 'prop-types';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { Switch, Chip, ListItem, ListItemAction, Icon } from 'react-mdl'; import { Switch, Chip, ListItem, ListItemAction, Icon } from 'react-mdl';
import Progress from './progress'; import Progress from './progress';
import PermissionComponent from '../common/permission-container';
import { UPDATE_FEATURE } from '../../permissions';
import { calc, styles as commonStyles } from '../common'; import { calc, styles as commonStyles } from '../common';
import styles from './feature.scss'; import styles from './feature.scss';
@ -14,7 +16,6 @@ const Feature = ({
metricsLastHour = { yes: 0, no: 0, isFallback: true }, metricsLastHour = { yes: 0, no: 0, isFallback: true },
metricsLastMinute = { yes: 0, no: 0, isFallback: true }, metricsLastMinute = { yes: 0, no: 0, isFallback: true },
revive, revive,
updateable,
}) => { }) => {
const { name, description, enabled, strategies } = feature; const { name, description, enabled, strategies } = feature;
const { showLastHour = false } = settings; const { showLastHour = false } = settings;
@ -42,12 +43,26 @@ const Feature = ({
<Progress strokeWidth={15} percentage={percent} isFallback={isStale} /> <Progress strokeWidth={15} percentage={percent} isFallback={isStale} />
</span> </span>
<span className={styles.listItemToggle}> <span className={styles.listItemToggle}>
<Switch <PermissionComponent
disabled={!updateable || toggleFeature === undefined} permission={UPDATE_FEATURE}
title={`Toggle ${name}`} component={
key="left-actions" <Switch
onChange={() => toggleFeature(name)} disabled={toggleFeature === undefined}
checked={enabled} title={`Toggle ${name}`}
key="left-actions"
onChange={() => toggleFeature(name)}
checked={enabled}
/>
}
otherwise={
<Switch
disabled
title={`Toggle ${name}`}
key="left-actions"
onChange={() => toggleFeature(name)}
checked={enabled}
/>
}
/> />
</span> </span>
<span className={['mdl-list__item-primary-content', styles.listItemLink].join(' ')}> <span className={['mdl-list__item-primary-content', styles.listItemLink].join(' ')}>
@ -60,10 +75,16 @@ const Feature = ({
{strategyChips} {strategyChips}
{summaryChip} {summaryChip}
</span> </span>
{updateable && revive ? ( {revive ? (
<ListItemAction onClick={() => revive(feature.name)}> <PermissionComponent
<Icon name="undo" /> permission={UPDATE_FEATURE}
</ListItemAction> component={
<ListItemAction onClick={() => revive(feature.name)}>
<Icon name="undo" />
</ListItemAction>
}
otherwise={<span />}
/>
) : ( ) : (
<span /> <span />
)} )}

View File

@ -8,10 +8,10 @@ import { HeaderTitle } from '../../common';
class StrategiesSectionComponent extends React.Component { class StrategiesSectionComponent extends React.Component {
static propTypes = { static propTypes = {
strategies: PropTypes.array.isRequired, strategies: PropTypes.array.isRequired,
addStrategy: PropTypes.func.isRequired, addStrategy: PropTypes.func,
removeStrategy: PropTypes.func.isRequired, removeStrategy: PropTypes.func,
updateStrategy: PropTypes.func.isRequired, updateStrategy: PropTypes.func,
fetchStrategies: PropTypes.func.isRequired, fetchStrategies: PropTypes.func,
}; };
componentWillMount() { componentWillMount() {

View File

@ -155,20 +155,29 @@ export default class ViewFeatureToggleComponent extends React.Component {
{this.isFeatureView ? ( {this.isFeatureView ? (
<PermissionComponent <PermissionComponent
permission={UPDATE_FEATURE} permission={UPDATE_FEATURE}
component={Textfield} component={
granted={{ <Textfield
onChange: v => setValue('description', v), floatingLabel
onBlur: updateFeatureToggle, style={{ width: '100%' }}
}} rows={1}
denied={{ label="Description"
disabled: true, required
}} value={featureToggle.description}
floatingLabel onChange={v => setValue('description', v)}
style={{ width: '100%' }} onBlur={updateFeatureToggle}
rows={1} />
label="Description" }
required otherwise={
value={featureToggle.description} <Textfield
disabled
floatingLabel
style={{ width: '100%' }}
rows={1}
label="Description"
required
value={featureToggle.description}
/>
}
/> />
) : ( ) : (
<Textfield <Textfield
@ -194,19 +203,27 @@ export default class ViewFeatureToggleComponent extends React.Component {
<span style={{ paddingRight: '24px' }}> <span style={{ paddingRight: '24px' }}>
<PermissionComponent <PermissionComponent
permission={UPDATE_FEATURE} permission={UPDATE_FEATURE}
component={Switch} component={
granted={{ <Switch
disabled: !this.isFeatureView, disabled={!this.isFeatureView}
}} ripple
denied={{ checked={featureToggle.enabled}
disabled: true, onChange={() => toggleFeature(featureToggle.name)}
}} >
ripple {featureToggle.enabled ? 'Enabled' : 'Disabled'}
checked={featureToggle.enabled} </Switch>
onChange={() => toggleFeature(featureToggle.name)} }
> otherwise={
{featureToggle.enabled ? 'Enabled' : 'Disabled'} <Switch
</PermissionComponent> disabled
ripple
checked={featureToggle.enabled}
onChange={() => toggleFeature(featureToggle.name)}
>
{featureToggle.enabled ? 'Enabled' : 'Disabled'}
</Switch>
}
/>
</span> </span>
{this.isFeatureView ? ( {this.isFeatureView ? (