mirror of
https://github.com/grafana/grafana.git
synced 2025-02-15 01:53:33 -06:00
Prometheus: Fix copy paste behaving as cut and paste (#28622)
This commit is contained in:
parent
05644e7042
commit
43a0167b01
@ -600,6 +600,6 @@ export abstract class LanguageProvider {
|
|||||||
* Returns startTask that resolves with a task list when main syntax is loaded.
|
* Returns startTask that resolves with a task list when main syntax is loaded.
|
||||||
* Task list consists of secondary promises that load more detailed language features.
|
* Task list consists of secondary promises that load more detailed language features.
|
||||||
*/
|
*/
|
||||||
abstract start: () => Promise<any[]>;
|
abstract start: () => Promise<Array<Promise<any>>>;
|
||||||
startTask?: Promise<any[]>;
|
startTask?: Promise<any[]>;
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import RCCascader from 'rc-cascader';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PromQlLanguageProvider, { DEFAULT_LOOKUP_METRICS_THRESHOLD } from '../language_provider';
|
import PromQlLanguageProvider, { DEFAULT_LOOKUP_METRICS_THRESHOLD } from '../language_provider';
|
||||||
import PromQueryField, { groupMetricsByPrefix, RECORDING_RULES_GROUP } from './PromQueryField';
|
import PromQueryField, { groupMetricsByPrefix, RECORDING_RULES_GROUP } from './PromQueryField';
|
||||||
import { DataSourceInstanceSettings } from '@grafana/data';
|
import { DataSourceInstanceSettings, dateTime } from '@grafana/data';
|
||||||
import { PromOptions } from '../types';
|
import { PromOptions } from '../types';
|
||||||
import { fireEvent, render, screen } from '@testing-library/react';
|
import { fireEvent, render, screen } from '@testing-library/react';
|
||||||
|
|
||||||
@ -51,61 +51,89 @@ describe('PromQueryField', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('refreshes metrics when the data source changes', async () => {
|
it('refreshes metrics when the data source changes', async () => {
|
||||||
|
const defaultProps = {
|
||||||
|
query: { expr: '', refId: '' },
|
||||||
|
onRunQuery: () => {},
|
||||||
|
onChange: () => {},
|
||||||
|
history: [],
|
||||||
|
};
|
||||||
const metrics = ['foo', 'bar'];
|
const metrics = ['foo', 'bar'];
|
||||||
const languageProvider = ({
|
|
||||||
histogramMetrics: [] as any,
|
|
||||||
metrics,
|
|
||||||
metricsMetadata: {},
|
|
||||||
lookupsDisabled: false,
|
|
||||||
lookupMetricsThreshold: DEFAULT_LOOKUP_METRICS_THRESHOLD,
|
|
||||||
start: () => {
|
|
||||||
return Promise.resolve([]);
|
|
||||||
},
|
|
||||||
} as unknown) as PromQlLanguageProvider;
|
|
||||||
|
|
||||||
const queryField = render(
|
const queryField = render(
|
||||||
<PromQueryField
|
<PromQueryField
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
datasource={{
|
datasource={{
|
||||||
languageProvider,
|
languageProvider: makeLanguageProvider({ metrics: [metrics] }),
|
||||||
}}
|
}}
|
||||||
query={{ expr: '', refId: '' }}
|
{...defaultProps}
|
||||||
onRunQuery={() => {}}
|
|
||||||
onChange={() => {}}
|
|
||||||
history={[]}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
let cascader = await queryField.findByRole('button');
|
checkMetricsInCascader(await screen.findByRole('button'), metrics);
|
||||||
fireEvent.keyDown(cascader, { keyCode: 40 });
|
|
||||||
let listNodes = screen.getAllByRole('menuitem');
|
|
||||||
for (const node of listNodes) {
|
|
||||||
expect(metrics).toContain(node.innerHTML);
|
|
||||||
}
|
|
||||||
|
|
||||||
const changedMetrics = ['baz', 'moo'];
|
const changedMetrics = ['baz', 'moo'];
|
||||||
queryField.rerender(
|
queryField.rerender(
|
||||||
<PromQueryField
|
<PromQueryField
|
||||||
|
// @ts-ignore
|
||||||
datasource={{
|
datasource={{
|
||||||
//@ts-ignore
|
languageProvider: makeLanguageProvider({ metrics: [changedMetrics] }),
|
||||||
languageProvider: {
|
|
||||||
...languageProvider,
|
|
||||||
metrics: changedMetrics,
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
query={{ expr: '', refId: '' }}
|
{...defaultProps}
|
||||||
onRunQuery={() => {}}
|
|
||||||
onChange={() => {}}
|
|
||||||
history={[]}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
cascader = await queryField.findByRole('button');
|
// If we check the cascader right away it should be in loading state
|
||||||
fireEvent.keyDown(cascader, { keyCode: 40 });
|
let cascader = screen.getByRole('button');
|
||||||
listNodes = screen.getAllByRole('menuitem');
|
expect(cascader.textContent).toContain('Loading');
|
||||||
for (const node of listNodes) {
|
checkMetricsInCascader(await screen.findByRole('button'), changedMetrics);
|
||||||
expect(changedMetrics).toContain(node.innerHTML);
|
});
|
||||||
}
|
|
||||||
|
it('refreshes metrics when time range changes but dont show loading state', async () => {
|
||||||
|
const defaultProps = {
|
||||||
|
query: { expr: '', refId: '' },
|
||||||
|
onRunQuery: () => {},
|
||||||
|
onChange: () => {},
|
||||||
|
history: [],
|
||||||
|
};
|
||||||
|
const metrics = ['foo', 'bar'];
|
||||||
|
const changedMetrics = ['baz', 'moo'];
|
||||||
|
const range = {
|
||||||
|
from: dateTime('2020-10-28T00:00:00Z'),
|
||||||
|
to: dateTime('2020-10-28T01:00:00Z'),
|
||||||
|
};
|
||||||
|
|
||||||
|
const languageProvider = makeLanguageProvider({ metrics: [metrics, changedMetrics] });
|
||||||
|
const queryField = render(
|
||||||
|
<PromQueryField
|
||||||
|
// @ts-ignore
|
||||||
|
datasource={{ languageProvider }}
|
||||||
|
range={{
|
||||||
|
...range,
|
||||||
|
raw: range,
|
||||||
|
}}
|
||||||
|
{...defaultProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
checkMetricsInCascader(await screen.findByRole('button'), metrics);
|
||||||
|
|
||||||
|
const newRange = {
|
||||||
|
from: dateTime('2020-10-28T01:00:00Z'),
|
||||||
|
to: dateTime('2020-10-28T02:00:00Z'),
|
||||||
|
};
|
||||||
|
queryField.rerender(
|
||||||
|
<PromQueryField
|
||||||
|
// @ts-ignore
|
||||||
|
datasource={{ languageProvider }}
|
||||||
|
range={{
|
||||||
|
...newRange,
|
||||||
|
raw: newRange,
|
||||||
|
}}
|
||||||
|
{...defaultProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
let cascader = screen.getByRole('button');
|
||||||
|
// Should not show loading
|
||||||
|
expect(cascader.textContent).toContain('Metrics');
|
||||||
|
checkMetricsInCascader(cascader, metrics);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -162,3 +190,26 @@ describe('groupMetricsByPrefix()', () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function makeLanguageProvider(options: { metrics: string[][] }) {
|
||||||
|
const metricsStack = [...options.metrics];
|
||||||
|
return ({
|
||||||
|
histogramMetrics: [] as any,
|
||||||
|
metrics: [],
|
||||||
|
metricsMetadata: {},
|
||||||
|
lookupsDisabled: false,
|
||||||
|
lookupMetricsThreshold: DEFAULT_LOOKUP_METRICS_THRESHOLD,
|
||||||
|
start() {
|
||||||
|
this.metrics = metricsStack.shift();
|
||||||
|
return Promise.resolve([]);
|
||||||
|
},
|
||||||
|
} as any) as PromQlLanguageProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkMetricsInCascader(cascader: HTMLElement, metrics: string[]) {
|
||||||
|
fireEvent.keyDown(cascader, { keyCode: 40 });
|
||||||
|
let listNodes = screen.getAllByRole('menuitem');
|
||||||
|
for (const node of listNodes) {
|
||||||
|
expect(metrics).toContain(node.innerHTML);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -17,14 +17,7 @@ import Prism from 'prismjs';
|
|||||||
// dom also includes Element polyfills
|
// dom also includes Element polyfills
|
||||||
import { PromQuery, PromOptions, PromMetricsMetadata } from '../types';
|
import { PromQuery, PromOptions, PromMetricsMetadata } from '../types';
|
||||||
import { CancelablePromise, makePromiseCancelable } from 'app/core/utils/CancelablePromise';
|
import { CancelablePromise, makePromiseCancelable } from 'app/core/utils/CancelablePromise';
|
||||||
import {
|
import { ExploreQueryFieldProps, QueryHint, isDataFrame, toLegacyResponseData, HistoryItem } from '@grafana/data';
|
||||||
ExploreQueryFieldProps,
|
|
||||||
QueryHint,
|
|
||||||
isDataFrame,
|
|
||||||
toLegacyResponseData,
|
|
||||||
HistoryItem,
|
|
||||||
AbsoluteTimeRange,
|
|
||||||
} from '@grafana/data';
|
|
||||||
import { DOMUtil, SuggestionsState } from '@grafana/ui';
|
import { DOMUtil, SuggestionsState } from '@grafana/ui';
|
||||||
import { PrometheusDatasource } from '../datasource';
|
import { PrometheusDatasource } from '../datasource';
|
||||||
|
|
||||||
@ -173,21 +166,27 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
|
|||||||
range,
|
range,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
let refreshed = false;
|
const rangeChanged =
|
||||||
if (range && prevProps.range) {
|
range &&
|
||||||
const absoluteRange: AbsoluteTimeRange = { from: range.from.valueOf(), to: range.to.valueOf() };
|
prevProps.range &&
|
||||||
const prevAbsoluteRange: AbsoluteTimeRange = {
|
!_.isEqual(
|
||||||
from: prevProps.range.from.valueOf(),
|
{ from: range.from.valueOf(), to: range.to.valueOf() },
|
||||||
to: prevProps.range.to.valueOf(),
|
{
|
||||||
};
|
from: prevProps.range.from.valueOf(),
|
||||||
|
to: prevProps.range.to.valueOf(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (!_.isEqual(absoluteRange, prevAbsoluteRange)) {
|
if (languageProvider !== prevProps.datasource.languageProvider) {
|
||||||
this.refreshMetrics();
|
// We reset this only on DS change so we do not flesh loading state on every rangeChange which happens on every
|
||||||
refreshed = true;
|
// query run if using relative range.
|
||||||
}
|
this.setState({
|
||||||
|
metricsOptions: [],
|
||||||
|
syntaxLoaded: false,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!refreshed && languageProvider !== prevProps.datasource.languageProvider) {
|
if (languageProvider !== prevProps.datasource.languageProvider || rangeChanged) {
|
||||||
this.refreshMetrics();
|
this.refreshMetrics();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,31 +205,35 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
|
|||||||
|
|
||||||
const result = isDataFrame(data.series[0]) ? data.series.map(toLegacyResponseData) : data.series;
|
const result = isDataFrame(data.series[0]) ? data.series.map(toLegacyResponseData) : data.series;
|
||||||
const hints = datasource.getQueryHints(query, result);
|
const hints = datasource.getQueryHints(query, result);
|
||||||
const hint = hints.length > 0 ? hints[0] : null;
|
let hint = hints.length > 0 ? hints[0] : null;
|
||||||
|
|
||||||
|
// Hint for big disabled lookups
|
||||||
|
if (!hint && !datasource.lookupsDisabled && datasource.languageProvider.lookupsDisabled) {
|
||||||
|
hint = {
|
||||||
|
label: `Dynamic label lookup is disabled for datasources with more than ${datasource.languageProvider.lookupMetricsThreshold} metrics.`,
|
||||||
|
type: 'INFO',
|
||||||
|
};
|
||||||
|
}
|
||||||
this.setState({ hint });
|
this.setState({ hint });
|
||||||
};
|
};
|
||||||
|
|
||||||
refreshMetrics = () => {
|
refreshMetrics = async () => {
|
||||||
const {
|
const {
|
||||||
datasource: { languageProvider },
|
datasource: { languageProvider },
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
this.setState({
|
|
||||||
syntaxLoaded: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
Prism.languages[PRISM_SYNTAX] = languageProvider.syntax;
|
Prism.languages[PRISM_SYNTAX] = languageProvider.syntax;
|
||||||
this.languageProviderInitializationPromise = makePromiseCancelable(languageProvider.start());
|
this.languageProviderInitializationPromise = makePromiseCancelable(languageProvider.start());
|
||||||
this.languageProviderInitializationPromise.promise
|
|
||||||
.then(remaining => {
|
try {
|
||||||
remaining.map((task: Promise<any>) => task.then(this.onUpdateLanguage).catch(() => {}));
|
const remainingTasks = await this.languageProviderInitializationPromise.promise;
|
||||||
})
|
await Promise.all(remainingTasks);
|
||||||
.then(() => this.onUpdateLanguage())
|
this.onUpdateLanguage();
|
||||||
.catch(err => {
|
} catch (err) {
|
||||||
if (!err.isCanceled) {
|
if (!err.isCanceled) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onChangeMetrics = (values: string[], selectedOptions: CascaderOption[]) => {
|
onChangeMetrics = (values: string[], selectedOptions: CascaderOption[]) => {
|
||||||
@ -278,10 +281,9 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
|
|||||||
|
|
||||||
onUpdateLanguage = () => {
|
onUpdateLanguage = () => {
|
||||||
const {
|
const {
|
||||||
datasource,
|
|
||||||
datasource: { languageProvider },
|
datasource: { languageProvider },
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { histogramMetrics, metrics, metricsMetadata, lookupMetricsThreshold } = languageProvider;
|
const { histogramMetrics, metrics, metricsMetadata } = languageProvider;
|
||||||
|
|
||||||
if (!metrics) {
|
if (!metrics) {
|
||||||
return;
|
return;
|
||||||
@ -298,17 +300,7 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
|
|||||||
]
|
]
|
||||||
: metricsByPrefix;
|
: metricsByPrefix;
|
||||||
|
|
||||||
// Hint for big disabled lookups
|
this.setState({ metricsOptions, syntaxLoaded: true });
|
||||||
let hint: QueryHint | null = null;
|
|
||||||
|
|
||||||
if (!datasource.lookupsDisabled && languageProvider.lookupsDisabled) {
|
|
||||||
hint = {
|
|
||||||
label: `Dynamic label lookup is disabled for datasources with more than ${lookupMetricsThreshold} metrics.`,
|
|
||||||
type: 'INFO',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ hint, metricsOptions, syntaxLoaded: true });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onTypeahead = async (typeahead: TypeaheadInput): Promise<TypeaheadOutput> => {
|
onTypeahead = async (typeahead: TypeaheadInput): Promise<TypeaheadOutput> => {
|
||||||
@ -328,8 +320,6 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
|
|||||||
{ history }
|
{ history }
|
||||||
);
|
);
|
||||||
|
|
||||||
// console.log('handleTypeahead', wrapperClasses, text, prefix, labelKey, result.context);
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user