diff --git a/web/ui/react-app/src/pages/graph/MetricsExplorer.test.tsx b/web/ui/react-app/src/pages/graph/MetricsExplorer.test.tsx
new file mode 100644
index 000000000..d864fb349
--- /dev/null
+++ b/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();
+ });
+
+ 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();
+ });
+});
diff --git a/web/ui/react-app/src/pages/graph/MetricsExplorer.tsx b/web/ui/react-app/src/pages/graph/MetricsExplorer.tsx
index 8c01ba757..1959fa826 100644
--- a/web/ui/react-app/src/pages/graph/MetricsExplorer.tsx
+++ b/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: '', post: '', shouldSort: true });
+
+interface MetricsExplorerProps {
show: boolean;
updateShow(show: boolean): void;
metrics: string[];
insertAtCursor(value: string): void;
}
-class MetricsExplorer extends Component {
+type MetricsExplorerState = {
+ searchTerm: string;
+};
+
+class MetricsExplorer extends Component {
+ constructor(props: MetricsExplorerProps) {
+ super(props);
+ this.state = { searchTerm: '' };
+ }
+ handleSearchTerm = (event: ChangeEvent): 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 (
-
+
Metrics Explorer
- {this.props.metrics.map((metric) => (
-
- {metric}
-
- ))}
+
+ {this.state.searchTerm.length > 0 &&
+ fuz
+ .filter(this.state.searchTerm, this.props.metrics)
+ .map((result: FuzzyResult) => (
+
+ ))}
+ {this.state.searchTerm.length === 0 &&
+ this.props.metrics.map((metric) => (
+
+ {metric}
+
+ ))}
);