Refactor: Decouple Label Browser from LocalStorage (#40449)

* Refactor: Decouple Label Browser from LocalStorage

* Clean up
This commit is contained in:
Piotr Jamróz
2021-10-15 14:18:11 +02:00
committed by GitHub
parent 7140867868
commit 6dc21d5899
7 changed files with 154 additions and 109 deletions

View File

@@ -3,8 +3,8 @@ import store from '../../store';
export interface Props<T> {
storageKey: string;
defaultValue?: T;
children: (value: T, onSaveToStore: (value: T) => void) => React.ReactNode;
defaultValue: T;
children: (value: T, onSaveToStore: (value: T) => void, onDeleteFromStore: () => void) => React.ReactNode;
}
interface State<T> {
@@ -32,10 +32,20 @@ export class LocalStorageValueProvider<T> extends PureComponent<Props<T>, State<
this.setState({ value });
};
onDeleteFromStore = () => {
const { storageKey, defaultValue } = this.props;
try {
store.delete(storageKey);
} catch (error) {
console.log(error);
}
this.setState({ value: defaultValue });
};
render() {
const { children } = this.props;
const { value } = this.state;
return <>{children(value, this.onSaveToStore)}</>;
return <>{children(value, this.onSaveToStore, this.onDeleteFromStore)}</>;
}
}

View File

