mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Loki: Make label browser accessible in query builder (#58525)
* add label browser button to query editor header * add dynamic button label text * add LabelBrowserModal.tsx * toggle label browser modal on click * pass required props to LabelBrowserModal * add placeholder to text input * render label browser inside of the modal * change button based on label status * remove label browser button from code mode * fix element overlap in label browser * fix undefined app in feature tracking * remove all any types * add tests for label browser button * update modal component width * update label loading function * add tests to LabelBrowserModal * fix broken mock datasource * update test names * use stack component for button spacing * revert modal width * update label search placeholder * remove unused import * add test assertion for closed modal * remove redundant if statement * remove unnecessary code * update error message and fix position * fix input placeholder text
This commit is contained in:
parent
ed72b02b27
commit
a098bdef58
@ -131,16 +131,11 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
|||||||
margin-bottom: ${theme.spacing(1)};
|
margin-bottom: ${theme.spacing(1)};
|
||||||
`,
|
`,
|
||||||
status: css`
|
status: css`
|
||||||
padding: ${theme.spacing(0.5)};
|
margin-bottom: ${theme.spacing(1)};
|
||||||
color: ${theme.colors.text.secondary};
|
color: ${theme.colors.text.secondary};
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
/* using absolute positioning because flex interferes with ellipsis */
|
|
||||||
position: absolute;
|
|
||||||
width: 50%;
|
|
||||||
right: 0;
|
|
||||||
text-align: right;
|
|
||||||
transition: opacity 100ms linear;
|
transition: opacity 100ms linear;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
`,
|
`,
|
||||||
@ -367,7 +362,7 @@ export class UnthemedLokiLabelBrowser extends React.Component<BrowserProps, Brow
|
|||||||
async fetchSeries(selector: string, lastFacetted?: string) {
|
async fetchSeries(selector: string, lastFacetted?: string) {
|
||||||
const { languageProvider } = this.props;
|
const { languageProvider } = this.props;
|
||||||
if (lastFacetted) {
|
if (lastFacetted) {
|
||||||
this.updateLabelState(lastFacetted, { loading: true }, `Facetting labels for ${selector}`);
|
this.updateLabelState(lastFacetted, { loading: true }, `Loading labels for ${selector}`);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const possibleLabels = await languageProvider.fetchSeriesLabels(selector, true);
|
const possibleLabels = await languageProvider.fetchSeriesLabels(selector, true);
|
||||||
@ -467,7 +462,12 @@ export class UnthemedLokiLabelBrowser extends React.Component<BrowserProps, Brow
|
|||||||
2. Find values for the selected labels
|
2. Find values for the selected labels
|
||||||
</Label>
|
</Label>
|
||||||
<div>
|
<div>
|
||||||
<Input onChange={this.onChangeSearch} aria-label="Filter expression for values" value={searchTerm} />
|
<Input
|
||||||
|
onChange={this.onChangeSearch}
|
||||||
|
aria-label="Filter expression for values"
|
||||||
|
value={searchTerm}
|
||||||
|
placeholder={'Enter a label value'}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.valueListArea}>
|
<div className={styles.valueListArea}>
|
||||||
{selectedLabels.map((label) => (
|
{selectedLabels.map((label) => (
|
||||||
@ -520,6 +520,9 @@ export class UnthemedLokiLabelBrowser extends React.Component<BrowserProps, Brow
|
|||||||
{selector}
|
{selector}
|
||||||
</div>
|
</div>
|
||||||
{validationStatus && <div className={styles.validationStatus}>{validationStatus}</div>}
|
{validationStatus && <div className={styles.validationStatus}>{validationStatus}</div>}
|
||||||
|
<div className={cx(styles.status, (status || error) && styles.statusShowing)}>
|
||||||
|
<span className={error ? styles.error : ''}>{error || status}</span>
|
||||||
|
</div>
|
||||||
<HorizontalGroup>
|
<HorizontalGroup>
|
||||||
<Button aria-label="Use selector as logs button" disabled={empty} onClick={this.onClickRunLogsQuery}>
|
<Button aria-label="Use selector as logs button" disabled={empty} onClick={this.onClickRunLogsQuery}>
|
||||||
Show logs
|
Show logs
|
||||||
@ -543,9 +546,6 @@ export class UnthemedLokiLabelBrowser extends React.Component<BrowserProps, Brow
|
|||||||
<Button aria-label="Selector clear button" variant="secondary" onClick={this.onClickClear}>
|
<Button aria-label="Selector clear button" variant="secondary" onClick={this.onClickClear}>
|
||||||
Clear
|
Clear
|
||||||
</Button>
|
</Button>
|
||||||
<div className={cx(styles.status, (status || error) && styles.statusShowing)}>
|
|
||||||
<span className={error ? styles.error : ''}>{error || status}</span>
|
|
||||||
</div>
|
|
||||||
</HorizontalGroup>
|
</HorizontalGroup>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -149,6 +149,11 @@ describe('LokiQueryEditorSelector', () => {
|
|||||||
expect(screen.getByText('Rate')).toBeInTheDocument();
|
expect(screen.getByText('Rate')).toBeInTheDocument();
|
||||||
expect(screen.getByText('$__interval')).toBeInTheDocument();
|
expect(screen.getByText('$__interval')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders the label browser button', async () => {
|
||||||
|
renderWithMode(QueryEditorMode.Code);
|
||||||
|
expect(await screen.findByTestId('label-browser-button')).toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function renderWithMode(mode: QueryEditorMode) {
|
function renderWithMode(mode: QueryEditorMode) {
|
||||||
|
@ -2,7 +2,7 @@ import React, { SyntheticEvent, useCallback, useEffect, useState } from 'react';
|
|||||||
|
|
||||||
import { CoreApp, LoadingState } from '@grafana/data';
|
import { CoreApp, LoadingState } from '@grafana/data';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
import { EditorHeader, EditorRows, FlexItem, Space } from '@grafana/experimental';
|
import { EditorHeader, EditorRows, FlexItem, Space, Stack } from '@grafana/experimental';
|
||||||
import { reportInteraction } from '@grafana/runtime';
|
import { reportInteraction } from '@grafana/runtime';
|
||||||
import { Button, ConfirmModal } from '@grafana/ui';
|
import { Button, ConfirmModal } from '@grafana/ui';
|
||||||
import { QueryEditorModeToggle } from 'app/plugins/datasource/prometheus/querybuilder/shared/QueryEditorModeToggle';
|
import { QueryEditorModeToggle } from 'app/plugins/datasource/prometheus/querybuilder/shared/QueryEditorModeToggle';
|
||||||
@ -10,6 +10,7 @@ import { QueryHeaderSwitch } from 'app/plugins/datasource/prometheus/querybuilde
|
|||||||
import { QueryEditorMode } from 'app/plugins/datasource/prometheus/querybuilder/shared/types';
|
import { QueryEditorMode } from 'app/plugins/datasource/prometheus/querybuilder/shared/types';
|
||||||
|
|
||||||
import { lokiQueryEditorExplainKey, useFlag } from '../../prometheus/querybuilder/shared/hooks/useFlag';
|
import { lokiQueryEditorExplainKey, useFlag } from '../../prometheus/querybuilder/shared/hooks/useFlag';
|
||||||
|
import { LabelBrowserModal } from '../querybuilder/components/LabelBrowserModal';
|
||||||
import { LokiQueryBuilderContainer } from '../querybuilder/components/LokiQueryBuilderContainer';
|
import { LokiQueryBuilderContainer } from '../querybuilder/components/LokiQueryBuilderContainer';
|
||||||
import { LokiQueryBuilderOptions } from '../querybuilder/components/LokiQueryBuilderOptions';
|
import { LokiQueryBuilderOptions } from '../querybuilder/components/LokiQueryBuilderOptions';
|
||||||
import { LokiQueryCodeEditor } from '../querybuilder/components/LokiQueryCodeEditor';
|
import { LokiQueryCodeEditor } from '../querybuilder/components/LokiQueryCodeEditor';
|
||||||
@ -25,10 +26,12 @@ export const testIds = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const LokiQueryEditor = React.memo<LokiQueryEditorProps>((props) => {
|
export const LokiQueryEditor = React.memo<LokiQueryEditorProps>((props) => {
|
||||||
const { onChange, onRunQuery, onAddQuery, data, app, queries } = props;
|
const { onChange, onRunQuery, onAddQuery, data, app, queries, datasource } = props;
|
||||||
const [parseModalOpen, setParseModalOpen] = useState(false);
|
const [parseModalOpen, setParseModalOpen] = useState(false);
|
||||||
const [queryPatternsModalOpen, setQueryPatternsModalOpen] = useState(false);
|
const [queryPatternsModalOpen, setQueryPatternsModalOpen] = useState(false);
|
||||||
const [dataIsStale, setDataIsStale] = useState(false);
|
const [dataIsStale, setDataIsStale] = useState(false);
|
||||||
|
const [labelBrowserVisible, setLabelBrowserVisible] = useState(false);
|
||||||
|
const [labelsLoaded, setLabelsLoaded] = useState(false);
|
||||||
const { flag: explain, setFlag: setExplain } = useFlag(lokiQueryEditorExplainKey);
|
const { flag: explain, setFlag: setExplain } = useFlag(lokiQueryEditorExplainKey);
|
||||||
|
|
||||||
const query = getQueryWithDefaults(props.query);
|
const query = getQueryWithDefaults(props.query);
|
||||||
@ -70,6 +73,30 @@ export const LokiQueryEditor = React.memo<LokiQueryEditorProps>((props) => {
|
|||||||
onChange(query);
|
onChange(query);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onClickChooserButton = () => {
|
||||||
|
setLabelBrowserVisible((visible) => !visible);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getChooserText = (logLabelsLoaded: boolean, hasLogLabels: boolean) => {
|
||||||
|
if (!logLabelsLoaded) {
|
||||||
|
return 'Loading labels...';
|
||||||
|
}
|
||||||
|
if (!hasLogLabels) {
|
||||||
|
return '(No labels found)';
|
||||||
|
}
|
||||||
|
return 'Label browser';
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
datasource.languageProvider.start().then(() => {
|
||||||
|
setLabelsLoaded(true);
|
||||||
|
});
|
||||||
|
}, [datasource]);
|
||||||
|
|
||||||
|
const hasLogLabels = datasource.languageProvider.getLabelKeys().length > 0;
|
||||||
|
const labelBrowserText = getChooserText(labelsLoaded, hasLogLabels);
|
||||||
|
const buttonDisabled = !(labelsLoaded && hasLogLabels);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
@ -93,25 +120,45 @@ export const LokiQueryEditor = React.memo<LokiQueryEditorProps>((props) => {
|
|||||||
onAddQuery={onAddQuery}
|
onAddQuery={onAddQuery}
|
||||||
/>
|
/>
|
||||||
<EditorHeader>
|
<EditorHeader>
|
||||||
<Button
|
<LabelBrowserModal
|
||||||
aria-label={selectors.components.QueryBuilder.queryPatterns}
|
isOpen={labelBrowserVisible}
|
||||||
variant="secondary"
|
languageProvider={datasource.languageProvider}
|
||||||
size="sm"
|
query={query}
|
||||||
onClick={() => {
|
app={app}
|
||||||
setQueryPatternsModalOpen((prevValue) => !prevValue);
|
onClose={() => setLabelBrowserVisible(false)}
|
||||||
|
onChange={onChangeInternal}
|
||||||
|
onRunQuery={onRunQuery}
|
||||||
|
/>
|
||||||
|
<Stack gap={1}>
|
||||||
|
<Button
|
||||||
|
aria-label={selectors.components.QueryBuilder.queryPatterns}
|
||||||
|
variant="secondary"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
setQueryPatternsModalOpen((prevValue) => !prevValue);
|
||||||
|
|
||||||
const visualQuery = buildVisualQueryFromString(query.expr || '');
|
const visualQuery = buildVisualQueryFromString(query.expr || '');
|
||||||
reportInteraction('grafana_loki_query_patterns_opened', {
|
reportInteraction('grafana_loki_query_patterns_opened', {
|
||||||
version: 'v2',
|
version: 'v2',
|
||||||
app: app ?? '',
|
app: app ?? '',
|
||||||
editorMode: query.editorMode,
|
editorMode: query.editorMode,
|
||||||
preSelectedOperationsCount: visualQuery.query.operations.length,
|
preSelectedOperationsCount: visualQuery.query.operations.length,
|
||||||
preSelectedLabelsCount: visualQuery.query.labels.length,
|
preSelectedLabelsCount: visualQuery.query.labels.length,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Kick start your query
|
Kick start your query
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
size="sm"
|
||||||
|
onClick={onClickChooserButton}
|
||||||
|
disabled={buttonDisabled}
|
||||||
|
data-testid="label-browser-button"
|
||||||
|
>
|
||||||
|
{labelBrowserText}
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
<QueryHeaderSwitch label="Explain" value={explain} onChange={onExplainChange} />
|
<QueryHeaderSwitch label="Explain" value={explain} onChange={onExplainChange} />
|
||||||
<FlexItem grow={1} />
|
<FlexItem grow={1} />
|
||||||
{app !== CoreApp.Explore && (
|
{app !== CoreApp.Explore && (
|
||||||
|
@ -13,7 +13,6 @@ import {
|
|||||||
TypeaheadInput,
|
TypeaheadInput,
|
||||||
BracesPlugin,
|
BracesPlugin,
|
||||||
DOMUtil,
|
DOMUtil,
|
||||||
Icon,
|
|
||||||
} from '@grafana/ui';
|
} from '@grafana/ui';
|
||||||
import { LocalStorageValueProvider } from 'app/core/components/LocalStorageValueProvider';
|
import { LocalStorageValueProvider } from 'app/core/components/LocalStorageValueProvider';
|
||||||
|
|
||||||
@ -22,21 +21,10 @@ import { LokiDatasource } from '../datasource';
|
|||||||
import { escapeLabelValueInSelector, shouldRefreshLabels } from '../languageUtils';
|
import { escapeLabelValueInSelector, shouldRefreshLabels } from '../languageUtils';
|
||||||
import { LokiQuery, LokiOptions } from '../types';
|
import { LokiQuery, LokiOptions } from '../types';
|
||||||
|
|
||||||
import { LokiLabelBrowser } from './LokiLabelBrowser';
|
|
||||||
import { MonacoQueryFieldWrapper } from './monaco-query-field/MonacoQueryFieldWrapper';
|
import { MonacoQueryFieldWrapper } from './monaco-query-field/MonacoQueryFieldWrapper';
|
||||||
|
|
||||||
const LAST_USED_LABELS_KEY = 'grafana.datasources.loki.browser.labels';
|
const LAST_USED_LABELS_KEY = 'grafana.datasources.loki.browser.labels';
|
||||||
|
|
||||||
function getChooserText(hasSyntax: boolean, hasLogLabels: boolean) {
|
|
||||||
if (!hasSyntax) {
|
|
||||||
return 'Loading labels...';
|
|
||||||
}
|
|
||||||
if (!hasLogLabels) {
|
|
||||||
return '(No labels found)';
|
|
||||||
}
|
|
||||||
return 'Label browser';
|
|
||||||
}
|
|
||||||
|
|
||||||
function willApplySuggestion(suggestion: string, { typeaheadContext, typeaheadText }: SuggestionsState): string {
|
function willApplySuggestion(suggestion: string, { typeaheadContext, typeaheadText }: SuggestionsState): string {
|
||||||
// Modify suggestion based on context
|
// Modify suggestion based on context
|
||||||
switch (typeaheadContext) {
|
switch (typeaheadContext) {
|
||||||
@ -191,11 +179,6 @@ export class LokiQueryField extends React.PureComponent<LokiQueryFieldProps, Lok
|
|||||||
onBlur,
|
onBlur,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const { labelsLoaded, labelBrowserVisible } = this.state;
|
|
||||||
const hasLogLabels = datasource.languageProvider.getLabelKeys().length > 0;
|
|
||||||
const chooserText = getChooserText(labelsLoaded, hasLogLabels);
|
|
||||||
const buttonDisabled = !(labelsLoaded && hasLogLabels);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LocalStorageValueProvider<string[]> storageKey={LAST_USED_LABELS_KEY} defaultValue={[]}>
|
<LocalStorageValueProvider<string[]> storageKey={LAST_USED_LABELS_KEY} defaultValue={[]}>
|
||||||
{(lastUsedLabels, onLastUsedLabelsSave, onLastUsedLabelsDelete) => {
|
{(lastUsedLabels, onLastUsedLabelsSave, onLastUsedLabelsDelete) => {
|
||||||
@ -205,14 +188,6 @@ export class LokiQueryField extends React.PureComponent<LokiQueryFieldProps, Lok
|
|||||||
className="gf-form-inline gf-form-inline--xs-view-flex-column flex-grow-1"
|
className="gf-form-inline gf-form-inline--xs-view-flex-column flex-grow-1"
|
||||||
data-testid={this.props['data-testid']}
|
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">
|
<div className="gf-form gf-form--grow flex-shrink-1 min-width-15">
|
||||||
{config.featureToggles.lokiMonacoEditor ? (
|
{config.featureToggles.lokiMonacoEditor ? (
|
||||||
<MonacoQueryFieldWrapper
|
<MonacoQueryFieldWrapper
|
||||||
@ -239,19 +214,6 @@ export class LokiQueryField extends React.PureComponent<LokiQueryFieldProps, Lok
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{labelBrowserVisible && (
|
|
||||||
<div className="gf-form">
|
|
||||||
<LokiLabelBrowser
|
|
||||||
languageProvider={datasource.languageProvider}
|
|
||||||
onChange={this.onChangeLabelBrowser}
|
|
||||||
lastUsedLabels={lastUsedLabels || []}
|
|
||||||
storeLastUsedLabels={onLastUsedLabelsSave}
|
|
||||||
deleteLastUsedLabels={onLastUsedLabelsDelete}
|
|
||||||
app={app}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{ExtraFieldElement}
|
{ExtraFieldElement}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { LokiDatasource } from '../../datasource';
|
||||||
|
import { createLokiDatasource } from '../../mocks';
|
||||||
|
import { LokiQuery } from '../../types';
|
||||||
|
|
||||||
|
import { LabelBrowserModal, Props } from './LabelBrowserModal';
|
||||||
|
|
||||||
|
jest.mock('@grafana/runtime', () => ({
|
||||||
|
...jest.requireActual('@grafana/runtime'),
|
||||||
|
reportInteraction: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('LabelBrowserModal', () => {
|
||||||
|
let datasource: LokiDatasource, props: Props;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
datasource = createLokiDatasource();
|
||||||
|
|
||||||
|
props = {
|
||||||
|
isOpen: true,
|
||||||
|
languageProvider: datasource.languageProvider,
|
||||||
|
query: {} as LokiQuery,
|
||||||
|
onClose: jest.fn(),
|
||||||
|
onChange: jest.fn(),
|
||||||
|
onRunQuery: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
jest.spyOn(datasource, 'metadataRequest').mockResolvedValue({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the label browser modal when open', () => {
|
||||||
|
render(<LabelBrowserModal {...props} />);
|
||||||
|
expect(screen.getByRole('heading', { name: /label browser/i })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("doesn't render the label browser modal when closed", () => {
|
||||||
|
render(<LabelBrowserModal {...props} isOpen={false} />);
|
||||||
|
expect(screen.queryByRole('heading', { name: /label browser/i })).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,57 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { CoreApp } from '@grafana/data';
|
||||||
|
import { Modal } from '@grafana/ui';
|
||||||
|
import { LocalStorageValueProvider } from 'app/core/components/LocalStorageValueProvider';
|
||||||
|
|
||||||
|
import LanguageProvider from '../../LanguageProvider';
|
||||||
|
import { LokiLabelBrowser } from '../../components/LokiLabelBrowser';
|
||||||
|
import { LokiQuery } from '../../types';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
isOpen: boolean;
|
||||||
|
languageProvider: LanguageProvider;
|
||||||
|
query: LokiQuery;
|
||||||
|
app?: CoreApp;
|
||||||
|
onClose: () => void;
|
||||||
|
onChange: (query: LokiQuery) => void;
|
||||||
|
onRunQuery: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LabelBrowserModal = (props: Props) => {
|
||||||
|
const { isOpen, onClose, languageProvider, app } = props;
|
||||||
|
|
||||||
|
const LAST_USED_LABELS_KEY = 'grafana.datasources.loki.browser.labels';
|
||||||
|
|
||||||
|
const changeQuery = (value: string) => {
|
||||||
|
const { query, onChange, onRunQuery } = props;
|
||||||
|
|
||||||
|
const nextQuery = { ...query, expr: value };
|
||||||
|
onChange(nextQuery);
|
||||||
|
onRunQuery();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onChange = (selector: string) => {
|
||||||
|
changeQuery(selector);
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal isOpen={isOpen} title="Label browser" onDismiss={onClose}>
|
||||||
|
<LocalStorageValueProvider<string[]> storageKey={LAST_USED_LABELS_KEY} defaultValue={[]}>
|
||||||
|
{(lastUsedLabels, onLastUsedLabelsSave, onLastUsedLabelsDelete) => {
|
||||||
|
return (
|
||||||
|
<LokiLabelBrowser
|
||||||
|
languageProvider={languageProvider}
|
||||||
|
onChange={onChange}
|
||||||
|
lastUsedLabels={lastUsedLabels}
|
||||||
|
storeLastUsedLabels={onLastUsedLabelsSave}
|
||||||
|
deleteLastUsedLabels={onLastUsedLabelsDelete}
|
||||||
|
app={app}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</LocalStorageValueProvider>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user