Add the create new service profile button by default at /routes (#1941)

* Show the call to action if all metric rows are UNKNOWN
* Also enable creating of a new service profile by default on the Top Routes page
* Fix bug in passing down props.classes from the Navigation component
* Adjust form appearance
This commit is contained in:
Risha Mars 2018-12-06 17:26:21 -08:00 committed by GitHub
parent 3ff971fd59
commit 692c4ca75b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 87 additions and 40 deletions

View File

@ -1,11 +1,12 @@
import Button from '@material-ui/core/Button';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import DialogTitle from '@material-ui/core/DialogTitle';
import IconButton from '@material-ui/core/IconButton';
import NoteAddIcon from '@material-ui/icons/NoteAdd';
import PropTypes from 'prop-types';
import React from 'react';
import TextField from '@material-ui/core/TextField';
@ -18,6 +19,9 @@ const styles = theme => ({
button: {
margin: theme.spacing.unit,
},
margin: {
marginRight: theme.spacing.unit,
},
container: {
display: 'flex',
flexWrap: 'wrap',
@ -62,19 +66,36 @@ class ConfigureProfilesMsg extends React.Component {
}
renderDownloadProfileForm = () => {
const { api, classes } = this.props;
const { api, classes, showAsIcon } = this.props;
let { query } = this.state;
let downloadUrl = api.prefixedUrl(`/profiles/new?service=${query.service}&namespace=${query.namespace}`);
let button;
return (
<React.Fragment>
if (showAsIcon) {
button = (
<IconButton
onClick={this.handleClickOpen}
aria-label="Add"
className={classes.margin}
variant="outlined">
<NoteAddIcon fontSize="small" />
</IconButton>
);
} else {
button = (
<Button
className={classes.button}
variant="outlined"
color="primary"
size="small"
onClick={this.handleClickOpen}>Create Service Profile
</Button>
);
}
return (
<React.Fragment>
{button}
<Dialog
open={this.state.open}
@ -117,26 +138,34 @@ class ConfigureProfilesMsg extends React.Component {
}
render() {
const { classes } = this.props;
const { showAsIcon } = this.props;
return (
<Card className={classes.root}>
if (showAsIcon) {
return this.renderDownloadProfileForm();
} else {
return (
<CardContent>
<Typography component="div">
No traffic found. Does the service have a service profile?
No named route traffic found. This could be because the service is not receiving any traffic,
or because there is no service profile configured. Does the service have a service profile?
{this.renderDownloadProfileForm()}
</Typography>
</CardContent>
</Card>
);
);
}
}
}
ConfigureProfilesMsg.propTypes = {
api: PropTypes.shape({
prefixedFetch: PropTypes.func.isRequired,
prefixedUrl: PropTypes.func.isRequired,
}).isRequired,
classes: PropTypes.shape({}).isRequired
classes: PropTypes.shape({}).isRequired,
showAsIcon: PropTypes.bool
};
ConfigureProfilesMsg.defaultProps = {
showAsIcon: false
};
export default withContext(withStyles(styles, { withTheme: true })(ConfigureProfilesMsg));

View File

@ -199,7 +199,7 @@ class NavigationBase extends React.Component {
);
}
render() {
const { classes, ChildComponent } = this.props;
const { classes, ChildComponent, ...otherProps } = this.props;
return (
<div className={classes.root}>
@ -287,7 +287,7 @@ class NavigationBase extends React.Component {
<main className={classNames(classes.content, {[classes.contentDrawerClose]: !this.state.drawerOpen})}>
<div className={classes.toolbar} />
<div className="main-content"><ChildComponent {...this.props} /></div>
<div className="main-content"><ChildComponent {...otherProps} /></div>
</main>
</div>
);

View File

@ -1,6 +1,8 @@
import Button from '@material-ui/core/Button';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import ConfigureProfilesMsg from './ConfigureProfilesMsg.jsx';
import Divider from '@material-ui/core/Divider';
import ErrorBanner from './ErrorBanner.jsx';
import FormControl from '@material-ui/core/FormControl';
import FormHelperText from '@material-ui/core/FormHelperText';
@ -12,15 +14,28 @@ import QueryToCliCmd from './QueryToCliCmd.jsx';
import React from 'react';
import Select from '@material-ui/core/Select';
import TopRoutesModule from './TopRoutesModule.jsx';
import Typography from '@material-ui/core/Typography';
import _ from 'lodash';
import { groupResourcesByNs } from './util/MetricUtils.jsx';
import { withContext } from './util/AppContext.jsx';
import { withStyles } from '@material-ui/core/styles';
const styles = theme => ({
root: {
marginTop: 3 * theme.spacing.unit,
marginBottom:theme.spacing.unit,
},
formControl: {
minWidth: 200,
},
});
class TopRoutes extends React.Component {
static propTypes = {
api: PropTypes.shape({
PrefixedLink: PropTypes.func.isRequired,
}).isRequired
}).isRequired,
classes: PropTypes.shape({}).isRequired
}
constructor(props) {
@ -125,20 +140,20 @@ class TopRoutes extends React.Component {
}
renderRoutesQueryForm = () => {
const { classes } = this.props;
return (
<CardContent>
<Grid container direction="column" spacing={16}>
<Grid item container spacing={8} alignItems="center">
<Grid item xs={6} md={3}>
<Grid item container spacing={32} alignItems="center" justify="flex-start">
<Grid item>
{ this.renderNamespaceDropdown("Namespace", "namespace", "Namespace to query") }
</Grid>
<Grid item xs={6} md={3}>
<Grid item>
{ this.renderServiceDropdown() }
</Grid>
</Grid>
<Grid item container spacing={8} alignItems="center">
<Grid item>
<Button
color="primary"
@ -148,6 +163,7 @@ class TopRoutes extends React.Component {
Start
</Button>
</Grid>
<Grid item>
<Button
color="default"
@ -159,13 +175,17 @@ class TopRoutes extends React.Component {
</Grid>
</Grid>
</Grid>
<Divider light className={classes.root} />
<Typography variant="caption">You can also create a new profile <ConfigureProfilesMsg showAsIcon={true} /></Typography>
</CardContent>
);
}
renderNamespaceDropdown = (title, key, helperText) => {
const { classes } = this.props;
return (
<FormControl>
<FormControl className={classes.formControl}>
<InputLabel htmlFor={`${key}-dropdown`}>{title}</InputLabel>
<Select
value={this.state.query[key]}
@ -186,25 +206,27 @@ class TopRoutes extends React.Component {
}
renderServiceDropdown = () => {
let key = "resource_name";
let services = _.chain(this.state.services)
.filter(['namespace', this.state.query.namespace])
.map(svc => `service/${svc.name}`).value();
let otherResources = this.state.resourcesByNs[this.state.query.namespace] || [];
const { classes } = this.props;
let { query, services, resourcesByNs } = this.state;
let { query } = this.state;
let dropdownOptions = _.sortBy(_.concat(services, otherResources));
let key = "resource_name";
let servicesWithPrefix = _.chain(services)
.filter(['namespace', query.namespace])
.map(svc => `service/${svc.name}`).value();
let otherResources = resourcesByNs[query.namespace] || [];
let dropdownOptions = _.sortBy(_.concat(servicesWithPrefix, otherResources));
let dropdownVal = _.isEmpty(query.resource_name) || _.isEmpty(query.resource_type) ? "" :
query.resource_type + "/" + query.resource_name;
return (
<FormControl>
<FormControl className={classes.formControl}>
<InputLabel htmlFor={`${key}-dropdown`}>Resource</InputLabel>
<Select
value={dropdownVal}
onChange={this.handleResourceSelect}
disabled={_.isEmpty(query.namespace)}
autoWidth
inputProps={{
name: key,
id: `${key}-dropdown`,
@ -221,13 +243,7 @@ class TopRoutes extends React.Component {
render() {
let query = this.state.query;
let from = '';
if (_.isEmpty(query.from_type)) {
from = query.from_name;
} else {
from = `${query.from_type}${_.isEmpty(query.from_name) ? "" : "/"}${query.from_name}`;
}
query.from = from;
let emptyQuery = _.isEmpty(query.resource_name) || _.isEmpty(query.resource_type);
return (
<div>
@ -237,7 +253,7 @@ class TopRoutes extends React.Component {
}
<Card>
{ this.renderRoutesQueryForm() }
{ _.isEmpty(query.resource_name) || _.isEmpty(query.resource_type) ? null :
{ emptyQuery ? null :
<QueryToCliCmd cmdName="routes" query={query} resource={query.resource_type + "/" + query.resource_name} /> }
{ !this.state.requestInProgress ? null : <TopRoutesModule query={this.state.query} /> }
</Card>
@ -246,4 +262,4 @@ class TopRoutes extends React.Component {
}
}
export default withContext(TopRoutes);
export default withContext(withStyles(styles, { withTheme: true })(TopRoutes));

View File

@ -41,13 +41,15 @@ class TopRoutesBase extends React.Component {
render() {
const {data, loading} = this.props;
let metrics = processTopRoutesResults(_.get(data, '[0].routes.rows', []));
let allRoutesUnknown = _.every(metrics, m => m.route === "UNKNOWN");
let showCallToAction = !loading && (_.isEmpty(metrics) || allRoutesUnknown);
return (
<React.Fragment>
{this.loading()}
{this.banner()}
{ !loading && _.isEmpty(metrics) ? <ConfigureProfilesMsg /> : null}
<TopRoutesTable rows={metrics} />
{ showCallToAction ? <ConfigureProfilesMsg /> : null}
</React.Fragment>
);
}