@@ -109,6 +109,9 @@ describe('LokiLabelBrowser', () => {
onChange: () => {},
autoSelect: 0,
languageProvider: (mockLanguageProvider as unknown) as LokiLanguageProvider,
lastUsedLabels: [],
storeLastUsedLabels: () => {},
deleteLastUsedLabels: () => {},
};
return defaults;

View File

@@ -13,7 +13,6 @@ import {
import LokiLanguageProvider from '../language_provider';
import PromQlLanguageProvider from '../../prometheus/language_provider';
import { css, cx } from '@emotion/css';
import store from 'app/core/store';
import { FixedSizeList } from 'react-window';
import { GrafanaTheme2 } from '@grafana/data';
import { sortBy } from 'lodash';
@@ -24,8 +23,6 @@ const MAX_VALUE_COUNT = 10000;
const MAX_AUTO_SELECT = 4;
const EMPTY_SELECTOR = '{}';
export const LAST_USED_LABELS_KEY = 'grafana.datasources.loki.browser.labels';
export interface BrowserProps {
// TODO #33976: Is it possible to use a common interface here? For example: LabelsLanguageProvider
languageProvider: LokiLanguageProvider | PromQlLanguageProvider;
@@ -33,6 +30,9 @@ export interface BrowserProps {
theme: GrafanaTheme2;
autoSelect?: number;
hide?: () => void;
lastUsedLabels: string[];
storeLastUsedLabels: (labels: string[]) => void;
deleteLastUsedLabels: () => void;
}
interface BrowserState {
@@ -208,7 +208,7 @@ export class UnthemedLokiLabelBrowser extends React.Component<BrowserProps, Brow
}));
return { labels, searchTerm: '', status: '', error: '', validationStatus: '' };
});
store.delete(LAST_USED_LABELS_KEY);
this.props.deleteLastUsedLabels();
};
onClickLabel = (name: string, value: string | undefined, event: React.MouseEvent<HTMLElement>) => {
@@ -261,9 +261,9 @@ export class UnthemedLokiLabelBrowser extends React.Component<BrowserProps, Brow
}
componentDidMount() {
const { languageProvider, autoSelect = MAX_AUTO_SELECT } = this.props;
const { languageProvider, autoSelect = MAX_AUTO_SELECT, lastUsedLabels } = this.props;
if (languageProvider) {
const selectedLabels: string[] = store.getObject(LAST_USED_LABELS_KEY, []);
const selectedLabels: string[] = lastUsedLabels;
languageProvider.start().then(() => {
let rawLabels: string[] = languageProvider.getLabelKeys();
if (rawLabels.length > MAX_LABEL_COUNT) {
@@ -295,7 +295,7 @@ export class UnthemedLokiLabelBrowser extends React.Component<BrowserProps, Brow
return;
}
const selectedLabels = this.state.labels.filter((label) => label.selected).map((label) => label.name);
store.setObject(LAST_USED_LABELS_KEY, selectedLabels);
this.props.storeLastUsedLabels(selectedLabels);
if (label.selected) {
// Refetch values for newly selected label...
if (!label.values) {

View File

@@ -17,6 +17,9 @@ import { LanguageMap, languages as prismLanguages } from 'prismjs';
import LokiLanguageProvider from '../language_provider';
import { shouldRefreshLabels } from '../language_utils';
import LokiDatasource from '../datasource';
import { LocalStorageValueProvider } from 'app/core/components/LocalStorageValueProvider';
const LAST_USED_LABELS_KEY = 'grafana.datasources.loki.browser.labels';
function getChooserText(hasSyntax: boolean, hasLogLabels: boolean) {
if (!hasSyntax) {
@@ -159,42 +162,54 @@ export class LokiQueryField extends React.PureComponent<LokiQueryFieldProps, Lok
const buttonDisabled = !(labelsLoaded && hasLogLabels);
return (
<>
<div
className="gf-form-inline gf-form-inline--xs-view-flex-column flex-grow-1"
data-testid={this.props['data-testid']}
>
<button
className="gf-form-label query-keyword pointer"
onClick={this.onClickChooserButton}
disabled={buttonDisabled}
>
{chooserText}
<Icon name={labelBrowserVisible ? 'angle-down' : 'angle-right'} />
</button>
<div className="gf-form gf-form--grow flex-shrink-1 min-width-15">
<QueryField
additionalPlugins={this.plugins}
cleanText={cleanText}
query={query.expr}
onTypeahead={this.onTypeahead}
onWillApplySuggestion={willApplySuggestion}
onChange={this.onChangeQuery}
onBlur={this.props.onBlur}
onRunQuery={this.props.onRunQuery}
placeholder={placeholder}
portalOrigin="loki"
/>
</div>
</div>
{labelBrowserVisible && (
<div className="gf-form">
<LokiLabelBrowser languageProvider={lokiLanguageProvider} onChange={this.onChangeLabelBrowser} />
</div>
)}
<LocalStorageValueProvider<string[]> storageKey={LAST_USED_LABELS_KEY} defaultValue={[]}>
{(lastUsedLabels, onLastUsedLabelsSave, onLastUsedLabelsDelete) => {
return (
<>
<div
className="gf-form-inline gf-form-inline--xs-view-flex-column flex-grow-1"
data-testid={this.props['data-testid']}
>
<button
className="gf-form-label query-keyword pointer"
onClick={this.onClickChooserButton}
disabled={buttonDisabled}
>
{chooserText}
<Icon name={labelBrowserVisible ? 'angle-down' : 'angle-right'} />
</button>
<div className="gf-form gf-form--grow flex-shrink-1 min-width-15">
<QueryField
additionalPlugins={this.plugins}
cleanText={cleanText}
query={query.expr}
onTypeahead={this.onTypeahead}
onWillApplySuggestion={willApplySuggestion}
onChange={this.onChangeQuery}
onBlur={this.props.onBlur}
onRunQuery={this.props.onRunQuery}
placeholder={placeholder}
portalOrigin="loki"
/>
</div>
</div>
{labelBrowserVisible && (
<div className="gf-form">
<LokiLabelBrowser
languageProvider={lokiLanguageProvider}
onChange={this.onChangeLabelBrowser}
lastUsedLabels={lastUsedLabels || []}
storeLastUsedLabels={onLastUsedLabelsSave}
deleteLastUsedLabels={onLastUsedLabelsDelete}
/>
</div>
)}
{ExtraFieldElement}
</>
{ExtraFieldElement}
</>
);
}}
</LocalStorageValueProvider>
);
}
}

View File

@@ -23,8 +23,10 @@ import { QueryEditorProps, QueryHint, isDataFrame, toLegacyResponseData, TimeRan
import { PrometheusDatasource } from '../datasource';
import { PrometheusMetricsBrowser } from './PrometheusMetricsBrowser';
import { MonacoQueryFieldLazy } from './monaco-query-field/MonacoQueryFieldLazy';
import { LocalStorageValueProvider } from 'app/core/components/LocalStorageValueProvider';
export const RECORDING_RULES_GROUP = '__recording_rules__';
const LAST_USED_LABELS_KEY = 'grafana.datasources.prometheus.browser.labels';
function getChooserText(metricsLookupDisabled: boolean, hasSyntax: boolean, hasMetrics: boolean) {
if (metricsLookupDisabled) {
@@ -277,66 +279,78 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
const isMonacoEditorEnabled = config.featureToggles.prometheusMonaco;
return (
<>
<div
className="gf-form-inline gf-form-inline--xs-view-flex-column flex-grow-1"
data-testid={this.props['data-testid']}
>
<button
className="gf-form-label query-keyword pointer"
onClick={this.onClickChooserButton}
disabled={buttonDisabled}
>
{chooserText}
<Icon name={labelBrowserVisible ? 'angle-down' : 'angle-right'} />
</button>
<LocalStorageValueProvider<string[]> storageKey={LAST_USED_LABELS_KEY} defaultValue={[]}>
{(lastUsedLabels, onLastUsedLabelsSave, onLastUsedLabelsDelete) => {
return (
<>
<div
className="gf-form-inline gf-form-inline--xs-view-flex-column flex-grow-1"
data-testid={this.props['data-testid']}
>
<button
className="gf-form-label query-keyword pointer"
onClick={this.onClickChooserButton}
disabled={buttonDisabled}
>
{chooserText}
<Icon name={labelBrowserVisible ? 'angle-down' : 'angle-right'} />
</button>
<div className="gf-form gf-form--grow flex-shrink-1 min-width-15">
{isMonacoEditorEnabled ? (
<MonacoQueryFieldLazy
languageProvider={languageProvider}
history={history}
onChange={this.onChangeQuery}
onRunQuery={this.props.onRunQuery}
initialValue={query.expr ?? ''}
/>
) : (
<QueryField
additionalPlugins={this.plugins}
cleanText={cleanText}
query={query.expr}
onTypeahead={this.onTypeahead}
onWillApplySuggestion={willApplySuggestion}
onBlur={this.props.onBlur}
onChange={this.onChangeQuery}
onRunQuery={this.props.onRunQuery}
placeholder={placeholder}
portalOrigin="prometheus"
syntaxLoaded={syntaxLoaded}
/>
)}
</div>
</div>
{labelBrowserVisible && (
<div className="gf-form">
<PrometheusMetricsBrowser languageProvider={languageProvider} onChange={this.onChangeLabelBrowser} />
</div>
)}
<div className="gf-form gf-form--grow flex-shrink-1 min-width-15">
{isMonacoEditorEnabled ? (
<MonacoQueryFieldLazy
languageProvider={languageProvider}
history={history}
onChange={this.onChangeQuery}
onRunQuery={this.props.onRunQuery}
initialValue={query.expr ?? ''}
/>
) : (
<QueryField
additionalPlugins={this.plugins}
cleanText={cleanText}
query={query.expr}
onTypeahead={this.onTypeahead}
onWillApplySuggestion={willApplySuggestion}
onBlur={this.props.onBlur}
onChange={this.onChangeQuery}
onRunQuery={this.props.onRunQuery}
placeholder={placeholder}
portalOrigin="prometheus"
syntaxLoaded={syntaxLoaded}
/>
)}
</div>
</div>
{labelBrowserVisible && (
<div className="gf-form">
<PrometheusMetricsBrowser
languageProvider={languageProvider}
onChange={this.onChangeLabelBrowser}
lastUsedLabels={lastUsedLabels || []}
storeLastUsedLabels={onLastUsedLabelsSave}
deleteLastUsedLabels={onLastUsedLabelsDelete}
/>
</div>
)}
{ExtraFieldElement}
{hint ? (
<div className="query-row-break">
<div className="prom-query-field-info text-warning">
{hint.label}{' '}
{hint.fix ? (
<a className="text-link muted" onClick={this.onClickHintFix}>
{hint.fix.label}
</a>
{ExtraFieldElement}
{hint ? (
<div className="query-row-break">
<div className="prom-query-field-info text-warning">
{hint.label}{' '}
{hint.fix ? (
<a className="text-link muted" onClick={this.onClickHintFix}>
{hint.fix.label}
</a>
) : null}
</div>
</div>
) : null}
</div>
</div>
) : null}
</>
</>
);
}}
</LocalStorageValueProvider>
);
}
}

View File

@@ -133,6 +133,9 @@ describe('PrometheusMetricsBrowser', () => {
onChange: () => {},
autoSelect: 0,
languageProvider: (mockLanguageProvider as unknown) as PromQlLanguageProvider,
lastUsedLabels: [],
storeLastUsedLabels: () => {},
deleteLastUsedLabels: () => {},
};
return defaults;

View File

@@ -12,7 +12,6 @@ import {
import PromQlLanguageProvider from '../language_provider';
import { escapeLabelValueInExactSelector, escapeLabelValueInRegexSelector } from '../language_utils';
import { css, cx } from '@emotion/css';
import store from 'app/core/store';
import { FixedSizeList } from 'react-window';
import { GrafanaTheme } from '@grafana/data';
@@ -24,14 +23,15 @@ const EMPTY_SELECTOR = '{}';
const METRIC_LABEL = '__name__';
const LIST_ITEM_SIZE = 25;
export const LAST_USED_LABELS_KEY = 'grafana.datasources.prometheus.browser.labels';
export interface BrowserProps {
languageProvider: PromQlLanguageProvider;
onChange: (selector: string) => void;
theme: GrafanaTheme;
autoSelect?: number;
hide?: () => void;
lastUsedLabels: string[];
storeLastUsedLabels: (labels: string[]) => void;
deleteLastUsedLabels: () => void;
}
interface BrowserState {
@@ -243,7 +243,7 @@ export class UnthemedPrometheusMetricsBrowser extends React.Component<BrowserPro
valueSearchTerm: '',
};
});
store.delete(LAST_USED_LABELS_KEY);
this.props.deleteLastUsedLabels();
// Get metrics
this.fetchValues(METRIC_LABEL, EMPTY_SELECTOR);
};
@@ -316,9 +316,9 @@ export class UnthemedPrometheusMetricsBrowser extends React.Component<BrowserPro
}
componentDidMount() {
const { languageProvider } = this.props;
const { languageProvider, lastUsedLabels } = this.props;
if (languageProvider) {
const selectedLabels: string[] = store.getObject(LAST_USED_LABELS_KEY, []);
const selectedLabels: string[] = lastUsedLabels;
languageProvider.start().then(() => {
let rawLabels: string[] = languageProvider.getLabelKeys();
// TODO too-many-metrics
@@ -353,7 +353,7 @@ export class UnthemedPrometheusMetricsBrowser extends React.Component<BrowserPro
return;
}
const selectedLabels = this.state.labels.filter((label) => label.selected).map((label) => label.name);
store.setObject(LAST_USED_LABELS_KEY, selectedLabels);
this.props.storeLastUsedLabels(selectedLabels);
if (label.selected) {
// Refetch values for newly selected label...
if (!label.values) {