mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-11 00:08:30 +01:00
feat(archive): reuse Feature
This commit is contained in:
parent
7d7ca48259
commit
98bf15bdbf
@ -192,11 +192,11 @@ export default class App extends Component {
|
|||||||
<FooterSection type="middle">
|
<FooterSection type="middle">
|
||||||
<FooterDropDownSection title="Menu">
|
<FooterDropDownSection title="Menu">
|
||||||
<FooterLinkList>
|
<FooterLinkList>
|
||||||
{createListItem('/features', 'Feature Toggles')}
|
{createListItem('/features', 'Feature Toggles', '')}
|
||||||
{createListItem('/strategies', 'Strategies')}
|
{createListItem('/strategies', 'Strategies', '')}
|
||||||
{createListItem('/history', 'Event History')}
|
{createListItem('/history', 'Event History', '')}
|
||||||
{createListItem('/archive', 'Archived Toggles')}
|
{createListItem('/archive', 'Archived Toggles', '')}
|
||||||
{createListItem('/applications', 'Applications')}
|
{createListItem('/applications', 'Applications', '')}
|
||||||
<a href="/api/admin/user/logout">Sign out</a>
|
<a href="/api/admin/user/logout">Sign out</a>
|
||||||
</FooterLinkList>
|
</FooterLinkList>
|
||||||
</FooterDropDownSection>
|
</FooterDropDownSection>
|
||||||
|
@ -1,16 +1,21 @@
|
|||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
|
import Feature from './../feature/feature-list-item-component';
|
||||||
import { Icon, Card, List, ListItem, ListItemContent, ListItemAction, Chip } from 'react-mdl';
|
import { Icon, Card, List, ListItem, ListItemContent, ListItemAction, Chip } from 'react-mdl';
|
||||||
|
//import { Textfield, Menu, MenuItem, Card, CardActions, List, Chip, MenuItemWithIcon, DropdownButton } from 'react-mdl';
|
||||||
import { styles as commonStyles } from '../common';
|
import { styles as commonStyles } from '../common';
|
||||||
import styles from './archive.scss';
|
import styles from './archive.scss';
|
||||||
|
|
||||||
class ArchiveList extends Component {
|
class ArchiveList extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
archive: PropTypes.array,
|
archive: PropTypes.array.isRequired,
|
||||||
fetchArchive: PropTypes.func,
|
fetchArchive: PropTypes.func,
|
||||||
revive: PropTypes.func,
|
featureMetrics: PropTypes.object.isRequired,
|
||||||
|
updateSetting: PropTypes.func.isRequired,
|
||||||
|
settings: PropTypes.object,
|
||||||
|
revive: PropTypes.func.optional,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -55,8 +60,86 @@ class ArchiveList extends Component {
|
|||||||
}
|
}
|
||||||
return display;
|
return display;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// render() {
|
||||||
|
// const { archive, featureMetrics, settings, revive } = this.props;
|
||||||
|
// archive.forEach(e => {
|
||||||
|
// e.reviveName = e.name;
|
||||||
|
// });
|
||||||
|
// return (
|
||||||
|
// <div>
|
||||||
|
// <div className={styles.toolbar}>
|
||||||
|
// <Textfield
|
||||||
|
// floatingLabel
|
||||||
|
// value={settings.filter}
|
||||||
|
// onChange={e => {
|
||||||
|
// this.setFilter(e.target.value);
|
||||||
|
// }}
|
||||||
|
// label="Search"
|
||||||
|
// style={{ width: '100%' }}
|
||||||
|
// />
|
||||||
|
// </div>
|
||||||
|
// <Card shadow={0} className={commonStyles.fullwidth} style={{ overflow: 'visible' }}>
|
||||||
|
// <CardActions>
|
||||||
|
// <DropdownButton id="metric" label={`Last ${settings.showLastHour ? 'hour' : 'minute'}`} />
|
||||||
|
// <Menu target="metric" onClick={() => this.toggleMetrics()} style={{ width: '168px' }}>
|
||||||
|
// <MenuItemWithIcon
|
||||||
|
// icon="hourglass_empty"
|
||||||
|
// disabled={!settings.showLastHour}
|
||||||
|
// data-target="minute"
|
||||||
|
// label="Last minute"
|
||||||
|
// />
|
||||||
|
// <MenuItemWithIcon
|
||||||
|
// icon="hourglass_full"
|
||||||
|
// disabled={settings.showLastHour}
|
||||||
|
// data-target="hour"
|
||||||
|
// label="Last hour"
|
||||||
|
// />
|
||||||
|
// </Menu>
|
||||||
|
// <DropdownButton id="sorting" label={`By ${settings.sort}`} />
|
||||||
|
// <Menu
|
||||||
|
// target="sorting"
|
||||||
|
// onClick={e => this.setSort(e.target.getAttribute('data-target'))}
|
||||||
|
// style={{ width: '168px' }}
|
||||||
|
// >
|
||||||
|
// <MenuItem disabled={settings.sort === 'name'} data-target="name">
|
||||||
|
// Name
|
||||||
|
// </MenuItem>
|
||||||
|
// <MenuItem disabled={settings.sort === 'enabled'} data-target="enabled">
|
||||||
|
// Enabled
|
||||||
|
// </MenuItem>
|
||||||
|
// <MenuItem disabled={settings.sort === 'created'} data-target="created">
|
||||||
|
// Created
|
||||||
|
// </MenuItem>
|
||||||
|
// <MenuItem disabled={settings.sort === 'strategies'} data-target="strategies">
|
||||||
|
// Strategies
|
||||||
|
// </MenuItem>
|
||||||
|
// <MenuItem disabled={settings.sort === 'metrics'} data-target="metrics">
|
||||||
|
// Metrics
|
||||||
|
// </MenuItem>
|
||||||
|
// </Menu>
|
||||||
|
// </CardActions>
|
||||||
|
// <hr />
|
||||||
|
// <List>
|
||||||
|
// {archive.map((feature, i) => (
|
||||||
|
// <Feature
|
||||||
|
// key={i}
|
||||||
|
// settings={settings}
|
||||||
|
// metricsLastHour={featureMetrics.lastHour[feature.name]}
|
||||||
|
// metricsLastMinute={featureMetrics.lastMinute[feature.name]}
|
||||||
|
// feature={feature}
|
||||||
|
// revive={revive}
|
||||||
|
// />
|
||||||
|
// ))}
|
||||||
|
// </List>
|
||||||
|
// </Card>
|
||||||
|
// </div>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { archive, revive } = this.props;
|
const { archive, featureMetrics, settings, revive } = this.props;
|
||||||
|
|
||||||
archive.forEach(e => {
|
archive.forEach(e => {
|
||||||
e.reviveName = e.name;
|
e.reviveName = e.name;
|
||||||
});
|
});
|
||||||
@ -73,57 +156,14 @@ class ArchiveList extends Component {
|
|||||||
<hr />
|
<hr />
|
||||||
<List>
|
<List>
|
||||||
{archive.map((feature, i) => (
|
{archive.map((feature, i) => (
|
||||||
<ListItem key={i} twoLine>
|
<Feature
|
||||||
<ListItemAction>
|
key={i}
|
||||||
{this.props.name && feature.name === this.props.name ? (
|
settings={settings}
|
||||||
<Icon name="keyboard_arrow_down" />
|
metricsLastHour={featureMetrics.lastHour[feature.name]}
|
||||||
) : (
|
metricsLastMinute={featureMetrics.lastMinute[feature.name]}
|
||||||
<Icon name="keyboard_arrow_right" />
|
feature={feature}
|
||||||
)}
|
revive={revive}
|
||||||
</ListItemAction>
|
/>
|
||||||
<ListItemContent>
|
|
||||||
{this.props.name && feature.name === this.props.name ? (
|
|
||||||
<Link
|
|
||||||
to={`/archive`}
|
|
||||||
className={[commonStyles.listLink, commonStyles.truncate].join(
|
|
||||||
' '
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{this.renderStrategiesInList(feature).map((strategyChip, i) => (
|
|
||||||
<span key={i}>{strategyChip}</span>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{feature.name}
|
|
||||||
<div className={'mdl-list__item-sub-title'}>
|
|
||||||
{feature.description}
|
|
||||||
</div>
|
|
||||||
<div className={'mdl-list__item-sub-title'}>
|
|
||||||
{this.renderStrategyDetail(feature)}
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
) : (
|
|
||||||
<Link
|
|
||||||
to={`/archive/${feature.name}`}
|
|
||||||
className={[commonStyles.listLink, commonStyles.truncate].join(
|
|
||||||
' '
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{feature.name}
|
|
||||||
|
|
||||||
{this.renderStrategiesInList(feature).map((strategyChip, i) => (
|
|
||||||
<span key={i}>{strategyChip}</span>
|
|
||||||
))}
|
|
||||||
|
|
||||||
<div className={'mdl-list__item-sub-title'}>
|
|
||||||
{feature.description}
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
</ListItemContent>
|
|
||||||
<ListItemAction onClick={() => revive(feature.name)}>
|
|
||||||
<Icon name="undo" />
|
|
||||||
</ListItemAction>
|
|
||||||
</ListItem>
|
|
||||||
))}
|
))}
|
||||||
</List>
|
</List>
|
||||||
</List>
|
</List>
|
||||||
|
@ -1,15 +1,80 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import ListComponent from './archive-list-component';
|
import ArchiveList from './archive-list-component';
|
||||||
import { fetchArchive, revive } from './../../store/archive-actions';
|
import { fetchArchive, revive } from './../../store/archive-actions';
|
||||||
|
import { updateSettingForGroup } from './../../store/settings/actions';
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => {
|
||||||
const archive = state.archive.get('list').toArray();
|
const featureMetrics = state.featureMetrics.toJS();
|
||||||
|
const settings = state.settings.toJS().feature || {};
|
||||||
|
let features = state.archive.get('list').toArray();
|
||||||
|
if (settings.filter) {
|
||||||
|
try {
|
||||||
|
const regex = new RegExp(settings.filter, 'i');
|
||||||
|
features = features.filter(
|
||||||
|
feature =>
|
||||||
|
regex.test(feature.name) ||
|
||||||
|
regex.test(feature.description) ||
|
||||||
|
feature.strategies.some(s => s && s.name && regex.test(s.name))
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
// Invalid filter regex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!settings.sort) {
|
||||||
|
settings.sort = 'name';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.sort === 'enabled') {
|
||||||
|
features = features.sort(
|
||||||
|
(a, b) =>
|
||||||
|
// eslint-disable-next-line
|
||||||
|
a.enabled === b.enabled ? 0 : a.enabled ? -1 : 1
|
||||||
|
);
|
||||||
|
} else if (settings.sort === 'created') {
|
||||||
|
features = features.sort((a, b) => (new Date(a.createdAt) > new Date(b.createdAt) ? -1 : 1));
|
||||||
|
} else if (settings.sort === 'name') {
|
||||||
|
features = features.sort((a, b) => {
|
||||||
|
if (a.name < b.name) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (a.name > b.name) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
} else if (settings.sort === 'strategies') {
|
||||||
|
features = features.sort((a, b) => (a.strategies.length > b.strategies.length ? -1 : 1));
|
||||||
|
} else if (settings.sort === 'metrics') {
|
||||||
|
const target = settings.showLastHour ? featureMetrics.lastHour : featureMetrics.lastMinute;
|
||||||
|
|
||||||
|
features = features.sort((a, b) => {
|
||||||
|
if (!target[a.name]) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (!target[b.name]) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (target[a.name].yes > target[b.name].yes) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
archive,
|
archive: features,
|
||||||
|
featureMetrics,
|
||||||
|
settings,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const ArchiveListContainer = connect(mapStateToProps, { fetchArchive, revive })(ListComponent);
|
const mapDispatchToProps = {
|
||||||
|
fetchArchive,
|
||||||
|
revive,
|
||||||
|
updateSetting: updateSettingForGroup('feature'),
|
||||||
|
};
|
||||||
|
|
||||||
|
const ArchiveListContainer = connect(mapStateToProps, mapDispatchToProps)(ArchiveList);
|
||||||
|
|
||||||
export default ArchiveListContainer;
|
export default ArchiveListContainer;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
import { Switch, Chip, ListItem } from 'react-mdl';
|
import { Switch, Chip, ListItem, ListItemAction, Icon } from 'react-mdl';
|
||||||
import Progress from './progress';
|
import Progress from './progress';
|
||||||
import { calc, styles as commonStyles } from '../common';
|
import { calc, styles as commonStyles } from '../common';
|
||||||
|
|
||||||
@ -13,6 +13,7 @@ const Feature = ({
|
|||||||
settings,
|
settings,
|
||||||
metricsLastHour = { yes: 0, no: 0, isFallback: true },
|
metricsLastHour = { yes: 0, no: 0, isFallback: true },
|
||||||
metricsLastMinute = { yes: 0, no: 0, isFallback: true },
|
metricsLastMinute = { yes: 0, no: 0, isFallback: true },
|
||||||
|
revive,
|
||||||
}) => {
|
}) => {
|
||||||
const { name, description, enabled, strategies } = feature;
|
const { name, description, enabled, strategies } = feature;
|
||||||
|
|
||||||
@ -42,6 +43,7 @@ const Feature = ({
|
|||||||
<span className={styles.listItemMetric}>
|
<span className={styles.listItemMetric}>
|
||||||
<Progress strokeWidth={15} percentage={percent} isFallback={isStale} />
|
<Progress strokeWidth={15} percentage={percent} isFallback={isStale} />
|
||||||
</span>
|
</span>
|
||||||
|
{toggleFeature ? ( // display feature list
|
||||||
<span className={styles.listItemToggle}>
|
<span className={styles.listItemToggle}>
|
||||||
<Switch
|
<Switch
|
||||||
title={`Toggle ${name}`}
|
title={`Toggle ${name}`}
|
||||||
@ -50,6 +52,10 @@ const Feature = ({
|
|||||||
checked={enabled}
|
checked={enabled}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
|
) : (
|
||||||
|
// display archive
|
||||||
|
<span />
|
||||||
|
)}
|
||||||
<span className={['mdl-list__item-primary-content', styles.listItemLink].join(' ')}>
|
<span className={['mdl-list__item-primary-content', styles.listItemLink].join(' ')}>
|
||||||
<Link
|
<Link
|
||||||
to={`/features/strategies/${name}`}
|
to={`/features/strategies/${name}`}
|
||||||
@ -63,6 +69,13 @@ const Feature = ({
|
|||||||
{strategyChips}
|
{strategyChips}
|
||||||
{summaryChip}
|
{summaryChip}
|
||||||
</span>
|
</span>
|
||||||
|
{revive ? (
|
||||||
|
<ListItemAction onClick={() => revive(feature.name)}>
|
||||||
|
<Icon name="undo" />
|
||||||
|
</ListItemAction>
|
||||||
|
) : (
|
||||||
|
<span />
|
||||||
|
)}
|
||||||
</ListItem>
|
</ListItem>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -73,6 +86,7 @@ Feature.propTypes = {
|
|||||||
settings: PropTypes.object,
|
settings: PropTypes.object,
|
||||||
metricsLastHour: PropTypes.object,
|
metricsLastHour: PropTypes.object,
|
||||||
metricsLastMinute: PropTypes.object,
|
metricsLastMinute: PropTypes.object,
|
||||||
|
revive: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Feature;
|
export default Feature;
|
||||||
|
@ -13,7 +13,7 @@ export default class FeatureListComponent extends React.PureComponent {
|
|||||||
featureMetrics: PropTypes.object.isRequired,
|
featureMetrics: PropTypes.object.isRequired,
|
||||||
fetchFeatureToggles: PropTypes.func.isRequired,
|
fetchFeatureToggles: PropTypes.func.isRequired,
|
||||||
updateSetting: PropTypes.func.isRequired,
|
updateSetting: PropTypes.func.isRequired,
|
||||||
toggleFeature: PropTypes.func.isRequired,
|
toggleFeature: PropTypes.func,
|
||||||
settings: PropTypes.object,
|
settings: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user