Skip to content

Commit

Permalink
UI: Add toggle to enable/disable metric autocomplete
Browse files Browse the repository at this point in the history
This change adds a toggle to enable or disable the metric autocomplete
functionality. By default it is enabled. This is a port of a change I
did in [Thanos][1].

[1]: thanos-io/thanos#3381

Signed-off-by: Jarod Watkins <[email protected]>
  • Loading branch information
ipstatic committed Oct 30, 2020
1 parent 63be30d commit bc792d4
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 34 deletions.
14 changes: 13 additions & 1 deletion web/ui/react-app/src/pages/graph/ExpressionInput.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ describe('ExpressionInput', () => {
// Do nothing.
},
loading: false,
enable: true,
};

let expressionInput: ReactWrapper;
Expand Down Expand Up @@ -174,8 +175,19 @@ describe('ExpressionInput', () => {
instance.createAutocompleteSection({ closeMenu: spyCloseMenu });
setTimeout(() => expect(spyCloseMenu).toHaveBeenCalled());
});
it('should not render list if enable is false', () => {
const input = mount(
<ExpressionInput autocompleteSections={{ title: ['foo', 'bar', 'baz'] }} {...({} as any)} enable={false} />
);
const instance: any = input.instance();
const spyCloseMenu = jest.fn();
instance.createAutocompleteSection({ closeMenu: spyCloseMenu });
setTimeout(() => expect(spyCloseMenu).toHaveBeenCalled());
});
it('should render autosuggest-dropdown', () => {
const input = mount(<ExpressionInput autocompleteSections={{ title: ['foo', 'bar', 'baz'] }} {...({} as any)} />);
const input = mount(
<ExpressionInput autocompleteSections={{ title: ['foo', 'bar', 'baz'] }} {...({} as any)} enable={true} />
);
const instance: any = input.instance();
const spyGetMenuProps = jest.fn();
const sections = instance.createAutocompleteSection({
Expand Down
66 changes: 34 additions & 32 deletions web/ui/react-app/src/pages/graph/ExpressionInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface ExpressionInputProps {
autocompleteSections: { [key: string]: string[] };
executeQuery: () => void;
loading: boolean;
enable: boolean;
}

interface ExpressionInputState {
Expand Down Expand Up @@ -76,38 +77,39 @@ class ExpressionInput extends Component<ExpressionInputProps, ExpressionInputSta
const { inputValue = '', closeMenu, highlightedIndex } = downshift;
const { autocompleteSections } = this.props;
let index = 0;
const sections = inputValue!.length
? Object.entries(autocompleteSections).reduce((acc, [title, items]) => {
const matches = this.getSearchMatches(inputValue!, items);
return !matches.length
? acc
: [
...acc,
<ul className="autosuggest-dropdown-list" key={title}>
<li className="autosuggest-dropdown-header">{title}</li>
{matches
.slice(0, 100) // Limit DOM rendering to 100 results, as DOM rendering is sloooow.
.map(({ original, string: text }) => {
const itemProps = downshift.getItemProps({
key: original,
index,
item: original,
style: {
backgroundColor: highlightedIndex === index++ ? 'lightgray' : 'white',
},
});
return (
<li
key={title}
{...itemProps}
dangerouslySetInnerHTML={{ __html: sanitizeHTML(text, { allowedTags: ['strong'] }) }}
/>
);
})}
</ul>,
];
}, [] as JSX.Element[])
: [];
const sections =
inputValue!.length && this.props.enable
? Object.entries(autocompleteSections).reduce((acc, [title, items]) => {
const matches = this.getSearchMatches(inputValue!, items);
return !matches.length
? acc
: [
...acc,
<ul className="autosuggest-dropdown-list" key={title}>
<li className="autosuggest-dropdown-header">{title}</li>
{matches
.slice(0, 100) // Limit DOM rendering to 100 results, as DOM rendering is sloooow.
.map(({ original, string: text }) => {
const itemProps = downshift.getItemProps({
key: original,
index,
item: original,
style: {
backgroundColor: highlightedIndex === index++ ? 'lightgray' : 'white',
},
});
return (
<li
key={title}
{...itemProps}
dangerouslySetInnerHTML={{ __html: sanitizeHTML(text, { allowedTags: ['strong'] }) }}
/>
);
})}
</ul>,
];
}, [] as JSX.Element[])
: [];

if (!sections.length) {
// This is ugly but is needed in order to sync state updates.
Expand Down
1 change: 1 addition & 0 deletions web/ui/react-app/src/pages/graph/Panel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const defaultProps = {
onExecuteQuery: (): void => {
// Do nothing.
},
enableMetricAutocomplete: true,
};

describe('Panel', () => {
Expand Down
2 changes: 2 additions & 0 deletions web/ui/react-app/src/pages/graph/Panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ interface PanelProps {
removePanel: () => void;
onExecuteQuery: (query: string) => void;
pathPrefix: string;
enableMetricAutocomplete: boolean;
}

interface PanelState {
Expand Down Expand Up @@ -233,6 +234,7 @@ class Panel extends Component<PanelProps, PanelState> {
onExpressionChange={this.handleExpressionChange}
executeQuery={this.executeQuery}
loading={this.state.loading}
enable={this.props.enableMetricAutocomplete}
autocompleteSections={{
'Query History': pastQueries,
'Metric Names': metricNames,
Expand Down
20 changes: 19 additions & 1 deletion web/ui/react-app/src/pages/graph/PanelList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,16 @@ interface PanelListProps extends RouteComponentProps {
metrics: string[];
useLocalTime: boolean;
queryHistoryEnabled: boolean;
enableMetricAutocomplete: boolean;
}

export const PanelListContent: FC<PanelListProps> = ({ metrics = [], useLocalTime, queryHistoryEnabled, ...rest }) => {
export const PanelListContent: FC<PanelListProps> = ({
metrics = [],
useLocalTime,
queryHistoryEnabled,
enableMetricAutocomplete,
...rest
}) => {
const [panels, setPanels] = useState(rest.panels);
const [historyItems, setLocalStorageHistoryItems] = useLocalStorage<string[]>('history', []);

Expand Down Expand Up @@ -95,6 +102,7 @@ export const PanelListContent: FC<PanelListProps> = ({ metrics = [], useLocalTim
useLocalTime={useLocalTime}
metricNames={metrics}
pastQueries={queryHistoryEnabled ? historyItems : []}
enableMetricAutocomplete={enableMetricAutocomplete}
/>
))}
<Button className="mb-3" color="primary" onClick={addPanel}>
Expand All @@ -108,6 +116,7 @@ const PanelList: FC<RouteComponentProps> = () => {
const [delta, setDelta] = useState(0);
const [useLocalTime, setUseLocalTime] = useLocalStorage('use-local-time', false);
const [enableQueryHistory, setEnableQueryHistory] = useLocalStorage('enable-query-history', false);
const [enableMetricAutocomplete, setEnableMetricAutocomplete] = useLocalStorage('enable-metric-autocomplete', true);

const pathPrefix = usePathPrefix();
const { response: metricsRes, error: metricsErr } = useFetch<string[]>(`${pathPrefix}/${API_PATH}/label/__name__/values`);
Expand Down Expand Up @@ -148,6 +157,14 @@ const PanelList: FC<RouteComponentProps> = () => {
>
Use local time
</Checkbox>
<Checkbox
wrapperStyles={{ marginLeft: 20, display: 'inline-block' }}
id="metric-autocomplete"
onChange={({ target }) => setEnableMetricAutocomplete(target.checked)}
defaultChecked={enableMetricAutocomplete}
>
Enable metric autocomplete
</Checkbox>
{(delta > 30 || timeErr) && (
<Alert color="danger">
<strong>Warning: </strong>
Expand All @@ -167,6 +184,7 @@ const PanelList: FC<RouteComponentProps> = () => {
useLocalTime={useLocalTime}
metrics={metricsRes.data}
queryHistoryEnabled={enableQueryHistory}
enableMetricAutocomplete={enableMetricAutocomplete}
/>
</>
);
Expand Down

0 comments on commit bc792d4

Please sign in to comment.