mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-31 00:16:47 +01:00
Merge pull request #121 from corinnekrych/archive.revisited
Archive.revisited
This commit is contained in:
commit
4b64762671
1
frontend/src/__mocks__/react-mdl.js
vendored
1
frontend/src/__mocks__/react-mdl.js
vendored
@ -1,5 +1,6 @@
|
||||
module.exports = {
|
||||
Card: 'react-mdl-Card',
|
||||
CardActions: 'react-mdl-CardActions',
|
||||
CardTitle: 'react-mdl-CardTitle',
|
||||
CardText: 'react-mdl-CardText',
|
||||
CardMenu: 'react-mdl-CardMenu',
|
||||
|
@ -192,11 +192,11 @@ export default class App extends Component {
|
||||
<FooterSection type="middle">
|
||||
<FooterDropDownSection title="Menu">
|
||||
<FooterLinkList>
|
||||
{createListItem('/features', 'Feature Toggles')}
|
||||
{createListItem('/strategies', 'Strategies')}
|
||||
{createListItem('/history', 'Event History')}
|
||||
{createListItem('/archive', 'Archived Toggles')}
|
||||
{createListItem('/applications', 'Applications')}
|
||||
{createListItem('/features', 'Feature Toggles', '')}
|
||||
{createListItem('/strategies', 'Strategies', '')}
|
||||
{createListItem('/history', 'Event History', '')}
|
||||
{createListItem('/archive', 'Archived Toggles', '')}
|
||||
{createListItem('/applications', 'Applications', '')}
|
||||
<a href="/api/admin/user/logout">Sign out</a>
|
||||
</FooterLinkList>
|
||||
</FooterDropDownSection>
|
||||
|
@ -1,5 +0,0 @@
|
||||
{
|
||||
"env": {
|
||||
"jest": true
|
||||
}
|
||||
}
|
@ -1,149 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly with archived toggles 1`] = `
|
||||
<react-mdl-Card
|
||||
className="fullwidth"
|
||||
shadow={0}
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"position": "relative",
|
||||
}
|
||||
}
|
||||
>
|
||||
<react-mdl-List>
|
||||
<react-mdl-ListItem
|
||||
className="archiveList"
|
||||
>
|
||||
<span
|
||||
className="listItemToggle"
|
||||
>
|
||||
Toggle name
|
||||
</span>
|
||||
<span
|
||||
className="listItemRevive"
|
||||
>
|
||||
Revive
|
||||
</span>
|
||||
</react-mdl-ListItem>
|
||||
<hr />
|
||||
<react-mdl-List>
|
||||
<react-mdl-ListItem
|
||||
twoLine={true}
|
||||
>
|
||||
<react-mdl-ListItemAction>
|
||||
<react-mdl-Icon
|
||||
name="keyboard_arrow_right"
|
||||
/>
|
||||
</react-mdl-ListItemAction>
|
||||
<react-mdl-ListItemContent>
|
||||
<a
|
||||
className="listLink truncate"
|
||||
onClick={[Function]}
|
||||
style={Object {}}
|
||||
>
|
||||
adin-pay-confirm-disabled
|
||||
<span>
|
||||
<span
|
||||
className="strategiesList hideLt920"
|
||||
>
|
||||
<react-mdl-Chip
|
||||
className="strategyChip"
|
||||
>
|
||||
default
|
||||
</react-mdl-Chip>
|
||||
</span>
|
||||
</span>
|
||||
<div
|
||||
className="mdl-list__item-sub-title"
|
||||
>
|
||||
Disables the confirm-functionality from API
|
||||
</div>
|
||||
</a>
|
||||
</react-mdl-ListItemContent>
|
||||
<react-mdl-ListItemAction
|
||||
onClick={[Function]}
|
||||
>
|
||||
<react-mdl-Icon
|
||||
name="undo"
|
||||
/>
|
||||
</react-mdl-ListItemAction>
|
||||
</react-mdl-ListItem>
|
||||
<react-mdl-ListItem
|
||||
twoLine={true}
|
||||
>
|
||||
<react-mdl-ListItemAction>
|
||||
<react-mdl-Icon
|
||||
name="keyboard_arrow_right"
|
||||
/>
|
||||
</react-mdl-ListItemAction>
|
||||
<react-mdl-ListItemContent>
|
||||
<a
|
||||
className="listLink truncate"
|
||||
onClick={[Function]}
|
||||
style={Object {}}
|
||||
>
|
||||
adin-pay-platform-sch-payment
|
||||
<span>
|
||||
<span
|
||||
className="strategiesList hideLt920"
|
||||
>
|
||||
<react-mdl-Chip
|
||||
className="strategyChip"
|
||||
>
|
||||
default
|
||||
</react-mdl-Chip>
|
||||
</span>
|
||||
</span>
|
||||
<div
|
||||
className="mdl-list__item-sub-title"
|
||||
>
|
||||
Enables use of schibsted payment from order-payment-management
|
||||
</div>
|
||||
</a>
|
||||
</react-mdl-ListItemContent>
|
||||
<react-mdl-ListItemAction
|
||||
onClick={[Function]}
|
||||
>
|
||||
<react-mdl-Icon
|
||||
name="undo"
|
||||
/>
|
||||
</react-mdl-ListItemAction>
|
||||
</react-mdl-ListItem>
|
||||
</react-mdl-List>
|
||||
</react-mdl-List>
|
||||
</div>
|
||||
</div>
|
||||
</react-mdl-Card>
|
||||
`;
|
||||
|
||||
exports[`renders correctly with no archived toggles 1`] = `
|
||||
<react-mdl-Card
|
||||
className="fullwidth"
|
||||
shadow={0}
|
||||
>
|
||||
<div
|
||||
className="emptyState"
|
||||
>
|
||||
<react-mdl-Icon
|
||||
className="mdl-color-text--grey-300"
|
||||
name="archive"
|
||||
style={
|
||||
Object {
|
||||
"fontSize": "56px",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<br />
|
||||
No archived feature toggles, go see
|
||||
<a
|
||||
onClick={[Function]}
|
||||
style={Object {}}
|
||||
>
|
||||
active toggles here
|
||||
</a>
|
||||
</div>
|
||||
</react-mdl-Card>
|
||||
`;
|
@ -1,35 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import ArchiveList from '../archive-list-component';
|
||||
import renderer from 'react-test-renderer';
|
||||
|
||||
jest.mock('react-mdl');
|
||||
|
||||
const archive = [
|
||||
{
|
||||
name: 'adin-pay-confirm-disabled',
|
||||
description: 'Disables the confirm-functionality from API',
|
||||
enabled: false,
|
||||
strategies: [{ name: 'default', parameters: {} }],
|
||||
createdAt: '2016-10-25T15:38:28.573Z',
|
||||
reviveName: 'adin-pay-confirm-disabled',
|
||||
},
|
||||
{
|
||||
name: 'adin-pay-platform-sch-payment',
|
||||
description: 'Enables use of schibsted payment from order-payment-management',
|
||||
enabled: true,
|
||||
strategies: [{ name: 'default', parameters: {} }],
|
||||
createdAt: '2016-08-03T12:41:35.631Z',
|
||||
reviveName: 'adin-pay-platform-sch-payment',
|
||||
},
|
||||
];
|
||||
|
||||
test('renders correctly with no archived toggles', () => {
|
||||
const tree = renderer.create(<ArchiveList fetchArchive={jest.fn()} archive={[]} />).toJSON();
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('renders correctly with archived toggles', () => {
|
||||
const tree = renderer.create(<ArchiveList fetchArchive={jest.fn()} archive={archive} />).toJSON();
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
@ -1,144 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link } from 'react-router';
|
||||
import { Icon, Card, List, ListItem, ListItemContent, ListItemAction, Chip } from 'react-mdl';
|
||||
import { styles as commonStyles } from '../common';
|
||||
import styles from './archive.scss';
|
||||
|
||||
class ArchiveList extends Component {
|
||||
static propTypes = {
|
||||
name: PropTypes.string,
|
||||
archive: PropTypes.array,
|
||||
fetchArchive: PropTypes.func,
|
||||
revive: PropTypes.func,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.props.fetchArchive();
|
||||
}
|
||||
renderStrategyDetail(feature) {
|
||||
let strategiesList = (
|
||||
<span>
|
||||
{feature.strategies.map((s, i) => (
|
||||
<span style={{ marginLeft: `8px` }} key={i}>
|
||||
<strong>{s.name}</strong>
|
||||
{Object.keys(s.parameters).map((p, j) => <i key={j}> {s.parameters[p]}</i>)}
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
);
|
||||
|
||||
return strategiesList;
|
||||
}
|
||||
renderStrategiesInList(feature) {
|
||||
let display = [];
|
||||
if (feature.strategies && feature.strategies.length > 0) {
|
||||
const strategiesToShow = Math.min(feature.strategies.length, 3);
|
||||
const remainingStrategies = feature.strategies.length - strategiesToShow;
|
||||
|
||||
const strategyChips =
|
||||
feature.strategies &&
|
||||
feature.strategies.slice(0, strategiesToShow).map((s, i) => (
|
||||
<span key={i} className={[styles.strategiesList, commonStyles.hideLt920].join(' ')}>
|
||||
<Chip className={styles.strategyChip}>{s.name}</Chip>
|
||||
</span>
|
||||
));
|
||||
const remaining = (
|
||||
<span className={[styles.strategiesList, commonStyles.hideLt920].join(' ')}>
|
||||
<Chip className={styles.strategyChip}>+{remainingStrategies}</Chip>
|
||||
</span>
|
||||
);
|
||||
if (remainingStrategies > 0) {
|
||||
display.push(remaining);
|
||||
}
|
||||
display.push(strategyChips);
|
||||
}
|
||||
return display;
|
||||
}
|
||||
render() {
|
||||
const { archive, revive } = this.props;
|
||||
archive.forEach(e => {
|
||||
e.reviveName = e.name;
|
||||
});
|
||||
return (
|
||||
<Card shadow={0} className={commonStyles.fullwidth}>
|
||||
{archive && archive.length > 0 ? (
|
||||
<div>
|
||||
<div style={{ position: 'relative' }}>
|
||||
<List>
|
||||
<ListItem className={styles.archiveList}>
|
||||
<span className={styles.listItemToggle}>Toggle name</span>
|
||||
<span className={styles.listItemRevive}>Revive</span>
|
||||
</ListItem>
|
||||
<hr />
|
||||
<List>
|
||||
{archive.map((feature, i) => (
|
||||
<ListItem key={i} twoLine>
|
||||
<ListItemAction>
|
||||
{this.props.name && feature.name === this.props.name ? (
|
||||
<Icon name="keyboard_arrow_down" />
|
||||
) : (
|
||||
<Icon name="keyboard_arrow_right" />
|
||||
)}
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className={commonStyles.emptyState}>
|
||||
<Icon name="archive" className="mdl-color-text--grey-300" style={{ fontSize: '56px' }} />
|
||||
<br />
|
||||
No archived feature toggles, go see <Link to="/features">active toggles here</Link>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ArchiveList;
|
@ -1,15 +1,16 @@
|
||||
import { connect } from 'react-redux';
|
||||
import ListComponent from './archive-list-component';
|
||||
import FeatureListComponent from './../feature/list-component';
|
||||
import { fetchArchive, revive } from './../../store/archive-actions';
|
||||
import { updateSettingForGroup } from './../../store/settings/actions';
|
||||
import { mapStateToPropsConfigurable } from '../feature/list-container';
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const archive = state.archive.get('list').toArray();
|
||||
|
||||
return {
|
||||
archive,
|
||||
};
|
||||
const mapStateToProps = mapStateToPropsConfigurable(false);
|
||||
const mapDispatchToProps = {
|
||||
fetchArchive,
|
||||
revive,
|
||||
updateSetting: updateSettingForGroup('feature'),
|
||||
};
|
||||
|
||||
const ArchiveListContainer = connect(mapStateToProps, { fetchArchive, revive })(ListComponent);
|
||||
const ArchiveListContainer = connect(mapStateToProps, mapDispatchToProps)(FeatureListComponent);
|
||||
|
||||
export default ArchiveListContainer;
|
||||
|
18
frontend/src/component/archive/view-container.js
Normal file
18
frontend/src/component/archive/view-container.js
Normal file
@ -0,0 +1,18 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { fetchArchive, revive } from './../../store/archive-actions';
|
||||
import ViewToggleComponent from './../feature/view-component';
|
||||
|
||||
export default connect(
|
||||
(state, props) => ({
|
||||
features: state.archive.get('list').toArray(),
|
||||
featureToggle: state.archive
|
||||
.get('list')
|
||||
.toArray()
|
||||
.find(toggle => toggle.name === props.featureToggleName),
|
||||
activeTab: props.activeTab,
|
||||
}),
|
||||
{
|
||||
fetchArchive,
|
||||
revive,
|
||||
}
|
||||
)(ViewToggleComponent);
|
@ -22,6 +22,7 @@ exports[`renders correctly with one feature 1`] = `
|
||||
>
|
||||
<react-mdl-Switch
|
||||
checked={false}
|
||||
disabled={true}
|
||||
onChange={[Function]}
|
||||
title="Toggle Another"
|
||||
/>
|
||||
@ -51,5 +52,6 @@ exports[`renders correctly with one feature 1`] = `
|
||||
gradualRolloutRandom
|
||||
</react-mdl-Chip>
|
||||
</span>
|
||||
<span />
|
||||
</react-mdl-ListItem>
|
||||
`;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
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 { calc, styles as commonStyles } from '../common';
|
||||
|
||||
@ -13,12 +13,11 @@ const Feature = ({
|
||||
settings,
|
||||
metricsLastHour = { yes: 0, no: 0, isFallback: true },
|
||||
metricsLastMinute = { yes: 0, no: 0, isFallback: true },
|
||||
revive,
|
||||
}) => {
|
||||
const { name, description, enabled, strategies } = feature;
|
||||
|
||||
const { showLastHour = false } = settings;
|
||||
const isStale = showLastHour ? metricsLastHour.isFallback : metricsLastMinute.isFallback;
|
||||
|
||||
const percent =
|
||||
1 *
|
||||
(showLastHour
|
||||
@ -27,7 +26,6 @@ const Feature = ({
|
||||
|
||||
const strategiesToShow = Math.min(strategies.length, 3);
|
||||
const remainingStrategies = strategies.length - strategiesToShow;
|
||||
|
||||
const strategyChips =
|
||||
strategies &&
|
||||
strategies.slice(0, strategiesToShow).map((s, i) => (
|
||||
@ -36,7 +34,7 @@ const Feature = ({
|
||||
</Chip>
|
||||
));
|
||||
const summaryChip = remainingStrategies > 0 && <Chip className={styles.strategyChip}>+{remainingStrategies}</Chip>;
|
||||
|
||||
const featureUrl = toggleFeature === undefined ? `/archive/strategies/${name}` : `/features/strategies/${name}`;
|
||||
return (
|
||||
<ListItem twoLine>
|
||||
<span className={styles.listItemMetric}>
|
||||
@ -44,6 +42,7 @@ const Feature = ({
|
||||
</span>
|
||||
<span className={styles.listItemToggle}>
|
||||
<Switch
|
||||
disabled={toggleFeature !== undefined}
|
||||
title={`Toggle ${name}`}
|
||||
key="left-actions"
|
||||
onChange={() => toggleFeature(name)}
|
||||
@ -51,10 +50,7 @@ const Feature = ({
|
||||
/>
|
||||
</span>
|
||||
<span className={['mdl-list__item-primary-content', styles.listItemLink].join(' ')}>
|
||||
<Link
|
||||
to={`/features/strategies/${name}`}
|
||||
className={[commonStyles.listLink, commonStyles.truncate].join(' ')}
|
||||
>
|
||||
<Link to={featureUrl} className={[commonStyles.listLink, commonStyles.truncate].join(' ')}>
|
||||
{name}
|
||||
<span className={['mdl-list__item-sub-title', commonStyles.truncate].join(' ')}>{description}</span>
|
||||
</Link>
|
||||
@ -63,6 +59,13 @@ const Feature = ({
|
||||
{strategyChips}
|
||||
{summaryChip}
|
||||
</span>
|
||||
{revive ? (
|
||||
<ListItemAction onClick={() => revive(feature.name)}>
|
||||
<Icon name="undo" />
|
||||
</ListItemAction>
|
||||
) : (
|
||||
<span />
|
||||
)}
|
||||
</ListItem>
|
||||
);
|
||||
};
|
||||
@ -73,6 +76,7 @@ Feature.propTypes = {
|
||||
settings: PropTypes.object,
|
||||
metricsLastHour: PropTypes.object,
|
||||
metricsLastMinute: PropTypes.object,
|
||||
revive: PropTypes.func,
|
||||
};
|
||||
|
||||
export default Feature;
|
||||
|
@ -45,7 +45,7 @@ const prepare = (methods, dispatch) => {
|
||||
methods.onCancel = evt => {
|
||||
evt.preventDefault();
|
||||
methods.clear();
|
||||
window.history.back();
|
||||
hashHistory.push(`/features`);
|
||||
};
|
||||
|
||||
methods.addStrategy = v => {
|
||||
|
@ -0,0 +1,30 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import StrategiesSection from './strategies-section-container';
|
||||
import { Button, Icon } from 'react-mdl';
|
||||
|
||||
class ViewFeatureComponent extends Component {
|
||||
render() {
|
||||
const { input, onCancel } = this.props;
|
||||
const configuredStrategies = input.strategies || [];
|
||||
|
||||
return (
|
||||
<section style={{ padding: '16px' }}>
|
||||
<StrategiesSection configuredStrategies={configuredStrategies} />
|
||||
<br />
|
||||
<Button type="cancel" ripple raised onClick={onCancel} style={{ float: 'right' }}>
|
||||
<Icon name="cancel" /> Cancel
|
||||
</Button>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ViewFeatureComponent.propTypes = {
|
||||
input: PropTypes.object,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
initCallRequired: PropTypes.bool,
|
||||
init: PropTypes.func,
|
||||
};
|
||||
|
||||
export default ViewFeatureComponent;
|
@ -0,0 +1,40 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { createMapper, createActions } from '../../input-helpers';
|
||||
import ViewFeatureToggleComponent from './form-view-feature-component';
|
||||
import { hashHistory } from 'react-router';
|
||||
|
||||
const ID = 'view-feature-toggle';
|
||||
function getId(props) {
|
||||
return [ID, props.featureToggle.name];
|
||||
}
|
||||
// TODO: need to scope to the active featureToggle
|
||||
// best is to emulate the "input-storage"?
|
||||
const mapStateToProps = createMapper({
|
||||
id: getId,
|
||||
getDefault: (state, ownProps) => {
|
||||
ownProps.featureToggle.strategies.forEach((strategy, index) => {
|
||||
strategy.id = Math.round(Math.random() * 1000000 * (1 + index));
|
||||
});
|
||||
return ownProps.featureToggle;
|
||||
},
|
||||
prepare: props => {
|
||||
props.editmode = true;
|
||||
return props;
|
||||
},
|
||||
});
|
||||
|
||||
const prepare = methods => {
|
||||
methods.onCancel = evt => {
|
||||
evt.preventDefault();
|
||||
methods.clear();
|
||||
hashHistory.push(`/archive`);
|
||||
};
|
||||
return methods;
|
||||
};
|
||||
|
||||
const actions = createActions({
|
||||
id: getId,
|
||||
prepare,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, actions)(ViewFeatureToggleComponent);
|
@ -5,7 +5,7 @@ import { Menu, MenuItem, IconButton } from 'react-mdl';
|
||||
class AddStrategy extends React.Component {
|
||||
static propTypes = {
|
||||
strategies: PropTypes.array.isRequired,
|
||||
addStrategy: PropTypes.func.isRequired,
|
||||
addStrategy: PropTypes.func,
|
||||
fetchStrategies: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
|
@ -9,9 +9,9 @@ class StrategiesList extends React.Component {
|
||||
static propTypes = {
|
||||
strategies: PropTypes.array.isRequired,
|
||||
configuredStrategies: PropTypes.array.isRequired,
|
||||
updateStrategy: PropTypes.func.isRequired,
|
||||
removeStrategy: PropTypes.func.isRequired,
|
||||
moveStrategy: PropTypes.func.isRequired,
|
||||
updateStrategy: PropTypes.func,
|
||||
removeStrategy: PropTypes.func,
|
||||
moveStrategy: PropTypes.func,
|
||||
};
|
||||
|
||||
render() {
|
||||
@ -27,8 +27,8 @@ class StrategiesList extends React.Component {
|
||||
key={strategy.id}
|
||||
strategy={strategy}
|
||||
moveStrategy={moveStrategy}
|
||||
removeStrategy={removeStrategy.bind(null, i)}
|
||||
updateStrategy={updateStrategy.bind(null, i)}
|
||||
removeStrategy={removeStrategy ? removeStrategy.bind(null, i) : null}
|
||||
updateStrategy={updateStrategy ? updateStrategy.bind(null, i) : null}
|
||||
strategyDefinition={strategies.find(s => s.name === strategy.name)}
|
||||
/>
|
||||
));
|
||||
|
@ -8,9 +8,9 @@ import { HeaderTitle } from '../../common';
|
||||
class StrategiesSectionComponent extends React.Component {
|
||||
static propTypes = {
|
||||
strategies: PropTypes.array.isRequired,
|
||||
addStrategy: PropTypes.func.isRequired,
|
||||
removeStrategy: PropTypes.func.isRequired,
|
||||
updateStrategy: PropTypes.func.isRequired,
|
||||
addStrategy: PropTypes.func,
|
||||
removeStrategy: PropTypes.func,
|
||||
updateStrategy: PropTypes.func,
|
||||
fetchStrategies: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
@ -25,7 +25,11 @@ class StrategiesSectionComponent extends React.Component {
|
||||
|
||||
return (
|
||||
<div>
|
||||
{this.props.addStrategy ? (
|
||||
<HeaderTitle title="Activation strategies" actions={<AddStrategy {...this.props} />} />
|
||||
) : (
|
||||
<span />
|
||||
)}
|
||||
<StrategiesList {...this.props} />
|
||||
</div>
|
||||
);
|
||||
|
@ -46,10 +46,10 @@ class StrategyConfigure extends React.Component {
|
||||
/* eslint-enable */
|
||||
static propTypes = {
|
||||
strategy: PropTypes.object.isRequired,
|
||||
strategyDefinition: PropTypes.object.isRequired,
|
||||
updateStrategy: PropTypes.func.isRequired,
|
||||
removeStrategy: PropTypes.func.isRequired,
|
||||
moveStrategy: PropTypes.func.isRequired,
|
||||
strategyDefinition: PropTypes.object,
|
||||
updateStrategy: PropTypes.func,
|
||||
removeStrategy: PropTypes.func,
|
||||
moveStrategy: PropTypes.func,
|
||||
isDragging: PropTypes.bool.isRequired,
|
||||
connectDragPreview: PropTypes.func.isRequired,
|
||||
connectDragSource: PropTypes.func.isRequired,
|
||||
@ -170,7 +170,11 @@ class StrategyConfigure extends React.Component {
|
||||
<Link title="View strategy" to={`/strategies/view/${name}`} className={styles.editLink}>
|
||||
<Icon name="link" />
|
||||
</Link>
|
||||
{this.props.removeStrategy ? (
|
||||
<IconButton title="Remove strategy from toggle" name="delete" onClick={this.handleRemove} />
|
||||
) : (
|
||||
<span />
|
||||
)}
|
||||
{connectDragSource(
|
||||
<span className={styles.reorderIcon}>
|
||||
<Icon name="reorder" />
|
||||
|
@ -7,13 +7,15 @@ import { Icon, FABButton, Textfield, Menu, MenuItem, Card, CardActions, List } f
|
||||
import { MenuItemWithIcon, DropdownButton, styles as commonStyles } from '../common';
|
||||
import styles from './feature.scss';
|
||||
|
||||
export default class FeatureListComponent extends React.PureComponent {
|
||||
export default class FeatureListComponent extends React.Component {
|
||||
static propTypes = {
|
||||
features: PropTypes.array.isRequired,
|
||||
featureMetrics: PropTypes.object.isRequired,
|
||||
fetchFeatureToggles: PropTypes.func.isRequired,
|
||||
fetchFeatureToggles: PropTypes.func,
|
||||
fetchArchive: PropTypes.func,
|
||||
revive: PropTypes.func,
|
||||
updateSetting: PropTypes.func.isRequired,
|
||||
toggleFeature: PropTypes.func.isRequired,
|
||||
toggleFeature: PropTypes.func,
|
||||
settings: PropTypes.object,
|
||||
};
|
||||
|
||||
@ -22,7 +24,11 @@ export default class FeatureListComponent extends React.PureComponent {
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.fetchFeatureToggles) {
|
||||
this.props.fetchFeatureToggles();
|
||||
} else {
|
||||
this.props.fetchArchive();
|
||||
}
|
||||
}
|
||||
|
||||
toggleMetrics() {
|
||||
@ -38,8 +44,10 @@ export default class FeatureListComponent extends React.PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { features, toggleFeature, featureMetrics, settings } = this.props;
|
||||
|
||||
const { features, toggleFeature, featureMetrics, settings, revive } = this.props;
|
||||
features.forEach(e => {
|
||||
e.reviveName = e.name;
|
||||
});
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.toolbar}>
|
||||
@ -108,6 +116,7 @@ export default class FeatureListComponent extends React.PureComponent {
|
||||
metricsLastMinute={featureMetrics.lastMinute[feature.name]}
|
||||
feature={feature}
|
||||
toggleFeature={toggleFeature}
|
||||
revive={revive}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
|
@ -4,10 +4,10 @@ import { updateSettingForGroup } from '../../store/settings/actions';
|
||||
|
||||
import FeatureListComponent from './list-component';
|
||||
|
||||
const mapStateToProps = state => {
|
||||
export const mapStateToPropsConfigurable = isFeature => state => {
|
||||
const featureMetrics = state.featureMetrics.toJS();
|
||||
const settings = state.settings.toJS().feature || {};
|
||||
let features = state.features.toJS();
|
||||
let features = isFeature ? state.features.toJS() : state.archive.get('list').toArray();
|
||||
if (settings.filter) {
|
||||
try {
|
||||
const regex = new RegExp(settings.filter, 'i');
|
||||
@ -69,7 +69,7 @@ const mapStateToProps = state => {
|
||||
settings,
|
||||
};
|
||||
};
|
||||
|
||||
const mapStateToProps = mapStateToPropsConfigurable(true);
|
||||
const mapDispatchToProps = {
|
||||
toggleFeature,
|
||||
fetchFeatureToggles,
|
||||
|
@ -6,6 +6,7 @@ import { hashHistory, Link } from 'react-router';
|
||||
import HistoryComponent from '../history/history-list-toggle-container';
|
||||
import MetricComponent from './metric-container';
|
||||
import EditFeatureToggle from './form/form-update-feature-container';
|
||||
import ViewFeatureToggle from './form/form-view-feature-container';
|
||||
import { styles as commonStyles } from '../common';
|
||||
|
||||
const TABS = {
|
||||
@ -15,24 +16,32 @@ const TABS = {
|
||||
};
|
||||
|
||||
export default class ViewFeatureToggleComponent extends React.Component {
|
||||
isFeatureView;
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.isFeatureView = !!props.fetchFeatureToggles;
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
activeTab: PropTypes.string.isRequired,
|
||||
featureToggleName: PropTypes.string.isRequired,
|
||||
features: PropTypes.array.isRequired,
|
||||
toggleFeature: PropTypes.func.isRequired,
|
||||
removeFeatureToggle: PropTypes.func.isRequired,
|
||||
fetchFeatureToggles: PropTypes.func.isRequired,
|
||||
editFeatureToggle: PropTypes.func.isRequired,
|
||||
toggleFeature: PropTypes.func,
|
||||
removeFeatureToggle: PropTypes.func,
|
||||
revive: PropTypes.func,
|
||||
fetchArchive: PropTypes.func,
|
||||
fetchFeatureToggles: PropTypes.func,
|
||||
editFeatureToggle: PropTypes.func,
|
||||
featureToggle: PropTypes.object,
|
||||
};
|
||||
|
||||
componentWillMount() {
|
||||
if (this.props.features.length === 0) {
|
||||
if (this.isFeatureView) {
|
||||
this.props.fetchFeatureToggles();
|
||||
} else {
|
||||
this.props.fetchArchive();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,14 +51,18 @@ export default class ViewFeatureToggleComponent extends React.Component {
|
||||
if (TABS[activeTab] === TABS.history) {
|
||||
return <HistoryComponent toggleName={featureToggleName} />;
|
||||
} else if (TABS[activeTab] === TABS.strategies) {
|
||||
if (this.isFeatureView) {
|
||||
return <EditFeatureToggle featureToggle={featureToggle} />;
|
||||
}
|
||||
return <ViewFeatureToggle featureToggle={featureToggle} />;
|
||||
} else {
|
||||
return <MetricComponent featureToggle={featureToggle} />;
|
||||
}
|
||||
}
|
||||
|
||||
goToTab(tabName, featureToggleName) {
|
||||
hashHistory.push(`/features/${tabName}/${featureToggleName}`);
|
||||
let view = this.props.fetchFeatureToggles ? 'features' : 'archive';
|
||||
hashHistory.push(`/${view}/${tabName}/${featureToggleName}`);
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -57,6 +70,7 @@ export default class ViewFeatureToggleComponent extends React.Component {
|
||||
featureToggle,
|
||||
features,
|
||||
activeTab,
|
||||
revive,
|
||||
// setValue,
|
||||
featureToggleName,
|
||||
toggleFeature,
|
||||
@ -94,6 +108,10 @@ export default class ViewFeatureToggleComponent extends React.Component {
|
||||
hashHistory.push('/features');
|
||||
}
|
||||
};
|
||||
const reviveToggle = () => {
|
||||
revive(featureToggle.name);
|
||||
hashHistory.push('/features');
|
||||
};
|
||||
const updateFeatureToggle = () => {
|
||||
let feature = { ...featureToggle };
|
||||
if (Array.isArray(feature.strategies)) {
|
||||
@ -113,6 +131,7 @@ export default class ViewFeatureToggleComponent extends React.Component {
|
||||
<Card shadow={0} className={commonStyles.fullwidth} style={{ overflow: 'visible' }}>
|
||||
<CardTitle style={{ paddingTop: '24px', wordBreak: 'break-all' }}>{featureToggle.name}</CardTitle>
|
||||
<CardText>
|
||||
{this.isFeatureView ? (
|
||||
<Textfield
|
||||
floatingLabel
|
||||
style={{ width: '100%' }}
|
||||
@ -123,6 +142,17 @@ export default class ViewFeatureToggleComponent extends React.Component {
|
||||
onChange={v => setValue('description', v)}
|
||||
onBlur={updateFeatureToggle}
|
||||
/>
|
||||
) : (
|
||||
<Textfield
|
||||
disabled
|
||||
floatingLabel
|
||||
style={{ width: '100%' }}
|
||||
rows={1}
|
||||
label="Description"
|
||||
required
|
||||
value={featureToggle.description}
|
||||
/>
|
||||
)}
|
||||
</CardText>
|
||||
|
||||
<CardActions
|
||||
@ -135,6 +165,7 @@ export default class ViewFeatureToggleComponent extends React.Component {
|
||||
>
|
||||
<span style={{ paddingRight: '24px' }}>
|
||||
<Switch
|
||||
disabled={this.isFeatureView}
|
||||
ripple
|
||||
checked={featureToggle.enabled}
|
||||
onChange={() => toggleFeature(featureToggle.name)}
|
||||
@ -142,9 +173,16 @@ export default class ViewFeatureToggleComponent extends React.Component {
|
||||
{featureToggle.enabled ? 'Enabled' : 'Disabled'}
|
||||
</Switch>
|
||||
</span>
|
||||
|
||||
{this.isFeatureView ? (
|
||||
<Button onClick={removeToggle} style={{ flexShrink: 0 }}>
|
||||
Archive
|
||||
</Button>
|
||||
) : (
|
||||
<Button onClick={reviveToggle} style={{ flexShrink: 0 }}>
|
||||
Revive
|
||||
</Button>
|
||||
)}
|
||||
</CardActions>
|
||||
<hr />
|
||||
<Tabs
|
||||
|
@ -23,6 +23,7 @@ import CreateStrategies from './page/strategies/create';
|
||||
import HistoryPage from './page/history';
|
||||
import HistoryTogglePage from './page/history/toggle';
|
||||
import Archive from './page/archive';
|
||||
import ShowArchive from './page/archive/show';
|
||||
import Applications from './page/applications';
|
||||
import ApplicationView from './page/applications/view';
|
||||
|
||||
@ -68,7 +69,7 @@ ReactDOM.render(
|
||||
</Route>
|
||||
<Route pageTitle="Archived Toggles" link="/archive">
|
||||
<Route pageTitle="Archived Toggles" path="/archive" component={Archive} />
|
||||
<Route pageTitle=":name" path="/archive/:name" component={Archive} />
|
||||
<Route pageTitle=":name" path="/archive/:activeTab/:name" component={ShowArchive} />
|
||||
</Route>
|
||||
|
||||
<Route pageTitle="Applications" link="/applications">
|
||||
|
14
frontend/src/page/archive/show.js
Normal file
14
frontend/src/page/archive/show.js
Normal file
@ -0,0 +1,14 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ViewFeatureToggle from './../../component/archive/view-container';
|
||||
|
||||
export default class Features extends PureComponent {
|
||||
static propTypes = {
|
||||
params: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { params } = this.props;
|
||||
return <ViewFeatureToggle featureToggleName={params.name} activeTab={params.activeTab} />;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user