1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-10-27 11:02:16 +01:00

Use 120 as width in prettier (#90)

This commit is contained in:
Simen Bekkhus 2017-08-28 21:40:44 +02:00 committed by GitHub
parent b176d63f56
commit 8ef9def08c
69 changed files with 325 additions and 1212 deletions

View File

@ -5,6 +5,15 @@
"finn-prettier" "finn-prettier"
], ],
"rules": { "rules": {
"no-shadow": 0 "no-shadow": 0,
"prettier/prettier": [
2,
{
"tabWidth": 4,
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 120
}
]
} }
} }

View File

@ -10,7 +10,7 @@
"env": { "env": {
"browser": true, "browser": true,
"commonjs": true, "commonjs": true,
"es6": true "es6": true
}, },
"globals": { "globals": {
"process": false "process": false
@ -23,6 +23,15 @@
}, },
"rules": { "rules": {
"no-shadow": 0, "no-shadow": 0,
"react/sort-comp": 0 "react/sort-comp": 0,
"prettier/prettier": [
2,
{
"tabWidth": 4,
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 120
}
]
} }
} }

View File

@ -89,18 +89,10 @@ export default class App extends Component {
return ( return (
<span> <span>
{result.map((entry, index) => ( {result.map((entry, index) => (
<span <span key={entry.link + index} className={index > 0 ? 'mdl-layout--large-screen-only' : ''}>
key={entry.link + index}
className={
index > 0 ? 'mdl-layout--large-screen-only' : ''
}
>
{index > 0 ? ' ' : null} {index > 0 ? ' ' : null}
<Link <Link
className={[ className={[styles.headerTitleLink, 'mdl-color-text--primary-contrast'].join(' ')}
styles.headerTitleLink,
'mdl-color-text--primary-contrast',
].join(' ')}
to={entry.link} to={entry.link}
> >
{entry.name} {entry.name}
@ -113,21 +105,13 @@ export default class App extends Component {
render() { render() {
const shouldUpdateScroll = (prevRouterProps, { location }) => { const shouldUpdateScroll = (prevRouterProps, { location }) => {
if ( if (prevRouterProps && location.pathname !== prevRouterProps.location.pathname) {
prevRouterProps &&
location.pathname !== prevRouterProps.location.pathname
) {
return location.action === 'POP'; return location.action === 'POP';
} else { } else {
return [0, 0]; return [0, 0];
} }
}; };
const createListItem = ( const createListItem = (path, caption, icon, isDrawerNavigation = false) => {
path,
caption,
icon,
isDrawerNavigation = false
) => {
const linkColor = const linkColor =
isDrawerNavigation && this.context.router.isActive(path) isDrawerNavigation && this.context.router.isActive(path)
? 'mdl-color-text--black' ? 'mdl-color-text--black'
@ -137,20 +121,11 @@ export default class App extends Component {
? 'mdl-color-text--black' ? 'mdl-color-text--black'
: 'mdl-color-text--grey-600'; : 'mdl-color-text--grey-600';
return ( return (
<Link <Link to={path} className={isDrawerNavigation && [styles.navigationLink, linkColor].join(' ')}>
to={path}
className={
isDrawerNavigation &&
[styles.navigationLink, linkColor].join(' ')
}
>
{icon && ( {icon && (
<Icon <Icon
name={icon} name={icon}
className={ className={isDrawerNavigation && [styles.navigationIcon, iconColor].join(' ')}
isDrawerNavigation &&
[styles.navigationIcon, iconColor].join(' ')
}
/> />
)} )}
{caption} {caption}
@ -168,79 +143,32 @@ export default class App extends Component {
</Navigation> </Navigation>
</Header> </Header>
<Drawer className="mdl-color--white"> <Drawer className="mdl-color--white">
<span <span className={[styles.drawerTitle, 'mdl-layout-title'].join(' ')}>
className={[ <img src="public/logo.png" width="32" height="32" className={styles.drawerTitleLogo} />
styles.drawerTitle, <span className={styles.drawerTitleText}>Unleash</span>
'mdl-layout-title',
].join(' ')}
>
<img
src="public/logo.png"
width="32"
height="32"
className={styles.drawerTitleLogo}
/>
<span className={styles.drawerTitleText}>
Unleash
</span>
</span> </span>
<hr /> <hr />
<Navigation className={styles.navigation}> <Navigation className={styles.navigation}>
{createListItem( {createListItem('/features', 'Feature Toggles', 'list', true)}
'/features', {createListItem('/strategies', 'Strategies', 'extension', true)}
'Feature Toggles', {createListItem('/history', 'Event History', 'history', true)}
'list', {createListItem('/archive', 'Archived Toggles', 'archive', true)}
true {createListItem('/applications', 'Applications', 'apps', true)}
)}
{createListItem(
'/strategies',
'Strategies',
'extension',
true
)}
{createListItem(
'/history',
'Event History',
'history',
true
)}
{createListItem(
'/archive',
'Archived Toggles',
'archive',
true
)}
{createListItem(
'/applications',
'Applications',
'apps',
true
)}
</Navigation> </Navigation>
<hr /> <hr />
<Navigation className={styles.navigation}> <Navigation className={styles.navigation}>
<a <a
href="https://github.com/Unleash" href="https://github.com/Unleash"
target="_blank" target="_blank"
className={[ className={[styles.navigationLink, 'mdl-color-text--grey-900'].join(' ')}
styles.navigationLink,
'mdl-color-text--grey-900',
].join(' ')}
> >
<i <i
className={[ className={['material-icons', styles.navigationIcon, styles.iconGitHub].join(' ')}
'material-icons',
styles.navigationIcon,
styles.iconGitHub,
].join(' ')}
/>GitHub />GitHub
</a> </a>
</Navigation> </Navigation>
</Drawer> </Drawer>
<ScrollContainer <ScrollContainer scrollKey="container" shouldUpdateScroll={shouldUpdateScroll}>
scrollKey="container"
shouldUpdateScroll={shouldUpdateScroll}
>
<Content className="mdl-color--grey-50"> <Content className="mdl-color--grey-50">
<Grid noSpacing className={styles.content}> <Grid noSpacing className={styles.content}>
<Cell col={12}> <Cell col={12}>
@ -252,54 +180,27 @@ export default class App extends Component {
<FooterSection type="middle"> <FooterSection type="middle">
<FooterDropDownSection title="Menu"> <FooterDropDownSection title="Menu">
<FooterLinkList> <FooterLinkList>
{createListItem( {createListItem('/features', 'Feature Toggles')}
'/features', {createListItem('/strategies', 'Strategies')}
'Feature Toggles' {createListItem('/history', 'Event History')}
)} {createListItem('/archive', 'Archived Toggles')}
{createListItem( {createListItem('/applications', 'Applications')}
'/strategies',
'Strategies'
)}
{createListItem(
'/history',
'Event History'
)}
{createListItem(
'/archive',
'Archived Toggles'
)}
{createListItem(
'/applications',
'Applications'
)}
</FooterLinkList> </FooterLinkList>
</FooterDropDownSection> </FooterDropDownSection>
<FooterDropDownSection title="Clients"> <FooterDropDownSection title="Clients">
<FooterLinkList> <FooterLinkList>
<a href="https://github.com/Unleash/unleash-client-node/"> <a href="https://github.com/Unleash/unleash-client-node/">Node.js</a>
Node.js <a href="https://github.com/Unleash/unleash-client-java/">Java</a>
</a> <a href="https://github.com/Unleash/unleash-client-go/">Go</a>
<a href="https://github.com/Unleash/unleash-client-java/">
Java
</a>
<a href="https://github.com/Unleash/unleash-client-go/">
Go
</a>
</FooterLinkList> </FooterLinkList>
</FooterDropDownSection> </FooterDropDownSection>
</FooterSection> </FooterSection>
<FooterSection type="bottom" logo="Unleash"> <FooterSection type="bottom" logo="Unleash">
<FooterLinkList> <FooterLinkList>
<a <a href="https://github.com/Unleash/unleash/" target="_blank">
href="https://github.com/Unleash/unleash/"
target="_blank"
>
GitHub GitHub
</a> </a>
<a <a href="https://finn.no" target="_blank">
href="https://finn.no"
target="_blank"
>
<small>A product by</small> FINN.no <small>A product by</small> FINN.no
</a> </a>
</FooterLinkList> </FooterLinkList>

View File

@ -6,9 +6,7 @@ import renderer from 'react-test-renderer';
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()} />).toJSON();
.create(<ClientApplications fetchApplication={jest.fn()} />)
.toJSON();
expect(tree).toMatchSnapshot(); expect(tree).toMatchSnapshot();
}); });

View File

@ -61,16 +61,7 @@ class ClientApplications extends PureComponent {
return <ProgressBar indeterminate />; return <ProgressBar indeterminate />;
} }
const { application, storeApplicationMetaData } = this.props; const { application, storeApplicationMetaData } = this.props;
const { const { appName, instances, strategies, seenToggles, url, description, icon = 'apps', color } = application;
appName,
instances,
strategies,
seenToggles,
url,
description,
icon = 'apps',
color,
} = application;
const content = const content =
this.state.activeTab === 0 ? ( this.state.activeTab === 0 ? (
@ -83,17 +74,8 @@ class ClientApplications extends PureComponent {
({ name, description, enabled, notFound }, i) => ({ name, description, enabled, notFound }, i) =>
notFound ? ( notFound ? (
<ListItem twoLine key={i}> <ListItem twoLine key={i}>
<ListItemContent <ListItemContent icon={'report'} subtitle={'Missing, want to create?'}>
icon={'report'} <Link to={`/features/create?name=${name}`}>{name}</Link>
subtitle={
'Missing, want to create?'
}
>
<Link
to={`/features/create?name=${name}`}
>
{name}
</Link>
</ListItemContent> </ListItemContent>
</ListItem> </ListItem>
) : ( ) : (
@ -101,22 +83,12 @@ class ClientApplications extends PureComponent {
<ListItemContent <ListItemContent
icon={ icon={
<span> <span>
<Switch <Switch disabled checked={!!enabled} />
disabled
checked={!!enabled}
/>
</span> </span>
} }
subtitle={shorten( subtitle={shorten(description, 60)}
description,
60
)}
> >
<Link <Link to={`/features/view/${name}`}>{shorten(name, 50)}</Link>
to={`/features/view/${name}`}
>
{shorten(name, 50)}
</Link>
</ListItemContent> </ListItemContent>
</ListItem> </ListItem>
) )
@ -131,33 +103,14 @@ class ClientApplications extends PureComponent {
({ name, description, notFound }, i) => ({ name, description, notFound }, i) =>
notFound ? ( notFound ? (
<ListItem twoLine key={`${name}-${i}`}> <ListItem twoLine key={`${name}-${i}`}>
<ListItemContent <ListItemContent icon={'report'} subtitle={'Missing, want to create?'}>
icon={'report'} <Link to={`/strategies/create?name=${name}`}>{name}</Link>
subtitle={
'Missing, want to create?'
}
>
<Link
to={`/strategies/create?name=${name}`}
>
{name}
</Link>
</ListItemContent> </ListItemContent>
</ListItem> </ListItem>
) : ( ) : (
<ListItem twoLine key={`${name}-${i}`}> <ListItem twoLine key={`${name}-${i}`}>
<ListItemContent <ListItemContent icon={'extension'} subtitle={shorten(description, 60)}>
icon={'extension'} <Link to={`/strategies/view/${name}`}>{shorten(name, 50)}</Link>
subtitle={shorten(
description,
60
)}
>
<Link
to={`/strategies/view/${name}`}
>
{shorten(name, 50)}
</Link>
</ListItemContent> </ListItemContent>
</ListItem> </ListItem>
) )
@ -168,32 +121,20 @@ class ClientApplications extends PureComponent {
<h6>{instances.length} Instances registered</h6> <h6>{instances.length} Instances registered</h6>
<hr /> <hr />
<List> <List>
{instances.map( {instances.map(({ instanceId, clientIp, lastSeen, sdkVersion }, i) => (
( <ListItem key={i} twoLine>
{ <ListItemContent
instanceId, icon="timeline"
clientIp, subtitle={
lastSeen, <span>
sdkVersion, {clientIp} last seen at <small>{formatFullDateTime(lastSeen)}</small>
}, </span>
i }
) => ( >
<ListItem key={i} twoLine> {instanceId} {sdkVersion ? `(${sdkVersion})` : ''}
<ListItemContent </ListItemContent>
icon="timeline" </ListItem>
subtitle={ ))}
<span>
{clientIp} last seen at{' '}
<small>{formatFullDateTime(lastSeen)}</small>
</span>
}
>
{instanceId}{' '}
{sdkVersion ? `(${sdkVersion})` : ''}
</ListItemContent>
</ListItem>
)
)}
</List> </List>
</Cell> </Cell>
</Grid> </Grid>
@ -206,46 +147,26 @@ class ClientApplications extends PureComponent {
<StatefulTextfield <StatefulTextfield
value={url} value={url}
label="URL" label="URL"
onBlur={e => onBlur={e => storeApplicationMetaData(appName, 'url', e.target.value)}
storeApplicationMetaData(
appName,
'url',
e.target.value
)}
/> />
<br /> <br />
<StatefulTextfield <StatefulTextfield
value={description} value={description}
label="Description" label="Description"
rows={5} rows={5}
onBlur={e => onBlur={e => storeApplicationMetaData(appName, 'description', e.target.value)}
storeApplicationMetaData(
appName,
'description',
e.target.value
)}
/> />
</Cell> </Cell>
<Cell col={6} tablet={12}> <Cell col={6} tablet={12}>
<StatefulTextfield <StatefulTextfield
value={icon} value={icon}
label="Select icon" label="Select icon"
onBlur={e => onBlur={e => storeApplicationMetaData(appName, 'icon', e.target.value)}
storeApplicationMetaData(
appName,
'icon',
e.target.value
)}
/> />
<StatefulTextfield <StatefulTextfield
value={color} value={color}
label="Select color" label="Select color"
onBlur={e => onBlur={e => storeApplicationMetaData(appName, 'color', e.target.value)}
storeApplicationMetaData(
appName,
'color',
e.target.value
)}
/> />
</Cell> </Cell>
</Grid> </Grid>
@ -253,13 +174,7 @@ class ClientApplications extends PureComponent {
return ( return (
<Card shadow={0} className={commonStyles.fullwidth}> <Card shadow={0} className={commonStyles.fullwidth}>
<CardTitle <CardTitle style={{ paddingTop: '24px', paddingRight: '64px', wordBreak: 'break-all' }}>
style={{
paddingTop: '24px',
paddingRight: '64px',
wordBreak: 'break-all',
}}
>
<Icon name={icon} /> {appName} <Icon name={icon} /> {appName}
</CardTitle> </CardTitle>
{description && <CardText>{description}</CardText>} {description && <CardText>{description}</CardText>}

View File

@ -1,9 +1,6 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import ApplicationEdit from './application-edit-component'; import ApplicationEdit from './application-edit-component';
import { import { fetchApplication, storeApplicationMetaData } from '../../store/application/actions';
fetchApplication,
storeApplicationMetaData,
} from '../../store/application/actions';
const mapStateToProps = (state, props) => { const mapStateToProps = (state, props) => {
let application = state.applications.getIn(['apps', props.appName]); let application = state.applications.getIn(['apps', props.appName]);

View File

@ -2,9 +2,7 @@ import { connect } from 'react-redux';
import ApplicationList from './application-list-component'; import ApplicationList from './application-list-component';
import { fetchAll } from '../../store/application/actions'; import { fetchAll } from '../../store/application/actions';
const mapStateToProps = state => ({ const mapStateToProps = state => ({ applications: state.applications.get('list').toJS() });
applications: state.applications.get('list').toJS(),
});
const Container = connect(mapStateToProps, { fetchAll })(ApplicationList); const Container = connect(mapStateToProps, { fetchAll })(ApplicationList);

View File

@ -103,7 +103,6 @@ exports[`renders correctly with no archived toggles 1`] = `
/> />
<br /> <br />
No archived feature toggles, go see No archived feature toggles, go see
<a <a
onClick={[Function]} onClick={[Function]}
style={Object {}} style={Object {}}

View File

@ -16,8 +16,7 @@ const archive = [
}, },
{ {
name: 'adin-pay-platform-sch-payment', name: 'adin-pay-platform-sch-payment',
description: description: 'Enables use of schibsted payment from order-payment-management',
'Enables use of schibsted payment from order-payment-management',
enabled: true, enabled: true,
strategies: [{ name: 'default', parameters: {} }], strategies: [{ name: 'default', parameters: {} }],
createdAt: '2016-08-03T12:41:35.631Z', createdAt: '2016-08-03T12:41:35.631Z',
@ -26,17 +25,13 @@ const archive = [
]; ];
test('renders correctly with no archived toggles', () => { test('renders correctly with no archived toggles', () => {
const tree = renderer const tree = renderer.create(<ArchiveList fetchArchive={jest.fn()} archive={[]} />).toJSON();
.create(<ArchiveList fetchArchive={jest.fn()} archive={[]} />)
.toJSON();
expect(tree).toMatchSnapshot(); expect(tree).toMatchSnapshot();
}); });
test('renders correctly with archived toggles', () => { test('renders correctly with archived toggles', () => {
const tree = renderer const tree = renderer.create(<ArchiveList fetchArchive={jest.fn()} archive={archive} />).toJSON();
.create(<ArchiveList fetchArchive={jest.fn()} archive={archive} />)
.toJSON();
expect(tree).toMatchSnapshot(); expect(tree).toMatchSnapshot();
}); });

View File

@ -10,8 +10,6 @@ const mapStateToProps = state => {
}; };
}; };
const ArchiveListContainer = connect(mapStateToProps, { fetchArchive, revive })( const ArchiveListContainer = connect(mapStateToProps, { fetchArchive, revive })(ListComponent);
ListComponent
);
export default ArchiveListContainer; export default ArchiveListContainer;

View File

@ -17,20 +17,12 @@ class ArchiveList extends Component {
<Card shadow={0} className={commonStyles.fullwidth}> <Card shadow={0} className={commonStyles.fullwidth}>
{archive.length > 0 ? ( {archive.length > 0 ? (
<div className={commonStyles.horisontalScroll}> <div className={commonStyles.horisontalScroll}>
<DataTable <DataTable rows={archive} className={commonStyles.fullwidth} style={{ border: 0 }}>
rows={archive}
className={commonStyles.fullwidth}
style={{ border: 0 }}
>
<TableHeader <TableHeader
style={{ width: '25px' }} style={{ width: '25px' }}
name="reviveName" name="reviveName"
cellFormatter={reviveName => ( cellFormatter={reviveName => (
<IconButton <IconButton colored name="undo" onClick={() => revive(reviveName)} />
colored
name="undo"
onClick={() => revive(reviveName)}
/>
)} )}
> >
Revive Revive
@ -50,14 +42,9 @@ class ArchiveList extends Component {
</div> </div>
) : ( ) : (
<div className={commonStyles.emptyState}> <div className={commonStyles.emptyState}>
<Icon <Icon name="archive" className="mdl-color-text--grey-300" style={{ fontSize: '56px' }} />
name="archive"
className="mdl-color-text--grey-300"
style={{ fontSize: '56px' }}
/>
<br /> <br />
No archived feature toggles, go see{' '} No archived feature toggles, go see <Link to="/features">active toggles here</Link>
<Link to="/features">active toggles here</Link>
</div> </div>
)} )}
</Card> </Card>

View File

@ -6,14 +6,7 @@ import renderer from 'react-test-renderer';
jest.mock('react-mdl'); jest.mock('react-mdl');
test('renders correctly with no clientInstances', () => { test('renders correctly with no clientInstances', () => {
const tree = renderer const tree = renderer.create(<ClientStrategies fetchClientInstances={jest.fn()} clientInstances={[]} />).toJSON();
.create(
<ClientStrategies
fetchClientInstances={jest.fn()}
clientInstances={[]}
/>
)
.toJSON();
expect(tree).toMatchSnapshot(); expect(tree).toMatchSnapshot();
}); });

View File

@ -16,11 +16,7 @@ class ClientStrategies extends Component {
const source = this.props.clientInstances; const source = this.props.clientInstances;
return ( return (
<DataTable <DataTable style={{ width: '100%' }} rows={source} selectable={false}>
style={{ width: '100%' }}
rows={source}
selectable={false}
>
<TableHeader name="instanceId">Instance ID</TableHeader> <TableHeader name="instanceId">Instance ID</TableHeader>
<TableHeader name="appName">Application name</TableHeader> <TableHeader name="appName">Application name</TableHeader>
<TableHeader name="clientIp">IP</TableHeader> <TableHeader name="clientIp">IP</TableHeader>

View File

@ -2,12 +2,8 @@ import { connect } from 'react-redux';
import ClientInstances from './client-instance-component'; import ClientInstances from './client-instance-component';
import { fetchClientInstances } from '../../store/client-instance-actions'; import { fetchClientInstances } from '../../store/client-instance-actions';
const mapStateToProps = state => ({ const mapStateToProps = state => ({ clientInstances: state.clientInstances.toJS() });
clientInstances: state.clientInstances.toJS(),
});
const StrategiesContainer = connect(mapStateToProps, { fetchClientInstances })( const StrategiesContainer = connect(mapStateToProps, { fetchClientInstances })(ClientInstances);
ClientInstances
);
export default StrategiesContainer; export default StrategiesContainer;

View File

@ -1,44 +1,22 @@
import React from 'react'; import React from 'react';
import { Link } from 'react-router'; import { Link } from 'react-router';
import { import { List, ListItem, ListItemContent, Button, Icon, Switch, MenuItem } from 'react-mdl';
List,
ListItem,
ListItemContent,
Button,
Icon,
Switch,
MenuItem,
} from 'react-mdl';
import styles from './common.scss'; import styles from './common.scss';
export { styles }; export { styles };
export const shorten = (str, len = 50) => export const shorten = (str, len = 50) => (str && str.length > len ? `${str.substring(0, len)}...` : str);
str && str.length > len ? `${str.substring(0, len)}...` : str;
export const AppsLinkList = ({ apps }) => ( export const AppsLinkList = ({ apps }) => (
<List> <List>
{apps.length > 0 && {apps.length > 0 &&
apps.map(({ appName, description = '-', icon = 'apps' }) => ( apps.map(({ appName, description = '-', icon = 'apps' }) => (
<ListItem twoLine key={appName}> <ListItem twoLine key={appName}>
<span <span className="mdl-list__item-primary-content" style={{ minWidth: 0 }}>
className="mdl-list__item-primary-content"
style={{ minWidth: 0 }}
>
<Icon name={icon} className="mdl-list__item-avatar" /> <Icon name={icon} className="mdl-list__item-avatar" />
<Link <Link to={`/applications/${appName}`} className={[styles.listLink, styles.truncate].join(' ')}>
to={`/applications/${appName}`}
className={[styles.listLink, styles.truncate].join(
' '
)}
>
{appName} {appName}
<span <span className={['mdl-list__item-sub-title', styles.truncate].join(' ')}>
className={[
'mdl-list__item-sub-title',
styles.truncate,
].join(' ')}
>
{description} {description}
</span> </span>
</Link> </Link>
@ -62,9 +40,7 @@ export const HeaderTitle = ({ title, actions, subtitle }) => (
{subtitle && <small>{subtitle}</small>} {subtitle && <small>{subtitle}</small>}
</div> </div>
{actions && ( {actions && <div style={{ flex: '1', textAlign: 'right' }}>{actions}</div>}
<div style={{ flex: '1', textAlign: 'right' }}>{actions}</div>
)}
</div> </div>
); );
@ -84,24 +60,13 @@ export const FormButtons = ({ submitText = 'Create', onCancel }) => (
{submitText} {submitText}
</Button> </Button>
&nbsp; &nbsp;
<Button <Button type="cancel" ripple raised onClick={onCancel} style={{ float: 'right' }}>
type="cancel"
ripple
raised
onClick={onCancel}
style={{ float: 'right' }}
>
<Icon name="cancel" />&nbsp;&nbsp;&nbsp; Cancel <Icon name="cancel" />&nbsp;&nbsp;&nbsp; Cancel
</Button> </Button>
</div> </div>
); );
export const SwitchWithLabel = ({ export const SwitchWithLabel = ({ onChange, checked, children, ...switchProps }) => (
onChange,
checked,
children,
...switchProps
}) => (
<span className={styles.switchWithLabel}> <span className={styles.switchWithLabel}>
<span className={styles.label}>{children}</span> <span className={styles.label}>{children}</span>
<span className={styles.switch}> <span className={styles.switch}>
@ -141,12 +106,7 @@ export function getIcon(type) {
} }
export const IconLink = ({ url, icon }) => ( export const IconLink = ({ url, icon }) => (
<a <a href={url} target="_blank" rel="noopener" className="mdl-color-text--grey-600">
href={url}
target="_blank"
rel="noopener"
className="mdl-color-text--grey-600"
>
<Icon name={icon} /> <Icon name={icon} />
</a> </a>
); );
@ -158,17 +118,8 @@ export const DropdownButton = ({ label, id }) => (
</Button> </Button>
); );
export const MenuItemWithIcon = ({ export const MenuItemWithIcon = ({ icon, label, disabled, ...menuItemProps }) => (
icon, <MenuItem disabled={disabled} style={{ display: 'flex', alignItems: 'center' }} {...menuItemProps}>
label,
disabled,
...menuItemProps
}) => (
<MenuItem
disabled={disabled}
style={{ display: 'flex', alignItems: 'center' }}
{...menuItemProps}
>
<Icon name={icon} style={{ paddingRight: '16px' }} /> <Icon name={icon} style={{ paddingRight: '16px' }} />
{label} {label}
</MenuItem> </MenuItem>
@ -176,11 +127,7 @@ export const MenuItemWithIcon = ({
const badNumbers = [NaN, Infinity, -Infinity]; const badNumbers = [NaN, Infinity, -Infinity];
export function calc(value, total, decimal) { export function calc(value, total, decimal) {
if ( if (typeof value !== 'number' || typeof total !== 'number' || typeof decimal !== 'number') {
typeof value !== 'number' ||
typeof total !== 'number' ||
typeof decimal !== 'number'
) {
return null; return null;
} }

View File

@ -7,5 +7,4 @@ const dateTimeOptions = {
second: '2-digit', second: '2-digit',
}; };
export const formatFullDateTime = v => export const formatFullDateTime = v => new Date(v).toLocaleString('nb-NO', dateTimeOptions);
new Date(v).toLocaleString('nb-NO', dateTimeOptions);

View File

@ -8,13 +8,7 @@ const ErrorComponent = ({ errors, ...props }) => {
const error = showError ? errors[0] : undefined; const error = showError ? errors[0] : undefined;
const muteError = () => props.muteError(error); const muteError = () => props.muteError(error);
return ( return (
<Snackbar <Snackbar action="Dismiss" active={showError} onActionClick={muteError} onTimeout={muteError} timeout={10000}>
action="Dismiss"
active={showError}
onActionClick={muteError}
onTimeout={muteError}
timeout={10000}
>
<Icon name="question_answer" /> {error} <Icon name="question_answer" /> {error}
</Snackbar> </Snackbar>
); );

View File

@ -17,23 +17,13 @@ const Feature = ({
const { name, description, enabled, strategies } = feature; const { name, description, enabled, strategies } = feature;
const { showLastHour = false } = settings; const { showLastHour = false } = settings;
const isStale = showLastHour const isStale = showLastHour ? metricsLastHour.isFallback : metricsLastMinute.isFallback;
? metricsLastHour.isFallback
: metricsLastMinute.isFallback;
const percent = const percent =
1 * 1 *
(showLastHour (showLastHour
? calc( ? calc(metricsLastHour.yes, metricsLastHour.yes + metricsLastHour.no, 0)
metricsLastHour.yes, : calc(metricsLastMinute.yes, metricsLastMinute.yes + metricsLastMinute.no, 0));
metricsLastHour.yes + metricsLastHour.no,
0
)
: calc(
metricsLastMinute.yes,
metricsLastMinute.yes + metricsLastMinute.no,
0
));
const strategiesToShow = Math.min(strategies.length, 3); const strategiesToShow = Math.min(strategies.length, 3);
const remainingStrategies = strategies.length - strategiesToShow; const remainingStrategies = strategies.length - strategiesToShow;
@ -45,18 +35,12 @@ const Feature = ({
{s.name} {s.name}
</Chip> </Chip>
)); ));
const summaryChip = remainingStrategies > 0 && ( const summaryChip = remainingStrategies > 0 && <Chip className={styles.strategyChip}>+{remainingStrategies}</Chip>;
<Chip className={styles.strategyChip}>+{remainingStrategies}</Chip>
);
return ( return (
<ListItem twoLine> <ListItem twoLine>
<span className={styles.listItemMetric}> <span className={styles.listItemMetric}>
<Progress <Progress strokeWidth={15} percentage={percent} isFallback={isStale} />
strokeWidth={15}
percentage={percent}
isFallback={isStale}
/>
</span> </span>
<span className={styles.listItemToggle}> <span className={styles.listItemToggle}>
<Switch <Switch
@ -66,36 +50,16 @@ const Feature = ({
checked={enabled} checked={enabled}
/> />
</span> </span>
<span <span className={['mdl-list__item-primary-content', styles.listItemLink].join(' ')}>
className={[
'mdl-list__item-primary-content',
styles.listItemLink,
].join(' ')}
>
<Link <Link
to={`/features/view/${name}`} to={`/features/view/${name}`}
className={[ className={[commonStyles.listLink, commonStyles.truncate].join(' ')}
commonStyles.listLink,
commonStyles.truncate,
].join(' ')}
> >
{name} {name}
<span <span className={['mdl-list__item-sub-title', commonStyles.truncate].join(' ')}>{description}</span>
className={[
'mdl-list__item-sub-title',
commonStyles.truncate,
].join(' ')}
>
{description}
</span>
</Link> </Link>
</span> </span>
<span <span className={[styles.listItemStrategies, commonStyles.hideLt920].join(' ')}>
className={[
styles.listItemStrategies,
commonStyles.hideLt920,
].join(' ')}
>
{strategyChips} {strategyChips}
{summaryChip} {summaryChip}
</span> </span>

View File

@ -1,9 +1,6 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { hashHistory } from 'react-router'; import { hashHistory } from 'react-router';
import { import { createFeatureToggles, validateName } from '../../store/feature-actions';
createFeatureToggles,
validateName,
} from '../../store/feature-actions';
import { createMapper, createActions } from '../input-helpers'; import { createMapper, createActions } from '../input-helpers';
import FormAddComponent from './form-add-component'; import FormAddComponent from './form-add-component';

View File

@ -92,10 +92,7 @@ class AddFeatureToggleComponent extends Component {
/> />
<br /> <br />
<FormButtons <FormButtons submitText={editmode ? 'Update' : 'Create'} onCancel={onCancel} />
submitText={editmode ? 'Update' : 'Create'}
onCancel={onCancel}
/>
</section> </section>
</form> </form>
); );

View File

@ -10,9 +10,7 @@ class AddStrategy extends React.Component {
}; };
addStrategy = strategyName => { addStrategy = strategyName => {
const selectedStrategy = this.props.strategies.find( const selectedStrategy = this.props.strategies.find(s => s.name === strategyName);
s => s.name === strategyName
);
const parameters = {}; const parameters = {};
selectedStrategy.parameters.forEach(({ name }) => { selectedStrategy.parameters.forEach(({ name }) => {
@ -37,14 +35,7 @@ class AddStrategy extends React.Component {
backgroundColor: 'rgb(247, 248, 255)', backgroundColor: 'rgb(247, 248, 255)',
}; };
return ( return (
<div <div style={{ position: 'relative', width: '25px', height: '25px', display: 'inline-block' }}>
style={{
position: 'relative',
width: '25px',
height: '25px',
display: 'inline-block',
}}
>
<IconButton <IconButton
name="add" name="add"
id="strategies-add" id="strategies-add"
@ -53,20 +44,10 @@ class AddStrategy extends React.Component {
title="Add Strategy" title="Add Strategy"
onClick={this.stopPropagation} onClick={this.stopPropagation}
/> />
<Menu <Menu target="strategies-add" valign="bottom" align="right" ripple style={menuStyle}>
target="strategies-add"
valign="bottom"
align="right"
ripple
style={menuStyle}
>
<MenuItem disabled>Add Strategy:</MenuItem> <MenuItem disabled>Add Strategy:</MenuItem>
{this.props.strategies.map(s => ( {this.props.strategies.map(s => (
<MenuItem <MenuItem key={s.name} title={s.description} onClick={() => this.addStrategy(s.name)}>
key={s.name}
title={s.description}
onClick={() => this.addStrategy(s.name)}
>
{s.name} {s.name}
</MenuItem> </MenuItem>
))} ))}

View File

@ -15,13 +15,7 @@ class StrategiesList extends React.Component {
}; };
render() { render() {
const { const { strategies, configuredStrategies, moveStrategy, removeStrategy, updateStrategy } = this.props;
strategies,
configuredStrategies,
moveStrategy,
removeStrategy,
updateStrategy,
} = this.props;
if (!configuredStrategies || configuredStrategies.length === 0) { if (!configuredStrategies || configuredStrategies.length === 0) {
return <i style={{ color: 'red' }}>No strategies added</i>; return <i style={{ color: 'red' }}>No strategies added</i>;
@ -35,14 +29,10 @@ class StrategiesList extends React.Component {
moveStrategy={moveStrategy} moveStrategy={moveStrategy}
removeStrategy={removeStrategy.bind(null, i)} removeStrategy={removeStrategy.bind(null, i)}
updateStrategy={updateStrategy.bind(null, i)} updateStrategy={updateStrategy.bind(null, i)}
strategyDefinition={strategies.find( strategyDefinition={strategies.find(s => s.name === strategy.name)}
s => s.name === strategy.name
)}
/> />
)); ));
return ( return <div style={{ display: 'flex', flexWrap: 'wrap' }}>{blocks}</div>;
<div style={{ display: 'flex', flexWrap: 'wrap' }}>{blocks}</div>
);
} }
} }

View File

@ -25,10 +25,7 @@ class StrategiesSection extends React.Component {
return ( return (
<div> <div>
<HeaderTitle <HeaderTitle title="Activation strategies" actions={<AddStrategy {...this.props} />} />
title="Activation strategies"
actions={<AddStrategy {...this.props} />}
/>
<StrategiesList {...this.props} /> <StrategiesList {...this.props} />
</div> </div>
); );

View File

@ -1,16 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { import { Textfield, Button, Card, CardTitle, CardText, CardActions, CardMenu, IconButton, Icon } from 'react-mdl';
Textfield,
Button,
Card,
CardTitle,
CardText,
CardActions,
CardMenu,
IconButton,
Icon,
} from 'react-mdl';
import { DragSource, DropTarget } from 'react-dnd'; import { DragSource, DropTarget } from 'react-dnd';
import { Link } from 'react-router'; import { Link } from 'react-router';
import StrategyInputPercentage from './strategy-input-percentage'; import StrategyInputPercentage from './strategy-input-percentage';
@ -91,25 +81,17 @@ class StrategyConfigure extends React.Component {
return parameters.map(({ name, type, description, required }) => { return parameters.map(({ name, type, description, required }) => {
let value = this.props.strategy.parameters[name]; let value = this.props.strategy.parameters[name];
if (type === 'percentage') { if (type === 'percentage') {
if ( if (value == null || (typeof value === 'string' && value === '')) {
value == null ||
(typeof value === 'string' && value === '')
) {
this.setConfig(name, 50); this.setConfig(name, 50);
} }
return ( return (
<div key={name}> <div key={name}>
<StrategyInputPercentage <StrategyInputPercentage
name={name} name={name}
onChange={this.handleConfigChange.bind( onChange={this.handleConfigChange.bind(this, name)}
this,
name
)}
value={1 * value} value={1 * value}
/> />
{description && ( {description && <p className={styles.helpText}>{description}</p>}
<p className={styles.helpText}>{description}</p>
)}
</div> </div>
); );
} else if (type === 'list') { } else if (type === 'list') {
@ -122,14 +104,8 @@ class StrategyConfigure extends React.Component {
} }
return ( return (
<div key={name}> <div key={name}>
<StrategyInputList <StrategyInputList name={name} list={list} setConfig={this.setConfig} />
name={name} {description && <p className={styles.helpText}>{description}</p>}
list={list}
setConfig={this.setConfig}
/>
{description && (
<p className={styles.helpText}>{description}</p>
)}
</div> </div>
); );
} else if (type === 'number') { } else if (type === 'number') {
@ -143,15 +119,10 @@ class StrategyConfigure extends React.Component {
style={{ width: '100%' }} style={{ width: '100%' }}
name={name} name={name}
label={name} label={name}
onChange={this.handleConfigChange.bind( onChange={this.handleConfigChange.bind(this, name)}
this,
name
)}
value={value} value={value}
/> />
{description && ( {description && <p className={styles.helpText}>{description}</p>}
<p className={styles.helpText}>{description}</p>
)}
</div> </div>
); );
} else { } else {
@ -164,15 +135,10 @@ class StrategyConfigure extends React.Component {
required={required} required={required}
name={name} name={name}
label={name} label={name}
onChange={this.handleConfigChange.bind( onChange={this.handleConfigChange.bind(this, name)}
this,
name
)}
value={value} value={value}
/> />
{description && ( {description && <p className={styles.helpText}>{description}</p>}
<p className={styles.helpText}>{description}</p>
)}
</div> </div>
); );
} }
@ -182,31 +148,18 @@ class StrategyConfigure extends React.Component {
} }
render() { render() {
const { const { isDragging, connectDragPreview, connectDragSource, connectDropTarget } = this.props;
isDragging,
connectDragPreview,
connectDragSource,
connectDropTarget,
} = this.props;
let item; let item;
if (this.props.strategyDefinition) { if (this.props.strategyDefinition) {
const inputFields = this.renderInputFields( const inputFields = this.renderInputFields(this.props.strategyDefinition);
this.props.strategyDefinition
);
const { name } = this.props.strategy; const { name } = this.props.strategy;
item = ( item = (
<Card <Card shadow={0} className={styles.card} style={{ opacity: isDragging ? '0.1' : '1' }}>
shadow={0}
className={styles.card}
style={{ opacity: isDragging ? '0.1' : '1' }}
>
<CardTitle className={styles.cardTitle}> <CardTitle className={styles.cardTitle}>
<Icon name="extension" />&nbsp;{name} <Icon name="extension" />&nbsp;{name}
</CardTitle> </CardTitle>
<CardText> <CardText>{this.props.strategyDefinition.description}</CardText>
{this.props.strategyDefinition.description}
</CardText>
{inputFields && ( {inputFields && (
<CardActions border style={{ padding: '20px' }}> <CardActions border style={{ padding: '20px' }}>
{inputFields} {inputFields}
@ -214,18 +167,10 @@ class StrategyConfigure extends React.Component {
)} )}
<CardMenu className="mdl-color-text--white"> <CardMenu className="mdl-color-text--white">
<Link <Link title="View strategy" to={`/strategies/view/${name}`} className={styles.editLink}>
title="View strategy"
to={`/strategies/view/${name}`}
className={styles.editLink}
>
<Icon name="link" /> <Icon name="link" />
</Link> </Link>
<IconButton <IconButton title="Remove strategy from toggle" name="delete" onClick={this.handleRemove} />
title="Remove strategy from toggle"
name="delete"
onClick={this.handleRemove}
/>
{connectDragSource( {connectDragSource(
<span className={styles.reorderIcon}> <span className={styles.reorderIcon}>
<Icon name="reorder" /> <Icon name="reorder" />
@ -241,17 +186,10 @@ class StrategyConfigure extends React.Component {
<CardTitle>"{name}" deleted?</CardTitle> <CardTitle>"{name}" deleted?</CardTitle>
<CardText> <CardText>
The strategy "{name}" does not exist on this server. The strategy "{name}" does not exist on this server.
<Link to={`/strategies/create?name=${name}`}> <Link to={`/strategies/create?name=${name}`}>Want to create it now?</Link>
Want to create it now?
</Link>
</CardText> </CardText>
<CardActions> <CardActions>
<Button <Button onClick={this.handleRemove} label="remove strategy" accent raised>
onClick={this.handleRemove}
label="remove strategy"
accent
raised
>
Remove Remove
</Button> </Button>
</CardActions> </CardActions>
@ -259,9 +197,7 @@ class StrategyConfigure extends React.Component {
); );
} }
return connectDropTarget( return connectDropTarget(connectDragPreview(<div className={styles.item}>{item}</div>));
connectDragPreview(<div className={styles.item}>{item}</div>)
);
} }
} }

View File

@ -46,10 +46,7 @@ export default class InputList extends Component {
onClose(index) { onClose(index) {
const { name, list, setConfig } = this.props; const { name, list, setConfig } = this.props;
list[index] = null; list[index] = null;
setConfig( setConfig(name, list.length === 1 ? '' : list.filter(Boolean).join(','));
name,
list.length === 1 ? '' : list.filter(Boolean).join(',')
);
} }
render() { render() {
@ -58,11 +55,7 @@ export default class InputList extends Component {
<div> <div>
<p>{name}</p> <p>{name}</p>
{list.map((entryValue, index) => ( {list.map((entryValue, index) => (
<Chip <Chip key={index + entryValue} style={{ marginRight: '3px' }} onClose={() => this.onClose(index)}>
key={index + entryValue}
style={{ marginRight: '3px' }}
onClose={() => this.onClose(index)}
>
{entryValue} {entryValue}
</Chip> </Chip>
))} ))}
@ -79,11 +72,7 @@ export default class InputList extends Component {
<IconButton <IconButton
name="add" name="add"
raised raised
style={{ style={{ flex: 1, flexGrow: 0, margin: '20px 0 0 10px' }}
flex: 1,
flexGrow: 0,
margin: '20px 0 0 10px',
}}
onClick={this.setValue} onClick={this.setValue}
/> />
</div> </div>

View File

@ -13,13 +13,6 @@ export default ({ name, value, onChange }) => (
<div style={labelStyle}> <div style={labelStyle}>
{name}: {value}% {name}: {value}%
</div> </div>
<Slider <Slider min={0} max={100} defaultValue={value} value={value} onChange={onChange} label={name} />
min={0}
max={100}
defaultValue={value}
value={value}
onChange={onChange}
label={name}
/>
</div> </div>
); );

View File

@ -2,22 +2,9 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Feature from './feature-list-item-component'; import Feature from './feature-list-item-component';
import { Link } from 'react-router'; import { Link } from 'react-router';
import { import { Icon, FABButton, Textfield, Menu, MenuItem, Card, CardActions, List } from 'react-mdl';
Icon,
FABButton,
Textfield,
Menu,
MenuItem,
Card,
CardActions,
List,
} from 'react-mdl';
import { import { MenuItemWithIcon, DropdownButton, styles as commonStyles } from '../common';
MenuItemWithIcon,
DropdownButton,
styles as commonStyles,
} from '../common';
import styles from './feature.scss'; import styles from './feature.scss';
export default class FeatureListComponent extends React.PureComponent { export default class FeatureListComponent extends React.PureComponent {
@ -47,10 +34,7 @@ export default class FeatureListComponent extends React.PureComponent {
} }
toggleMetrics() { toggleMetrics() {
this.props.updateSetting( this.props.updateSetting('showLastHour', !this.props.settings.showLastHour);
'showLastHour',
!this.props.settings.showLastHour
);
} }
setFilter(v) { setFilter(v) {
@ -62,12 +46,7 @@ export default class FeatureListComponent extends React.PureComponent {
} }
render() { render() {
const { const { features, toggleFeature, featureMetrics, settings } = this.props;
features,
toggleFeature,
featureMetrics,
settings,
} = this.props;
return ( return (
<div> <div>
@ -81,32 +60,16 @@ export default class FeatureListComponent extends React.PureComponent {
label="Search" label="Search"
style={{ width: '100%' }} style={{ width: '100%' }}
/> />
<Link <Link to="/features/create" className={styles.toolbarButton}>
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 <Card shadow={0} className={commonStyles.fullwidth} style={{ overflow: 'visible' }}>
shadow={0}
className={commonStyles.fullwidth}
style={{ overflow: 'visible' }}
>
<CardActions> <CardActions>
<DropdownButton <DropdownButton id="metric" label={`Last ${settings.showLastHour ? 'hour' : 'minute'}`} />
id="metric" <Menu target="metric" onClick={() => this.toggleMetrics()} style={{ width: '168px' }}>
label={`Last ${settings.showLastHour
? 'hour'
: 'minute'}`}
/>
<Menu
target="metric"
onClick={() => this.toggleMetrics()}
style={{ width: '168px' }}
>
<MenuItemWithIcon <MenuItemWithIcon
icon="hourglass_empty" icon="hourglass_empty"
disabled={!settings.showLastHour} disabled={!settings.showLastHour}
@ -120,46 +83,25 @@ export default class FeatureListComponent extends React.PureComponent {
label="Last hour" label="Last hour"
/> />
</Menu> </Menu>
<DropdownButton <DropdownButton id="sorting" label={`By ${settings.sort}`} />
id="sorting"
label={`By ${settings.sort}`}
/>
<Menu <Menu
target="sorting" target="sorting"
onClick={e => onClick={e => this.setSort(e.target.getAttribute('data-target'))}
this.setSort(
e.target.getAttribute('data-target')
)}
style={{ width: '168px' }} style={{ width: '168px' }}
> >
<MenuItem <MenuItem disabled={settings.sort === 'name'} data-target="name">
disabled={settings.sort === 'name'}
data-target="name"
>
Name Name
</MenuItem> </MenuItem>
<MenuItem <MenuItem disabled={settings.sort === 'enabled'} data-target="enabled">
disabled={settings.sort === 'enabled'}
data-target="enabled"
>
Enabled Enabled
</MenuItem> </MenuItem>
<MenuItem <MenuItem disabled={settings.sort === 'created'} data-target="created">
disabled={settings.sort === 'created'}
data-target="created"
>
Created Created
</MenuItem> </MenuItem>
<MenuItem <MenuItem disabled={settings.sort === 'strategies'} data-target="strategies">
disabled={settings.sort === 'strategies'}
data-target="strategies"
>
Strategies Strategies
</MenuItem> </MenuItem>
<MenuItem <MenuItem disabled={settings.sort === 'metrics'} data-target="metrics">
disabled={settings.sort === 'metrics'}
data-target="metrics"
>
Metrics Metrics
</MenuItem> </MenuItem>
</Menu> </Menu>
@ -170,12 +112,8 @@ export default class FeatureListComponent extends React.PureComponent {
<Feature <Feature
key={i} key={i}
settings={settings} settings={settings}
metricsLastHour={ metricsLastHour={featureMetrics.lastHour[feature.name]}
featureMetrics.lastHour[feature.name] metricsLastMinute={featureMetrics.lastMinute[feature.name]}
}
metricsLastMinute={
featureMetrics.lastMinute[feature.name]
}
feature={feature} feature={feature}
toggleFeature={toggleFeature} toggleFeature={toggleFeature}
/> />

View File

@ -1,8 +1,5 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { import { toggleFeature, fetchFeatureToggles } from '../../store/feature-actions';
toggleFeature,
fetchFeatureToggles,
} from '../../store/feature-actions';
import { fetchFeatureMetrics } from '../../store/feature-metrics-actions'; import { fetchFeatureMetrics } from '../../store/feature-metrics-actions';
import { updateSettingForGroup } from '../../store/settings/actions'; import { updateSettingForGroup } from '../../store/settings/actions';
@ -33,9 +30,7 @@ const mapStateToProps = state => {
a.enabled === b.enabled ? 0 : a.enabled ? -1 : 1 a.enabled === b.enabled ? 0 : a.enabled ? -1 : 1
); );
} else if (settings.sort === 'created') { } else if (settings.sort === 'created') {
features = features.sort( features = features.sort((a, b) => (new Date(a.createdAt) > new Date(b.createdAt) ? -1 : 1));
(a, b) => (new Date(a.createdAt) > new Date(b.createdAt) ? -1 : 1)
);
} else if (settings.sort === 'name') { } else if (settings.sort === 'name') {
features = features.sort((a, b) => { features = features.sort((a, b) => {
if (a.name < b.name) { if (a.name < b.name) {
@ -47,13 +42,9 @@ const mapStateToProps = state => {
return 0; return 0;
}); });
} else if (settings.sort === 'strategies') { } else if (settings.sort === 'strategies') {
features = features.sort( features = features.sort((a, b) => (a.strategies.length > b.strategies.length ? -1 : 1));
(a, b) => (a.strategies.length > b.strategies.length ? -1 : 1)
);
} else if (settings.sort === 'metrics') { } else if (settings.sort === 'metrics') {
const target = settings.showLastHour const target = settings.showLastHour ? featureMetrics.lastHour : featureMetrics.lastMinute;
? featureMetrics.lastHour
: featureMetrics.lastMinute;
features = features.sort((a, b) => { features = features.sort((a, b) => {
if (!target[a.name]) { if (!target[a.name]) {
@ -83,8 +74,6 @@ const mapDispatchToProps = {
updateSetting: updateSettingForGroup('feature'), updateSetting: updateSettingForGroup('feature'),
}; };
const FeatureListContainer = connect(mapStateToProps, mapDispatchToProps)( const FeatureListContainer = connect(mapStateToProps, mapDispatchToProps)(FeatureListComponent);
FeatureListComponent
);
export default FeatureListContainer; export default FeatureListContainer;

View File

@ -12,10 +12,7 @@ const StrategyChipItem = ({ strategy }) => (
<ChipContact className="mdl-color--blue-grey mdl-color-text--white"> <ChipContact className="mdl-color--blue-grey mdl-color-text--white">
<Icon style={{ marginTop: '3px' }} name="link" /> <Icon style={{ marginTop: '3px' }} name="link" />
</ChipContact> </ChipContact>
<Link <Link to={`/strategies/view/${strategy.name}`} className="mdl-color-text--blue-grey">
to={`/strategies/view/${strategy.name}`}
className="mdl-color-text--blue-grey"
>
{strategy.name} {strategy.name}
</Link> </Link>
</Chip> </Chip>
@ -25,9 +22,7 @@ const StrategyChipItem = ({ strategy }) => (
const StrategiesList = ({ strategies }) => ( const StrategiesList = ({ strategies }) => (
<div style={{ verticalAlign: 'middle', paddingTop: '14px' }}> <div style={{ verticalAlign: 'middle', paddingTop: '14px' }}>
With {strategies.length > 1 ? 'strategies' : 'strategy'}{' '} With {strategies.length > 1 ? 'strategies' : 'strategy'}{' '}
{strategies.map((strategy, i) => ( {strategies.map((strategy, i) => <StrategyChipItem key={i} strategy={strategy} />)}
<StrategyChipItem key={i} strategy={strategy} />
))}
</div> </div>
); );
@ -59,10 +54,8 @@ export default class MetricComponent extends React.Component {
seenApps = [], seenApps = [],
} = metrics; } = metrics;
const lastHourPercent = const lastHourPercent = 1 * calc(lastHour.yes, lastHour.yes + lastHour.no, 0);
1 * calc(lastHour.yes, lastHour.yes + lastHour.no, 0); const lastMinutePercent = 1 * calc(lastMinute.yes, lastMinute.yes + lastMinute.no, 0);
const lastMinutePercent =
1 * calc(lastMinute.yes, lastMinute.yes + lastMinute.no, 0);
return ( return (
<div style={{ padding: '16px' }}> <div style={{ padding: '16px' }}>
@ -75,9 +68,7 @@ export default class MetricComponent extends React.Component {
animatePercentageText animatePercentageText
/> />
{lastMinute.isFallback ? ( {lastMinute.isFallback ? (
<p className="mdl-color-text--grey-500"> <p className="mdl-color-text--grey-500">No metrics available</p>
No metrics available
</p>
) : ( ) : (
<p> <p>
<strong>Last minute</strong> <strong>Last minute</strong>
@ -86,14 +77,9 @@ export default class MetricComponent extends React.Component {
)} )}
</Cell> </Cell>
<Cell col={4} tablet={4} phone={12}> <Cell col={4} tablet={4} phone={12}>
<Progress <Progress percentage={lastHourPercent} isFallback={lastHour.isFallback} />
percentage={lastHourPercent}
isFallback={lastHour.isFallback}
/>
{lastHour.isFallback ? ( {lastHour.isFallback ? (
<p className="mdl-color-text--grey-500"> <p className="mdl-color-text--grey-500">No metrics available</p>
No metrics available
</p>
) : ( ) : (
<p> <p>
<strong>Last hour</strong> <strong>Last hour</strong>
@ -115,20 +101,14 @@ export default class MetricComponent extends React.Component {
/> />
<div> <div>
<small> <small>
<strong> <strong>Not used in an app in the last hour.</strong>
Not used in an app in the last hour. This might be due to your client implementation is not reporting usage.
</strong>
This might be due to your client
implementation is not reporting usage.
</small> </small>
</div> </div>
</div> </div>
)} )}
<AppsLinkList apps={seenApps} /> <AppsLinkList apps={seenApps} />
<span> <span>Created {formatFullDateTime(featureToggle.createdAt)}</span>
Created{' '}
{formatFullDateTime(featureToggle.createdAt)}
</span>
</Cell> </Cell>
</Grid> </Grid>
<hr /> <hr />

View File

@ -1,9 +1,6 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { import { fetchFeatureMetrics, fetchSeenApps } from '../../store/feature-metrics-actions';
fetchFeatureMetrics,
fetchSeenApps,
} from '../../store/feature-metrics-actions';
import MatricComponent from './metric-component'; import MatricComponent from './metric-component';
@ -18,10 +15,7 @@ function getMetricsForToggle(state, toggleName) {
} }
if (state.featureMetrics.hasIn(['lastHour', toggleName])) { if (state.featureMetrics.hasIn(['lastHour', toggleName])) {
result.lastHour = state.featureMetrics.getIn(['lastHour', toggleName]); result.lastHour = state.featureMetrics.getIn(['lastHour', toggleName]);
result.lastMinute = state.featureMetrics.getIn([ result.lastMinute = state.featureMetrics.getIn(['lastMinute', toggleName]);
'lastMinute',
toggleName,
]);
} }
return result; return result;
} }

View File

@ -41,9 +41,7 @@ class Progress extends Component {
const TOTAL_ANIMATION_TIME = 5000; const TOTAL_ANIMATION_TIME = 5000;
const diff = start > target ? -(start - target) : target - start; const diff = start > target ? -(start - target) : target - start;
const perCycle = TOTAL_ANIMATION_TIME / diff; const perCycle = TOTAL_ANIMATION_TIME / diff;
const cyclesCounter = Math.round( const cyclesCounter = Math.round(Math.abs(TOTAL_ANIMATION_TIME / perCycle));
Math.abs(TOTAL_ANIMATION_TIME / perCycle)
);
const perCycleTime = Math.round(Math.abs(perCycle)); const perCycleTime = Math.round(Math.abs(perCycle));
return { return {
@ -95,9 +93,7 @@ class Progress extends Component {
const diameter = Math.PI * 2 * radius; const diameter = Math.PI * 2 * radius;
const progressStyle = { const progressStyle = {
strokeDasharray: `${diameter}px ${diameter}px`, strokeDasharray: `${diameter}px ${diameter}px`,
strokeDashoffset: `${(100 - this.state.percentage) / strokeDashoffset: `${(100 - this.state.percentage) / 100 * diameter}px`,
100 *
diameter}px`,
}; };
return isFallback ? ( return isFallback ? (
@ -113,9 +109,7 @@ class Progress extends Component {
) : ( ) : (
<svg viewBox="0 0 100 100"> <svg viewBox="0 0 100 100">
<path <path
className={[styles.trail, 'mdl-color-text--grey-300'].join( className={[styles.trail, 'mdl-color-text--grey-300'].join(' ')}
' '
)}
d={pathDescription} d={pathDescription}
strokeWidth={strokeWidth} strokeWidth={strokeWidth}
fillOpacity={0} fillOpacity={0}

View File

@ -1,16 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { import { Tabs, Tab, ProgressBar, Button, Card, CardTitle, CardText, CardActions, Switch } from 'react-mdl';
Tabs,
Tab,
ProgressBar,
Button,
Card,
CardTitle,
CardText,
CardActions,
Switch,
} from 'react-mdl';
import { hashHistory, Link } from 'react-router'; import { hashHistory, Link } from 'react-router';
import HistoryComponent from '../history/history-list-toggle-container'; import HistoryComponent from '../history/history-list-toggle-container';
@ -90,9 +80,7 @@ export default class ViewFeatureToggleComponent extends React.Component {
); );
} }
const activeTabId = TABS[this.props.activeTab] const activeTabId = TABS[this.props.activeTab] ? TABS[this.props.activeTab] : TABS.view;
? TABS[this.props.activeTab]
: TABS.view;
const tabContent = this.getTabContent(activeTab); const tabContent = this.getTabContent(activeTab);
const removeToggle = () => { const removeToggle = () => {
@ -106,16 +94,8 @@ export default class ViewFeatureToggleComponent extends React.Component {
}; };
return ( return (
<Card <Card shadow={0} className={commonStyles.fullwidth} style={{ overflow: 'visible' }}>
shadow={0} <CardTitle style={{ paddingTop: '24px', wordBreak: 'break-all' }}>{featureToggle.name}</CardTitle>
className={commonStyles.fullwidth}
style={{ overflow: 'visible' }}
>
<CardTitle
style={{ paddingTop: '24px', wordBreak: 'break-all' }}
>
{featureToggle.name}
</CardTitle>
<CardText>{featureToggle.description}</CardText> <CardText>{featureToggle.description}</CardText>
<CardActions <CardActions
border border
@ -145,22 +125,9 @@ export default class ViewFeatureToggleComponent extends React.Component {
tabBarProps={{ style: { width: '100%' } }} tabBarProps={{ style: { width: '100%' } }}
className="mdl-color--grey-100" className="mdl-color--grey-100"
> >
<Tab <Tab onClick={() => this.goToTab('view', featureToggleName)}>Metrics</Tab>
onClick={() => this.goToTab('view', featureToggleName)} <Tab onClick={() => this.goToTab('edit', featureToggleName)}>Edit</Tab>
> <Tab onClick={() => this.goToTab('history', featureToggleName)}>History</Tab>
Metrics
</Tab>
<Tab
onClick={() => this.goToTab('edit', featureToggleName)}
>
Edit
</Tab>
<Tab
onClick={() =>
this.goToTab('history', featureToggleName)}
>
History
</Tab>
</Tabs> </Tabs>
{tabContent} {tabContent}
</Card> </Card>

View File

@ -1,19 +1,13 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { import { fetchFeatureToggles, toggleFeature, removeFeatureToggle } from '../../store/feature-actions';
fetchFeatureToggles,
toggleFeature,
removeFeatureToggle,
} from '../../store/feature-actions';
import ViewToggleComponent from './view-component'; import ViewToggleComponent from './view-component';
export default connect( export default connect(
(state, props) => ({ (state, props) => ({
features: state.features.toJS(), features: state.features.toJS(),
featureToggle: state.features featureToggle: state.features.toJS().find(toggle => toggle.name === props.featureToggleName),
.toJS()
.find(toggle => toggle.name === props.featureToggleName),
activeTab: props.activeTab, activeTab: props.activeTab,
}), }),
{ {

View File

@ -9,8 +9,6 @@ const mapStateToProps = state => {
}; };
}; };
const HistoryListContainer = connect(mapStateToProps, { fetchHistory })( const HistoryListContainer = connect(mapStateToProps, { fetchHistory })(HistoryComponent);
HistoryComponent
);
export default HistoryListContainer; export default HistoryListContainer;

View File

@ -84,18 +84,12 @@ class HistoryItem extends PureComponent {
changes = entry.diffs.map(buildDiff); changes = entry.diffs.map(buildDiff);
} else { } else {
// Just show the data if there is no diff yet. // Just show the data if there is no diff yet.
changes = ( changes = <div className={KLASSES.N}>{JSON.stringify(entry.data, null, 2)}</div>;
<div className={KLASSES.N}>
{JSON.stringify(entry.data, null, 2)}
</div>
);
} }
return ( return (
<pre style={{ overflowX: 'auto', overflowY: 'hidden' }}> <pre style={{ overflowX: 'auto', overflowY: 'hidden' }}>
<code className="smalltext man"> <code className="smalltext man">{changes.length === 0 ? '(no changes)' : changes}</code>
{changes.length === 0 ? '(no changes)' : changes}
</code>
</pre> </pre>
); );
} }

View File

@ -2,11 +2,7 @@ import React, { Component } from 'react';
import HistoryItemDiff from './history-item-diff'; import HistoryItemDiff from './history-item-diff';
import HistoryItemJson from './history-item-json'; import HistoryItemJson from './history-item-json';
import { Table, TableHeader } from 'react-mdl'; import { Table, TableHeader } from 'react-mdl';
import { import { DataTableHeader, SwitchWithLabel, styles as commonStyles } from '../common';
DataTableHeader,
SwitchWithLabel,
styles as commonStyles,
} from '../common';
import { formatFullDateTime } from '../common/util'; import { formatFullDateTime } from '../common/util';
import styles from './history.scss'; import styles from './history.scss';
@ -26,11 +22,7 @@ class HistoryList extends Component {
const truncateTableCell = v => ( const truncateTableCell = v => (
<span <span
className={commonStyles.truncate} className={commonStyles.truncate}
style={{ style={{ display: 'inline-block', verticalAlign: 'middle', width: '100%' }}
display: 'inline-block',
verticalAlign: 'middle',
width: '100%',
}}
> >
{v} {v}
</span> </span>
@ -39,9 +31,7 @@ class HistoryList extends Component {
let entries; let entries;
if (showData) { if (showData) {
entries = history.map(entry => ( entries = history.map(entry => <HistoryItemJson key={`log${entry.id}`} entry={entry} />);
<HistoryItemJson key={`log${entry.id}`} entry={entry} />
));
} else { } else {
entries = ( entries = (
<Table <Table
@ -55,33 +45,16 @@ class HistoryList extends Component {
) )
)} )}
className={commonStyles.fullwidth} className={commonStyles.fullwidth}
style={{ style={{ border: 0, tableLayout: 'fixed', minWidth: '840px' }}
border: 0,
tableLayout: 'fixed',
minWidth: '840px',
}}
> >
<TableHeader <TableHeader name="type" cellFormatter={truncateTableCell} style={{ width: '136px' }}>
name="type"
cellFormatter={truncateTableCell}
style={{ width: '136px' }}
>
Type Type
</TableHeader> </TableHeader>
<TableHeader <TableHeader name="createdBy" cellFormatter={truncateTableCell} style={{ width: '115px' }}>
name="createdBy"
cellFormatter={truncateTableCell}
style={{ width: '115px' }}
>
User User
</TableHeader> </TableHeader>
<TableHeader name="diff">Diff</TableHeader> <TableHeader name="diff">Diff</TableHeader>
<TableHeader <TableHeader numeric name="createdAt" cellFormatter={formatFullDateTime} style={{ width: '165px' }}>
numeric
name="createdAt"
cellFormatter={formatFullDateTime}
style={{ width: '165px' }}
>
Time Time
</TableHeader> </TableHeader>
</Table> </Table>
@ -93,10 +66,7 @@ class HistoryList extends Component {
<DataTableHeader <DataTableHeader
title={this.props.title} title={this.props.title}
actions={ actions={
<SwitchWithLabel <SwitchWithLabel checked={showData} onChange={this.toggleShowDiff.bind(this)}>
checked={showData}
onChange={this.toggleShowDiff.bind(this)}
>
Full events Full events
</SwitchWithLabel> </SwitchWithLabel>
} }

View File

@ -52,44 +52,23 @@ export function createActions({ id, prepare = v => v }) {
}, },
setValue(key, value) { setValue(key, value) {
dispatch( dispatch(createSet({ id: getId(id, ownProps), key, value }));
createSet({ id: getId(id, ownProps), key, value })
);
}, },
pushToList(key, value) { pushToList(key, value) {
dispatch( dispatch(createPush({ id: getId(id, ownProps), key, value }));
createPush({ id: getId(id, ownProps), key, value })
);
}, },
removeFromList(key, index) { removeFromList(key, index) {
dispatch( dispatch(createPop({ id: getId(id, ownProps), key, index }));
createPop({ id: getId(id, ownProps), key, index })
);
}, },
moveItem(key, index, toIndex) { moveItem(key, index, toIndex) {
dispatch( dispatch(createMove({ id: getId(id, ownProps), key, index, toIndex }));
createMove({
id: getId(id, ownProps),
key,
index,
toIndex,
})
);
}, },
updateInList(key, index, newValue, merge = false) { updateInList(key, index, newValue, merge = false) {
dispatch( dispatch(createUp({ id: getId(id, ownProps), key, index, newValue, merge }));
createUp({
id: getId(id, ownProps),
key,
index,
newValue,
merge,
})
);
}, },
incValue(key) { incValue(key) {

View File

@ -13,19 +13,12 @@ const prepare = (methods, dispatch) => {
// clean // clean
const parameters = (input.parameters || []) const parameters = (input.parameters || [])
.filter(name => !!name) .filter(name => !!name)
.map( .map(({ name, type = 'string', description = '', required = false }) => ({
({ name,
name, type,
type = 'string', description,
description = '', required,
required = false, }));
}) => ({
name,
type,
description,
required,
})
);
createStrategy({ createStrategy({
name: input.name, name: input.name,

View File

@ -1,15 +1,7 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { import { Textfield, IconButton, Menu, MenuItem, Checkbox, Grid, Cell } from 'react-mdl';
Textfield,
IconButton,
Menu,
MenuItem,
Checkbox,
Grid,
Cell,
} from 'react-mdl';
import { FormButtons } from '../common'; import { FormButtons } from '../common';
const trim = value => { const trim = value => {
@ -25,13 +17,7 @@ function gerArrayWithEntries(num) {
} }
const Parameter = ({ set, input = {}, index }) => ( const Parameter = ({ set, input = {}, index }) => (
<div <div style={{ background: '#f1f1f1', padding: '16px 20px', marginBottom: '20px' }}>
style={{
background: '#f1f1f1',
padding: '16px 20px',
marginBottom: '20px',
}}
>
<Textfield <Textfield
style={{ width: '50%' }} style={{ width: '50%' }}
floatingLabel floatingLabel
@ -46,8 +32,7 @@ const Parameter = ({ set, input = {}, index }) => (
style={{ style={{
borderRadius: '2px', borderRadius: '2px',
cursor: 'pointer', cursor: 'pointer',
boxShadow: boxShadow: '0 2px 2px 0 rgba(0,0,0,.04),0 3px 1px -2px rgba(0,0,0,.1),0 1px 5px 0 rgba(0,0,0,.12)',
'0 2px 2px 0 rgba(0,0,0,.04),0 3px 1px -2px rgba(0,0,0,.1),0 1px 5px 0 rgba(0,0,0,.12)',
marginLeft: '10px', marginLeft: '10px',
border: '1px solid #f1f1f1', border: '1px solid #f1f1f1',
backgroundColor: 'white', backgroundColor: 'white',
@ -55,22 +40,13 @@ const Parameter = ({ set, input = {}, index }) => (
}} }}
> >
{input.type || 'string'} {input.type || 'string'}
<IconButton <IconButton name="arrow_drop_down" onClick={evt => evt.preventDefault()} />
name="arrow_drop_down"
onClick={evt => evt.preventDefault()}
/>
</span> </span>
<Menu target={`${index}-type-menu`} align="right"> <Menu target={`${index}-type-menu`} align="right">
<MenuItem onClick={() => set({ type: 'string' })}> <MenuItem onClick={() => set({ type: 'string' })}>string</MenuItem>
string <MenuItem onClick={() => set({ type: 'percentage' })}>percentage</MenuItem>
</MenuItem>
<MenuItem onClick={() => set({ type: 'percentage' })}>
percentage
</MenuItem>
<MenuItem onClick={() => set({ type: 'list' })}>list</MenuItem> <MenuItem onClick={() => set({ type: 'list' })}>list</MenuItem>
<MenuItem onClick={() => set({ type: 'number' })}> <MenuItem onClick={() => set({ type: 'number' })}>number</MenuItem>
number
</MenuItem>
</Menu> </Menu>
</div> </div>
<Textfield <Textfield
@ -95,8 +71,7 @@ const EditHeader = () => (
<div> <div>
<h4 style={{ marginTop: '16px' }}>Edit strategy</h4> <h4 style={{ marginTop: '16px' }}>Edit strategy</h4>
<p style={{ background: '#ffb7b7', padding: '16px 20px' }}> <p style={{ background: '#ffb7b7', padding: '16px 20px' }}>
Be carefull! Changing a strategy definition might also require Be carefull! Changing a strategy definition might also require changes to the implementation in the clients.
changes to the implementation in the clients.
</p> </p>
</div> </div>
); );
@ -110,12 +85,7 @@ const CreateHeader = () => (
const Parameters = ({ input = [], count = 0, updateInList }) => ( const Parameters = ({ input = [], count = 0, updateInList }) => (
<div> <div>
{gerArrayWithEntries(count).map((v, i) => ( {gerArrayWithEntries(count).map((v, i) => (
<Parameter <Parameter key={i} set={v => updateInList('parameters', i, v, true)} index={i} input={input[i]} />
key={i}
set={v => updateInList('parameters', i, v, true)}
index={i}
input={input[i]}
/>
))} ))}
</div> </div>
); );
@ -139,24 +109,13 @@ class AddStrategy extends Component {
if (this.props.initCallRequired === true) { if (this.props.initCallRequired === true) {
this.props.init(this.props.input); this.props.init(this.props.input);
if (this.props.input.parameters) { if (this.props.input.parameters) {
this.props.setValue( this.props.setValue('_params', this.props.input.parameters.length);
'_params',
this.props.input.parameters.length
);
} }
} }
} }
render() { render() {
const { const { input, setValue, updateInList, incValue, onCancel, editmode = false, onSubmit } = this.props;
input,
setValue,
updateInList,
incValue,
onCancel,
editmode = false,
onSubmit,
} = this.props;
return ( return (
<Grid className="mdl-color--white"> <Grid className="mdl-color--white">
@ -170,8 +129,7 @@ class AddStrategy extends Component {
required required
disabled={editmode} disabled={editmode}
pattern="^[0-9a-zA-Z\.\-]+$" pattern="^[0-9a-zA-Z\.\-]+$"
onChange={({ target }) => onChange={({ target }) => setValue('name', trim(target.value))}
setValue('name', trim(target.value))}
value={input.name} value={input.name}
/> />
<br /> <br />
@ -181,15 +139,10 @@ class AddStrategy extends Component {
rows={1} rows={1}
label="Description" label="Description"
name="description" name="description"
onChange={({ target }) => onChange={({ target }) => setValue('description', target.value)}
setValue('description', target.value)}
value={input.description} value={input.description}
/> />
<Parameters <Parameters input={input.parameters} count={input._params} updateInList={updateInList} />
input={input.parameters}
count={input._params}
updateInList={updateInList}
/>
<IconButton <IconButton
raised raised
name="add" name="add"
@ -202,10 +155,7 @@ class AddStrategy extends Component {
&nbsp;Add parameter &nbsp;Add parameter
<br /> <br />
<hr /> <hr />
<FormButtons <FormButtons submitText={editmode ? 'Update' : 'Create'} onCancel={onCancel} />
submitText={editmode ? 'Update' : 'Create'}
onCancel={onCancel}
/>
</form> </form>
</Cell> </Cell>
</Grid> </Grid>

View File

@ -28,19 +28,12 @@ const prepare = (methods, dispatch) => {
// clean // clean
const parameters = (input.parameters || []) const parameters = (input.parameters || [])
.filter(name => !!name) .filter(name => !!name)
.map( .map(({ name, type = 'string', description = '', required = false }) => ({
({ name,
name, type,
type = 'string', description,
description = '', required,
required = false, }));
}) => ({
name,
type,
description,
required,
})
);
updateStrategy({ updateStrategy({
name: input.name, name: input.name,

View File

@ -2,14 +2,7 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Link } from 'react-router'; import { Link } from 'react-router';
import { import { List, ListItem, ListItemContent, IconButton, Grid, Cell } from 'react-mdl';
List,
ListItem,
ListItemContent,
IconButton,
Grid,
Cell,
} from 'react-mdl';
import { HeaderTitle } from '../common'; import { HeaderTitle } from '../common';
class StrategiesListComponent extends Component { class StrategiesListComponent extends Component {
@ -33,10 +26,7 @@ class StrategiesListComponent extends Component {
<IconButton <IconButton
raised raised
name="add" name="add"
onClick={() => onClick={() => this.context.router.push('/strategies/create')}
this.context.router.push(
'/strategies/create'
)}
title="Add new strategy" title="Add new strategy"
/> />
} }
@ -45,24 +35,15 @@ class StrategiesListComponent extends Component {
{strategies.length > 0 ? ( {strategies.length > 0 ? (
strategies.map((strategy, i) => ( strategies.map((strategy, i) => (
<ListItem key={i} twoLine> <ListItem key={i} twoLine>
<ListItemContent <ListItemContent icon="extension" subtitle={strategy.description}>
icon="extension" <Link to={`/strategies/view/${strategy.name}`}>
subtitle={strategy.description}
>
<Link
to={`/strategies/view/${strategy.name}`}
>
<strong>{strategy.name}</strong> <strong>{strategy.name}</strong>
</Link> </Link>
</ListItemContent> </ListItemContent>
{strategy.editable === false ? ( {strategy.editable === false ? (
'' ''
) : ( ) : (
<IconButton <IconButton name="delete" onClick={() => removeStrategy(strategy)} />
name="delete"
onClick={() =>
removeStrategy(strategy)}
/>
)} )}
</ListItem> </ListItem>
)) ))

View File

@ -20,8 +20,6 @@ const mapDispatchToProps = dispatch => ({
fetchStrategies: () => fetchStrategies()(dispatch), fetchStrategies: () => fetchStrategies()(dispatch),
}); });
const StrategiesListContainer = connect(mapStateToProps, mapDispatchToProps)( const StrategiesListContainer = connect(mapStateToProps, mapDispatchToProps)(StrategiesListComponent);
StrategiesListComponent
);
export default StrategiesListContainer; export default StrategiesListContainer;

View File

@ -13,15 +13,8 @@ class ShowStrategyComponent extends PureComponent {
renderParameters(params) { renderParameters(params) {
if (params) { if (params) {
return params.map(({ name, type, description, required }, i) => ( return params.map(({ name, type, description, required }, i) => (
<ListItem <ListItem twoLine key={`${name}-${i}`} title={required ? 'Required' : ''}>
twoLine <ListItemContent avatar={required ? 'add' : ' '} subtitle={description}>
key={`${name}-${i}`}
title={required ? 'Required' : ''}
>
<ListItemContent
avatar={required ? 'add' : ' '}
subtitle={description}
>
{name} <small>({type})</small> {name} <small>({type})</small>
</ListItemContent> </ListItemContent>
</ListItem> </ListItem>

View File

@ -54,9 +54,7 @@ export default class StrategyDetails extends Component {
} }
render() { render() {
const activeTabId = TABS[this.props.activeTab] const activeTabId = TABS[this.props.activeTab] ? TABS[this.props.activeTab] : TABS.view;
? TABS[this.props.activeTab]
: TABS.view;
const strategy = this.props.strategy; const strategy = this.props.strategy;
if (!strategy) { if (!strategy) {
return <ProgressBar indeterminate />; return <ProgressBar indeterminate />;
@ -67,17 +65,12 @@ export default class StrategyDetails extends Component {
return ( return (
<Grid className="mdl-color--white"> <Grid className="mdl-color--white">
<Cell col={12}> <Cell col={12}>
<HeaderTitle <HeaderTitle title={strategy.name} subtitle={strategy.description} />
title={strategy.name}
subtitle={strategy.description}
/>
{strategy.editable === false ? ( {strategy.editable === false ? (
'' ''
) : ( ) : (
<Tabs activeTab={activeTabId} ripple> <Tabs activeTab={activeTabId} ripple>
<Tab onClick={() => this.goToTab('view')}> <Tab onClick={() => this.goToTab('view')}>Details</Tab>
Details
</Tab>
<Tab onClick={() => this.goToTab('edit')}>Edit</Tab> <Tab onClick={() => this.goToTab('edit')}>Edit</Tab>
</Tabs> </Tabs>
)} )}

View File

@ -5,17 +5,10 @@ import { fetchAll } from '../../store/application/actions';
import { fetchFeatureToggles } from '../../store/feature-actions'; import { fetchFeatureToggles } from '../../store/feature-actions';
const mapStateToProps = (state, props) => { const mapStateToProps = (state, props) => {
let strategy = state.strategies let strategy = state.strategies.get('list').find(n => n.name === props.strategyName);
.get('list') const applications = state.applications.get('list').filter(app => app.strategies.includes(props.strategyName));
.find(n => n.name === props.strategyName);
const applications = state.applications
.get('list')
.filter(app => app.strategies.includes(props.strategyName));
const toggles = state.features.filter( const toggles = state.features.filter(
toggle => toggle => toggle.get('strategies').findIndex(s => s.name === props.strategyName) > -1
toggle
.get('strategies')
.findIndex(s => s.name === props.strategyName) > -1
); );
return { return {

View File

@ -15,11 +15,7 @@ export default class ShowUserComponent extends React.Component {
render() { render() {
return ( return (
<a <a className="mdl-navigation__link" href="#edit-user" onClick={this.openEdit}>
className="mdl-navigation__link"
href="#edit-user"
onClick={this.openEdit}
>
<Tooltip label={this.props.user.userName || 'Unknown'} large> <Tooltip label={this.props.user.userName || 'Unknown'} large>
<Icon name="account_circle" /> <Icon name="account_circle" />
</Tooltip> </Tooltip>

View File

@ -39,25 +39,17 @@ class EditUserComponent extends React.Component {
render() { render() {
return ( return (
<div> <div>
<Modal <Modal isOpen={this.props.user.showDialog} contentLabel="test" style={customStyles}>
isOpen={this.props.user.showDialog}
contentLabel="test"
style={customStyles}
>
<h2>Action required</h2> <h2>Action required</h2>
<div> <div>
<p> <p>You have to specify a username to use Unleash. This will allow us to track your changes.</p>
You have to specify a username to use Unleash. This
will allow us to track your changes.
</p>
<form onSubmit={this.handleSubmit}> <form onSubmit={this.handleSubmit}>
<Textfield <Textfield
label="Username" label="Username"
name="username" name="username"
required required
value={this.props.user.userName} value={this.props.user.userName}
onChange={e => onChange={e => this.props.updateUserName(e.target.value)}
this.props.updateUserName(e.target.value)}
/> />
<br /> <br />
<Button raised accent> <Button raised accent>

View File

@ -4,10 +4,7 @@ const URI = 'api/admin/features';
function validateToggle(featureToggle) { function validateToggle(featureToggle) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if ( if (!featureToggle.strategies || featureToggle.strategies.length === 0) {
!featureToggle.strategies ||
featureToggle.strategies.length === 0
) {
reject(new Error('You must add at least one activation strategy')); reject(new Error('You must add at least one activation strategy'));
} else { } else {
resolve(featureToggle); resolve(featureToggle);

View File

@ -1,9 +1,7 @@
const defaultErrorMessage = 'Unexptected exception when talking to unleash-api'; const defaultErrorMessage = 'Unexptected exception when talking to unleash-api';
function extractJoiMsg(body) { function extractJoiMsg(body) {
return body.details.length > 0 return body.details.length > 0 ? body.details[0].message : defaultErrorMessage;
? body.details[0].message
: defaultErrorMessage;
} }
function extractLegacyMsg(body) { function extractLegacyMsg(body) {
return body && body.length > 0 ? body[0].msg : defaultErrorMessage; return body && body.length > 0 ? body[0].msg : defaultErrorMessage;
@ -14,10 +12,7 @@ export function throwIfNotSuccess(response) {
if (response.status > 399 && response.status < 404) { if (response.status > 399 && response.status < 404) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
response.json().then(body => { response.json().then(body => {
const errorMsg = const errorMsg = body && body.isJoi ? extractJoiMsg(body) : extractLegacyMsg(body);
body && body.isJoi
? extractJoiMsg(body)
: extractLegacyMsg(body);
let error = new Error(errorMsg); let error = new Error(errorMsg);
error.statusCode = response.status; error.statusCode = response.status;
reject(error); reject(error);

View File

@ -4,13 +4,7 @@ import 'react-mdl/extra/material.js';
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import { import { applyRouterMiddleware, Router, Route, IndexRedirect, hashHistory } from 'react-router';
applyRouterMiddleware,
Router,
Route,
IndexRedirect,
hashHistory,
} from 'react-router';
import { useScroll } from 'react-router-scroll'; import { useScroll } from 'react-router-scroll';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import thunkMiddleware from 'redux-thunk'; import thunkMiddleware from 'redux-thunk';
@ -33,60 +27,31 @@ import ApplicationView from './page/applications/view';
let composeEnhancers; let composeEnhancers;
if ( if (process.env.NODE_ENV !== 'production' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) {
process.env.NODE_ENV !== 'production' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
) {
composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__; composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__;
} else { } else {
composeEnhancers = compose; composeEnhancers = compose;
} }
const unleashStore = createStore( const unleashStore = createStore(store, composeEnhancers(applyMiddleware(thunkMiddleware)));
store,
composeEnhancers(applyMiddleware(thunkMiddleware))
);
// "pageTitle" and "link" attributes are for internal usage only // "pageTitle" and "link" attributes are for internal usage only
ReactDOM.render( ReactDOM.render(
<Provider store={unleashStore}> <Provider store={unleashStore}>
<Router <Router history={hashHistory} render={applyRouterMiddleware(useScroll())}>
history={hashHistory}
render={applyRouterMiddleware(useScroll())}
>
<Route path="/" component={App}> <Route path="/" component={App}>
<IndexRedirect to="/features" /> <IndexRedirect to="/features" />
<Route pageTitle="Feature Toggles" link="/features"> <Route pageTitle="Feature Toggles" link="/features">
<Route <Route pageTitle="Feature toggles" path="/features" component={Features} />
pageTitle="Feature toggles" <Route pageTitle="New" path="/features/create" component={CreateFeatureToggle} />
path="/features" <Route pageTitle=":name" path="/features/:activeTab/:name" component={ViewFeatureToggle} />
component={Features}
/>
<Route
pageTitle="New"
path="/features/create"
component={CreateFeatureToggle}
/>
<Route
pageTitle=":name"
path="/features/:activeTab/:name"
component={ViewFeatureToggle}
/>
</Route> </Route>
<Route pageTitle="Strategies" link="/strategies"> <Route pageTitle="Strategies" link="/strategies">
<Route <Route pageTitle="Strategies" path="/strategies" component={Strategies} />
pageTitle="Strategies" <Route pageTitle="New" path="/strategies/create" component={CreateStrategies} />
path="/strategies"
component={Strategies}
/>
<Route
pageTitle="New"
path="/strategies/create"
component={CreateStrategies}
/>
<Route <Route
pageTitle=":strategyName" pageTitle=":strategyName"
path="/strategies/:activeTab/:strategyName" path="/strategies/:activeTab/:strategyName"
@ -95,34 +60,14 @@ ReactDOM.render(
</Route> </Route>
<Route pageTitle="Event History" link="/history"> <Route pageTitle="Event History" link="/history">
<Route <Route pageTitle="Event history" path="/history" component={HistoryPage} />
pageTitle="Event history" <Route pageTitle=":toggleName" path="/history/:toggleName" component={HistoryTogglePage} />
path="/history"
component={HistoryPage}
/>
<Route
pageTitle=":toggleName"
path="/history/:toggleName"
component={HistoryTogglePage}
/>
</Route> </Route>
<Route <Route pageTitle="Archived Toggles" path="/archive" component={Archive} />
pageTitle="Archived Toggles"
path="/archive"
component={Archive}
/>
<Route pageTitle="Applications" link="/applications"> <Route pageTitle="Applications" link="/applications">
<Route <Route pageTitle="Applications" path="/applications" component={Applications} />
pageTitle="Applications" <Route pageTitle=":name" path="/applications/:name" component={ApplicationView} />
path="/applications"
component={Applications}
/>
<Route
pageTitle=":name"
path="/applications/:name"
component={ApplicationView}
/>
</Route> </Route>
</Route> </Route>
</Router> </Router>

View File

@ -2,9 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ApplicationEditComponent from '../../component/application/application-edit-container'; import ApplicationEditComponent from '../../component/application/application-edit-container';
const render = ({ params }) => ( const render = ({ params }) => <ApplicationEditComponent appName={params.name} />;
<ApplicationEditComponent appName={params.name} />
);
render.propTypes = { render.propTypes = {
params: PropTypes.object.isRequired, params: PropTypes.object.isRequired,

View File

@ -9,11 +9,6 @@ export default class Features extends PureComponent {
render() { render() {
const { params } = this.props; const { params } = this.props;
return ( return <ViewFeatureToggle featureToggleName={params.name} activeTab={params.activeTab} />;
<ViewFeatureToggle
featureToggleName={params.name}
activeTab={params.activeTab}
/>
);
} }
} }

View File

@ -2,9 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import HistoryListToggle from '../../component/history/history-list-toggle-container'; import HistoryListToggle from '../../component/history/history-list-toggle-container';
const render = ({ params }) => ( const render = ({ params }) => <HistoryListToggle toggleName={params.toggleName} />;
<HistoryListToggle toggleName={params.toggleName} />
);
render.propTypes = { render.propTypes = {
params: PropTypes.object.isRequired, params: PropTypes.object.isRequired,

View File

@ -2,12 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ShowStrategy from '../../component/strategies/strategy-details-container'; import ShowStrategy from '../../component/strategies/strategy-details-container';
const render = ({ params }) => ( const render = ({ params }) => <ShowStrategy strategyName={params.strategyName} activeTab={params.activeTab} />;
<ShowStrategy
strategyName={params.strategyName}
activeTab={params.activeTab}
/>
);
render.propTypes = { render.propTypes = {
params: PropTypes.object.isRequired, params: PropTypes.object.isRequired,

View File

@ -2,8 +2,7 @@ import api from '../../data/applications-api';
export const RECEIVE_ALL_APPLICATIONS = 'RECEIVE_ALL_APPLICATIONS'; export const RECEIVE_ALL_APPLICATIONS = 'RECEIVE_ALL_APPLICATIONS';
export const ERROR_RECEIVE_ALL_APPLICATIONS = 'ERROR_RECEIVE_ALL_APPLICATIONS'; export const ERROR_RECEIVE_ALL_APPLICATIONS = 'ERROR_RECEIVE_ALL_APPLICATIONS';
export const ERROR_UPDATING_APPLICATION_DATA = export const ERROR_UPDATING_APPLICATION_DATA = 'ERROR_UPDATING_APPLICATION_DATA';
'ERROR_UPDATING_APPLICATION_DATA';
export const RECEIVE_APPLICATION = 'RECEIVE_APPLICATION'; export const RECEIVE_APPLICATION = 'RECEIVE_APPLICATION';
@ -17,10 +16,7 @@ const recieveApplication = json => ({
value: json, value: json,
}); });
const errorReceiveApplications = ( const errorReceiveApplications = (statusCode, type = ERROR_RECEIVE_ALL_APPLICATIONS) => ({
statusCode,
type = ERROR_RECEIVE_ALL_APPLICATIONS
) => ({
type, type,
statusCode, statusCode,
}); });
@ -37,14 +33,7 @@ export function storeApplicationMetaData(appName, key, value) {
return dispatch => return dispatch =>
api api
.storeApplicationMetaData(appName, key, value) .storeApplicationMetaData(appName, key, value)
.catch(error => .catch(error => dispatch(errorReceiveApplications(error, ERROR_UPDATING_APPLICATION_DATA)));
dispatch(
errorReceiveApplications(
error,
ERROR_UPDATING_APPLICATION_DATA
)
)
);
} }
export function fetchApplication(appName) { export function fetchApplication(appName) {

View File

@ -8,10 +8,7 @@ function getInitState() {
const store = (state = getInitState(), action) => { const store = (state = getInitState(), action) => {
switch (action.type) { switch (action.type) {
case RECEIVE_APPLICATION: case RECEIVE_APPLICATION:
return state.setIn( return state.setIn(['apps', action.value.appName], new Map(action.value));
['apps', action.value.appName],
new Map(action.value)
);
case RECEIVE_ALL_APPLICATIONS: case RECEIVE_ALL_APPLICATIONS:
return state.set('list', new List(action.value.applications)); return state.set('list', new List(action.value.applications));
default: default:

View File

@ -8,9 +8,7 @@ function getInitState() {
const archiveStore = (state = getInitState(), action) => { const archiveStore = (state = getInitState(), action) => {
switch (action.type) { switch (action.type) {
case REVIVE_TOGGLE: case REVIVE_TOGGLE:
return state.update('list', list => return state.update('list', list => list.filter(item => item.name !== action.value));
list.filter(item => item.name !== action.value)
);
case RECEIVE_ARCHIVE: case RECEIVE_ARCHIVE:
return state.set('list', new List(action.value)); return state.set('list', new List(action.value));
default: default:

View File

@ -7,11 +7,7 @@ import {
ERROR_UPDATE_FEATURE_TOGGLE, ERROR_UPDATE_FEATURE_TOGGLE,
} from './feature-actions'; } from './feature-actions';
import { import { ERROR_UPDATING_STRATEGY, ERROR_CREATING_STRATEGY, ERROR_RECEIVE_STRATEGIES } from './strategy/actions';
ERROR_UPDATING_STRATEGY,
ERROR_CREATING_STRATEGY,
ERROR_RECEIVE_STRATEGIES,
} from './strategy/actions';
const debug = require('debug')('unleash:error-store'); const debug = require('debug')('unleash:error-store');
@ -40,9 +36,7 @@ const strategies = (state = getInitState(), action) => {
case ERROR_RECEIVE_STRATEGIES: case ERROR_RECEIVE_STRATEGIES:
return addErrorIfNotAlreadyInList(state, action.error.message); return addErrorIfNotAlreadyInList(state, action.error.message);
case MUTE_ERROR: case MUTE_ERROR:
return state.update('list', list => return state.update('list', list => list.remove(list.indexOf(action.error)));
list.remove(list.indexOf(action.error))
);
default: default:
return state; return state;
} }

View File

@ -85,9 +85,7 @@ export function requestUpdateFeatureToggle(featureToggle) {
return api return api
.update(featureToggle) .update(featureToggle)
.then(() => .then(() => dispatch({ type: UPDATE_FEATURE_TOGGLE, featureToggle }))
dispatch({ type: UPDATE_FEATURE_TOGGLE, featureToggle })
)
.catch(dispatchAndThrow(dispatch, ERROR_UPDATE_FEATURE_TOGGLE)); .catch(dispatchAndThrow(dispatch, ERROR_UPDATE_FEATURE_TOGGLE));
}; };
} }
@ -98,9 +96,7 @@ export function removeFeatureToggle(featureToggleName) {
return api return api
.remove(featureToggleName) .remove(featureToggleName)
.then(() => .then(() => dispatch({ type: REMOVE_FEATURE_TOGGLE, featureToggleName }))
dispatch({ type: REMOVE_FEATURE_TOGGLE, featureToggleName })
)
.catch(dispatchAndThrow(dispatch, ERROR_REMOVE_FEATURE_TOGGLE)); .catch(dispatchAndThrow(dispatch, ERROR_REMOVE_FEATURE_TOGGLE));
}; };
} }

View File

@ -1,14 +1,8 @@
import { Map as $Map, fromJS } from 'immutable'; import { Map as $Map, fromJS } from 'immutable';
import { import { RECEIVE_FEATURE_METRICS, RECEIVE_SEEN_APPS } from './feature-metrics-actions';
RECEIVE_FEATURE_METRICS,
RECEIVE_SEEN_APPS,
} from './feature-metrics-actions';
const metrics = ( const metrics = (state = fromJS({ lastHour: {}, lastMinute: {}, seenApps: {} }), action) => {
state = fromJS({ lastHour: {}, lastMinute: {}, seenApps: {} }),
action
) => {
switch (action.type) { switch (action.type) {
case RECEIVE_SEEN_APPS: case RECEIVE_SEEN_APPS:
return state.set('seenApps', new $Map(action.value)); return state.set('seenApps', new $Map(action.value));

View File

@ -16,9 +16,7 @@ const features = (state = new List([]), action) => {
return state.push(new $Map(action.featureToggle)); return state.push(new $Map(action.featureToggle));
case REMOVE_FEATURE_TOGGLE: case REMOVE_FEATURE_TOGGLE:
debug(REMOVE_FEATURE_TOGGLE, action); debug(REMOVE_FEATURE_TOGGLE, action);
return state.filter( return state.filter(toggle => toggle.get('name') !== action.featureToggleName);
toggle => toggle.get('name') !== action.featureToggleName
);
case TOGGLE_FEATURE_TOGGLE: case TOGGLE_FEATURE_TOGGLE:
debug(TOGGLE_FEATURE_TOGGLE, action); debug(TOGGLE_FEATURE_TOGGLE, action);
return state.map(toggle => { return state.map(toggle => {

View File

@ -8,10 +8,7 @@ function getInitState() {
const historyStore = (state = getInitState(), action) => { const historyStore = (state = getInitState(), action) => {
switch (action.type) { switch (action.type) {
case RECEIVE_HISTORY_FOR_TOGGLE: case RECEIVE_HISTORY_FOR_TOGGLE:
return state.setIn( return state.setIn(['toggles', action.value.toggleName], new List(action.value.events));
['toggles', action.value.toggleName],
new List(action.value.events)
);
case RECEIVE_HISTORY: case RECEIVE_HISTORY:
return state.set('list', new List(action.value)); return state.set('list', new List(action.value));
default: default:

View File

@ -9,41 +9,12 @@ export const actions = {
MOVE: 'MOVE', MOVE: 'MOVE',
}; };
export const createInit = ({ id, value }) => ({ export const createInit = ({ id, value }) => ({ type: actions.INIT, id, value });
type: actions.INIT, export const createInc = ({ id, key }) => ({ type: actions.INCREMENT_VALUE, id, key });
id, export const createSet = ({ id, key, value }) => ({ type: actions.SET_VALUE, id, key, value });
value, export const createPush = ({ id, key, value }) => ({ type: actions.LIST_PUSH, id, key, value });
}); export const createPop = ({ id, key, index }) => ({ type: actions.LIST_POP, id, key, index });
export const createInc = ({ id, key }) => ({ export const createMove = ({ id, key, index, toIndex }) => ({ type: actions.MOVE, id, key, index, toIndex });
type: actions.INCREMENT_VALUE,
id,
key,
});
export const createSet = ({ id, key, value }) => ({
type: actions.SET_VALUE,
id,
key,
value,
});
export const createPush = ({ id, key, value }) => ({
type: actions.LIST_PUSH,
id,
key,
value,
});
export const createPop = ({ id, key, index }) => ({
type: actions.LIST_POP,
id,
key,
index,
});
export const createMove = ({ id, key, index, toIndex }) => ({
type: actions.MOVE,
id,
key,
index,
toIndex,
});
export const createUp = ({ id, key, index, newValue, merge }) => ({ export const createUp = ({ id, key, index, newValue, merge }) => ({
type: actions.LIST_UP, type: actions.LIST_UP,
id, id,

View File

@ -7,5 +7,4 @@ export const updateSetting = (group, field, value) => ({
value, value,
}); });
export const updateSettingForGroup = group => (field, value) => export const updateSettingForGroup = group => (field, value) => updateSetting(group, field, value);
updateSetting(group, field, value);

View File

@ -15,10 +15,7 @@ function getInitState() {
} }
function updateSetting(state, action) { function updateSetting(state, action) {
const newState = state.updateIn( const newState = state.updateIn([action.group, action.field], () => action.value);
[action.group, action.field],
() => action.value
);
localStorage.setItem(SETTINGS, JSON.stringify(newState.toJSON())); localStorage.setItem(SETTINGS, JSON.stringify(newState.toJSON()));
return newState; return newState;

View File

@ -1,10 +1,5 @@
import { List, Map as $Map } from 'immutable'; import { List, Map as $Map } from 'immutable';
import { import { RECEIVE_STRATEGIES, REMOVE_STRATEGY, ADD_STRATEGY, UPDATE_STRATEGY } from './actions';
RECEIVE_STRATEGIES,
REMOVE_STRATEGY,
ADD_STRATEGY,
UPDATE_STRATEGY,
} from './actions';
function getInitState() { function getInitState() {
return new $Map({ list: new List() }); return new $Map({ list: new List() });

View File

@ -20,9 +20,7 @@ function readCookie() {
} }
function writeCookie(userName) { function writeCookie(userName) {
document.cookie = `${COOKIE_NAME}=${encodeURIComponent( document.cookie = `${COOKIE_NAME}=${encodeURIComponent(userName)}; expires=Thu, 18 Dec 2099 12:00:00 UTC`;
userName
)}; expires=Thu, 18 Dec 2099 12:00:00 UTC`;
} }
function getInitState() { function getInitState() {

View File

@ -54,17 +54,14 @@ module.exports = {
sourceMap: true, sourceMap: true,
modules: true, modules: true,
importLoaders: 1, importLoaders: 1,
localIdentName: localIdentName: '[name]__[local]___[hash:base64:5]',
'[name]__[local]___[hash:base64:5]',
}, },
}, },
{ {
loader: 'sass-loader', loader: 'sass-loader',
options: { options: {
// data: '@import "theme/_config.scss";', // data: '@import "theme/_config.scss";',
includePaths: [ includePaths: [path.resolve(__dirname, './src')],
path.resolve(__dirname, './src'),
],
}, },
}, },
], ],
@ -72,10 +69,7 @@ module.exports = {
}, },
{ {
test: /\.css$/, test: /\.css$/,
loader: ExtractTextPlugin.extract({ loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader' }),
fallback: 'style-loader',
use: 'css-loader',
}),
}, },
], ],
}, },

View File

@ -1253,7 +1253,15 @@ chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3:
strip-ansi "^3.0.0" strip-ansi "^3.0.0"
supports-color "^2.0.0" supports-color "^2.0.0"
chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0: chalk@^2.0.0, chalk@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.0.1.tgz#dbec49436d2ae15f536114e76d14656cdbc0f44d"
dependencies:
ansi-styles "^3.1.0"
escape-string-regexp "^1.0.5"
supports-color "^4.0.0"
chalk@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.1.0.tgz#ac5becf14fa21b99c6c92ca7a7d7cfd5b17e743e" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.1.0.tgz#ac5becf14fa21b99c6c92ca7a7d7cfd5b17e743e"
dependencies: dependencies:
@ -2465,10 +2473,14 @@ fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2:
mkdirp ">=0.5 0" mkdirp ">=0.5 0"
rimraf "2" rimraf "2"
function-bind@^1.0.2, function-bind@^1.1.0: function-bind@^1.0.2:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.0.tgz#16176714c801798e4e8f2cf7f7529467bb4a5771" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.0.tgz#16176714c801798e4e8f2cf7f7529467bb4a5771"
function-bind@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
functional-red-black-tree@^1.0.1: functional-red-black-tree@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
@ -3364,7 +3376,14 @@ js-tokens@^3.0.0:
version "3.0.2" version "3.0.2"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
js-yaml@^3.7.0, js-yaml@^3.9.1: js-yaml@^3.7.0:
version "3.9.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.9.0.tgz#4ffbbf25c2ac963b8299dc74da7e3740de1c18ce"
dependencies:
argparse "^1.0.7"
esprima "^4.0.0"
js-yaml@^3.9.1:
version "3.9.1" version "3.9.1"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.9.1.tgz#08775cebdfdd359209f0d2acd383c8f86a6904a0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.9.1.tgz#08775cebdfdd359209f0d2acd383c8f86a6904a0"
dependencies: dependencies: