mirror of
https://github.com/Unleash/unleash.git
synced 2025-11-10 01:19:53 +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',
|
Cell: 'react-mdl-Cell',
|
||||||
Chip: 'react-mdl-Chip',
|
Chip: 'react-mdl-Chip',
|
||||||
Grid: 'react-mdl-Grid',
|
Grid: 'react-mdl-Grid',
|
||||||
|
Button: 'react-mdl-Button',
|
||||||
|
FABButton: 'react-mdl-FABButton',
|
||||||
Icon: 'react-mdl-Icon',
|
Icon: 'react-mdl-Icon',
|
||||||
IconButton: 'react-mdl-IconButton',
|
IconButton: 'react-mdl-IconButton',
|
||||||
List: 'react-mdl-List',
|
List: 'react-mdl-List',
|
||||||
|
|||||||
@ -5,3 +5,356 @@ exports[`renders correctly if no application 1`] = `
|
|||||||
indeterminate={true}
|
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 ClientApplications from '../application-edit-component';
|
||||||
import renderer from 'react-test-renderer';
|
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');
|
jest.mock('react-mdl');
|
||||||
|
|
||||||
test('renders correctly if no application', () => {
|
test('renders correctly if no application', () => {
|
||||||
const tree = renderer
|
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();
|
.toJSON();
|
||||||
|
|
||||||
expect(tree).toMatchSnapshot();
|
expect(tree).toMatchSnapshot();
|
||||||
|
|||||||
@ -23,7 +23,6 @@ import {
|
|||||||
import { IconLink, shorten, styles as commonStyles } from '../common';
|
import { IconLink, shorten, styles as commonStyles } from '../common';
|
||||||
import { formatFullDateTimeWithLocale } from '../common/util';
|
import { formatFullDateTimeWithLocale } from '../common/util';
|
||||||
import { CREATE_FEATURE, CREATE_STRATEGY, UPDATE_APPLICATION } from '../../permissions';
|
import { CREATE_FEATURE, CREATE_STRATEGY, UPDATE_APPLICATION } from '../../permissions';
|
||||||
import PermissionComponent from '../common/permission-container';
|
|
||||||
|
|
||||||
class StatefulTextfield extends Component {
|
class StatefulTextfield extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@ -63,6 +62,7 @@ class ClientApplications extends PureComponent {
|
|||||||
application: PropTypes.object,
|
application: PropTypes.object,
|
||||||
location: PropTypes.object,
|
location: PropTypes.object,
|
||||||
storeApplicationMetaData: PropTypes.func.isRequired,
|
storeApplicationMetaData: PropTypes.func.isRequired,
|
||||||
|
hasPermission: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@ -80,7 +80,7 @@ class ClientApplications extends PureComponent {
|
|||||||
if (!this.props.application) {
|
if (!this.props.application) {
|
||||||
return <ProgressBar indeterminate />;
|
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 { appName, instances, strategies, seenToggles, url, description, icon = 'apps', color } = application;
|
||||||
|
|
||||||
const content =
|
const content =
|
||||||
@ -93,26 +93,17 @@ class ClientApplications extends PureComponent {
|
|||||||
{seenToggles.map(
|
{seenToggles.map(
|
||||||
({ name, description, enabled, notFound }, i) =>
|
({ name, description, enabled, notFound }, i) =>
|
||||||
notFound ? (
|
notFound ? (
|
||||||
<PermissionComponent
|
|
||||||
permission={CREATE_FEATURE}
|
|
||||||
component={
|
|
||||||
<ListItem twoLine key={i}>
|
<ListItem twoLine key={i}>
|
||||||
<ListItemContent
|
{hasPermission(CREATE_FEATURE) ? (
|
||||||
icon={'report'}
|
<ListItemContent icon={'report'} subtitle={'Missing, want to create?'}>
|
||||||
subtitle={'Missing, want to create?'}
|
|
||||||
>
|
|
||||||
<Link to={`/features/create?name=${name}`}>{name}</Link>
|
<Link to={`/features/create?name=${name}`}>{name}</Link>
|
||||||
</ListItemContent>
|
</ListItemContent>
|
||||||
</ListItem>
|
) : (
|
||||||
}
|
|
||||||
otherwise={
|
|
||||||
<ListItem twoLine key={i}>
|
|
||||||
<ListItemContent icon={'report'} subtitle={'Missing'}>
|
<ListItemContent icon={'report'} subtitle={'Missing'}>
|
||||||
{name}
|
{name}
|
||||||
</ListItemContent>
|
</ListItemContent>
|
||||||
|
)}
|
||||||
</ListItem>
|
</ListItem>
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<ListItem twoLine key={i}>
|
<ListItem twoLine key={i}>
|
||||||
<ListItemContent
|
<ListItemContent
|
||||||
@ -137,26 +128,17 @@ class ClientApplications extends PureComponent {
|
|||||||
{strategies.map(
|
{strategies.map(
|
||||||
({ name, description, notFound }, i) =>
|
({ name, description, notFound }, i) =>
|
||||||
notFound ? (
|
notFound ? (
|
||||||
<PermissionComponent
|
|
||||||
permission={CREATE_STRATEGY}
|
|
||||||
component={
|
|
||||||
<ListItem twoLine key={`${name}-${i}`}>
|
<ListItem twoLine key={`${name}-${i}`}>
|
||||||
<ListItemContent
|
{hasPermission(CREATE_STRATEGY) ? (
|
||||||
icon={'report'}
|
<ListItemContent icon={'report'} subtitle={'Missing, want to create?'}>
|
||||||
subtitle={'Missing, want to create?'}
|
|
||||||
>
|
|
||||||
<Link to={`/strategies/create?name=${name}`}>{name}</Link>
|
<Link to={`/strategies/create?name=${name}`}>{name}</Link>
|
||||||
</ListItemContent>
|
</ListItemContent>
|
||||||
</ListItem>
|
) : (
|
||||||
}
|
|
||||||
otherwise={
|
|
||||||
<ListItem twoLine key={`${name}-${i}`}>
|
|
||||||
<ListItemContent icon={'report'} subtitle={'Missing'}>
|
<ListItemContent icon={'report'} subtitle={'Missing'}>
|
||||||
{name}
|
{name}
|
||||||
</ListItemContent>
|
</ListItemContent>
|
||||||
|
)}
|
||||||
</ListItem>
|
</ListItem>
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<ListItem twoLine key={`${name}-${i}`}>
|
<ListItem twoLine key={`${name}-${i}`}>
|
||||||
<ListItemContent icon={'extension'} subtitle={shorten(description, 60)}>
|
<ListItemContent icon={'extension'} subtitle={shorten(description, 60)}>
|
||||||
@ -235,9 +217,7 @@ class ClientApplications extends PureComponent {
|
|||||||
</CardMenu>
|
</CardMenu>
|
||||||
)}
|
)}
|
||||||
<hr />
|
<hr />
|
||||||
<PermissionComponent
|
{hasPermission(UPDATE_APPLICATION) ? (
|
||||||
permission={UPDATE_APPLICATION}
|
|
||||||
component={
|
|
||||||
<Tabs
|
<Tabs
|
||||||
activeTab={this.state.activeTab}
|
activeTab={this.state.activeTab}
|
||||||
onChange={tabId => this.setState({ activeTab: tabId })}
|
onChange={tabId => this.setState({ activeTab: tabId })}
|
||||||
@ -248,8 +228,9 @@ class ClientApplications extends PureComponent {
|
|||||||
<Tab>Details</Tab>
|
<Tab>Details</Tab>
|
||||||
<Tab>Edit</Tab>
|
<Tab>Edit</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
}
|
) : (
|
||||||
/>
|
''
|
||||||
|
)}
|
||||||
|
|
||||||
{content}
|
{content}
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import ApplicationEdit from './application-edit-component';
|
import ApplicationEdit from './application-edit-component';
|
||||||
import { fetchApplication, storeApplicationMetaData } from './../../store/application/actions';
|
import { fetchApplication, storeApplicationMetaData } from './../../store/application/actions';
|
||||||
|
import { hasPermission } from '../../permissions';
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => {
|
const mapStateToProps = (state, props) => {
|
||||||
let application = state.applications.getIn(['apps', props.appName]);
|
let application = state.applications.getIn(['apps', props.appName]);
|
||||||
@ -11,6 +12,7 @@ const mapStateToProps = (state, props) => {
|
|||||||
return {
|
return {
|
||||||
application,
|
application,
|
||||||
location,
|
location,
|
||||||
|
hasPermission: hasPermission.bind(null, state.user.get('profile')),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { fetchArchive, revive } from './../../store/archive-actions';
|
import { fetchArchive, revive } from './../../store/archive-actions';
|
||||||
import ViewToggleComponent from './../feature/view-component';
|
import ViewToggleComponent from './../feature/view-component';
|
||||||
|
import { hasPermission } from '../../permissions';
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
(state, props) => ({
|
(state, props) => ({
|
||||||
@ -10,6 +11,7 @@ export default connect(
|
|||||||
.toArray()
|
.toArray()
|
||||||
.find(toggle => toggle.name === props.featureToggleName),
|
.find(toggle => toggle.name === props.featureToggleName),
|
||||||
activeTab: props.activeTab,
|
activeTab: props.activeTab,
|
||||||
|
hasPermission: hasPermission.bind(null, state.user.get('profile')),
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
fetchArchive,
|
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
|
<react-mdl-Switch
|
||||||
checked={false}
|
checked={false}
|
||||||
disabled={true}
|
disabled={true}
|
||||||
onChange={[Function]}
|
|
||||||
title="Toggle Another"
|
title="Toggle Another"
|
||||||
/>
|
/>
|
||||||
</span>
|
</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 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';
|
||||||
@ -25,11 +22,9 @@ 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(
|
||||||
<Provider store={createStore(state => state, store)}>
|
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<Feature
|
<Feature
|
||||||
key={0}
|
key={0}
|
||||||
@ -38,9 +33,9 @@ test('renders correctly with one feature', () => {
|
|||||||
metricsLastMinute={featureMetrics.lastMinute[feature.name]}
|
metricsLastMinute={featureMetrics.lastMinute[feature.name]}
|
||||||
feature={feature}
|
feature={feature}
|
||||||
toggleFeature={jest.fn()}
|
toggleFeature={jest.fn()}
|
||||||
|
hasPermission={permission => permission === UPDATE_FEATURE}
|
||||||
/>
|
/>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
</Provider>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(tree).toMatchSnapshot();
|
expect(tree).toMatchSnapshot();
|
||||||
@ -61,11 +56,9 @@ test('renders correctly with one feature without permission', () => {
|
|||||||
],
|
],
|
||||||
createdAt: '2018-02-04T20:27:52.127Z',
|
createdAt: '2018-02-04T20:27:52.127Z',
|
||||||
};
|
};
|
||||||
const store = { user: new $Map({ profile: { permissions: [] } }) };
|
|
||||||
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(
|
||||||
<Provider store={createStore(state => state, store)}>
|
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<Feature
|
<Feature
|
||||||
key={0}
|
key={0}
|
||||||
@ -74,9 +67,9 @@ test('renders correctly with one feature without permission', () => {
|
|||||||
metricsLastMinute={featureMetrics.lastMinute[feature.name]}
|
metricsLastMinute={featureMetrics.lastMinute[feature.name]}
|
||||||
feature={feature}
|
feature={feature}
|
||||||
toggleFeature={jest.fn()}
|
toggleFeature={jest.fn()}
|
||||||
|
hasPermission={() => false}
|
||||||
/>
|
/>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
</Provider>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(tree).toMatchSnapshot();
|
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 { 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 { UPDATE_FEATURE } from '../../permissions';
|
||||||
import { calc, styles as commonStyles } from '../common';
|
import { calc, styles as commonStyles } from '../common';
|
||||||
|
|
||||||
@ -16,6 +15,7 @@ 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,
|
||||||
|
hasPermission,
|
||||||
}) => {
|
}) => {
|
||||||
const { name, description, enabled, strategies } = feature;
|
const { name, description, enabled, strategies } = feature;
|
||||||
const { showLastHour = false } = settings;
|
const { showLastHour = false } = settings;
|
||||||
@ -43,9 +43,7 @@ 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}>
|
||||||
<PermissionComponent
|
{hasPermission(UPDATE_FEATURE) ? (
|
||||||
permission={UPDATE_FEATURE}
|
|
||||||
component={
|
|
||||||
<Switch
|
<Switch
|
||||||
disabled={toggleFeature === undefined}
|
disabled={toggleFeature === undefined}
|
||||||
title={`Toggle ${name}`}
|
title={`Toggle ${name}`}
|
||||||
@ -53,17 +51,9 @@ const Feature = ({
|
|||||||
onChange={() => toggleFeature(name)}
|
onChange={() => toggleFeature(name)}
|
||||||
checked={enabled}
|
checked={enabled}
|
||||||
/>
|
/>
|
||||||
}
|
) : (
|
||||||
otherwise={
|
<Switch disabled title={`Toggle ${name}`} key="left-actions" checked={enabled} />
|
||||||
<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(' ')}>
|
||||||
<Link to={featureUrl} className={[commonStyles.listLink, commonStyles.truncate].join(' ')}>
|
<Link to={featureUrl} className={[commonStyles.listLink, commonStyles.truncate].join(' ')}>
|
||||||
@ -75,16 +65,10 @@ const Feature = ({
|
|||||||
{strategyChips}
|
{strategyChips}
|
||||||
{summaryChip}
|
{summaryChip}
|
||||||
</span>
|
</span>
|
||||||
{revive ? (
|
{revive && hasPermission(UPDATE_FEATURE) ? (
|
||||||
<PermissionComponent
|
|
||||||
permission={UPDATE_FEATURE}
|
|
||||||
component={
|
|
||||||
<ListItemAction onClick={() => revive(feature.name)}>
|
<ListItemAction onClick={() => revive(feature.name)}>
|
||||||
<Icon name="undo" />
|
<Icon name="undo" />
|
||||||
</ListItemAction>
|
</ListItemAction>
|
||||||
}
|
|
||||||
otherwise={<span />}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<span />
|
<span />
|
||||||
)}
|
)}
|
||||||
@ -99,6 +83,7 @@ Feature.propTypes = {
|
|||||||
metricsLastHour: PropTypes.object,
|
metricsLastHour: PropTypes.object,
|
||||||
metricsLastMinute: PropTypes.object,
|
metricsLastMinute: PropTypes.object,
|
||||||
revive: PropTypes.func,
|
revive: PropTypes.func,
|
||||||
|
hasPermission: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Feature;
|
export default Feature;
|
||||||
|
|||||||
@ -1,5 +1,32 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// 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`] = `
|
exports[`renders strategy with empty list as param 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@ -79,3 +79,11 @@ it('spy onClose', () => {
|
|||||||
wrapper.find('react-mdl-Chip').simulate('close', closeMock);
|
wrapper.find('react-mdl-Chip').simulate('close', closeMock);
|
||||||
expect(onClose).toHaveBeenCalled();
|
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 (
|
return (
|
||||||
<div key={name}>
|
<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>}
|
{description && <p className={styles.helpText}>{description}</p>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -7,6 +7,7 @@ export default class InputList extends Component {
|
|||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
list: PropTypes.array.isRequired,
|
list: PropTypes.array.isRequired,
|
||||||
setConfig: PropTypes.func.isRequired,
|
setConfig: PropTypes.func.isRequired,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
onBlur(e) {
|
onBlur(e) {
|
||||||
@ -49,16 +50,23 @@ export default class InputList extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { name, list } = this.props;
|
const { name, list, disabled } = this.props;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>{name}</p>
|
<p>{name}</p>
|
||||||
{list.map((entryValue, index) => (
|
{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}
|
{entryValue}
|
||||||
</Chip>
|
</Chip>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
{disabled ? (
|
||||||
|
''
|
||||||
|
) : (
|
||||||
<div style={{ display: 'flex' }}>
|
<div style={{ display: 'flex' }}>
|
||||||
<Textfield
|
<Textfield
|
||||||
name={`${name}_input`}
|
name={`${name}_input`}
|
||||||
@ -78,6 +86,7 @@ export default class InputList extends Component {
|
|||||||
onClick={this.setValue}
|
onClick={this.setValue}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</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 { MenuItemWithIcon, DropdownButton, styles as commonStyles } from '../common';
|
||||||
import styles from './feature.scss';
|
import styles from './feature.scss';
|
||||||
import { CREATE_FEATURE } from '../../permissions';
|
import { CREATE_FEATURE } from '../../permissions';
|
||||||
import PermissionComponent from '../common/permission-container';
|
|
||||||
|
|
||||||
export default class FeatureListComponent extends React.Component {
|
export default class FeatureListComponent extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@ -21,6 +20,7 @@ export default class FeatureListComponent extends React.Component {
|
|||||||
toggleFeature: PropTypes.func,
|
toggleFeature: PropTypes.func,
|
||||||
settings: PropTypes.object,
|
settings: PropTypes.object,
|
||||||
history: PropTypes.object.isRequired,
|
history: PropTypes.object.isRequired,
|
||||||
|
hasPermission: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -48,7 +48,7 @@ export default class FeatureListComponent extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { features, toggleFeature, featureMetrics, settings, revive } = this.props;
|
const { features, toggleFeature, featureMetrics, settings, revive, hasPermission } = this.props;
|
||||||
features.forEach(e => {
|
features.forEach(e => {
|
||||||
e.reviveName = e.name;
|
e.reviveName = e.name;
|
||||||
});
|
});
|
||||||
@ -64,16 +64,15 @@ export default class FeatureListComponent extends React.Component {
|
|||||||
label="Search"
|
label="Search"
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
/>
|
/>
|
||||||
<PermissionComponent
|
{hasPermission(CREATE_FEATURE) ? (
|
||||||
permission={CREATE_FEATURE}
|
|
||||||
component={
|
|
||||||
<Link to="/features/create" className={styles.toolbarButton}>
|
<Link to="/features/create" className={styles.toolbarButton}>
|
||||||
<FABButton accent title="Create feature toggle">
|
<FABButton accent title="Create feature toggle">
|
||||||
<Icon name="add" />
|
<Icon name="add" />
|
||||||
</FABButton>
|
</FABButton>
|
||||||
</Link>
|
</Link>
|
||||||
}
|
) : (
|
||||||
/>
|
''
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Card shadow={0} className={commonStyles.fullwidth} style={{ overflow: 'visible' }}>
|
<Card shadow={0} className={commonStyles.fullwidth} style={{ overflow: 'visible' }}>
|
||||||
<CardActions>
|
<CardActions>
|
||||||
@ -126,6 +125,7 @@ export default class FeatureListComponent extends React.Component {
|
|||||||
feature={feature}
|
feature={feature}
|
||||||
toggleFeature={toggleFeature}
|
toggleFeature={toggleFeature}
|
||||||
revive={revive}
|
revive={revive}
|
||||||
|
hasPermission={hasPermission}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</List>
|
</List>
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { updateSettingForGroup } from '../../store/settings/actions';
|
|||||||
|
|
||||||
import FeatureListComponent from './list-component';
|
import FeatureListComponent from './list-component';
|
||||||
import { logoutUser } from '../../store/user/actions';
|
import { logoutUser } from '../../store/user/actions';
|
||||||
|
import { hasPermission } from '../../permissions';
|
||||||
|
|
||||||
export const mapStateToPropsConfigurable = isFeature => state => {
|
export const mapStateToPropsConfigurable = isFeature => state => {
|
||||||
const featureMetrics = state.featureMetrics.toJS();
|
const featureMetrics = state.featureMetrics.toJS();
|
||||||
@ -68,6 +69,7 @@ export const mapStateToPropsConfigurable = isFeature => state => {
|
|||||||
features,
|
features,
|
||||||
featureMetrics,
|
featureMetrics,
|
||||||
settings,
|
settings,
|
||||||
|
hasPermission: hasPermission.bind(null, state.user.get('profile')),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
const mapStateToProps = mapStateToPropsConfigurable(true);
|
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 ViewFeatureToggle from './form/form-view-feature-container';
|
||||||
import { styles as commonStyles } from '../common';
|
import { styles as commonStyles } from '../common';
|
||||||
import { CREATE_FEATURE, DELETE_FEATURE, UPDATE_FEATURE } from '../../permissions';
|
import { CREATE_FEATURE, DELETE_FEATURE, UPDATE_FEATURE } from '../../permissions';
|
||||||
import PermissionComponent from '../common/permission-container';
|
|
||||||
|
|
||||||
const TABS = {
|
const TABS = {
|
||||||
strategies: 0,
|
strategies: 0,
|
||||||
@ -36,6 +35,7 @@ export default class ViewFeatureToggleComponent extends React.Component {
|
|||||||
editFeatureToggle: PropTypes.func,
|
editFeatureToggle: PropTypes.func,
|
||||||
featureToggle: PropTypes.object,
|
featureToggle: PropTypes.object,
|
||||||
history: PropTypes.object.isRequired,
|
history: PropTypes.object.isRequired,
|
||||||
|
hasPermission: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
@ -49,24 +49,14 @@ export default class ViewFeatureToggleComponent extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getTabContent(activeTab) {
|
getTabContent(activeTab) {
|
||||||
const { features, featureToggle, featureToggleName } = this.props;
|
const { features, featureToggle, featureToggleName, hasPermission } = this.props;
|
||||||
|
|
||||||
if (TABS[activeTab] === TABS.history) {
|
if (TABS[activeTab] === TABS.history) {
|
||||||
return <HistoryComponent toggleName={featureToggleName} />;
|
return <HistoryComponent toggleName={featureToggleName} />;
|
||||||
} else if (TABS[activeTab] === TABS.strategies) {
|
} else if (TABS[activeTab] === TABS.strategies) {
|
||||||
if (this.isFeatureView) {
|
if (this.isFeatureView && hasPermission(UPDATE_FEATURE)) {
|
||||||
return (
|
return (
|
||||||
<PermissionComponent
|
<EditFeatureToggle featureToggle={featureToggle} features={features} history={this.props.history} />
|
||||||
permission={UPDATE_FEATURE}
|
|
||||||
component={
|
|
||||||
<EditFeatureToggle
|
|
||||||
featureToggle={featureToggle}
|
|
||||||
features={features}
|
|
||||||
history={this.props.history}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
otherwise={<ViewFeatureToggle featureToggle={featureToggle} />}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return <ViewFeatureToggle featureToggle={featureToggle} />;
|
return <ViewFeatureToggle featureToggle={featureToggle} />;
|
||||||
@ -90,6 +80,7 @@ export default class ViewFeatureToggleComponent extends React.Component {
|
|||||||
featureToggleName,
|
featureToggleName,
|
||||||
toggleFeature,
|
toggleFeature,
|
||||||
removeFeatureToggle,
|
removeFeatureToggle,
|
||||||
|
hasPermission,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
if (!featureToggle) {
|
if (!featureToggle) {
|
||||||
@ -99,9 +90,7 @@ export default class ViewFeatureToggleComponent extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
Could not find the toggle{' '}
|
Could not find the toggle{' '}
|
||||||
<PermissionComponent
|
{hasPermission(CREATE_FEATURE) ? (
|
||||||
permission={CREATE_FEATURE}
|
|
||||||
component={
|
|
||||||
<Link
|
<Link
|
||||||
to={{
|
to={{
|
||||||
pathname: '/features/create',
|
pathname: '/features/create',
|
||||||
@ -110,9 +99,9 @@ export default class ViewFeatureToggleComponent extends React.Component {
|
|||||||
>
|
>
|
||||||
{featureToggleName}
|
{featureToggleName}
|
||||||
</Link>
|
</Link>
|
||||||
}
|
) : (
|
||||||
otherwise={featureToggleName}
|
featureToggleName
|
||||||
/>
|
)}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -152,10 +141,7 @@ export default class ViewFeatureToggleComponent extends React.Component {
|
|||||||
<Card shadow={0} className={commonStyles.fullwidth} style={{ overflow: 'visible' }}>
|
<Card shadow={0} className={commonStyles.fullwidth} style={{ overflow: 'visible' }}>
|
||||||
<CardTitle style={{ paddingTop: '24px', wordBreak: 'break-all' }}>{featureToggle.name}</CardTitle>
|
<CardTitle style={{ paddingTop: '24px', wordBreak: 'break-all' }}>{featureToggle.name}</CardTitle>
|
||||||
<CardText>
|
<CardText>
|
||||||
{this.isFeatureView ? (
|
{this.isFeatureView && hasPermission(UPDATE_FEATURE) ? (
|
||||||
<PermissionComponent
|
|
||||||
permission={UPDATE_FEATURE}
|
|
||||||
component={
|
|
||||||
<Textfield
|
<Textfield
|
||||||
floatingLabel
|
floatingLabel
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
@ -166,19 +152,6 @@ export default class ViewFeatureToggleComponent extends React.Component {
|
|||||||
onChange={v => setValue('description', v)}
|
onChange={v => setValue('description', v)}
|
||||||
onBlur={updateFeatureToggle}
|
onBlur={updateFeatureToggle}
|
||||||
/>
|
/>
|
||||||
}
|
|
||||||
otherwise={
|
|
||||||
<Textfield
|
|
||||||
disabled
|
|
||||||
floatingLabel
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
rows={1}
|
|
||||||
label="Description"
|
|
||||||
required
|
|
||||||
value={featureToggle.description}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<Textfield
|
<Textfield
|
||||||
disabled
|
disabled
|
||||||
@ -201,9 +174,7 @@ export default class ViewFeatureToggleComponent extends React.Component {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span style={{ paddingRight: '24px' }}>
|
<span style={{ paddingRight: '24px' }}>
|
||||||
<PermissionComponent
|
{hasPermission(UPDATE_FEATURE) ? (
|
||||||
permission={UPDATE_FEATURE}
|
|
||||||
component={
|
|
||||||
<Switch
|
<Switch
|
||||||
disabled={!this.isFeatureView}
|
disabled={!this.isFeatureView}
|
||||||
ripple
|
ripple
|
||||||
@ -212,38 +183,29 @@ export default class ViewFeatureToggleComponent extends React.Component {
|
|||||||
>
|
>
|
||||||
{featureToggle.enabled ? 'Enabled' : 'Disabled'}
|
{featureToggle.enabled ? 'Enabled' : 'Disabled'}
|
||||||
</Switch>
|
</Switch>
|
||||||
}
|
) : (
|
||||||
otherwise={
|
<Switch disabled ripple checked={featureToggle.enabled}>
|
||||||
<Switch
|
|
||||||
disabled
|
|
||||||
ripple
|
|
||||||
checked={featureToggle.enabled}
|
|
||||||
onChange={() => toggleFeature(featureToggle.name)}
|
|
||||||
>
|
|
||||||
{featureToggle.enabled ? 'Enabled' : 'Disabled'}
|
{featureToggle.enabled ? 'Enabled' : 'Disabled'}
|
||||||
</Switch>
|
</Switch>
|
||||||
}
|
)}
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{this.isFeatureView ? (
|
{this.isFeatureView ? (
|
||||||
<PermissionComponent
|
<Button
|
||||||
permission={DELETE_FEATURE}
|
disabled={!hasPermission(DELETE_FEATURE)}
|
||||||
component={
|
onClick={removeToggle}
|
||||||
<Button onClick={removeToggle} style={{ flexShrink: 0 }}>
|
style={{ flexShrink: 0 }}
|
||||||
|
>
|
||||||
Archive
|
Archive
|
||||||
</Button>
|
</Button>
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<PermissionComponent
|
<Button
|
||||||
permission={UPDATE_FEATURE}
|
disabled={!hasPermission(UPDATE_FEATURE)}
|
||||||
component={
|
onClick={reviveToggle}
|
||||||
<Button onClick={reviveToggle} style={{ flexShrink: 0 }}>
|
style={{ flexShrink: 0 }}
|
||||||
|
>
|
||||||
Revive
|
Revive
|
||||||
</Button>
|
</Button>
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</CardActions>
|
</CardActions>
|
||||||
<hr />
|
<hr />
|
||||||
|
|||||||
@ -8,12 +8,14 @@ import {
|
|||||||
} from './../../store/feature-actions';
|
} from './../../store/feature-actions';
|
||||||
|
|
||||||
import ViewToggleComponent from './view-component';
|
import ViewToggleComponent from './view-component';
|
||||||
|
import { hasPermission } from '../../permissions';
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
(state, props) => ({
|
(state, props) => ({
|
||||||
features: state.features.toJS(),
|
features: state.features.toJS(),
|
||||||
featureToggle: state.features.toJS().find(toggle => toggle.name === props.featureToggleName),
|
featureToggle: state.features.toJS().find(toggle => toggle.name === props.featureToggleName),
|
||||||
activeTab: props.activeTab,
|
activeTab: props.activeTab,
|
||||||
|
hasPermission: hasPermission.bind(null, state.user.get('profile')),
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
fetchFeatureToggles,
|
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 { List, ListItem, ListItemContent, IconButton, Grid, Cell } from 'react-mdl';
|
||||||
import { HeaderTitle } from '../common';
|
import { HeaderTitle } from '../common';
|
||||||
import { CREATE_STRATEGY, DELETE_STRATEGY } from '../../permissions';
|
import { CREATE_STRATEGY, DELETE_STRATEGY } from '../../permissions';
|
||||||
import PermissionComponent from '../common/permission-container';
|
|
||||||
|
|
||||||
class StrategiesListComponent extends Component {
|
class StrategiesListComponent extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@ -13,6 +12,7 @@ class StrategiesListComponent extends Component {
|
|||||||
fetchStrategies: PropTypes.func.isRequired,
|
fetchStrategies: PropTypes.func.isRequired,
|
||||||
removeStrategy: PropTypes.func.isRequired,
|
removeStrategy: PropTypes.func.isRequired,
|
||||||
history: PropTypes.object.isRequired,
|
history: PropTypes.object.isRequired,
|
||||||
|
hasPermission: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -20,7 +20,7 @@ class StrategiesListComponent extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { strategies, removeStrategy } = this.props;
|
const { strategies, removeStrategy, hasPermission } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid className="mdl-color--white">
|
<Grid className="mdl-color--white">
|
||||||
@ -28,17 +28,16 @@ class StrategiesListComponent extends Component {
|
|||||||
<HeaderTitle
|
<HeaderTitle
|
||||||
title="Strategies"
|
title="Strategies"
|
||||||
actions={
|
actions={
|
||||||
<PermissionComponent
|
hasPermission(CREATE_STRATEGY) ? (
|
||||||
permission={CREATE_STRATEGY}
|
|
||||||
component={
|
|
||||||
<IconButton
|
<IconButton
|
||||||
raised
|
raised
|
||||||
name="add"
|
name="add"
|
||||||
onClick={() => this.props.history.push('/strategies/create')}
|
onClick={() => this.props.history.push('/strategies/create')}
|
||||||
title="Add new strategy"
|
title="Add new strategy"
|
||||||
/>
|
/>
|
||||||
}
|
) : (
|
||||||
/>
|
''
|
||||||
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<List>
|
<List>
|
||||||
@ -50,15 +49,10 @@ class StrategiesListComponent extends Component {
|
|||||||
<strong>{strategy.name}</strong>
|
<strong>{strategy.name}</strong>
|
||||||
</Link>
|
</Link>
|
||||||
</ListItemContent>
|
</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>
|
</ListItem>
|
||||||
))
|
))
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import StrategiesListComponent from './list-component.jsx';
|
import StrategiesListComponent from './list-component.jsx';
|
||||||
import { fetchStrategies, removeStrategy } from './../../store/strategy/actions';
|
import { fetchStrategies, removeStrategy } from './../../store/strategy/actions';
|
||||||
|
import { hasPermission } from '../../permissions';
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => {
|
||||||
const list = state.strategies.get('list').toArray();
|
const list = state.strategies.get('list').toArray();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
strategies: list,
|
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 EditStrategy from './edit-container';
|
||||||
import { HeaderTitle } from '../common';
|
import { HeaderTitle } from '../common';
|
||||||
import { UPDATE_STRATEGY } from '../../permissions';
|
import { UPDATE_STRATEGY } from '../../permissions';
|
||||||
import PermissionComponent from '../common/permission-container';
|
|
||||||
|
|
||||||
const TABS = {
|
const TABS = {
|
||||||
view: 0,
|
view: 0,
|
||||||
@ -23,6 +22,7 @@ export default class StrategyDetails extends Component {
|
|||||||
fetchApplications: PropTypes.func.isRequired,
|
fetchApplications: PropTypes.func.isRequired,
|
||||||
fetchFeatureToggles: PropTypes.func.isRequired,
|
fetchFeatureToggles: PropTypes.func.isRequired,
|
||||||
history: PropTypes.object.isRequired,
|
history: PropTypes.object.isRequired,
|
||||||
|
hasPermission: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -68,18 +68,13 @@ export default class StrategyDetails extends Component {
|
|||||||
<Grid className="mdl-color--white">
|
<Grid className="mdl-color--white">
|
||||||
<Cell col={12}>
|
<Cell col={12}>
|
||||||
<HeaderTitle title={strategy.name} subtitle={strategy.description} />
|
<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>
|
<Tabs activeTab={activeTabId} ripple>
|
||||||
<Tab onClick={() => this.goToTab('view')}>Details</Tab>
|
<Tab onClick={() => this.goToTab('view')}>Details</Tab>
|
||||||
<Tab onClick={() => this.goToTab('edit')}>Edit</Tab>
|
<Tab onClick={() => this.goToTab('edit')}>Edit</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import ShowStrategy from './strategy-details-component';
|
|||||||
import { fetchStrategies } from './../../store/strategy/actions';
|
import { fetchStrategies } from './../../store/strategy/actions';
|
||||||
import { fetchAll } from './../../store/application/actions';
|
import { fetchAll } from './../../store/application/actions';
|
||||||
import { fetchFeatureToggles } from './../../store/feature-actions';
|
import { fetchFeatureToggles } from './../../store/feature-actions';
|
||||||
|
import { hasPermission } from '../../permissions';
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => {
|
const mapStateToProps = (state, props) => {
|
||||||
let strategy = state.strategies.get('list').find(n => n.name === props.strategyName);
|
let strategy = state.strategies.get('list').find(n => n.name === props.strategyName);
|
||||||
@ -17,6 +18,7 @@ const mapStateToProps = (state, props) => {
|
|||||||
applications: applications && applications.toJS(),
|
applications: applications && applications.toJS(),
|
||||||
toggles: toggles && toggles.toJS(),
|
toggles: toggles && toggles.toJS(),
|
||||||
activeTab: props.activeTab,
|
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 UPDATE_STRATEGY = 'UPDATE_STRATEGY';
|
||||||
export const DELETE_STRATEGY = 'DELETE_STRATEGY';
|
export const DELETE_STRATEGY = 'DELETE_STRATEGY';
|
||||||
export const UPDATE_APPLICATION = 'UPDATE_APPLICATION';
|
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