mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-11 00:08:30 +01:00
feature: Add support for permission system in unleash frontend
refactored permission-based components added tests for all permission-based components
This commit is contained in:
parent
ba7ec688eb
commit
85fb0f9b89
2
frontend/src/__mocks__/react-mdl.js
vendored
2
frontend/src/__mocks__/react-mdl.js
vendored
@ -9,6 +9,8 @@ module.exports = {
|
||||
Cell: 'react-mdl-Cell',
|
||||
Chip: 'react-mdl-Chip',
|
||||
Grid: 'react-mdl-Grid',
|
||||
Button: 'react-mdl-Button',
|
||||
FABButton: 'react-mdl-FABButton',
|
||||
Icon: 'react-mdl-Icon',
|
||||
IconButton: 'react-mdl-IconButton',
|
||||
List: 'react-mdl-List',
|
||||
|
@ -5,3 +5,356 @@ exports[`renders correctly if no application 1`] = `
|
||||
indeterminate={true}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`renders correctly with permissions 1`] = `
|
||||
<react-mdl-Card
|
||||
className="fullwidth"
|
||||
shadow={0}
|
||||
>
|
||||
<react-mdl-CardTitle
|
||||
style={
|
||||
Object {
|
||||
"paddingRight": "64px",
|
||||
"paddingTop": "24px",
|
||||
"wordBreak": "break-all",
|
||||
}
|
||||
}
|
||||
>
|
||||
<react-mdl-Icon
|
||||
name="apps"
|
||||
/>
|
||||
|
||||
test-app
|
||||
</react-mdl-CardTitle>
|
||||
<react-mdl-CardText>
|
||||
app description
|
||||
</react-mdl-CardText>
|
||||
<react-mdl-CardMenu>
|
||||
<a
|
||||
className="mdl-color-text--grey-600"
|
||||
href="http://example.org"
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
>
|
||||
<react-mdl-Icon
|
||||
name="link"
|
||||
/>
|
||||
</a>
|
||||
</react-mdl-CardMenu>
|
||||
<hr />
|
||||
<react-mdl-Tabs
|
||||
activeTab={0}
|
||||
className="mdl-color--grey-100"
|
||||
onChange={[Function]}
|
||||
ripple={true}
|
||||
tabBarProps={
|
||||
Object {
|
||||
"style": Object {
|
||||
"width": "100%",
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
<react-mdl-Tab>
|
||||
Details
|
||||
</react-mdl-Tab>
|
||||
<react-mdl-Tab>
|
||||
Edit
|
||||
</react-mdl-Tab>
|
||||
</react-mdl-Tabs>
|
||||
<react-mdl-Grid
|
||||
style={
|
||||
Object {
|
||||
"margin": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<react-mdl-Cell
|
||||
col={6}
|
||||
phone={12}
|
||||
tablet={4}
|
||||
>
|
||||
<h6>
|
||||
Toggles
|
||||
</h6>
|
||||
<hr />
|
||||
<react-mdl-List>
|
||||
<react-mdl-ListItem
|
||||
twoLine={true}
|
||||
>
|
||||
<react-mdl-ListItemContent
|
||||
icon={
|
||||
<span>
|
||||
<react-mdl-Switch
|
||||
checked={true}
|
||||
disabled={true}
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
subtitle="this is A toggle"
|
||||
>
|
||||
<a
|
||||
href="/features/view/ToggleA"
|
||||
onClick={[Function]}
|
||||
>
|
||||
ToggleA
|
||||
</a>
|
||||
</react-mdl-ListItemContent>
|
||||
</react-mdl-ListItem>
|
||||
<react-mdl-ListItem
|
||||
twoLine={true}
|
||||
>
|
||||
<react-mdl-ListItemContent
|
||||
icon="report"
|
||||
subtitle="Missing, want to create?"
|
||||
>
|
||||
<a
|
||||
href="/features/create?name=ToggleB"
|
||||
onClick={[Function]}
|
||||
>
|
||||
ToggleB
|
||||
</a>
|
||||
</react-mdl-ListItemContent>
|
||||
</react-mdl-ListItem>
|
||||
</react-mdl-List>
|
||||
</react-mdl-Cell>
|
||||
<react-mdl-Cell
|
||||
col={6}
|
||||
phone={12}
|
||||
tablet={4}
|
||||
>
|
||||
<h6>
|
||||
Implemented strategies
|
||||
</h6>
|
||||
<hr />
|
||||
<react-mdl-List>
|
||||
<react-mdl-ListItem
|
||||
twoLine={true}
|
||||
>
|
||||
<react-mdl-ListItemContent
|
||||
icon="extension"
|
||||
subtitle="A description"
|
||||
>
|
||||
<a
|
||||
href="/strategies/view/StrategyA"
|
||||
onClick={[Function]}
|
||||
>
|
||||
StrategyA
|
||||
</a>
|
||||
</react-mdl-ListItemContent>
|
||||
</react-mdl-ListItem>
|
||||
<react-mdl-ListItem
|
||||
twoLine={true}
|
||||
>
|
||||
<react-mdl-ListItemContent
|
||||
icon="report"
|
||||
subtitle="Missing, want to create?"
|
||||
>
|
||||
<a
|
||||
href="/strategies/create?name=StrategyB"
|
||||
onClick={[Function]}
|
||||
>
|
||||
StrategyB
|
||||
</a>
|
||||
</react-mdl-ListItemContent>
|
||||
</react-mdl-ListItem>
|
||||
</react-mdl-List>
|
||||
</react-mdl-Cell>
|
||||
<react-mdl-Cell
|
||||
col={12}
|
||||
tablet={12}
|
||||
>
|
||||
<h6>
|
||||
1
|
||||
Instances registered
|
||||
</h6>
|
||||
<hr />
|
||||
<react-mdl-List>
|
||||
<react-mdl-ListItem
|
||||
twoLine={true}
|
||||
>
|
||||
<react-mdl-ListItemContent
|
||||
icon="timeline"
|
||||
subtitle={
|
||||
<span>
|
||||
123.123.123.123
|
||||
last seen at
|
||||
|
||||
<small>
|
||||
02/23/2017, 3:56:49 PM
|
||||
</small>
|
||||
</span>
|
||||
}
|
||||
>
|
||||
instance-1
|
||||
|
||||
(4.0)
|
||||
</react-mdl-ListItemContent>
|
||||
</react-mdl-ListItem>
|
||||
</react-mdl-List>
|
||||
</react-mdl-Cell>
|
||||
</react-mdl-Grid>
|
||||
</react-mdl-Card>
|
||||
`;
|
||||
|
||||
exports[`renders correctly without permission 1`] = `
|
||||
<react-mdl-Card
|
||||
className="fullwidth"
|
||||
shadow={0}
|
||||
>
|
||||
<react-mdl-CardTitle
|
||||
style={
|
||||
Object {
|
||||
"paddingRight": "64px",
|
||||
"paddingTop": "24px",
|
||||
"wordBreak": "break-all",
|
||||
}
|
||||
}
|
||||
>
|
||||
<react-mdl-Icon
|
||||
name="apps"
|
||||
/>
|
||||
|
||||
test-app
|
||||
</react-mdl-CardTitle>
|
||||
<react-mdl-CardText>
|
||||
app description
|
||||
</react-mdl-CardText>
|
||||
<react-mdl-CardMenu>
|
||||
<a
|
||||
className="mdl-color-text--grey-600"
|
||||
href="http://example.org"
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
>
|
||||
<react-mdl-Icon
|
||||
name="link"
|
||||
/>
|
||||
</a>
|
||||
</react-mdl-CardMenu>
|
||||
<hr />
|
||||
|
||||
<react-mdl-Grid
|
||||
style={
|
||||
Object {
|
||||
"margin": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<react-mdl-Cell
|
||||
col={6}
|
||||
phone={12}
|
||||
tablet={4}
|
||||
>
|
||||
<h6>
|
||||
Toggles
|
||||
</h6>
|
||||
<hr />
|
||||
<react-mdl-List>
|
||||
<react-mdl-ListItem
|
||||
twoLine={true}
|
||||
>
|
||||
<react-mdl-ListItemContent
|
||||
icon={
|
||||
<span>
|
||||
<react-mdl-Switch
|
||||
checked={true}
|
||||
disabled={true}
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
subtitle="this is A toggle"
|
||||
>
|
||||
<a
|
||||
href="/features/view/ToggleA"
|
||||
onClick={[Function]}
|
||||
>
|
||||
ToggleA
|
||||
</a>
|
||||
</react-mdl-ListItemContent>
|
||||
</react-mdl-ListItem>
|
||||
<react-mdl-ListItem
|
||||
twoLine={true}
|
||||
>
|
||||
<react-mdl-ListItemContent
|
||||
icon="report"
|
||||
subtitle="Missing"
|
||||
>
|
||||
ToggleB
|
||||
</react-mdl-ListItemContent>
|
||||
</react-mdl-ListItem>
|
||||
</react-mdl-List>
|
||||
</react-mdl-Cell>
|
||||
<react-mdl-Cell
|
||||
col={6}
|
||||
phone={12}
|
||||
tablet={4}
|
||||
>
|
||||
<h6>
|
||||
Implemented strategies
|
||||
</h6>
|
||||
<hr />
|
||||
<react-mdl-List>
|
||||
<react-mdl-ListItem
|
||||
twoLine={true}
|
||||
>
|
||||
<react-mdl-ListItemContent
|
||||
icon="extension"
|
||||
subtitle="A description"
|
||||
>
|
||||
<a
|
||||
href="/strategies/view/StrategyA"
|
||||
onClick={[Function]}
|
||||
>
|
||||
StrategyA
|
||||
</a>
|
||||
</react-mdl-ListItemContent>
|
||||
</react-mdl-ListItem>
|
||||
<react-mdl-ListItem
|
||||
twoLine={true}
|
||||
>
|
||||
<react-mdl-ListItemContent
|
||||
icon="report"
|
||||
subtitle="Missing"
|
||||
>
|
||||
StrategyB
|
||||
</react-mdl-ListItemContent>
|
||||
</react-mdl-ListItem>
|
||||
</react-mdl-List>
|
||||
</react-mdl-Cell>
|
||||
<react-mdl-Cell
|
||||
col={12}
|
||||
tablet={12}
|
||||
>
|
||||
<h6>
|
||||
1
|
||||
Instances registered
|
||||
</h6>
|
||||
<hr />
|
||||
<react-mdl-List>
|
||||
<react-mdl-ListItem
|
||||
twoLine={true}
|
||||
>
|
||||
<react-mdl-ListItemContent
|
||||
icon="timeline"
|
||||
subtitle={
|
||||
<span>
|
||||
123.123.123.123
|
||||
last seen at
|
||||
|
||||
<small>
|
||||
02/23/2017, 3:56:49 PM
|
||||
</small>
|
||||
</span>
|
||||
}
|
||||
>
|
||||
instance-1
|
||||
|
||||
(4.0)
|
||||
</react-mdl-ListItemContent>
|
||||
</react-mdl-ListItem>
|
||||
</react-mdl-List>
|
||||
</react-mdl-Cell>
|
||||
</react-mdl-Grid>
|
||||
</react-mdl-Card>
|
||||
`;
|
||||
|
@ -2,12 +2,130 @@ import React from 'react';
|
||||
|
||||
import ClientApplications from '../application-edit-component';
|
||||
import renderer from 'react-test-renderer';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { CREATE_FEATURE, CREATE_STRATEGY, UPDATE_APPLICATION } from '../../../permissions';
|
||||
|
||||
jest.mock('react-mdl');
|
||||
|
||||
test('renders correctly if no application', () => {
|
||||
const tree = renderer
|
||||
.create(<ClientApplications fetchApplication={jest.fn()} storeApplicationMetaData={jest.fn()} />)
|
||||
.create(
|
||||
<ClientApplications
|
||||
fetchApplication={jest.fn()}
|
||||
storeApplicationMetaData={jest.fn()}
|
||||
hasPermission={() => true}
|
||||
/>
|
||||
)
|
||||
.toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('renders correctly without permission', () => {
|
||||
const tree = renderer
|
||||
.create(
|
||||
<MemoryRouter>
|
||||
<ClientApplications
|
||||
fetchApplication={jest.fn()}
|
||||
storeApplicationMetaData={jest.fn()}
|
||||
application={{
|
||||
appName: 'test-app',
|
||||
instances: [
|
||||
{
|
||||
instanceId: 'instance-1',
|
||||
clientIp: '123.123.123.123',
|
||||
lastSeen: 1487861809466,
|
||||
sdkVersion: '4.0',
|
||||
},
|
||||
],
|
||||
strategies: [
|
||||
{
|
||||
name: 'StrategyA',
|
||||
description: 'A description',
|
||||
},
|
||||
{
|
||||
name: 'StrategyB',
|
||||
description: 'B description',
|
||||
notFound: true,
|
||||
},
|
||||
],
|
||||
seenToggles: [
|
||||
{
|
||||
name: 'ToggleA',
|
||||
description: 'this is A toggle',
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
name: 'ToggleB',
|
||||
description: 'this is B toggle',
|
||||
enabled: false,
|
||||
notFound: true,
|
||||
},
|
||||
],
|
||||
url: 'http://example.org',
|
||||
description: 'app description',
|
||||
}}
|
||||
location={{ locale: 'en-GB' }}
|
||||
hasPermission={() => false}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
)
|
||||
.toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('renders correctly with permissions', () => {
|
||||
const tree = renderer
|
||||
.create(
|
||||
<MemoryRouter>
|
||||
<ClientApplications
|
||||
fetchApplication={jest.fn()}
|
||||
storeApplicationMetaData={jest.fn()}
|
||||
application={{
|
||||
appName: 'test-app',
|
||||
instances: [
|
||||
{
|
||||
instanceId: 'instance-1',
|
||||
clientIp: '123.123.123.123',
|
||||
lastSeen: 1487861809466,
|
||||
sdkVersion: '4.0',
|
||||
},
|
||||
],
|
||||
strategies: [
|
||||
{
|
||||
name: 'StrategyA',
|
||||
description: 'A description',
|
||||
},
|
||||
{
|
||||
name: 'StrategyB',
|
||||
description: 'B description',
|
||||
notFound: true,
|
||||
},
|
||||
],
|
||||
seenToggles: [
|
||||
{
|
||||
name: 'ToggleA',
|
||||
description: 'this is A toggle',
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
name: 'ToggleB',
|
||||
description: 'this is B toggle',
|
||||
enabled: false,
|
||||
notFound: true,
|
||||
},
|
||||
],
|
||||
url: 'http://example.org',
|
||||
description: 'app description',
|
||||
}}
|
||||
location={{ locale: 'en-GB' }}
|
||||
hasPermission={permission =>
|
||||
[CREATE_FEATURE, CREATE_STRATEGY, UPDATE_APPLICATION].indexOf(permission) !== -1
|
||||
}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
)
|
||||
.toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
|
@ -23,7 +23,6 @@ import {
|
||||
import { IconLink, shorten, styles as commonStyles } from '../common';
|
||||
import { formatFullDateTimeWithLocale } from '../common/util';
|
||||
import { CREATE_FEATURE, CREATE_STRATEGY, UPDATE_APPLICATION } from '../../permissions';
|
||||
import PermissionComponent from '../common/permission-container';
|
||||
|
||||
class StatefulTextfield extends Component {
|
||||
static propTypes = {
|
||||
@ -63,6 +62,7 @@ class ClientApplications extends PureComponent {
|
||||
application: PropTypes.object,
|
||||
location: PropTypes.object,
|
||||
storeApplicationMetaData: PropTypes.func.isRequired,
|
||||
hasPermission: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
@ -80,7 +80,7 @@ class ClientApplications extends PureComponent {
|
||||
if (!this.props.application) {
|
||||
return <ProgressBar indeterminate />;
|
||||
}
|
||||
const { application, storeApplicationMetaData } = this.props;
|
||||
const { application, storeApplicationMetaData, hasPermission } = this.props;
|
||||
const { appName, instances, strategies, seenToggles, url, description, icon = 'apps', color } = application;
|
||||
|
||||
const content =
|
||||
@ -93,26 +93,17 @@ class ClientApplications extends PureComponent {
|
||||
{seenToggles.map(
|
||||
({ name, description, enabled, notFound }, i) =>
|
||||
notFound ? (
|
||||
<PermissionComponent
|
||||
permission={CREATE_FEATURE}
|
||||
component={
|
||||
<ListItem twoLine key={i}>
|
||||
<ListItemContent
|
||||
icon={'report'}
|
||||
subtitle={'Missing, want to create?'}
|
||||
>
|
||||
<Link to={`/features/create?name=${name}`}>{name}</Link>
|
||||
</ListItemContent>
|
||||
</ListItem>
|
||||
}
|
||||
otherwise={
|
||||
<ListItem twoLine key={i}>
|
||||
<ListItemContent icon={'report'} subtitle={'Missing'}>
|
||||
{name}
|
||||
</ListItemContent>
|
||||
</ListItem>
|
||||
}
|
||||
/>
|
||||
<ListItem twoLine key={i}>
|
||||
{hasPermission(CREATE_FEATURE) ? (
|
||||
<ListItemContent icon={'report'} subtitle={'Missing, want to create?'}>
|
||||
<Link to={`/features/create?name=${name}`}>{name}</Link>
|
||||
</ListItemContent>
|
||||
) : (
|
||||
<ListItemContent icon={'report'} subtitle={'Missing'}>
|
||||
{name}
|
||||
</ListItemContent>
|
||||
)}
|
||||
</ListItem>
|
||||
) : (
|
||||
<ListItem twoLine key={i}>
|
||||
<ListItemContent
|
||||
@ -137,26 +128,17 @@ class ClientApplications extends PureComponent {
|
||||
{strategies.map(
|
||||
({ name, description, notFound }, i) =>
|
||||
notFound ? (
|
||||
<PermissionComponent
|
||||
permission={CREATE_STRATEGY}
|
||||
component={
|
||||
<ListItem twoLine key={`${name}-${i}`}>
|
||||
<ListItemContent
|
||||
icon={'report'}
|
||||
subtitle={'Missing, want to create?'}
|
||||
>
|
||||
<Link to={`/strategies/create?name=${name}`}>{name}</Link>
|
||||
</ListItemContent>
|
||||
</ListItem>
|
||||
}
|
||||
otherwise={
|
||||
<ListItem twoLine key={`${name}-${i}`}>
|
||||
<ListItemContent icon={'report'} subtitle={'Missing'}>
|
||||
{name}
|
||||
</ListItemContent>
|
||||
</ListItem>
|
||||
}
|
||||
/>
|
||||
<ListItem twoLine key={`${name}-${i}`}>
|
||||
{hasPermission(CREATE_STRATEGY) ? (
|
||||
<ListItemContent icon={'report'} subtitle={'Missing, want to create?'}>
|
||||
<Link to={`/strategies/create?name=${name}`}>{name}</Link>
|
||||
</ListItemContent>
|
||||
) : (
|
||||
<ListItemContent icon={'report'} subtitle={'Missing'}>
|
||||
{name}
|
||||
</ListItemContent>
|
||||
)}
|
||||
</ListItem>
|
||||
) : (
|
||||
<ListItem twoLine key={`${name}-${i}`}>
|
||||
<ListItemContent icon={'extension'} subtitle={shorten(description, 60)}>
|
||||
@ -235,21 +217,20 @@ class ClientApplications extends PureComponent {
|
||||
</CardMenu>
|
||||
)}
|
||||
<hr />
|
||||
<PermissionComponent
|
||||
permission={UPDATE_APPLICATION}
|
||||
component={
|
||||
<Tabs
|
||||
activeTab={this.state.activeTab}
|
||||
onChange={tabId => this.setState({ activeTab: tabId })}
|
||||
ripple
|
||||
tabBarProps={{ style: { width: '100%' } }}
|
||||
className="mdl-color--grey-100"
|
||||
>
|
||||
<Tab>Details</Tab>
|
||||
<Tab>Edit</Tab>
|
||||
</Tabs>
|
||||
}
|
||||
/>
|
||||
{hasPermission(UPDATE_APPLICATION) ? (
|
||||
<Tabs
|
||||
activeTab={this.state.activeTab}
|
||||
onChange={tabId => this.setState({ activeTab: tabId })}
|
||||
ripple
|
||||
tabBarProps={{ style: { width: '100%' } }}
|
||||
className="mdl-color--grey-100"
|
||||
>
|
||||
<Tab>Details</Tab>
|
||||
<Tab>Edit</Tab>
|
||||
</Tabs>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
|
||||
{content}
|
||||
</Card>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { connect } from 'react-redux';
|
||||
import ApplicationEdit from './application-edit-component';
|
||||
import { fetchApplication, storeApplicationMetaData } from './../../store/application/actions';
|
||||
import { hasPermission } from '../../permissions';
|
||||
|
||||
const mapStateToProps = (state, props) => {
|
||||
let application = state.applications.getIn(['apps', props.appName]);
|
||||
@ -11,6 +12,7 @@ const mapStateToProps = (state, props) => {
|
||||
return {
|
||||
application,
|
||||
location,
|
||||
hasPermission: hasPermission.bind(null, state.user.get('profile')),
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { fetchArchive, revive } from './../../store/archive-actions';
|
||||
import ViewToggleComponent from './../feature/view-component';
|
||||
import { hasPermission } from '../../permissions';
|
||||
|
||||
export default connect(
|
||||
(state, props) => ({
|
||||
@ -10,6 +11,7 @@ export default connect(
|
||||
.toArray()
|
||||
.find(toggle => toggle.name === props.featureToggleName),
|
||||
activeTab: props.activeTab,
|
||||
hasPermission: hasPermission.bind(null, state.user.get('profile')),
|
||||
}),
|
||||
{
|
||||
fetchArchive,
|
||||
|
@ -1,21 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { ADMIN } from '../../permissions';
|
||||
|
||||
const PermissionComponent = ({ user, permission, component, otherwise }) => {
|
||||
if (
|
||||
user &&
|
||||
(!user.permissions || user.permissions.indexOf(ADMIN) !== -1 || user.permissions.indexOf(permission) !== -1)
|
||||
) {
|
||||
return component;
|
||||
}
|
||||
return otherwise || '';
|
||||
};
|
||||
|
||||
PermissionComponent.propTypes = {
|
||||
user: PropTypes.object,
|
||||
component: PropTypes.node,
|
||||
otherwise: PropTypes.node,
|
||||
permission: PropTypes.string,
|
||||
};
|
||||
|
||||
export default PermissionComponent;
|
@ -1,8 +0,0 @@
|
||||
import { connect } from 'react-redux';
|
||||
import PermissionComponent from './permission-component';
|
||||
|
||||
const mapStateToProps = state => ({ user: state.user.get('profile') });
|
||||
|
||||
const Container = connect(mapStateToProps)(PermissionComponent);
|
||||
|
||||
export default Container;
|
@ -79,7 +79,6 @@ exports[`renders correctly with one feature without permission 1`] = `
|
||||
<react-mdl-Switch
|
||||
checked={false}
|
||||
disabled={true}
|
||||
onChange={[Function]}
|
||||
title="Toggle Another"
|
||||
/>
|
||||
</span>
|
||||
|
@ -0,0 +1,332 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly with one feature 1`] = `
|
||||
<div>
|
||||
<div
|
||||
className="toolbar"
|
||||
>
|
||||
<react-mdl-Textfield
|
||||
floatingLabel={true}
|
||||
label="Search"
|
||||
onChange={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<a
|
||||
className="toolbarButton"
|
||||
href="/features/create"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<react-mdl-FABButton
|
||||
accent={true}
|
||||
title="Create feature toggle"
|
||||
>
|
||||
<react-mdl-Icon
|
||||
name="add"
|
||||
/>
|
||||
</react-mdl-FABButton>
|
||||
</a>
|
||||
</div>
|
||||
<react-mdl-Card
|
||||
className="fullwidth"
|
||||
shadow={0}
|
||||
style={
|
||||
Object {
|
||||
"overflow": "visible",
|
||||
}
|
||||
}
|
||||
>
|
||||
<react-mdl-CardActions>
|
||||
<react-mdl-Button
|
||||
className="dropdownButton"
|
||||
id="metric"
|
||||
>
|
||||
Last minute
|
||||
<react-mdl-Icon
|
||||
className="mdl-color-text--grey-600"
|
||||
name="arrow_drop_down"
|
||||
/>
|
||||
</react-mdl-Button>
|
||||
<react-mdl-Menu
|
||||
onClick={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"width": "168px",
|
||||
}
|
||||
}
|
||||
target="metric"
|
||||
>
|
||||
<react-mdl-MenuItem
|
||||
data-target="minute"
|
||||
disabled={true}
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"display": "flex",
|
||||
}
|
||||
}
|
||||
>
|
||||
<react-mdl-Icon
|
||||
name="hourglass_empty"
|
||||
style={
|
||||
Object {
|
||||
"paddingRight": "16px",
|
||||
}
|
||||
}
|
||||
/>
|
||||
Last minute
|
||||
</react-mdl-MenuItem>
|
||||
<react-mdl-MenuItem
|
||||
data-target="hour"
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"display": "flex",
|
||||
}
|
||||
}
|
||||
>
|
||||
<react-mdl-Icon
|
||||
name="hourglass_full"
|
||||
style={
|
||||
Object {
|
||||
"paddingRight": "16px",
|
||||
}
|
||||
}
|
||||
/>
|
||||
Last hour
|
||||
</react-mdl-MenuItem>
|
||||
</react-mdl-Menu>
|
||||
<react-mdl-Button
|
||||
className="dropdownButton"
|
||||
id="sorting"
|
||||
>
|
||||
By name
|
||||
<react-mdl-Icon
|
||||
className="mdl-color-text--grey-600"
|
||||
name="arrow_drop_down"
|
||||
/>
|
||||
</react-mdl-Button>
|
||||
<react-mdl-Menu
|
||||
onClick={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"width": "168px",
|
||||
}
|
||||
}
|
||||
target="sorting"
|
||||
>
|
||||
<react-mdl-MenuItem
|
||||
data-target="name"
|
||||
disabled={true}
|
||||
>
|
||||
Name
|
||||
</react-mdl-MenuItem>
|
||||
<react-mdl-MenuItem
|
||||
data-target="enabled"
|
||||
disabled={false}
|
||||
>
|
||||
Enabled
|
||||
</react-mdl-MenuItem>
|
||||
<react-mdl-MenuItem
|
||||
data-target="created"
|
||||
disabled={false}
|
||||
>
|
||||
Created
|
||||
</react-mdl-MenuItem>
|
||||
<react-mdl-MenuItem
|
||||
data-target="strategies"
|
||||
disabled={false}
|
||||
>
|
||||
Strategies
|
||||
</react-mdl-MenuItem>
|
||||
<react-mdl-MenuItem
|
||||
data-target="metrics"
|
||||
disabled={false}
|
||||
>
|
||||
Metrics
|
||||
</react-mdl-MenuItem>
|
||||
</react-mdl-Menu>
|
||||
</react-mdl-CardActions>
|
||||
<hr />
|
||||
<react-mdl-List>
|
||||
<Feature
|
||||
feature={
|
||||
Object {
|
||||
"name": "Another",
|
||||
"reviveName": "Another",
|
||||
}
|
||||
}
|
||||
hasPermission={[Function]}
|
||||
settings={
|
||||
Object {
|
||||
"sort": "name",
|
||||
}
|
||||
}
|
||||
toggleFeature={[MockFunction]}
|
||||
/>
|
||||
</react-mdl-List>
|
||||
</react-mdl-Card>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders correctly with one feature without permissions 1`] = `
|
||||
<div>
|
||||
<div
|
||||
className="toolbar"
|
||||
>
|
||||
<react-mdl-Textfield
|
||||
floatingLabel={true}
|
||||
label="Search"
|
||||
onChange={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
||||
</div>
|
||||
<react-mdl-Card
|
||||
className="fullwidth"
|
||||
shadow={0}
|
||||
style={
|
||||
Object {
|
||||
"overflow": "visible",
|
||||
}
|
||||
}
|
||||
>
|
||||
<react-mdl-CardActions>
|
||||
<react-mdl-Button
|
||||
className="dropdownButton"
|
||||
id="metric"
|
||||
>
|
||||
Last minute
|
||||
<react-mdl-Icon
|
||||
className="mdl-color-text--grey-600"
|
||||
name="arrow_drop_down"
|
||||
/>
|
||||
</react-mdl-Button>
|
||||
<react-mdl-Menu
|
||||
onClick={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"width": "168px",
|
||||
}
|
||||
}
|
||||
target="metric"
|
||||
>
|
||||
<react-mdl-MenuItem
|
||||
data-target="minute"
|
||||
disabled={true}
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"display": "flex",
|
||||
}
|
||||
}
|
||||
>
|
||||
<react-mdl-Icon
|
||||
name="hourglass_empty"
|
||||
style={
|
||||
Object {
|
||||
"paddingRight": "16px",
|
||||
}
|
||||
}
|
||||
/>
|
||||
Last minute
|
||||
</react-mdl-MenuItem>
|
||||
<react-mdl-MenuItem
|
||||
data-target="hour"
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"display": "flex",
|
||||
}
|
||||
}
|
||||
>
|
||||
<react-mdl-Icon
|
||||
name="hourglass_full"
|
||||
style={
|
||||
Object {
|
||||
"paddingRight": "16px",
|
||||
}
|
||||
}
|
||||
/>
|
||||
Last hour
|
||||
</react-mdl-MenuItem>
|
||||
</react-mdl-Menu>
|
||||
<react-mdl-Button
|
||||
className="dropdownButton"
|
||||
id="sorting"
|
||||
>
|
||||
By name
|
||||
<react-mdl-Icon
|
||||
className="mdl-color-text--grey-600"
|
||||
name="arrow_drop_down"
|
||||
/>
|
||||
</react-mdl-Button>
|
||||
<react-mdl-Menu
|
||||
onClick={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"width": "168px",
|
||||
}
|
||||
}
|
||||
target="sorting"
|
||||
>
|
||||
<react-mdl-MenuItem
|
||||
data-target="name"
|
||||
disabled={true}
|
||||
>
|
||||
Name
|
||||
</react-mdl-MenuItem>
|
||||
<react-mdl-MenuItem
|
||||
data-target="enabled"
|
||||
disabled={false}
|
||||
>
|
||||
Enabled
|
||||
</react-mdl-MenuItem>
|
||||
<react-mdl-MenuItem
|
||||
data-target="created"
|
||||
disabled={false}
|
||||
>
|
||||
Created
|
||||
</react-mdl-MenuItem>
|
||||
<react-mdl-MenuItem
|
||||
data-target="strategies"
|
||||
disabled={false}
|
||||
>
|
||||
Strategies
|
||||
</react-mdl-MenuItem>
|
||||
<react-mdl-MenuItem
|
||||
data-target="metrics"
|
||||
disabled={false}
|
||||
>
|
||||
Metrics
|
||||
</react-mdl-MenuItem>
|
||||
</react-mdl-Menu>
|
||||
</react-mdl-CardActions>
|
||||
<hr />
|
||||
<react-mdl-List>
|
||||
<Feature
|
||||
feature={
|
||||
Object {
|
||||
"name": "Another",
|
||||
"reviveName": "Another",
|
||||
}
|
||||
}
|
||||
hasPermission={[Function]}
|
||||
settings={
|
||||
Object {
|
||||
"sort": "name",
|
||||
}
|
||||
}
|
||||
toggleFeature={[MockFunction]}
|
||||
/>
|
||||
</react-mdl-List>
|
||||
</react-mdl-Card>
|
||||
</div>
|
||||
`;
|
@ -0,0 +1,144 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly with one feature 1`] = `
|
||||
<react-mdl-Card
|
||||
className="fullwidth"
|
||||
shadow={0}
|
||||
style={
|
||||
Object {
|
||||
"overflow": "visible",
|
||||
}
|
||||
}
|
||||
>
|
||||
<react-mdl-CardTitle
|
||||
style={
|
||||
Object {
|
||||
"paddingTop": "24px",
|
||||
"wordBreak": "break-all",
|
||||
}
|
||||
}
|
||||
>
|
||||
Another
|
||||
</react-mdl-CardTitle>
|
||||
<react-mdl-CardText>
|
||||
<react-mdl-Textfield
|
||||
floatingLabel={true}
|
||||
label="Description"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
required={true}
|
||||
rows={1}
|
||||
style={
|
||||
Object {
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
value="another's description"
|
||||
/>
|
||||
</react-mdl-CardText>
|
||||
<react-mdl-CardActions
|
||||
border={true}
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"display": "flex",
|
||||
"justifyContent": "space-between",
|
||||
}
|
||||
}
|
||||
>
|
||||
<span
|
||||
style={
|
||||
Object {
|
||||
"paddingRight": "24px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<react-mdl-Switch
|
||||
checked={false}
|
||||
disabled={false}
|
||||
onChange={[Function]}
|
||||
ripple={true}
|
||||
>
|
||||
Disabled
|
||||
</react-mdl-Switch>
|
||||
</span>
|
||||
<react-mdl-Button
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"flexShrink": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
Archive
|
||||
</react-mdl-Button>
|
||||
</react-mdl-CardActions>
|
||||
<hr />
|
||||
<react-mdl-Tabs
|
||||
activeTab={0}
|
||||
className="mdl-color--grey-100"
|
||||
ripple={true}
|
||||
tabBarProps={
|
||||
Object {
|
||||
"style": Object {
|
||||
"width": "100%",
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
<react-mdl-Tab
|
||||
onClick={[Function]}
|
||||
>
|
||||
Strategies
|
||||
</react-mdl-Tab>
|
||||
<react-mdl-Tab
|
||||
onClick={[Function]}
|
||||
>
|
||||
Metrics
|
||||
</react-mdl-Tab>
|
||||
<react-mdl-Tab
|
||||
onClick={[Function]}
|
||||
>
|
||||
History
|
||||
</react-mdl-Tab>
|
||||
</react-mdl-Tabs>
|
||||
<UpdateFeatureToggleComponent
|
||||
featureToggle={
|
||||
Object {
|
||||
"createdAt": "2018-02-04T20:27:52.127Z",
|
||||
"description": "another's description",
|
||||
"enabled": false,
|
||||
"name": "Another",
|
||||
"strategies": Array [
|
||||
Object {
|
||||
"name": "gradualRolloutRandom",
|
||||
"parameters": Object {
|
||||
"percentage": 50,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
features={
|
||||
Array [
|
||||
Object {
|
||||
"createdAt": "2018-02-04T20:27:52.127Z",
|
||||
"description": "another's description",
|
||||
"enabled": false,
|
||||
"name": "Another",
|
||||
"strategies": Array [
|
||||
Object {
|
||||
"name": "gradualRolloutRandom",
|
||||
"parameters": Object {
|
||||
"percentage": 50,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
history={Object {}}
|
||||
/>
|
||||
</react-mdl-Card>
|
||||
`;
|
@ -1,8 +1,5 @@
|
||||
import React from 'react';
|
||||
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 renderer from 'react-test-renderer';
|
||||
@ -25,22 +22,20 @@ test('renders correctly with one feature', () => {
|
||||
],
|
||||
createdAt: '2018-02-04T20:27:52.127Z',
|
||||
};
|
||||
const store = { user: new $Map({ profile: { permissions: [UPDATE_FEATURE] } }) };
|
||||
const featureMetrics = { lastHour: {}, lastMinute: {}, seenApps: {} };
|
||||
const settings = { sort: 'name' };
|
||||
const tree = renderer.create(
|
||||
<Provider store={createStore(state => state, store)}>
|
||||
<MemoryRouter>
|
||||
<Feature
|
||||
key={0}
|
||||
settings={settings}
|
||||
metricsLastHour={featureMetrics.lastHour[feature.name]}
|
||||
metricsLastMinute={featureMetrics.lastMinute[feature.name]}
|
||||
feature={feature}
|
||||
toggleFeature={jest.fn()}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
<MemoryRouter>
|
||||
<Feature
|
||||
key={0}
|
||||
settings={settings}
|
||||
metricsLastHour={featureMetrics.lastHour[feature.name]}
|
||||
metricsLastMinute={featureMetrics.lastMinute[feature.name]}
|
||||
feature={feature}
|
||||
toggleFeature={jest.fn()}
|
||||
hasPermission={permission => permission === UPDATE_FEATURE}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
@ -61,22 +56,20 @@ test('renders correctly with one feature without permission', () => {
|
||||
],
|
||||
createdAt: '2018-02-04T20:27:52.127Z',
|
||||
};
|
||||
const store = { user: new $Map({ profile: { permissions: [] } }) };
|
||||
const featureMetrics = { lastHour: {}, lastMinute: {}, seenApps: {} };
|
||||
const settings = { sort: 'name' };
|
||||
const tree = renderer.create(
|
||||
<Provider store={createStore(state => state, store)}>
|
||||
<MemoryRouter>
|
||||
<Feature
|
||||
key={0}
|
||||
settings={settings}
|
||||
metricsLastHour={featureMetrics.lastHour[feature.name]}
|
||||
metricsLastMinute={featureMetrics.lastMinute[feature.name]}
|
||||
feature={feature}
|
||||
toggleFeature={jest.fn()}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
<MemoryRouter>
|
||||
<Feature
|
||||
key={0}
|
||||
settings={settings}
|
||||
metricsLastHour={featureMetrics.lastHour[feature.name]}
|
||||
metricsLastMinute={featureMetrics.lastMinute[feature.name]}
|
||||
feature={feature}
|
||||
toggleFeature={jest.fn()}
|
||||
hasPermission={() => false}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
|
@ -0,0 +1,64 @@
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
import FeatureListComponent from './../list-component';
|
||||
import renderer from 'react-test-renderer';
|
||||
import { CREATE_FEATURE } from '../../../permissions';
|
||||
|
||||
jest.mock('react-mdl');
|
||||
jest.mock('../feature-list-item-component', () => ({
|
||||
__esModule: true,
|
||||
default: 'Feature',
|
||||
}));
|
||||
|
||||
test('renders correctly with one feature', () => {
|
||||
const features = [
|
||||
{
|
||||
name: 'Another',
|
||||
},
|
||||
];
|
||||
const featureMetrics = { lastHour: {}, lastMinute: {}, seenApps: {} };
|
||||
const settings = { sort: 'name' };
|
||||
const tree = renderer.create(
|
||||
<MemoryRouter>
|
||||
<FeatureListComponent
|
||||
updateSetting={jest.fn()}
|
||||
settings={settings}
|
||||
history={{}}
|
||||
featureMetrics={featureMetrics}
|
||||
features={features}
|
||||
toggleFeature={jest.fn()}
|
||||
fetchFeatureToggles={jest.fn()}
|
||||
hasPermission={permission => permission === CREATE_FEATURE}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('renders correctly with one feature without permissions', () => {
|
||||
const features = [
|
||||
{
|
||||
name: 'Another',
|
||||
},
|
||||
];
|
||||
const featureMetrics = { lastHour: {}, lastMinute: {}, seenApps: {} };
|
||||
const settings = { sort: 'name' };
|
||||
const tree = renderer.create(
|
||||
<MemoryRouter>
|
||||
<FeatureListComponent
|
||||
updateSetting={jest.fn()}
|
||||
settings={settings}
|
||||
history={{}}
|
||||
featureMetrics={featureMetrics}
|
||||
features={features}
|
||||
toggleFeature={jest.fn()}
|
||||
fetchFeatureToggles={jest.fn()}
|
||||
hasPermission={() => false}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
import ViewFeatureToggleComponent from './../view-component';
|
||||
import renderer from 'react-test-renderer';
|
||||
import { DELETE_FEATURE, UPDATE_FEATURE } from '../../../permissions';
|
||||
|
||||
jest.mock('react-mdl');
|
||||
jest.mock('../form/form-update-feature-container', () => ({
|
||||
__esModule: true,
|
||||
default: 'UpdateFeatureToggleComponent',
|
||||
}));
|
||||
|
||||
test('renders correctly with one feature', () => {
|
||||
const feature = {
|
||||
name: 'Another',
|
||||
description: "another's description",
|
||||
enabled: false,
|
||||
strategies: [
|
||||
{
|
||||
name: 'gradualRolloutRandom',
|
||||
parameters: {
|
||||
percentage: 50,
|
||||
},
|
||||
},
|
||||
],
|
||||
createdAt: '2018-02-04T20:27:52.127Z',
|
||||
};
|
||||
const tree = renderer.create(
|
||||
<MemoryRouter>
|
||||
<ViewFeatureToggleComponent
|
||||
activeTab={'strategies'}
|
||||
featureToggleName="another"
|
||||
features={[feature]}
|
||||
featureToggle={feature}
|
||||
fetchFeatureToggles={jest.fn()}
|
||||
history={{}}
|
||||
hasPermission={permission => [DELETE_FEATURE, UPDATE_FEATURE].indexOf(permission) !== -1}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Switch, Chip, ListItem, ListItemAction, Icon } from 'react-mdl';
|
||||
import Progress from './progress';
|
||||
import PermissionComponent from '../common/permission-container';
|
||||
import { UPDATE_FEATURE } from '../../permissions';
|
||||
import { calc, styles as commonStyles } from '../common';
|
||||
|
||||
@ -16,6 +15,7 @@ const Feature = ({
|
||||
metricsLastHour = { yes: 0, no: 0, isFallback: true },
|
||||
metricsLastMinute = { yes: 0, no: 0, isFallback: true },
|
||||
revive,
|
||||
hasPermission,
|
||||
}) => {
|
||||
const { name, description, enabled, strategies } = feature;
|
||||
const { showLastHour = false } = settings;
|
||||
@ -43,27 +43,17 @@ const Feature = ({
|
||||
<Progress strokeWidth={15} percentage={percent} isFallback={isStale} />
|
||||
</span>
|
||||
<span className={styles.listItemToggle}>
|
||||
<PermissionComponent
|
||||
permission={UPDATE_FEATURE}
|
||||
component={
|
||||
<Switch
|
||||
disabled={toggleFeature === undefined}
|
||||
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}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{hasPermission(UPDATE_FEATURE) ? (
|
||||
<Switch
|
||||
disabled={toggleFeature === undefined}
|
||||
title={`Toggle ${name}`}
|
||||
key="left-actions"
|
||||
onChange={() => toggleFeature(name)}
|
||||
checked={enabled}
|
||||
/>
|
||||
) : (
|
||||
<Switch disabled title={`Toggle ${name}`} key="left-actions" checked={enabled} />
|
||||
)}
|
||||
</span>
|
||||
<span className={['mdl-list__item-primary-content', styles.listItemLink].join(' ')}>
|
||||
<Link to={featureUrl} className={[commonStyles.listLink, commonStyles.truncate].join(' ')}>
|
||||
@ -75,16 +65,10 @@ const Feature = ({
|
||||
{strategyChips}
|
||||
{summaryChip}
|
||||
</span>
|
||||
{revive ? (
|
||||
<PermissionComponent
|
||||
permission={UPDATE_FEATURE}
|
||||
component={
|
||||
<ListItemAction onClick={() => revive(feature.name)}>
|
||||
<Icon name="undo" />
|
||||
</ListItemAction>
|
||||
}
|
||||
otherwise={<span />}
|
||||
/>
|
||||
{revive && hasPermission(UPDATE_FEATURE) ? (
|
||||
<ListItemAction onClick={() => revive(feature.name)}>
|
||||
<Icon name="undo" />
|
||||
</ListItemAction>
|
||||
) : (
|
||||
<span />
|
||||
)}
|
||||
@ -99,6 +83,7 @@ Feature.propTypes = {
|
||||
metricsLastHour: PropTypes.object,
|
||||
metricsLastMinute: PropTypes.object,
|
||||
revive: PropTypes.func,
|
||||
hasPermission: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Feature;
|
||||
|
@ -1,5 +1,32 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly when disabled 1`] = `
|
||||
<div>
|
||||
<p>
|
||||
featureName
|
||||
</p>
|
||||
<react-mdl-Chip
|
||||
style={
|
||||
Object {
|
||||
"marginRight": "3px",
|
||||
}
|
||||
}
|
||||
>
|
||||
item1
|
||||
</react-mdl-Chip>
|
||||
<react-mdl-Chip
|
||||
style={
|
||||
Object {
|
||||
"marginRight": "3px",
|
||||
}
|
||||
}
|
||||
>
|
||||
item2
|
||||
</react-mdl-Chip>
|
||||
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders strategy with empty list as param 1`] = `
|
||||
<div>
|
||||
<p>
|
||||
|
@ -79,3 +79,11 @@ it('spy onClose', () => {
|
||||
wrapper.find('react-mdl-Chip').simulate('close', closeMock);
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('renders correctly when disabled', () => {
|
||||
const list = ['item1', 'item2'];
|
||||
const name = 'featureName';
|
||||
const tree = renderer.create(<InputList list={list} name={name} setConfig={jest.fn()} disabled />);
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
@ -104,7 +104,12 @@ class StrategyConfigure extends React.Component {
|
||||
}
|
||||
return (
|
||||
<div key={name}>
|
||||
<StrategyInputList name={name} list={list} setConfig={this.setConfig} />
|
||||
<StrategyInputList
|
||||
name={name}
|
||||
list={list}
|
||||
disabled={!this.props.updateStrategy}
|
||||
setConfig={this.setConfig}
|
||||
/>
|
||||
{description && <p className={styles.helpText}>{description}</p>}
|
||||
</div>
|
||||
);
|
||||
|
@ -7,6 +7,7 @@ export default class InputList extends Component {
|
||||
name: PropTypes.string.isRequired,
|
||||
list: PropTypes.array.isRequired,
|
||||
setConfig: PropTypes.func.isRequired,
|
||||
disabled: PropTypes.bool,
|
||||
};
|
||||
|
||||
onBlur(e) {
|
||||
@ -49,35 +50,43 @@ export default class InputList extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { name, list } = this.props;
|
||||
const { name, list, disabled } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<p>{name}</p>
|
||||
{list.map((entryValue, index) => (
|
||||
<Chip key={index + entryValue} style={{ marginRight: '3px' }} onClose={() => this.onClose(index)}>
|
||||
<Chip
|
||||
key={index + entryValue}
|
||||
style={{ marginRight: '3px' }}
|
||||
onClose={disabled ? undefined : () => this.onClose(index)}
|
||||
>
|
||||
{entryValue}
|
||||
</Chip>
|
||||
))}
|
||||
|
||||
<div style={{ display: 'flex' }}>
|
||||
<Textfield
|
||||
name={`${name}_input`}
|
||||
style={{ width: '100%', flex: 1 }}
|
||||
floatingLabel
|
||||
label="Add list entry"
|
||||
onFocus={this.onFocus.bind(this)}
|
||||
onBlur={this.onBlur.bind(this)}
|
||||
ref={input => {
|
||||
this.textInput = input;
|
||||
}}
|
||||
/>
|
||||
<IconButton
|
||||
name="add"
|
||||
raised
|
||||
style={{ flex: 1, flexGrow: 0, margin: '20px 0 0 10px' }}
|
||||
onClick={this.setValue}
|
||||
/>
|
||||
</div>
|
||||
{disabled ? (
|
||||
''
|
||||
) : (
|
||||
<div style={{ display: 'flex' }}>
|
||||
<Textfield
|
||||
name={`${name}_input`}
|
||||
style={{ width: '100%', flex: 1 }}
|
||||
floatingLabel
|
||||
label="Add list entry"
|
||||
onFocus={this.onFocus.bind(this)}
|
||||
onBlur={this.onBlur.bind(this)}
|
||||
ref={input => {
|
||||
this.textInput = input;
|
||||
}}
|
||||
/>
|
||||
<IconButton
|
||||
name="add"
|
||||
raised
|
||||
style={{ flex: 1, flexGrow: 0, margin: '20px 0 0 10px' }}
|
||||
onClick={this.setValue}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import { Icon, FABButton, Textfield, Menu, MenuItem, Card, CardActions, List } f
|
||||
import { MenuItemWithIcon, DropdownButton, styles as commonStyles } from '../common';
|
||||
import styles from './feature.scss';
|
||||
import { CREATE_FEATURE } from '../../permissions';
|
||||
import PermissionComponent from '../common/permission-container';
|
||||
|
||||
export default class FeatureListComponent extends React.Component {
|
||||
static propTypes = {
|
||||
@ -21,6 +20,7 @@ export default class FeatureListComponent extends React.Component {
|
||||
toggleFeature: PropTypes.func,
|
||||
settings: PropTypes.object,
|
||||
history: PropTypes.object.isRequired,
|
||||
hasPermission: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
@ -48,7 +48,7 @@ export default class FeatureListComponent extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { features, toggleFeature, featureMetrics, settings, revive } = this.props;
|
||||
const { features, toggleFeature, featureMetrics, settings, revive, hasPermission } = this.props;
|
||||
features.forEach(e => {
|
||||
e.reviveName = e.name;
|
||||
});
|
||||
@ -64,16 +64,15 @@ export default class FeatureListComponent extends React.Component {
|
||||
label="Search"
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
<PermissionComponent
|
||||
permission={CREATE_FEATURE}
|
||||
component={
|
||||
<Link to="/features/create" className={styles.toolbarButton}>
|
||||
<FABButton accent title="Create feature toggle">
|
||||
<Icon name="add" />
|
||||
</FABButton>
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
{hasPermission(CREATE_FEATURE) ? (
|
||||
<Link to="/features/create" className={styles.toolbarButton}>
|
||||
<FABButton accent title="Create feature toggle">
|
||||
<Icon name="add" />
|
||||
</FABButton>
|
||||
</Link>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>
|
||||
<Card shadow={0} className={commonStyles.fullwidth} style={{ overflow: 'visible' }}>
|
||||
<CardActions>
|
||||
@ -126,6 +125,7 @@ export default class FeatureListComponent extends React.Component {
|
||||
feature={feature}
|
||||
toggleFeature={toggleFeature}
|
||||
revive={revive}
|
||||
hasPermission={hasPermission}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
|
@ -4,6 +4,7 @@ import { updateSettingForGroup } from '../../store/settings/actions';
|
||||
|
||||
import FeatureListComponent from './list-component';
|
||||
import { logoutUser } from '../../store/user/actions';
|
||||
import { hasPermission } from '../../permissions';
|
||||
|
||||
export const mapStateToPropsConfigurable = isFeature => state => {
|
||||
const featureMetrics = state.featureMetrics.toJS();
|
||||
@ -68,6 +69,7 @@ export const mapStateToPropsConfigurable = isFeature => state => {
|
||||
features,
|
||||
featureMetrics,
|
||||
settings,
|
||||
hasPermission: hasPermission.bind(null, state.user.get('profile')),
|
||||
};
|
||||
};
|
||||
const mapStateToProps = mapStateToPropsConfigurable(true);
|
||||
|
@ -9,7 +9,6 @@ import EditFeatureToggle from './form/form-update-feature-container';
|
||||
import ViewFeatureToggle from './form/form-view-feature-container';
|
||||
import { styles as commonStyles } from '../common';
|
||||
import { CREATE_FEATURE, DELETE_FEATURE, UPDATE_FEATURE } from '../../permissions';
|
||||
import PermissionComponent from '../common/permission-container';
|
||||
|
||||
const TABS = {
|
||||
strategies: 0,
|
||||
@ -36,6 +35,7 @@ export default class ViewFeatureToggleComponent extends React.Component {
|
||||
editFeatureToggle: PropTypes.func,
|
||||
featureToggle: PropTypes.object,
|
||||
history: PropTypes.object.isRequired,
|
||||
hasPermission: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
componentWillMount() {
|
||||
@ -49,24 +49,14 @@ export default class ViewFeatureToggleComponent extends React.Component {
|
||||
}
|
||||
|
||||
getTabContent(activeTab) {
|
||||
const { features, featureToggle, featureToggleName } = this.props;
|
||||
const { features, featureToggle, featureToggleName, hasPermission } = this.props;
|
||||
|
||||
if (TABS[activeTab] === TABS.history) {
|
||||
return <HistoryComponent toggleName={featureToggleName} />;
|
||||
} else if (TABS[activeTab] === TABS.strategies) {
|
||||
if (this.isFeatureView) {
|
||||
if (this.isFeatureView && hasPermission(UPDATE_FEATURE)) {
|
||||
return (
|
||||
<PermissionComponent
|
||||
permission={UPDATE_FEATURE}
|
||||
component={
|
||||
<EditFeatureToggle
|
||||
featureToggle={featureToggle}
|
||||
features={features}
|
||||
history={this.props.history}
|
||||
/>
|
||||
}
|
||||
otherwise={<ViewFeatureToggle featureToggle={featureToggle} />}
|
||||
/>
|
||||
<EditFeatureToggle featureToggle={featureToggle} features={features} history={this.props.history} />
|
||||
);
|
||||
}
|
||||
return <ViewFeatureToggle featureToggle={featureToggle} />;
|
||||
@ -90,6 +80,7 @@ export default class ViewFeatureToggleComponent extends React.Component {
|
||||
featureToggleName,
|
||||
toggleFeature,
|
||||
removeFeatureToggle,
|
||||
hasPermission,
|
||||
} = this.props;
|
||||
|
||||
if (!featureToggle) {
|
||||
@ -99,20 +90,18 @@ export default class ViewFeatureToggleComponent extends React.Component {
|
||||
return (
|
||||
<span>
|
||||
Could not find the toggle{' '}
|
||||
<PermissionComponent
|
||||
permission={CREATE_FEATURE}
|
||||
component={
|
||||
<Link
|
||||
to={{
|
||||
pathname: '/features/create',
|
||||
query: { name: featureToggleName },
|
||||
}}
|
||||
>
|
||||
{featureToggleName}
|
||||
</Link>
|
||||
}
|
||||
otherwise={featureToggleName}
|
||||
/>
|
||||
{hasPermission(CREATE_FEATURE) ? (
|
||||
<Link
|
||||
to={{
|
||||
pathname: '/features/create',
|
||||
query: { name: featureToggleName },
|
||||
}}
|
||||
>
|
||||
{featureToggleName}
|
||||
</Link>
|
||||
) : (
|
||||
featureToggleName
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@ -152,32 +141,16 @@ export default class ViewFeatureToggleComponent extends React.Component {
|
||||
<Card shadow={0} className={commonStyles.fullwidth} style={{ overflow: 'visible' }}>
|
||||
<CardTitle style={{ paddingTop: '24px', wordBreak: 'break-all' }}>{featureToggle.name}</CardTitle>
|
||||
<CardText>
|
||||
{this.isFeatureView ? (
|
||||
<PermissionComponent
|
||||
permission={UPDATE_FEATURE}
|
||||
component={
|
||||
<Textfield
|
||||
floatingLabel
|
||||
style={{ width: '100%' }}
|
||||
rows={1}
|
||||
label="Description"
|
||||
required
|
||||
value={featureToggle.description}
|
||||
onChange={v => setValue('description', v)}
|
||||
onBlur={updateFeatureToggle}
|
||||
/>
|
||||
}
|
||||
otherwise={
|
||||
<Textfield
|
||||
disabled
|
||||
floatingLabel
|
||||
style={{ width: '100%' }}
|
||||
rows={1}
|
||||
label="Description"
|
||||
required
|
||||
value={featureToggle.description}
|
||||
/>
|
||||
}
|
||||
{this.isFeatureView && hasPermission(UPDATE_FEATURE) ? (
|
||||
<Textfield
|
||||
floatingLabel
|
||||
style={{ width: '100%' }}
|
||||
rows={1}
|
||||
label="Description"
|
||||
required
|
||||
value={featureToggle.description}
|
||||
onChange={v => setValue('description', v)}
|
||||
onBlur={updateFeatureToggle}
|
||||
/>
|
||||
) : (
|
||||
<Textfield
|
||||
@ -201,49 +174,38 @@ export default class ViewFeatureToggleComponent extends React.Component {
|
||||
}}
|
||||
>
|
||||
<span style={{ paddingRight: '24px' }}>
|
||||
<PermissionComponent
|
||||
permission={UPDATE_FEATURE}
|
||||
component={
|
||||
<Switch
|
||||
disabled={!this.isFeatureView}
|
||||
ripple
|
||||
checked={featureToggle.enabled}
|
||||
onChange={() => toggleFeature(featureToggle.name)}
|
||||
>
|
||||
{featureToggle.enabled ? 'Enabled' : 'Disabled'}
|
||||
</Switch>
|
||||
}
|
||||
otherwise={
|
||||
<Switch
|
||||
disabled
|
||||
ripple
|
||||
checked={featureToggle.enabled}
|
||||
onChange={() => toggleFeature(featureToggle.name)}
|
||||
>
|
||||
{featureToggle.enabled ? 'Enabled' : 'Disabled'}
|
||||
</Switch>
|
||||
}
|
||||
/>
|
||||
{hasPermission(UPDATE_FEATURE) ? (
|
||||
<Switch
|
||||
disabled={!this.isFeatureView}
|
||||
ripple
|
||||
checked={featureToggle.enabled}
|
||||
onChange={() => toggleFeature(featureToggle.name)}
|
||||
>
|
||||
{featureToggle.enabled ? 'Enabled' : 'Disabled'}
|
||||
</Switch>
|
||||
) : (
|
||||
<Switch disabled ripple checked={featureToggle.enabled}>
|
||||
{featureToggle.enabled ? 'Enabled' : 'Disabled'}
|
||||
</Switch>
|
||||
)}
|
||||
</span>
|
||||
|
||||
{this.isFeatureView ? (
|
||||
<PermissionComponent
|
||||
permission={DELETE_FEATURE}
|
||||
component={
|
||||
<Button onClick={removeToggle} style={{ flexShrink: 0 }}>
|
||||
Archive
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
disabled={!hasPermission(DELETE_FEATURE)}
|
||||
onClick={removeToggle}
|
||||
style={{ flexShrink: 0 }}
|
||||
>
|
||||
Archive
|
||||
</Button>
|
||||
) : (
|
||||
<PermissionComponent
|
||||
permission={UPDATE_FEATURE}
|
||||
component={
|
||||
<Button onClick={reviveToggle} style={{ flexShrink: 0 }}>
|
||||
Revive
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
disabled={!hasPermission(UPDATE_FEATURE)}
|
||||
onClick={reviveToggle}
|
||||
style={{ flexShrink: 0 }}
|
||||
>
|
||||
Revive
|
||||
</Button>
|
||||
)}
|
||||
</CardActions>
|
||||
<hr />
|
||||
|
@ -8,12 +8,14 @@ import {
|
||||
} from './../../store/feature-actions';
|
||||
|
||||
import ViewToggleComponent from './view-component';
|
||||
import { hasPermission } from '../../permissions';
|
||||
|
||||
export default connect(
|
||||
(state, props) => ({
|
||||
features: state.features.toJS(),
|
||||
featureToggle: state.features.toJS().find(toggle => toggle.name === props.featureToggleName),
|
||||
activeTab: props.activeTab,
|
||||
hasPermission: hasPermission.bind(null, state.user.get('profile')),
|
||||
}),
|
||||
{
|
||||
fetchFeatureToggles,
|
||||
|
@ -0,0 +1,138 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly with one strategy 1`] = `
|
||||
<react-mdl-Grid
|
||||
className="mdl-color--white"
|
||||
>
|
||||
<react-mdl-Cell
|
||||
col={12}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"borderBottom": "1px solid #f1f1f1",
|
||||
"display": "flex",
|
||||
"marginBottom": "10px",
|
||||
"padding": "16px 20px ",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"flex": "2",
|
||||
}
|
||||
}
|
||||
>
|
||||
<h6
|
||||
style={
|
||||
Object {
|
||||
"margin": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
Strategies
|
||||
</h6>
|
||||
</div>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"flex": "1",
|
||||
"textAlign": "right",
|
||||
}
|
||||
}
|
||||
>
|
||||
<react-mdl-IconButton
|
||||
name="add"
|
||||
onClick={[Function]}
|
||||
raised={true}
|
||||
title="Add new strategy"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<react-mdl-List>
|
||||
<react-mdl-ListItem
|
||||
twoLine={true}
|
||||
>
|
||||
<react-mdl-ListItemContent
|
||||
icon="extension"
|
||||
subtitle="another's description"
|
||||
>
|
||||
<a
|
||||
href="/strategies/view/Another"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<strong>
|
||||
Another
|
||||
</strong>
|
||||
</a>
|
||||
</react-mdl-ListItemContent>
|
||||
<react-mdl-IconButton
|
||||
name="delete"
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</react-mdl-ListItem>
|
||||
</react-mdl-List>
|
||||
</react-mdl-Cell>
|
||||
</react-mdl-Grid>
|
||||
`;
|
||||
|
||||
exports[`renders correctly with one strategy without permissions 1`] = `
|
||||
<react-mdl-Grid
|
||||
className="mdl-color--white"
|
||||
>
|
||||
<react-mdl-Cell
|
||||
col={12}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"borderBottom": "1px solid #f1f1f1",
|
||||
"display": "flex",
|
||||
"marginBottom": "10px",
|
||||
"padding": "16px 20px ",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"flex": "2",
|
||||
}
|
||||
}
|
||||
>
|
||||
<h6
|
||||
style={
|
||||
Object {
|
||||
"margin": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
Strategies
|
||||
</h6>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<react-mdl-List>
|
||||
<react-mdl-ListItem
|
||||
twoLine={true}
|
||||
>
|
||||
<react-mdl-ListItemContent
|
||||
icon="extension"
|
||||
subtitle="another's description"
|
||||
>
|
||||
<a
|
||||
href="/strategies/view/Another"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<strong>
|
||||
Another
|
||||
</strong>
|
||||
</a>
|
||||
</react-mdl-ListItemContent>
|
||||
|
||||
</react-mdl-ListItem>
|
||||
</react-mdl-List>
|
||||
</react-mdl-Cell>
|
||||
</react-mdl-Grid>
|
||||
`;
|
@ -0,0 +1,167 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly with one strategy 1`] = `
|
||||
<react-mdl-Grid
|
||||
className="mdl-color--white"
|
||||
>
|
||||
<react-mdl-Cell
|
||||
col={12}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"borderBottom": "1px solid #f1f1f1",
|
||||
"display": "flex",
|
||||
"marginBottom": "10px",
|
||||
"padding": "16px 20px ",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"flex": "2",
|
||||
}
|
||||
}
|
||||
>
|
||||
<h6
|
||||
style={
|
||||
Object {
|
||||
"margin": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
Another
|
||||
</h6>
|
||||
<small>
|
||||
another's description
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<react-mdl-Tabs
|
||||
ripple={true}
|
||||
>
|
||||
<react-mdl-Tab
|
||||
onClick={[Function]}
|
||||
>
|
||||
Details
|
||||
</react-mdl-Tab>
|
||||
<react-mdl-Tab
|
||||
onClick={[Function]}
|
||||
>
|
||||
Edit
|
||||
</react-mdl-Tab>
|
||||
</react-mdl-Tabs>
|
||||
<section>
|
||||
<div
|
||||
className="content"
|
||||
>
|
||||
<div>
|
||||
<react-mdl-Grid>
|
||||
<react-mdl-Cell
|
||||
col={12}
|
||||
>
|
||||
<h6>
|
||||
Parameters
|
||||
</h6>
|
||||
<hr />
|
||||
<react-mdl-List>
|
||||
<react-mdl-ListItem
|
||||
title="Required"
|
||||
twoLine={true}
|
||||
>
|
||||
<react-mdl-ListItemContent
|
||||
avatar="add"
|
||||
subtitle="customList"
|
||||
>
|
||||
customParam
|
||||
|
||||
<small>
|
||||
(
|
||||
list
|
||||
)
|
||||
</small>
|
||||
</react-mdl-ListItemContent>
|
||||
</react-mdl-ListItem>
|
||||
</react-mdl-List>
|
||||
</react-mdl-Cell>
|
||||
<react-mdl-Cell
|
||||
col={6}
|
||||
tablet={12}
|
||||
>
|
||||
<h6>
|
||||
Applications using this strategy
|
||||
</h6>
|
||||
<hr />
|
||||
<react-mdl-List>
|
||||
<react-mdl-ListItem
|
||||
twoLine={true}
|
||||
>
|
||||
<span
|
||||
className="mdl-list__item-primary-content"
|
||||
style={
|
||||
Object {
|
||||
"minWidth": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<react-mdl-Icon
|
||||
className="mdl-list__item-avatar"
|
||||
name="apps"
|
||||
/>
|
||||
<a
|
||||
className="listLink truncate"
|
||||
href="/applications/appA"
|
||||
onClick={[Function]}
|
||||
>
|
||||
appA
|
||||
<span
|
||||
className="mdl-list__item-sub-title truncate"
|
||||
>
|
||||
app description
|
||||
</span>
|
||||
</a>
|
||||
</span>
|
||||
</react-mdl-ListItem>
|
||||
</react-mdl-List>
|
||||
</react-mdl-Cell>
|
||||
<react-mdl-Cell
|
||||
col={6}
|
||||
tablet={12}
|
||||
>
|
||||
<h6>
|
||||
Toggles using this strategy
|
||||
</h6>
|
||||
<hr />
|
||||
<react-mdl-List
|
||||
className="truncate"
|
||||
style={
|
||||
Object {
|
||||
"textAlign": "left",
|
||||
}
|
||||
}
|
||||
>
|
||||
<react-mdl-ListItem
|
||||
twoLine={true}
|
||||
>
|
||||
<react-mdl-ListItemContent
|
||||
avatar="toggle"
|
||||
subtitle="toggle description"
|
||||
>
|
||||
<a
|
||||
href="/features/view/toggleA"
|
||||
onClick={[Function]}
|
||||
>
|
||||
toggleA
|
||||
</a>
|
||||
</react-mdl-ListItemContent>
|
||||
</react-mdl-ListItem>
|
||||
</react-mdl-List>
|
||||
</react-mdl-Cell>
|
||||
</react-mdl-Grid>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</react-mdl-Cell>
|
||||
</react-mdl-Grid>
|
||||
`;
|
@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
import StrategiesListComponent from '../list-component';
|
||||
import renderer from 'react-test-renderer';
|
||||
import { CREATE_STRATEGY, DELETE_STRATEGY } from '../../../permissions';
|
||||
|
||||
jest.mock('react-mdl');
|
||||
|
||||
test('renders correctly with one strategy', () => {
|
||||
const strategy = {
|
||||
name: 'Another',
|
||||
description: "another's description",
|
||||
};
|
||||
const tree = renderer.create(
|
||||
<MemoryRouter>
|
||||
<StrategiesListComponent
|
||||
strategies={[strategy]}
|
||||
fetchStrategies={jest.fn()}
|
||||
removeStrategy={jest.fn()}
|
||||
history={{}}
|
||||
hasPermission={permission => [CREATE_STRATEGY, DELETE_STRATEGY].indexOf(permission) !== -1}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('renders correctly with one strategy without permissions', () => {
|
||||
const strategy = {
|
||||
name: 'Another',
|
||||
description: "another's description",
|
||||
};
|
||||
const tree = renderer.create(
|
||||
<MemoryRouter>
|
||||
<StrategiesListComponent
|
||||
strategies={[strategy]}
|
||||
fetchStrategies={jest.fn()}
|
||||
removeStrategy={jest.fn()}
|
||||
history={{}}
|
||||
hasPermission={() => false}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
@ -0,0 +1,54 @@
|
||||
import React from 'react';
|
||||
|
||||
import StrategyDetails from '../strategy-details-component';
|
||||
import renderer from 'react-test-renderer';
|
||||
import { UPDATE_STRATEGY } from '../../../permissions';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
jest.mock('react-mdl');
|
||||
|
||||
test('renders correctly with one strategy', () => {
|
||||
const strategy = {
|
||||
name: 'Another',
|
||||
description: "another's description",
|
||||
editable: true,
|
||||
parameters: [
|
||||
{
|
||||
name: 'customParam',
|
||||
type: 'list',
|
||||
description: 'customList',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
const applications = [
|
||||
{
|
||||
appName: 'appA',
|
||||
description: 'app description',
|
||||
},
|
||||
];
|
||||
const toggles = [
|
||||
{
|
||||
name: 'toggleA',
|
||||
description: 'toggle description',
|
||||
},
|
||||
];
|
||||
const tree = renderer.create(
|
||||
<MemoryRouter>
|
||||
<StrategyDetails
|
||||
strategyName={'Another'}
|
||||
strategy={strategy}
|
||||
activeTab="view"
|
||||
applications={applications}
|
||||
toggles={toggles}
|
||||
fetchStrategies={jest.fn()}
|
||||
fetchApplications={jest.fn()}
|
||||
fetchFeatureToggles={jest.fn()}
|
||||
history={{}}
|
||||
hasPermission={permission => [UPDATE_STRATEGY].indexOf(permission) !== -1}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
@ -5,7 +5,6 @@ import { Link } from 'react-router-dom';
|
||||
import { List, ListItem, ListItemContent, IconButton, Grid, Cell } from 'react-mdl';
|
||||
import { HeaderTitle } from '../common';
|
||||
import { CREATE_STRATEGY, DELETE_STRATEGY } from '../../permissions';
|
||||
import PermissionComponent from '../common/permission-container';
|
||||
|
||||
class StrategiesListComponent extends Component {
|
||||
static propTypes = {
|
||||
@ -13,6 +12,7 @@ class StrategiesListComponent extends Component {
|
||||
fetchStrategies: PropTypes.func.isRequired,
|
||||
removeStrategy: PropTypes.func.isRequired,
|
||||
history: PropTypes.object.isRequired,
|
||||
hasPermission: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
@ -20,7 +20,7 @@ class StrategiesListComponent extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { strategies, removeStrategy } = this.props;
|
||||
const { strategies, removeStrategy, hasPermission } = this.props;
|
||||
|
||||
return (
|
||||
<Grid className="mdl-color--white">
|
||||
@ -28,17 +28,16 @@ class StrategiesListComponent extends Component {
|
||||
<HeaderTitle
|
||||
title="Strategies"
|
||||
actions={
|
||||
<PermissionComponent
|
||||
permission={CREATE_STRATEGY}
|
||||
component={
|
||||
<IconButton
|
||||
raised
|
||||
name="add"
|
||||
onClick={() => this.props.history.push('/strategies/create')}
|
||||
title="Add new strategy"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
hasPermission(CREATE_STRATEGY) ? (
|
||||
<IconButton
|
||||
raised
|
||||
name="add"
|
||||
onClick={() => this.props.history.push('/strategies/create')}
|
||||
title="Add new strategy"
|
||||
/>
|
||||
) : (
|
||||
''
|
||||
)
|
||||
}
|
||||
/>
|
||||
<List>
|
||||
@ -50,15 +49,10 @@ class StrategiesListComponent extends Component {
|
||||
<strong>{strategy.name}</strong>
|
||||
</Link>
|
||||
</ListItemContent>
|
||||
{strategy.editable === false ? (
|
||||
{strategy.editable === false || !hasPermission(DELETE_STRATEGY) ? (
|
||||
''
|
||||
) : (
|
||||
<PermissionComponent
|
||||
permission={DELETE_STRATEGY}
|
||||
component={
|
||||
<IconButton name="delete" onClick={() => removeStrategy(strategy)} />
|
||||
}
|
||||
/>
|
||||
<IconButton name="delete" onClick={() => removeStrategy(strategy)} />
|
||||
)}
|
||||
</ListItem>
|
||||
))
|
||||
|
@ -1,12 +1,14 @@
|
||||
import { connect } from 'react-redux';
|
||||
import StrategiesListComponent from './list-component.jsx';
|
||||
import { fetchStrategies, removeStrategy } from './../../store/strategy/actions';
|
||||
import { hasPermission } from '../../permissions';
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const list = state.strategies.get('list').toArray();
|
||||
|
||||
return {
|
||||
strategies: list,
|
||||
hasPermission: hasPermission.bind(null, state.user.get('profile')),
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -5,7 +5,6 @@ import ShowStrategy from './show-strategy-component';
|
||||
import EditStrategy from './edit-container';
|
||||
import { HeaderTitle } from '../common';
|
||||
import { UPDATE_STRATEGY } from '../../permissions';
|
||||
import PermissionComponent from '../common/permission-container';
|
||||
|
||||
const TABS = {
|
||||
view: 0,
|
||||
@ -23,6 +22,7 @@ export default class StrategyDetails extends Component {
|
||||
fetchApplications: PropTypes.func.isRequired,
|
||||
fetchFeatureToggles: PropTypes.func.isRequired,
|
||||
history: PropTypes.object.isRequired,
|
||||
hasPermission: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
@ -68,18 +68,13 @@ export default class StrategyDetails extends Component {
|
||||
<Grid className="mdl-color--white">
|
||||
<Cell col={12}>
|
||||
<HeaderTitle title={strategy.name} subtitle={strategy.description} />
|
||||
{strategy.editable === false ? (
|
||||
{strategy.editable === false || !this.props.hasPermission(UPDATE_STRATEGY) ? (
|
||||
''
|
||||
) : (
|
||||
<PermissionComponent
|
||||
permission={UPDATE_STRATEGY}
|
||||
component={
|
||||
<Tabs activeTab={activeTabId} ripple>
|
||||
<Tab onClick={() => this.goToTab('view')}>Details</Tab>
|
||||
<Tab onClick={() => this.goToTab('edit')}>Edit</Tab>
|
||||
</Tabs>
|
||||
}
|
||||
/>
|
||||
<Tabs activeTab={activeTabId} ripple>
|
||||
<Tab onClick={() => this.goToTab('view')}>Details</Tab>
|
||||
<Tab onClick={() => this.goToTab('edit')}>Edit</Tab>
|
||||
</Tabs>
|
||||
)}
|
||||
|
||||
<section>
|
||||
|
@ -3,6 +3,7 @@ import ShowStrategy from './strategy-details-component';
|
||||
import { fetchStrategies } from './../../store/strategy/actions';
|
||||
import { fetchAll } from './../../store/application/actions';
|
||||
import { fetchFeatureToggles } from './../../store/feature-actions';
|
||||
import { hasPermission } from '../../permissions';
|
||||
|
||||
const mapStateToProps = (state, props) => {
|
||||
let strategy = state.strategies.get('list').find(n => n.name === props.strategyName);
|
||||
@ -17,6 +18,7 @@ const mapStateToProps = (state, props) => {
|
||||
applications: applications && applications.toJS(),
|
||||
toggles: toggles && toggles.toJS(),
|
||||
activeTab: props.activeTab,
|
||||
hasPermission: hasPermission.bind(null, state.user.get('profile')),
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -6,3 +6,10 @@ export const CREATE_STRATEGY = 'CREATE_STRATEGY';
|
||||
export const UPDATE_STRATEGY = 'UPDATE_STRATEGY';
|
||||
export const DELETE_STRATEGY = 'DELETE_STRATEGY';
|
||||
export const UPDATE_APPLICATION = 'UPDATE_APPLICATION';
|
||||
|
||||
export function hasPermission(user, permission) {
|
||||
return (
|
||||
user &&
|
||||
(!user.permissions || user.permissions.indexOf(ADMIN) !== -1 || user.permissions.indexOf(permission) !== -1)
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user