Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Query Browser: Highlight matching strings in autocomplete suggestions #6107

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions frontend/public/components/monitoring/_monitoring.scss
Expand Up @@ -255,6 +255,10 @@ $monitoring-line-height: 18px;
font-size: ($font-size-base - 1);
}

.query-browser__autocomplete-match {
font-weight: var(--pf-global--FontWeight--bold);
}

.query-browser__clear-icon {
font-size: 18px !important;
padding: 4px !important;
Expand Down
32 changes: 28 additions & 4 deletions frontend/public/components/monitoring/metrics.tsx
Expand Up @@ -374,6 +374,27 @@ const queryInputStateToProps = ({ UI }: RootState, { index }) => ({
text: UI.getIn(['queryBrowser', 'queries', index, 'text']),
});

// Highlight characters in `text` based on the search string `token`
const HighlightMatches: React.FC<{ text: string; token: string }> = ({ text, token }) => {
// Autocompletion uses fuzzy matching, so the entire `token` string may not be a substring of
// `text`. Instead, we find the longest starting substring of `token` that exists in `text` and
// highlight it. Then we repeat with the remainder of `token` and `text` and continue until all
// the characters of `token` have been found somewhere in `text`.
for (let sub = token; sub.length > 0; sub = sub.slice(0, -1)) {
const i = text.toLowerCase().indexOf(sub);
if (i !== -1) {
return (
<>
{text.slice(0, i)}
<span className="query-browser__autocomplete-match">{text.slice(i, i + sub.length)}</span>
<HighlightMatches text={text.slice(i + sub.length)} token={token.slice(sub.length)} />
</>
);
}
}
return <>{text}</>;
};

const QueryInput_: React.FC<QueryInputProps> = ({
index,
metrics,
Expand All @@ -388,14 +409,17 @@ const QueryInput_: React.FC<QueryInputProps> = ({
const getTextBeforeCursor = () =>
inputRef.current.value.substring(0, inputRef.current.selectionEnd);

const onChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
patchQuery({ text: e.target.value });

const updateToken = _.debounce(() => {
// Metric and function names can only contain the characters a-z, A-Z, 0-9, '_' and ':'
const allTokens = getTextBeforeCursor().split(/[^a-zA-Z0-9_:]+/);

// We always do case insensitive autocompletion, so convert to lower case immediately
setToken(_.toLower(_.last(allTokens)));
}, 200);

const onChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
patchQuery({ text: e.target.value });
updateToken();
};

const onKeyDown = (e: React.KeyboardEvent) => {
Expand Down Expand Up @@ -506,7 +530,7 @@ const QueryInput_: React.FC<QueryInputProps> = ({
onMouseDown={onMouseDown}
type="button"
>
{s}
<HighlightMatches text={s} token={token} />
</button>
</li>
))}
Expand Down