Browse Source

Feat UI metrics search (#9629)

* feat: add search to metrics explorer

Signed-off-by: mtfoley <mtfoley.mae@gmail.com>

* fix: ui-lint and ui-build errors

Signed-off-by: mtfoley <mtfoley.mae@gmail.com>

* feat: use @nexucis/fuzzy

Signed-off-by: mtfoley <mtfoley.mae@gmail.com>

* chore: code style and delete commented test

Signed-off-by: mtfoley <mtfoley.mae@gmail.com>

* rename Props to MetricsExplorerProps

Signed-off-by: mtfoley <mtfoley.mae@gmail.com>
pull/9702/head
Matthew 3 years ago committed by GitHub
parent
commit
628211c25a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 46
      web/ui/react-app/src/pages/graph/MetricsExplorer.test.tsx
  2. 49
      web/ui/react-app/src/pages/graph/MetricsExplorer.tsx

46
web/ui/react-app/src/pages/graph/MetricsExplorer.test.tsx

@ -0,0 +1,46 @@
import * as React from 'react';
import { mount, ReactWrapper } from 'enzyme';
import MetricsExplorer from './MetricsExplorer';
import { Input } from 'reactstrap';
describe('MetricsExplorer', () => {
const spyInsertAtCursor = jest.fn().mockImplementation((value: string) => {
value = value;
});
const metricsExplorerProps = {
show: true,
updateShow: (show: boolean): void => {
show = show;
},
metrics: ['go_test_1', 'prometheus_test_1'],
insertAtCursor: spyInsertAtCursor,
};
let metricsExplorer: ReactWrapper;
beforeEach(() => {
metricsExplorer = mount(<MetricsExplorer {...metricsExplorerProps} />);
});
it('renders an Input[type=text]', () => {
const input = metricsExplorer.find(Input);
expect(input.prop('type')).toEqual('text');
});
it('lists all metrics in props', () => {
const metrics = metricsExplorer.find('.metric');
expect(metrics).toHaveLength(metricsExplorerProps.metrics.length);
});
it('filters metrics with search', () => {
const input = metricsExplorer.find(Input);
input.simulate('change', { target: { value: 'go' } });
const metrics = metricsExplorer.find('.metric');
expect(metrics).toHaveLength(1);
});
it('handles click on metric', () => {
const metric = metricsExplorer.find('.metric').at(0);
metric.simulate('click');
expect(metricsExplorerProps.insertAtCursor).toHaveBeenCalled();
});
});

49
web/ui/react-app/src/pages/graph/MetricsExplorer.tsx

@ -1,33 +1,60 @@
import React, { Component } from 'react';
import { Modal, ModalBody, ModalHeader } from 'reactstrap';
import React, { Component, ChangeEvent } from 'react';
import { Modal, ModalBody, ModalHeader, Input } from 'reactstrap';
import { Fuzzy, FuzzyResult } from '@nexucis/fuzzy';
interface Props {
const fuz = new Fuzzy({ pre: '<strong>', post: '</strong>', shouldSort: true });
interface MetricsExplorerProps {
show: boolean;
updateShow(show: boolean): void;
metrics: string[];
insertAtCursor(value: string): void;
}
class MetricsExplorer extends Component<Props> {
type MetricsExplorerState = {
searchTerm: string;
};
class MetricsExplorer extends Component<MetricsExplorerProps, MetricsExplorerState> {
constructor(props: MetricsExplorerProps) {
super(props);
this.state = { searchTerm: '' };
}
handleSearchTerm = (event: ChangeEvent<HTMLInputElement>): void => {
this.setState({ searchTerm: event.target.value });
};
handleMetricClick = (query: string): void => {
this.props.insertAtCursor(query);
this.props.updateShow(false);
this.setState({ searchTerm: '' });
};
toggle = (): void => {
this.props.updateShow(!this.props.show);
};
render(): JSX.Element {
return (
<Modal isOpen={this.props.show} toggle={this.toggle} className="metrics-explorer">
<Modal isOpen={this.props.show} toggle={this.toggle} className="metrics-explorer" scrollable>
<ModalHeader toggle={this.toggle}>Metrics Explorer</ModalHeader>
<ModalBody>
{this.props.metrics.map((metric) => (
<p key={metric} className="metric" onClick={this.handleMetricClick.bind(this, metric)}>
{metric}
</p>
))}
<Input placeholder="Search" value={this.state.searchTerm} type="text" onChange={this.handleSearchTerm} />
{this.state.searchTerm.length > 0 &&
fuz
.filter(this.state.searchTerm, this.props.metrics)
.map((result: FuzzyResult) => (
<p
key={result.original}
className="metric"
onClick={this.handleMetricClick.bind(this, result.original)}
dangerouslySetInnerHTML={{ __html: result.rendered }}
></p>
))}
{this.state.searchTerm.length === 0 &&
this.props.metrics.map((metric) => (
<p key={metric} className="metric" onClick={this.handleMetricClick.bind(this, metric)}>
{metric}
</p>
))}
</ModalBody>
</Modal>
);

Loading…
Cancel
Save