Prometheus: Feedback link for metric encyclopedia plus performance improvements (#64576)

* add filtered results

* add feedback link

* correctly debounce fuzzy search thank you leon

* reduce calls to filterMetrics()in letter search

* clean up the alphabet type

* bug fix, filter type should be subtractive

* each filter is don;t explicitly, subtracting from the results of each

* Revert "each filter is don;t explicitly, subtracting from the results of each"

This reverts commit 5648788451.

* each filter is filtered explicitly, one after the other, subtracting from the results of each
pull/64769/merge
Brendan O'Handley 2 years ago committed by GitHub
parent dfee4249e8
commit 2e4b134743
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 40
      public/app/plugins/datasource/prometheus/querybuilder/components/FeedbackLink.tsx
  2. 244
      public/app/plugins/datasource/prometheus/querybuilder/components/MetricEncyclopediaModal.tsx

@ -0,0 +1,40 @@
import { css } from '@emotion/css';
import React from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { Stack } from '@grafana/experimental';
import { Icon, useStyles2 } from '@grafana/ui';
export interface Props {
feedbackUrl?: string;
}
export function FeedbackLink({ feedbackUrl }: Props) {
const styles = useStyles2(getStyles);
return (
<Stack gap={1}>
<a
href={feedbackUrl}
className={styles.link}
title="The Metric Encyclopedia is new, please let us know how we can improve it"
target="_blank"
rel="noreferrer noopener"
>
<Icon name="comment-alt-message" /> Give feedback
</a>
</Stack>
);
}
function getStyles(theme: GrafanaTheme2) {
return {
link: css({
color: theme.colors.text.secondary,
fontSize: theme.typography.bodySmall.fontSize,
':hover': {
color: theme.colors.text.link,
},
}),
};
}

@ -27,6 +27,8 @@ import { promQueryModeller } from '../PromQueryModeller';
import { regexifyLabelValuesQueryString } from '../shared/parsingUtils';
import { PromVisualQuery } from '../types';
import { FeedbackLink } from './FeedbackLink';
type Props = {
datasource: PrometheusDatasource;
isOpen: boolean;
@ -90,8 +92,6 @@ const uf = new uFuzzy({
});
function fuzzySearch(haystack: string[], query: string, setter: React.Dispatch<React.SetStateAction<number[]>>) {
// console.log('fuzzySearch');
const idxs = uf.filter(haystack, query);
idxs && setter(idxs);
}
@ -125,6 +125,8 @@ export const MetricEncyclopediaModal = (props: Props) => {
const [selectedTypes, setSelectedTypes] = useState<Array<SelectableValue<string>>>([]);
const [letterSearch, setLetterSearch] = useState<string | null>(null);
const [totalMetricCount, setTotalMetricCount] = useState<number>(0);
const [filteredMetricCount, setFilteredMetricCount] = useState<number>();
// backend search metric names by text
const [useBackend, setUseBackend] = useState<boolean>(false);
@ -186,6 +188,8 @@ export const MetricEncyclopediaModal = (props: Props) => {
})
);
setTotalMetricCount(metricsData.length);
setFilteredMetricCount(metricsData.length);
setIsLoading(false);
}, [query, datasource]);
@ -230,51 +234,41 @@ export const MetricEncyclopediaModal = (props: Props) => {
* Filter
*
* @param metrics
* @param skipLetterSearch
* @param skipLetterSearch used to show the alphabet letters as clickable before filtering out letters (needs to be refactored)
* @returns
*/
function filterMetrics(metrics: MetricsData, skipLetterSearch?: boolean): MetricsData {
let filteredMetrics: MetricsData = metrics;
if (fuzzySearchQuery || excludeNullMetadata || (letterSearch && !skipLetterSearch) || selectedTypes.length > 0) {
if (fuzzySearchQuery) {
filteredMetrics = filteredMetrics.filter((m: MetricData, idx) => {
let keepMetric = false;
// search by text
if (fuzzySearchQuery) {
if (useBackend) {
// skip for backend!
keepMetric = true;
} else if (fullMetaSearch) {
keepMetric = fuzzyMetaSearchResults.includes(idx);
} else {
keepMetric = fuzzyNameSearchResults.includes(idx);
}
if (useBackend) {
// skip for backend!
return true;
} else if (fullMetaSearch) {
return fuzzyMetaSearchResults.includes(idx);
} else {
return fuzzyNameSearchResults.includes(idx);
}
});
}
// user clicks the alphabet search
// backend and frontend
if (letterSearch && !skipLetterSearch) {
const letters: string[] = [letterSearch, letterSearch.toLowerCase()];
keepMetric = letters.includes(m.value[0]);
}
// select by type, counter, gauge, etc
// skip for backend because no metadata is returned
if (selectedTypes.length > 0 && !useBackend) {
// return the metric that matches the type
// return the metric if it has no type AND we are NOT excluding metrics without metadata
// Matches type
const matchesSelectedType = selectedTypes.some((t) => t.value === m.type);
if (letterSearch && !skipLetterSearch) {
filteredMetrics = filteredMetrics.filter((m: MetricData, idx) => {
const letters: string[] = [letterSearch, letterSearch.toLowerCase()];
return letters.includes(m.value[0]);
});
}
// missing type
const hasNoType = !m.type;
if (selectedTypes.length > 0 && !useBackend) {
filteredMetrics = filteredMetrics.filter((m: MetricData, idx) => {
// Matches type
const matchesSelectedType = selectedTypes.some((t) => t.value === m.type);
return matchesSelectedType || (hasNoType && !excludeNullMetadata);
}
// missing type
const hasNoType = !m.type;
return keepMetric;
return matchesSelectedType || (hasNoType && !excludeNullMetadata);
});
}
@ -287,6 +281,10 @@ export const MetricEncyclopediaModal = (props: Props) => {
function displayedMetrics(metrics: MetricsData) {
const filteredSorted: MetricsData = filterMetrics(metrics).sort(alphabetically(true, hasMetaDataFilters()));
if (filteredMetricCount !== filteredSorted.length && filteredSorted.length !== 0) {
setFilteredMetricCount(filteredSorted.length);
}
const displayedMetrics: MetricsData = sliceMetrics(filteredSorted, pageNum, resultsPerPage);
return displayedMetrics;
@ -297,6 +295,7 @@ export const MetricEncyclopediaModal = (props: Props) => {
const debouncedBackendSearch = useMemo(
() =>
debounce(async (metricText: string) => {
setIsLoading(true);
const queryString = regexifyLabelValuesQueryString(metricText);
const labelsParams = query.labels.map((label) => {
@ -318,11 +317,86 @@ export const MetricEncyclopediaModal = (props: Props) => {
});
setMetrics(metrics);
setFilteredMetricCount(metrics.length);
setIsLoading(false);
}, 300),
[datasource, query.labels]
);
function letterSearchComponent() {
const alphabetCheck: { [char: string]: number } = {
A: 0,
B: 0,
C: 0,
D: 0,
E: 0,
F: 0,
G: 0,
H: 0,
I: 0,
J: 0,
K: 0,
L: 0,
M: 0,
N: 0,
O: 0,
P: 0,
Q: 0,
R: 0,
S: 0,
T: 0,
U: 0,
V: 0,
W: 0,
X: 0,
Y: 0,
Z: 0,
};
filterMetrics(metrics, true).forEach((m: MetricData, idx) => {
const metricFirstLetter = m.value[0].toUpperCase();
if (alphabet.includes(metricFirstLetter) && !alphabetCheck[metricFirstLetter]) {
alphabetCheck[metricFirstLetter] += 1;
}
});
// return the alphabet components with the correct style and behavior
return Object.keys(alphabetCheck).map((letter: string) => {
// const active: boolean = .some((m: MetricData) => {
// return m.value[0] === letter || m.value[0] === letter?.toLowerCase();
// });
const active: boolean = alphabetCheck[letter] > 0;
// starts with letter search
// filter by starts with letter
// if same letter searched null out remove letter search
function updateLetterSearch() {
if (letterSearch === letter) {
setLetterSearch(null);
} else {
setLetterSearch(letter);
}
setPageNum(1);
}
// selected letter to filter by
const selectedClass: string = letterSearch === letter ? styles.selAlpha : '';
// these letters are represented in the list of metrics
const activeClass: string = active ? styles.active : styles.gray;
return (
<span
onClick={active ? updateLetterSearch : () => {}}
className={`${selectedClass} ${activeClass}`}
key={letter}
data-testid={'letter-' + letter}
>
{letter + ' '}
{/* {idx !== coll.length - 1 ? '|': ''} */}
</span>
);
});
}
return (
<Modal
data-testid={testIds.metricModal}
@ -331,8 +405,9 @@ export const MetricEncyclopediaModal = (props: Props) => {
onDismiss={onClose}
aria-label="Metric Encyclopedia"
>
<FeedbackLink feedbackUrl="https://forms.gle/DEMAJHoAMpe3e54CA" />
<div className={styles.spacing}>
Browse {metrics.length} metric{metrics.length > 1 ? 's' : ''} by text, by type, alphabetically or select a
Browse {totalMetricCount} metric{totalMetricCount > 1 ? 's' : ''} by text, by type, alphabetically or select a
variable.
{isLoading && (
<div className={styles.inlineSpinner}>
@ -358,7 +433,6 @@ export const MetricEncyclopediaModal = (props: Props) => {
// get all metrics data if a user erases everything in the input
updateMetricsMetadata();
} else if (useBackend) {
setIsLoading(true);
debouncedBackendSearch(value);
} else {
// search either the names or all metadata
@ -462,69 +536,8 @@ export const MetricEncyclopediaModal = (props: Props) => {
}}
/>
</div>
<h5 className={`${styles.center} ${styles.topPadding}`}>Results</h5>
<div className={`${styles.center} ${styles.bottomPadding}`}>
{[
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'O',
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
'Y',
'Z',
].map((letter, idx, coll) => {
const active: boolean = filterMetrics(metrics, true).some((m: MetricData) => {
return m.value[0] === letter || m.value[0] === letter?.toLowerCase();
});
// starts with letter search
// filter by starts with letter
// if same letter searched null out remove letter search
function updateLetterSearch() {
if (letterSearch === letter) {
setLetterSearch(null);
} else {
setLetterSearch(letter);
}
setPageNum(1);
}
// selected letter to filter by
const selectedClass: string = letterSearch === letter ? styles.selAlpha : '';
// these letters are represented in the list of metrics
const activeClass: string = active ? styles.active : styles.gray;
return (
<span
onClick={active ? updateLetterSearch : () => {}}
className={`${selectedClass} ${activeClass}`}
key={letter}
data-testid={'letter-' + letter}
>
{letter + ' '}
{/* {idx !== coll.length - 1 ? '|': ''} */}
</span>
);
})}
</div>
<h5 className={`${styles.center} ${styles.topPadding}`}>{filteredMetricCount} Results</h5>
<div className={`${styles.center} ${styles.bottomPadding}`}>{letterSearchComponent()}</div>
{metrics &&
displayedMetrics(metrics).map((metric: MetricData, idx) => {
return (
@ -717,3 +730,32 @@ export const testIds = {
resultsPerPage: 'results-per-page',
setUseBackend: 'set-use-backend',
};
const alphabet = [
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'O',
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
'Y',
'Z',
];

Loading…
Cancel
Save