import { StringParam, withQueryParams } from 'use-query-params'; import { defaultMaxRps, emptyTapQuery, httpMethods, tapQueryPropType, tapQueryProps, tapResourceTypes, } from './util/TapUtils.jsx'; import Button from '@material-ui/core/Button'; import Card from '@material-ui/core/Card'; import CardContent from '@material-ui/core/CardContent'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import ExpansionPanel from '@material-ui/core/ExpansionPanel'; import ExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails'; import ExpansionPanelSummary from '@material-ui/core/ExpansionPanelSummary'; import FormControl from '@material-ui/core/FormControl'; import FormHelperText from '@material-ui/core/FormHelperText'; import Grid from '@material-ui/core/Grid'; import InputLabel from '@material-ui/core/InputLabel'; import MenuItem from '@material-ui/core/MenuItem'; import PropTypes from 'prop-types'; import QueryToCliCmd from './QueryToCliCmd.jsx'; import React from 'react'; import Select from '@material-ui/core/Select'; import TextField from '@material-ui/core/TextField'; import { Trans } from '@lingui/macro'; import Typography from '@material-ui/core/Typography'; import _flatten from 'lodash/flatten'; import _isEmpty from 'lodash/isEmpty'; import _isEqual from 'lodash/isEqual'; import _isNil from 'lodash/isNil'; import _map from 'lodash/map'; import _merge from 'lodash/merge'; import _noop from 'lodash/noop'; import _omit from 'lodash/omit'; import _pick from 'lodash/pick'; import _some from 'lodash/some'; import _uniq from 'lodash/uniq'; import _values from 'lodash/values'; import { withStyles } from '@material-ui/core/styles'; const getResourceList = (resourcesByNs, ns) => { return resourcesByNs[ns] || _uniq(_flatten(_values(resourcesByNs))); }; const urlPropsQueryConfig = {}; Object.keys(tapQueryProps).forEach(value => { urlPropsQueryConfig[value] = StringParam; }); const styles = theme => ({ root: { display: 'flex', flexWrap: 'wrap', }, formControlWrapper: { minWidth: 200, }, formControl: { padding: theme.spacing(1), paddingLeft: 0, margin: 0, minWidth: 'inherit', maxWidth: '100%', width: 'auto', }, selectEmpty: { 'margin-top': '32px', }, card: { maxWidth: '100%', }, actions: { display: 'flex', 'padding-left': '32px', }, expand: { transform: 'rotate(0deg)', transition: theme.transitions.create('transform', { duration: theme.transitions.duration.shortest, }), marginLeft: 'auto', [theme.breakpoints.up('sm')]: { marginRight: -8, }, }, expandOpen: { transform: 'rotate(180deg)', }, resetButton: { marginLeft: theme.spacing(1), }, }); class TapQueryForm extends React.Component { static getDerivedStateFromProps(props, state) { if (!_isEqual(props.resourcesByNs, state.resourcesByNs)) { const resourcesByNs = props.resourcesByNs; const authoritiesByNs = props.authoritiesByNs; const namespaces = Object.keys(resourcesByNs).sort(); const resourceNames = getResourceList(resourcesByNs, state.query.namespace); const toResourceNames = getResourceList(resourcesByNs, state.query.toNamespace); const authorities = getResourceList(authoritiesByNs, state.query.namespace); return _merge(state, { resourcesByNs, authoritiesByNs, autocomplete: { namespace: namespaces, resource: resourceNames, toNamespace: namespaces, toResource: toResourceNames, authority: authorities, }, }); } else { return null; } } constructor(props) { super(props); const query = _merge({}, props.currentQuery, _pick(props.query, Object.keys(tapQueryProps))); props.updateQuery(query); const advancedFormExpanded = _some( _omit(query, ['namespace', 'resource']), v => !_isEmpty(v), ); this.state = { query, advancedFormExpanded, authoritiesByNs: {}, resourcesByNs: {}, autocomplete: { namespace: [], resource: [], toNamespace: [], toResource: [], authority: [], }, }; } handleFormChange = (name, scopeResource) => { const { query, autocomplete, resourcesByNs, authoritiesByNs } = this.state; const { updateQuery } = this.props; const state = { query, autocomplete, }; const shouldScopeAuthority = name === 'namespace'; const newQueryValues = {}; return event => { const formVal = event.target.value; state.query[name] = formVal; newQueryValues[name] = formVal; if (!_isNil(scopeResource)) { // scope the available typeahead resources to the selected namespace state.autocomplete[scopeResource] = resourcesByNs[formVal]; state.query[scopeResource] = `namespace/${formVal}`; newQueryValues[scopeResource] = `namespace/${formVal}`; } if (shouldScopeAuthority) { state.autocomplete.authority = authoritiesByNs[formVal]; } this.setState(state); updateQuery(state.query); this.handleUrlUpdate(newQueryValues); }; } // Each time state.query is updated, this method calls setQuery provided // by useQueryParams HOC to partially update url query params that have // changed handleUrlUpdate = query => { const { setQuery } = this.props; setQuery({ ...query }); } handleFormEvent = name => { const { query } = this.state; const { updateQuery } = this.props; const state = { query, }; return event => { state.query[name] = event.target.value; this.handleUrlUpdate(state.query); this.setState(state); updateQuery(state.query); }; } handleAdvancedFormExpandClick = () => { const { advancedFormExpanded } = this.state; this.setState({ advancedFormExpanded: !advancedFormExpanded }); } autoCompleteData = name => { const { autocomplete, query } = this.state; return _uniq( autocomplete[name].filter(d => d.indexOf(query[name]) !== -1), ).sort(); } resetTapForm = () => { const { updateQuery, handleTapClear } = this.props; this.setState({ query: emptyTapQuery(), }); this.handleUrlUpdate(emptyTapQuery()); updateQuery(emptyTapQuery(), true); handleTapClear(); } renderResourceSelect = (resourceKey, namespaceKey) => { const { autocomplete, query } = this.state; const { classes } = this.props; const selectedNs = query[namespaceKey]; const nsEmpty = _isNil(selectedNs) || _isEmpty(selectedNs); const resourceOptions = tapResourceTypes.concat( autocomplete[resourceKey] || [], nsEmpty ? [] : [`namespace/${selectedNs}`], ).sort(); return ( {resourceKey === 'resource' ? formResource : formToResource} ); } renderNamespaceSelect = (title, namespaceKey, resourceKey) => { const { autocomplete, query } = this.state; const { classes } = this.props; return ( {title} ); } renderTapButton = (tapInProgress, tapIsClosing) => { const { query } = this.state; const { handleTapStart, handleTapStop } = this.props; if (tapIsClosing) { return ( ); } else if (tapInProgress) { return ( ); } else { return ( ); } } renderTextInput = (title, key, helperText) => { const { query } = this.state; const { classes } = this.props; return ( ); } renderAdvancedTapFormContent() { const { autocomplete, query } = this.state; const { classes } = this.props; return ( {this.renderNamespaceSelect(formToNamespace, 'toNamespace', 'toResource')} {this.renderResourceSelect('toResource', 'toNamespace')} formAuthority formAuthorityHelpText { this.renderTextInput(formPath, 'path', formPathHelpText) } { this.renderTextInput(formScheme, 'scheme', formSchemeHelpText) } { this.renderTextInput(formMaxRPS, 'maxRps', formMaxRPSHelpText {defaultMaxRps}) } formHTTPMethod formHTTPMethodHelpText ); } renderAdvancedTapForm() { const { advancedFormExpanded } = this.state; return ( }> {advancedFormExpanded ? formHideFilters : formShowFilters} {this.renderAdvancedTapFormContent()} ); } render() { const { query } = this.state; const { tapRequestInProgress, tapIsClosing, cmdName, enableAdvancedForm, classes } = this.props; return ( {this.renderNamespaceSelect(formNamespace, 'namespace', 'resource')} {this.renderResourceSelect('resource', 'namespace')} { this.renderTapButton(tapRequestInProgress, tapIsClosing) } { !enableAdvancedForm ? null : this.renderAdvancedTapForm() } ); } } TapQueryForm.propTypes = { authoritiesByNs: PropTypes.shape({}).isRequired, cmdName: PropTypes.string.isRequired, currentQuery: tapQueryPropType.isRequired, enableAdvancedForm: PropTypes.bool, handleTapClear: PropTypes.func, handleTapStart: PropTypes.func.isRequired, handleTapStop: PropTypes.func.isRequired, query: tapQueryPropType.isRequired, resourcesByNs: PropTypes.shape({}).isRequired, setQuery: PropTypes.func.isRequired, tapIsClosing: PropTypes.bool, tapRequestInProgress: PropTypes.bool.isRequired, updateQuery: PropTypes.func.isRequired, }; TapQueryForm.defaultProps = { enableAdvancedForm: true, handleTapClear: _noop, tapIsClosing: false, }; export default withQueryParams(urlPropsQueryConfig, withStyles(styles)(TapQueryForm));