mirror of https://github.com/grafana/grafana
Loki: Refactor query editor so query field can be used independently (#33276)
* Refactor Loki so LokiQueryField can be used independently * Refactor PromQueryEditorpull/32834/head
parent
c809d63065
commit
c32c682f81
@ -1,16 +1,185 @@ |
||||
import React, { FunctionComponent } from 'react'; |
||||
import { LokiQueryFieldForm, LokiQueryFieldFormProps } from './LokiQueryFieldForm'; |
||||
// Libraries
|
||||
import React, { ReactNode } from 'react'; |
||||
|
||||
type LokiQueryFieldProps = Omit< |
||||
LokiQueryFieldFormProps, |
||||
'labelsLoaded' | 'onLoadOptions' | 'onLabelsRefresh' | 'absoluteRange' |
||||
>; |
||||
import { |
||||
SlatePrism, |
||||
TypeaheadOutput, |
||||
SuggestionsState, |
||||
QueryField, |
||||
TypeaheadInput, |
||||
BracesPlugin, |
||||
DOMUtil, |
||||
Icon, |
||||
} from '@grafana/ui'; |
||||
|
||||
export const LokiQueryField: FunctionComponent<LokiQueryFieldProps> = (props) => { |
||||
const { datasource, range, ...otherProps } = props; |
||||
const absoluteTimeRange = { from: range!.from!.valueOf(), to: range!.to!.valueOf() }; // Range here is never optional
|
||||
// Utils & Services
|
||||
// dom also includes Element polyfills
|
||||
import { Plugin, Node } from 'slate'; |
||||
import { LokiLabelBrowser } from './LokiLabelBrowser'; |
||||
|
||||
return <LokiQueryFieldForm datasource={datasource} absoluteRange={absoluteTimeRange} {...otherProps} />; |
||||
}; |
||||
// Types
|
||||
import { ExploreQueryFieldProps, AbsoluteTimeRange } from '@grafana/data'; |
||||
import { LokiQuery, LokiOptions } from '../types'; |
||||
import { LanguageMap, languages as prismLanguages } from 'prismjs'; |
||||
import LokiLanguageProvider, { LokiHistoryItem } from '../language_provider'; |
||||
import LokiDatasource from '../datasource'; |
||||
|
||||
export default LokiQueryField; |
||||
function getChooserText(hasSyntax: boolean, hasLogLabels: boolean) { |
||||
if (!hasSyntax) { |
||||
return 'Loading labels...'; |
||||
} |
||||
if (!hasLogLabels) { |
||||
return '(No logs found)'; |
||||
} |
||||
return 'Log browser'; |
||||
} |
||||
|
||||
function willApplySuggestion(suggestion: string, { typeaheadContext, typeaheadText }: SuggestionsState): string { |
||||
// Modify suggestion based on context
|
||||
switch (typeaheadContext) { |
||||
case 'context-labels': { |
||||
const nextChar = DOMUtil.getNextCharacter(); |
||||
if (!nextChar || nextChar === '}' || nextChar === ',') { |
||||
suggestion += '='; |
||||
} |
||||
break; |
||||
} |
||||
|
||||
case 'context-label-values': { |
||||
// Always add quotes and remove existing ones instead
|
||||
if (!typeaheadText.match(/^(!?=~?"|")/)) { |
||||
suggestion = `"${suggestion}`; |
||||
} |
||||
if (DOMUtil.getNextCharacter() !== '"') { |
||||
suggestion = `${suggestion}"`; |
||||
} |
||||
break; |
||||
} |
||||
|
||||
default: |
||||
} |
||||
return suggestion; |
||||
} |
||||
|
||||
export interface LokiQueryFieldProps extends ExploreQueryFieldProps<LokiDatasource, LokiQuery, LokiOptions> { |
||||
history: LokiHistoryItem[]; |
||||
absoluteRange: AbsoluteTimeRange; |
||||
ExtraFieldElement?: ReactNode; |
||||
} |
||||
|
||||
interface LokiQueryFieldState { |
||||
labelsLoaded: boolean; |
||||
labelBrowserVisible: boolean; |
||||
} |
||||
|
||||
export class LokiQueryField extends React.PureComponent<LokiQueryFieldProps, LokiQueryFieldState> { |
||||
plugins: Plugin[]; |
||||
|
||||
constructor(props: LokiQueryFieldProps) { |
||||
super(props); |
||||
|
||||
this.state = { labelsLoaded: false, labelBrowserVisible: false }; |
||||
|
||||
this.plugins = [ |
||||
BracesPlugin(), |
||||
SlatePrism( |
||||
{ |
||||
onlyIn: (node: Node) => node.object === 'block' && node.type === 'code_block', |
||||
getSyntax: (node: Node) => 'logql', |
||||
}, |
||||
{ ...(prismLanguages as LanguageMap), logql: this.props.datasource.languageProvider.getSyntax() } |
||||
), |
||||
]; |
||||
} |
||||
|
||||
async componentDidUpdate() { |
||||
await this.props.datasource.languageProvider.start(); |
||||
this.setState({ labelsLoaded: true }); |
||||
} |
||||
|
||||
onChangeLogLabels = (selector: string) => { |
||||
this.onChangeQuery(selector, true); |
||||
this.setState({ labelBrowserVisible: false }); |
||||
}; |
||||
|
||||
onChangeQuery = (value: string, override?: boolean) => { |
||||
// Send text change to parent
|
||||
const { query, onChange, onRunQuery } = this.props; |
||||
if (onChange) { |
||||
const nextQuery = { ...query, expr: value }; |
||||
onChange(nextQuery); |
||||
|
||||
if (override && onRunQuery) { |
||||
onRunQuery(); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
onClickChooserButton = () => { |
||||
this.setState((state) => ({ labelBrowserVisible: !state.labelBrowserVisible })); |
||||
}; |
||||
|
||||
onTypeahead = async (typeahead: TypeaheadInput): Promise<TypeaheadOutput> => { |
||||
const { datasource } = this.props; |
||||
|
||||
if (!datasource.languageProvider) { |
||||
return { suggestions: [] }; |
||||
} |
||||
|
||||
const lokiLanguageProvider = datasource.languageProvider as LokiLanguageProvider; |
||||
const { history } = this.props; |
||||
const { prefix, text, value, wrapperClasses, labelKey } = typeahead; |
||||
|
||||
const result = await lokiLanguageProvider.provideCompletionItems( |
||||
{ text, value, prefix, wrapperClasses, labelKey }, |
||||
{ history } |
||||
); |
||||
return result; |
||||
}; |
||||
|
||||
render() { |
||||
const { ExtraFieldElement, query, datasource } = this.props; |
||||
const { labelsLoaded, labelBrowserVisible } = this.state; |
||||
const lokiLanguageProvider = datasource.languageProvider as LokiLanguageProvider; |
||||
const cleanText = datasource.languageProvider ? lokiLanguageProvider.cleanText : undefined; |
||||
const hasLogLabels = lokiLanguageProvider.getLabelKeys().length > 0; |
||||
const chooserText = getChooserText(labelsLoaded, hasLogLabels); |
||||
const buttonDisabled = !(labelsLoaded && hasLogLabels); |
||||
|
||||
return ( |
||||
<> |
||||
<div className="gf-form-inline gf-form-inline--xs-view-flex-column flex-grow-1"> |
||||
<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="Enter a Loki query (run with Shift+Enter)" |
||||
portalOrigin="loki" |
||||
/> |
||||
</div> |
||||
</div> |
||||
{labelBrowserVisible && ( |
||||
<div className="gf-form"> |
||||
<LokiLabelBrowser languageProvider={lokiLanguageProvider} onChange={this.onChangeLogLabels} /> |
||||
</div> |
||||
)} |
||||
|
||||
{ExtraFieldElement} |
||||
</> |
||||
); |
||||
} |
||||
} |
||||
|
||||
@ -1,194 +0,0 @@ |
||||
// Libraries
|
||||
import React, { ReactNode } from 'react'; |
||||
|
||||
import { |
||||
SlatePrism, |
||||
TypeaheadOutput, |
||||
SuggestionsState, |
||||
QueryField, |
||||
TypeaheadInput, |
||||
BracesPlugin, |
||||
DOMUtil, |
||||
Icon, |
||||
} from '@grafana/ui'; |
||||
|
||||
// Utils & Services
|
||||
// dom also includes Element polyfills
|
||||
import { Plugin, Node } from 'slate'; |
||||
import { LokiLabelBrowser } from './LokiLabelBrowser'; |
||||
|
||||
// Types
|
||||
import { ExploreQueryFieldProps, AbsoluteTimeRange } from '@grafana/data'; |
||||
import { LokiQuery, LokiOptions } from '../types'; |
||||
import { LanguageMap, languages as prismLanguages } from 'prismjs'; |
||||
import LokiLanguageProvider, { LokiHistoryItem } from '../language_provider'; |
||||
import LokiDatasource from '../datasource'; |
||||
import LokiOptionFields from './LokiOptionFields'; |
||||
|
||||
function getChooserText(hasSyntax: boolean, hasLogLabels: boolean) { |
||||
if (!hasSyntax) { |
||||
return 'Loading labels...'; |
||||
} |
||||
if (!hasLogLabels) { |
||||
return '(No logs found)'; |
||||
} |
||||
return 'Log browser'; |
||||
} |
||||
|
||||
function willApplySuggestion(suggestion: string, { typeaheadContext, typeaheadText }: SuggestionsState): string { |
||||
// Modify suggestion based on context
|
||||
switch (typeaheadContext) { |
||||
case 'context-labels': { |
||||
const nextChar = DOMUtil.getNextCharacter(); |
||||
if (!nextChar || nextChar === '}' || nextChar === ',') { |
||||
suggestion += '='; |
||||
} |
||||
break; |
||||
} |
||||
|
||||
case 'context-label-values': { |
||||
// Always add quotes and remove existing ones instead
|
||||
if (!typeaheadText.match(/^(!?=~?"|")/)) { |
||||
suggestion = `"${suggestion}`; |
||||
} |
||||
if (DOMUtil.getNextCharacter() !== '"') { |
||||
suggestion = `${suggestion}"`; |
||||
} |
||||
break; |
||||
} |
||||
|
||||
default: |
||||
} |
||||
return suggestion; |
||||
} |
||||
|
||||
export interface LokiQueryFieldFormProps extends ExploreQueryFieldProps<LokiDatasource, LokiQuery, LokiOptions> { |
||||
history: LokiHistoryItem[]; |
||||
absoluteRange: AbsoluteTimeRange; |
||||
ExtraFieldElement?: ReactNode; |
||||
runOnBlur?: boolean; |
||||
} |
||||
|
||||
interface LokiQueryFieldFormState { |
||||
labelsLoaded: boolean; |
||||
labelBrowserVisible: boolean; |
||||
} |
||||
|
||||
export class LokiQueryFieldForm extends React.PureComponent<LokiQueryFieldFormProps, LokiQueryFieldFormState> { |
||||
plugins: Plugin[]; |
||||
|
||||
constructor(props: LokiQueryFieldFormProps) { |
||||
super(props); |
||||
|
||||
this.state = { labelsLoaded: false, labelBrowserVisible: false }; |
||||
|
||||
this.plugins = [ |
||||
BracesPlugin(), |
||||
SlatePrism( |
||||
{ |
||||
onlyIn: (node: Node) => node.object === 'block' && node.type === 'code_block', |
||||
getSyntax: (node: Node) => 'logql', |
||||
}, |
||||
{ ...(prismLanguages as LanguageMap), logql: this.props.datasource.languageProvider.getSyntax() } |
||||
), |
||||
]; |
||||
} |
||||
|
||||
async componentDidUpdate() { |
||||
await this.props.datasource.languageProvider.start(); |
||||
this.setState({ labelsLoaded: true }); |
||||
} |
||||
|
||||
onChangeLogLabels = (selector: string) => { |
||||
this.onChangeQuery(selector, true); |
||||
this.setState({ labelBrowserVisible: false }); |
||||
}; |
||||
|
||||
onChangeQuery = (value: string, override?: boolean) => { |
||||
// Send text change to parent
|
||||
const { query, onChange, onRunQuery } = this.props; |
||||
if (onChange) { |
||||
const nextQuery = { ...query, expr: value }; |
||||
onChange(nextQuery); |
||||
|
||||
if (override && onRunQuery) { |
||||
onRunQuery(); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
onClickChooserButton = () => { |
||||
this.setState((state) => ({ labelBrowserVisible: !state.labelBrowserVisible })); |
||||
}; |
||||
|
||||
onTypeahead = async (typeahead: TypeaheadInput): Promise<TypeaheadOutput> => { |
||||
const { datasource } = this.props; |
||||
|
||||
if (!datasource.languageProvider) { |
||||
return { suggestions: [] }; |
||||
} |
||||
|
||||
const lokiLanguageProvider = datasource.languageProvider as LokiLanguageProvider; |
||||
const { history } = this.props; |
||||
const { prefix, text, value, wrapperClasses, labelKey } = typeahead; |
||||
|
||||
const result = await lokiLanguageProvider.provideCompletionItems( |
||||
{ text, value, prefix, wrapperClasses, labelKey }, |
||||
{ history } |
||||
); |
||||
return result; |
||||
}; |
||||
|
||||
render() { |
||||
const { ExtraFieldElement, query, datasource, runOnBlur } = this.props; |
||||
const { labelsLoaded, labelBrowserVisible } = this.state; |
||||
const lokiLanguageProvider = datasource.languageProvider as LokiLanguageProvider; |
||||
const cleanText = datasource.languageProvider ? lokiLanguageProvider.cleanText : undefined; |
||||
const hasLogLabels = lokiLanguageProvider.getLabelKeys().length > 0; |
||||
const chooserText = getChooserText(labelsLoaded, hasLogLabels); |
||||
const buttonDisabled = !(labelsLoaded && hasLogLabels); |
||||
|
||||
return ( |
||||
<> |
||||
<div className="gf-form-inline gf-form-inline--xs-view-flex-column flex-grow-1"> |
||||
<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="Enter a Loki query (run with Shift+Enter)" |
||||
portalOrigin="loki" |
||||
/> |
||||
</div> |
||||
</div> |
||||
{labelBrowserVisible && ( |
||||
<div className="gf-form"> |
||||
<LokiLabelBrowser languageProvider={lokiLanguageProvider} onChange={this.onChangeLogLabels} /> |
||||
</div> |
||||
)} |
||||
<LokiOptionFields |
||||
queryType={query.instant ? 'instant' : 'range'} |
||||
lineLimitValue={query?.maxLines?.toString() || ''} |
||||
query={query} |
||||
onRunQuery={this.props.onRunQuery} |
||||
onChange={this.props.onChange} |
||||
runOnBlur={runOnBlur} |
||||
/> |
||||
{ExtraFieldElement} |
||||
</> |
||||
); |
||||
} |
||||
} |
||||
@ -1,197 +1,197 @@ |
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP |
||||
|
||||
exports[`Render PromQueryEditor with basic options should render 1`] = ` |
||||
<div> |
||||
<PromQueryField |
||||
datasource={ |
||||
Object { |
||||
"createQuery": [MockFunction], |
||||
"getPrometheusTime": [MockFunction], |
||||
} |
||||
} |
||||
history={Array []} |
||||
onChange={[Function]} |
||||
onRunQuery={[Function]} |
||||
query={ |
||||
Object { |
||||
"expr": "", |
||||
"refId": "A", |
||||
} |
||||
} |
||||
/> |
||||
<div |
||||
className="gf-form-inline" |
||||
> |
||||
<div |
||||
className="gf-form" |
||||
> |
||||
<FormLabel |
||||
tooltip="Controls the name of the time series, using name or pattern. For example |
||||
{{hostname}} will be replaced with label value for the label hostname." |
||||
width={7} |
||||
> |
||||
Legend |
||||
</FormLabel> |
||||
<input |
||||
className="gf-form-input" |
||||
onBlur={[Function]} |
||||
onChange={[Function]} |
||||
placeholder="legend format" |
||||
type="text" |
||||
value="" |
||||
/> |
||||
</div> |
||||
<PromQueryField |
||||
ExtraFieldElement={ |
||||
<div |
||||
className="gf-form" |
||||
className="gf-form-inline" |
||||
> |
||||
<FormLabel |
||||
tooltip={ |
||||
<React.Fragment> |
||||
An additional lower limit for the step parameter of the Prometheus query and for the |
||||
|
||||
<code> |
||||
$__interval |
||||
</code> |
||||
and |
||||
<code> |
||||
$__rate_interval |
||||
</code> |
||||
variables. The limit is absolute and not modified by the "Resolution" setting. |
||||
</React.Fragment> |
||||
} |
||||
width={7} |
||||
<div |
||||
className="gf-form" |
||||
> |
||||
Min step |
||||
</FormLabel> |
||||
<input |
||||
className="gf-form-input width-8" |
||||
onBlur={[Function]} |
||||
onChange={[Function]} |
||||
placeholder="" |
||||
type="text" |
||||
value="" |
||||
/> |
||||
</div> |
||||
<div |
||||
className="gf-form" |
||||
> |
||||
<FormLabel |
||||
tooltip="Controls the name of the time series, using name or pattern. For example |
||||
{{hostname}} will be replaced with label value for the label hostname." |
||||
width={7} |
||||
> |
||||
Legend |
||||
</FormLabel> |
||||
<input |
||||
className="gf-form-input" |
||||
onBlur={[Function]} |
||||
onChange={[Function]} |
||||
placeholder="legend format" |
||||
type="text" |
||||
value="" |
||||
/> |
||||
</div> |
||||
<div |
||||
className="gf-form-label" |
||||
className="gf-form" |
||||
> |
||||
Resolution |
||||
<FormLabel |
||||
tooltip={ |
||||
<React.Fragment> |
||||
An additional lower limit for the step parameter of the Prometheus query and for the |
||||
|
||||
<code> |
||||
$__interval |
||||
</code> |
||||
and |
||||
<code> |
||||
$__rate_interval |
||||
</code> |
||||
variables. The limit is absolute and not modified by the "Resolution" setting. |
||||
</React.Fragment> |
||||
} |
||||
width={7} |
||||
> |
||||
Min step |
||||
</FormLabel> |
||||
<input |
||||
className="gf-form-input width-8" |
||||
onBlur={[Function]} |
||||
onChange={[Function]} |
||||
placeholder="" |
||||
type="text" |
||||
value="" |
||||
/> |
||||
</div> |
||||
<Select |
||||
isSearchable={false} |
||||
onChange={[Function]} |
||||
options={ |
||||
Array [ |
||||
<div |
||||
className="gf-form" |
||||
> |
||||
<div |
||||
className="gf-form-label" |
||||
> |
||||
Resolution |
||||
</div> |
||||
<Select |
||||
isSearchable={false} |
||||
onChange={[Function]} |
||||
options={ |
||||
Array [ |
||||
Object { |
||||
"label": "1/1", |
||||
"value": 1, |
||||
}, |
||||
Object { |
||||
"label": "1/2", |
||||
"value": 2, |
||||
}, |
||||
Object { |
||||
"label": "1/3", |
||||
"value": 3, |
||||
}, |
||||
Object { |
||||
"label": "1/4", |
||||
"value": 4, |
||||
}, |
||||
Object { |
||||
"label": "1/5", |
||||
"value": 5, |
||||
}, |
||||
Object { |
||||
"label": "1/10", |
||||
"value": 10, |
||||
}, |
||||
] |
||||
} |
||||
value={ |
||||
Object { |
||||
"label": "1/1", |
||||
"value": 1, |
||||
}, |
||||
Object { |
||||
"label": "1/2", |
||||
"value": 2, |
||||
}, |
||||
Object { |
||||
"label": "1/3", |
||||
"value": 3, |
||||
}, |
||||
Object { |
||||
"label": "1/4", |
||||
"value": 4, |
||||
}, |
||||
Object { |
||||
"label": "1/5", |
||||
"value": 5, |
||||
}, |
||||
Object { |
||||
"label": "1/10", |
||||
"value": 10, |
||||
}, |
||||
] |
||||
} |
||||
value={ |
||||
Object { |
||||
"label": "1/1", |
||||
"value": 1, |
||||
} |
||||
} |
||||
} |
||||
/> |
||||
</div> |
||||
<div |
||||
className="gf-form" |
||||
> |
||||
/> |
||||
</div> |
||||
<div |
||||
className="gf-form-label width-7" |
||||
className="gf-form" |
||||
> |
||||
Format |
||||
</div> |
||||
<Select |
||||
isSearchable={false} |
||||
onChange={[Function]} |
||||
options={ |
||||
Array [ |
||||
<div |
||||
className="gf-form-label width-7" |
||||
> |
||||
Format |
||||
</div> |
||||
<Select |
||||
isSearchable={false} |
||||
onChange={[Function]} |
||||
options={ |
||||
Array [ |
||||
Object { |
||||
"label": "Time series", |
||||
"value": "time_series", |
||||
}, |
||||
Object { |
||||
"label": "Table", |
||||
"value": "table", |
||||
}, |
||||
Object { |
||||
"label": "Heatmap", |
||||
"value": "heatmap", |
||||
}, |
||||
] |
||||
} |
||||
value={ |
||||
Object { |
||||
"label": "Time series", |
||||
"value": "time_series", |
||||
}, |
||||
Object { |
||||
"label": "Table", |
||||
"value": "table", |
||||
}, |
||||
Object { |
||||
"label": "Heatmap", |
||||
"value": "heatmap", |
||||
}, |
||||
] |
||||
} |
||||
value={ |
||||
} |
||||
} |
||||
width={16} |
||||
/> |
||||
<Switch |
||||
checked={false} |
||||
label="Instant" |
||||
onChange={[Function]} |
||||
/> |
||||
<FormLabel |
||||
tooltip="Link to Graph in Prometheus" |
||||
width={10} |
||||
> |
||||
<Memo(PromLink) |
||||
datasource={ |
||||
Object { |
||||
"createQuery": [MockFunction], |
||||
"getPrometheusTime": [MockFunction], |
||||
} |
||||
} |
||||
query={ |
||||
Object { |
||||
"exemplar": true, |
||||
"expr": "", |
||||
"interval": "", |
||||
"legendFormat": "", |
||||
"refId": "A", |
||||
} |
||||
} |
||||
/> |
||||
</FormLabel> |
||||
</div> |
||||
<PromExemplarField |
||||
datasource={ |
||||
Object { |
||||
"label": "Time series", |
||||
"value": "time_series", |
||||
"createQuery": [MockFunction], |
||||
"getPrometheusTime": [MockFunction], |
||||
} |
||||
} |
||||
width={16} |
||||
/> |
||||
<Switch |
||||
checked={false} |
||||
label="Instant" |
||||
isEnabled={true} |
||||
onChange={[Function]} |
||||
/> |
||||
<FormLabel |
||||
tooltip="Link to Graph in Prometheus" |
||||
width={10} |
||||
> |
||||
<Memo(PromLink) |
||||
datasource={ |
||||
Object { |
||||
"createQuery": [MockFunction], |
||||
"getPrometheusTime": [MockFunction], |
||||
} |
||||
} |
||||
query={ |
||||
Object { |
||||
"exemplar": true, |
||||
"expr": "", |
||||
"interval": "", |
||||
"legendFormat": "", |
||||
"refId": "A", |
||||
} |
||||
} |
||||
/> |
||||
</FormLabel> |
||||
</div> |
||||
<PromExemplarField |
||||
datasource={ |
||||
Object { |
||||
"createQuery": [MockFunction], |
||||
"getPrometheusTime": [MockFunction], |
||||
} |
||||
} |
||||
isEnabled={true} |
||||
onChange={[Function]} |
||||
/> |
||||
</div> |
||||
</div> |
||||
} |
||||
datasource={ |
||||
Object { |
||||
"createQuery": [MockFunction], |
||||
"getPrometheusTime": [MockFunction], |
||||
} |
||||
} |
||||
history={Array []} |
||||
onChange={[Function]} |
||||
onRunQuery={[Function]} |
||||
query={ |
||||
Object { |
||||
"expr": "", |
||||
"refId": "A", |
||||
} |
||||
} |
||||
/> |
||||
`; |
||||
|
||||
Loading…
Reference in new issue