Commit 7e8bd0c1 by Torkel Ödegaard Committed by GitHub

Chore: Fix typescript strict null errors from 674 -> 565 (#26057)

* Chore: Fix typescript strict null errors

* Added new limit

* Fixed ts issue

* fixed tests

* trying to fix type inference

* Fixing more ts errors

* Revert tsconfig option

* Fix

* Fixed code

* More fixes

* fix tests

* Updated snapshot
parent 4fc984f7
......@@ -15,20 +15,10 @@ export interface DataSourcePluginOptionsEditorProps<JSONData = DataSourceJsonDat
}
// Utility type to extract the query type TQuery from a class extending DataSourceApi<TQuery, TOptions>
export type DataSourceQueryType<DSType extends DataSourceApi<any, any>> = DSType extends DataSourceApi<
infer TQuery,
infer _TOptions
>
? TQuery
: never;
export type DataSourceQueryType<DSType> = DSType extends DataSourceApi<infer TQuery, any> ? TQuery : never;
// Utility type to extract the options type TOptions from a class extending DataSourceApi<TQuery, TOptions>
export type DataSourceOptionsType<DSType extends DataSourceApi<any, any>> = DSType extends DataSourceApi<
infer _TQuery,
infer TOptions
>
? TOptions
: never;
export type DataSourceOptionsType<DSType> = DSType extends DataSourceApi<any, infer TOptions> ? TOptions : never;
export class DataSourcePlugin<
DSType extends DataSourceApi<TQuery, TOptions>,
......@@ -453,7 +443,6 @@ export interface DataQueryTimings {
}
export interface QueryFix {
type: string;
label: string;
action?: QueryFixAction;
}
......@@ -472,6 +461,7 @@ export interface QueryHint {
export interface MetricFindValue {
text: string;
expandable?: boolean;
}
export interface DataSourceJsonData {
......
......@@ -230,7 +230,7 @@ kbn.interval_to_ms = (str: string) => {
return info.sec * 1000 * info.count;
};
kbn.interval_to_seconds = (str: string) => {
kbn.interval_to_seconds = (str: string): number => {
const info = kbn.describe_interval(str);
return info.sec * info.count;
};
......
......@@ -58,7 +58,7 @@ export interface GetDataOptions {
}
export class PanelQueryRunner {
private subject?: ReplaySubject<PanelData>;
private subject: ReplaySubject<PanelData>;
private subscription?: Unsubscribable;
private lastResult?: PanelData;
private dataConfigSource: DataConfigSource;
......@@ -244,7 +244,7 @@ export class PanelQueryRunner {
}
}
getLastResult(): PanelData {
getLastResult(): PanelData | undefined {
return this.lastResult;
}
}
......
......@@ -265,9 +265,9 @@ export class TemplateSrv implements BaseTemplateSrv {
return variableName;
}
variableExists(expression: string) {
variableExists(expression: string): boolean {
const name = this.getVariableName(expression);
return name && this.getVariableAtIndex(name) !== void 0;
return (name && this.getVariableAtIndex(name)) !== undefined;
}
highlightVariablesAsHtml(str: string) {
......
......@@ -77,6 +77,7 @@ exports[`LokiExploreQueryEditor should render component 1`] = `
},
Symbol(length): 0,
},
"logLabelFetchTs": 0,
"request": [Function],
"seriesCache": LRUCache {
Symbol(max): 10,
......
......@@ -8,7 +8,7 @@ import { ArrayVector, Field, FieldType, LinkModel } from '@grafana/data';
import { getFieldLinksForExplore } from '../../../../features/explore/utils/links';
type Props = {
derivedFields: DerivedFieldConfig[];
derivedFields?: DerivedFieldConfig[];
className?: string;
};
export const DebugSection = (props: Props) => {
......@@ -92,7 +92,7 @@ function makeDebugFields(derivedFields: DerivedFieldConfig[], debugText: string)
try {
const testMatch = debugText.match(field.matcherRegex);
const value = testMatch && testMatch[1];
let link: LinkModel<Field> = null;
let link: LinkModel<Field> | null = null;
if (field.url && value) {
link = getFieldLinksForExplore(
......@@ -116,7 +116,6 @@ function makeDebugFields(derivedFields: DerivedFieldConfig[], debugText: string)
href: link && link.href,
} as DebugField;
} catch (error) {
console.error(error);
return {
name: field.name,
error,
......
......@@ -20,6 +20,7 @@ type Props = {
value?: DerivedFieldConfig[];
onChange: (value: DerivedFieldConfig[]) => void;
};
export const DerivedFields = (props: Props) => {
const { value, onChange } = props;
const theme = useTheme();
......
......@@ -370,7 +370,7 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
getTime(date: string | DateTime, roundUp: boolean) {
if (typeof date === 'string') {
date = dateMath.parse(date, roundUp);
date = dateMath.parse(date, roundUp)!;
}
return Math.ceil(date.valueOf() * 1e6);
......@@ -517,9 +517,9 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
return annotations;
}
showContextToggle = (row?: LogRowModel) => {
return row && row.searchWords && row.searchWords.length > 0;
};
showContextToggle(row?: LogRowModel): boolean {
return (row && row.searchWords && row.searchWords.length > 0) === true;
}
throwUnless = (err: any, condition: boolean, target: LokiQuery) => {
if (condition) {
......
......@@ -58,7 +58,7 @@ export function addHistoryMetadata(item: CompletionItem, history: LokiHistoryIte
export default class LokiLanguageProvider extends LanguageProvider {
labelKeys: string[];
logLabelOptions: any[];
logLabelFetchTs?: number;
logLabelFetchTs: number;
started: boolean;
initialRange: AbsoluteTimeRange;
datasource: LokiDatasource;
......@@ -77,6 +77,7 @@ export default class LokiLanguageProvider extends LanguageProvider {
this.datasource = datasource;
this.labelKeys = [];
this.logLabelFetchTs = 0;
Object.assign(this, initialValues);
}
......@@ -127,6 +128,11 @@ export default class LokiLanguageProvider extends LanguageProvider {
*/
async provideCompletionItems(input: TypeaheadInput, context?: TypeaheadContext): Promise<TypeaheadOutput> {
const { wrapperClasses, value, prefix, text } = input;
const emptyResult: TypeaheadOutput = { suggestions: [] };
if (!value) {
return emptyResult;
}
// Local text properties
const empty = value?.document.text.length === 0;
......@@ -169,18 +175,16 @@ export default class LokiLanguageProvider extends LanguageProvider {
return this.getTermCompletionItems();
}
return {
suggestions: [],
};
return emptyResult;
}
getBeginningCompletionItems = (context: TypeaheadContext): TypeaheadOutput => {
getBeginningCompletionItems = (context?: TypeaheadContext): TypeaheadOutput => {
return {
suggestions: [...this.getEmptyCompletionItems(context).suggestions, ...this.getTermCompletionItems().suggestions],
};
};
getEmptyCompletionItems(context: TypeaheadContext): TypeaheadOutput {
getEmptyCompletionItems(context?: TypeaheadContext): TypeaheadOutput {
const history = context?.history;
const suggestions = [];
......@@ -386,7 +390,7 @@ export default class LokiLanguageProvider extends LanguageProvider {
async fetchLogLabels(absoluteRange: AbsoluteTimeRange): Promise<any> {
const url = '/loki/api/v1/label';
try {
this.logLabelFetchTs = Date.now();
this.logLabelFetchTs = Date.now().valueOf();
const rangeParams = absoluteRange ? rangeToParams(absoluteRange) : {};
const res = await this.request(url, rangeParams);
this.labelKeys = res.slice().sort();
......@@ -398,7 +402,7 @@ export default class LokiLanguageProvider extends LanguageProvider {
}
async refreshLogLabels(absoluteRange: AbsoluteTimeRange, forceRefresh?: boolean) {
if ((this.labelKeys && Date.now() - this.logLabelFetchTs > LABEL_REFRESH_INTERVAL) || forceRefresh) {
if ((this.labelKeys && Date.now().valueOf() - this.logLabelFetchTs > LABEL_REFRESH_INTERVAL) || forceRefresh) {
await this.fetchLogLabels(absoluteRange);
}
}
......@@ -409,7 +413,7 @@ export default class LokiLanguageProvider extends LanguageProvider {
* @param name
*/
fetchSeriesLabels = async (match: string, absoluteRange: AbsoluteTimeRange): Promise<Record<string, string[]>> => {
const rangeParams: { start?: number; end?: number } = absoluteRange ? rangeToParams(absoluteRange) : {};
const rangeParams = absoluteRange ? rangeToParams(absoluteRange) : { start: 0, end: 0 };
const url = '/loki/api/v1/series';
const { start, end } = rangeParams;
......@@ -447,7 +451,7 @@ export default class LokiLanguageProvider extends LanguageProvider {
async fetchLabelValues(key: string, absoluteRange: AbsoluteTimeRange): Promise<string[]> {
const url = `/loki/api/v1/label/${key}/values`;
let values: string[] = [];
const rangeParams: { start?: number; end?: number } = absoluteRange ? rangeToParams(absoluteRange) : {};
const rangeParams = absoluteRange ? rangeToParams(absoluteRange) : { start: 0, end: 0 };
const { start, end } = rangeParams;
const cacheKey = this.generateCacheKey(url, start, end, key);
......
......@@ -18,7 +18,7 @@ describe('getHighlighterExpressionsFromQuery', () => {
});
it('returns null if filter term is not wrapped in double quotes', () => {
expect(getHighlighterExpressionsFromQuery('{foo="bar"} |= x')).toEqual(null);
expect(getHighlighterExpressionsFromQuery('{foo="bar"} |= x')).toEqual([]);
});
it('escapes filter term if regex filter operator is not used', () => {
......
import escapeRegExp from 'lodash/escapeRegExp';
export function formatQuery(selector: string): string {
export function formatQuery(selector: string | undefined): string {
return `${selector || ''}`.trim();
}
......@@ -11,6 +11,7 @@ export function formatQuery(selector: string): string {
export function getHighlighterExpressionsFromQuery(input: string): string[] {
let expression = input;
const results = [];
// Consume filter expression from left to right
while (expression) {
const filterStart = expression.search(/\|=|\|~|!=|!~/);
......@@ -43,8 +44,9 @@ export function getHighlighterExpressionsFromQuery(input: string): string[] {
const regexOperator = filterOperator === '|~';
results.push(regexOperator ? unwrappedFilterTerm : escapeRegExp(unwrappedFilterTerm));
} else {
return null;
return [];
}
}
return results;
}
......@@ -15,6 +15,7 @@ import {
Field,
QueryResultMetaStat,
QueryResultMeta,
TimeSeriesValue,
} from '@grafana/data';
import templateSrv from 'app/features/templating/template_srv';
......@@ -154,16 +155,14 @@ function lokiMatrixToTimeSeries(matrixResult: LokiMatrixResult, options: Transfo
};
}
function lokiPointsToTimeseriesPoints(
data: Array<[number, string]>,
options: TransformerOptions
): Array<[number, number]> {
function lokiPointsToTimeseriesPoints(data: Array<[number, string]>, options: TransformerOptions): TimeSeriesValue[][] {
const stepMs = options.step * 1000;
const datapoints: Array<[number, number]> = [];
const datapoints: TimeSeriesValue[][] = [];
let baseTimestampMs = options.start / 1e6;
for (const [time, value] of data) {
let datapointValue = parseFloat(value);
let datapointValue: TimeSeriesValue = parseFloat(value);
if (isNaN(datapointValue)) {
datapointValue = null;
}
......@@ -198,7 +197,7 @@ export function lokiResultsToTableModel(
// Collect all labels across all metrics
const metricLabels: Set<string> = new Set<string>(
lokiResults.reduce((acc, cur) => acc.concat(Object.keys(cur.metric)), [])
lokiResults.reduce((acc, cur) => acc.concat(Object.keys(cur.metric)), [] as string[])
);
// Sort metric labels, create columns for them and record their index
......@@ -245,9 +244,9 @@ function createMetricLabel(labelData: { [key: string]: string }, options?: Trans
let label =
options === undefined || _.isEmpty(options.legendFormat)
? getOriginalMetricName(labelData)
: renderTemplate(templateSrv.replace(options.legendFormat), labelData);
: renderTemplate(templateSrv.replace(options.legendFormat ?? ''), labelData);
if (!label) {
if (!label && options) {
label = options.query;
}
return label;
......@@ -272,11 +271,13 @@ export function decamelize(s: string): string {
}
// Turn loki stats { metric: value } into meta stat { title: metric, value: value }
function lokiStatsToMetaStat(stats: LokiStats): QueryResultMetaStat[] {
function lokiStatsToMetaStat(stats: LokiStats | undefined): QueryResultMetaStat[] {
const result: QueryResultMetaStat[] = [];
if (!stats) {
return result;
}
for (const section in stats) {
const values = stats[section];
for (const label in values) {
......@@ -293,6 +294,7 @@ function lokiStatsToMetaStat(stats: LokiStats): QueryResultMetaStat[] {
result.push({ displayName: title, value, unit });
}
}
return result;
}
......@@ -309,6 +311,7 @@ export function lokiStreamsToDataframes(
const custom = {
lokiQueryStatKey: 'Summary: total bytes processed',
};
const series: DataFrame[] = data.map(stream => {
const dataFrame = lokiStreamResultToDataFrame(stream, reverse);
enhanceDataFrame(dataFrame, config);
......@@ -406,10 +409,10 @@ export function rangeQueryResponseToTimeSeries(
const transformerOptions: TransformerOptions = {
format: target.format,
legendFormat: target.legendFormat,
start: query.start,
end: query.end,
step: query.step,
legendFormat: target.legendFormat ?? '',
start: query.start!,
end: query.end!,
step: query.step!,
query: query.query,
responseListLength,
refId: target.refId,
......
......@@ -59,7 +59,7 @@ export interface LokiVectorResponse {
}
export interface LokiMatrixResult {
metric: { [label: string]: string };
metric: Record<string, string>;
values: Array<[number, string]>;
}
......@@ -115,8 +115,8 @@ export type DerivedFieldConfig = {
};
export interface TransformerOptions {
format: string;
legendFormat: string;
format?: string;
legendFormat?: string;
step: number;
start: number;
end: number;
......
......@@ -38,14 +38,17 @@ export class MixedDatasource extends DataSourceApi<DataQuery> {
// Build groups of queries to run in parallel
const sets: { [key: string]: DataQuery[] } = groupBy(queries, 'datasource');
const mixed: BatchedQueries[] = [];
for (const key in sets) {
const targets = sets[key];
const dsName: string | undefined = targets[0].datasource;
const dsName = targets[0].datasource;
mixed.push({
datasource: getDataSourceSrv().get(dsName, request.scopedVars),
targets,
});
}
return this.batchQueries(mixed, request);
}
......
......@@ -39,8 +39,8 @@ export default class OpenTsDatasource extends DataSourceApi<OpenTsdbQuery, OpenT
// Called once per panel (graph)
query(options: DataQueryRequest<OpenTsdbQuery>) {
const start = this.convertToTSDBTime(options.rangeRaw.from, false, options.timezone);
const end = this.convertToTSDBTime(options.rangeRaw.to, true, options.timezone);
const start = this.convertToTSDBTime(options.range.raw.from, false, options.timezone);
const end = this.convertToTSDBTime(options.range.raw.to, true, options.timezone);
const qs: any[] = [];
_.each(options.targets, target => {
......
......@@ -19,7 +19,7 @@ export function PromExploreExtraField(props: PromExploreExtraFieldProps) {
return (
<div className="gf-form-inline" aria-label="Prometheus extra field">
<div className="gf-form">
<InlineFormLabel width={5} tooltip={hasTooltip ? tooltipContent : null}>
<InlineFormLabel width={5} tooltip={hasTooltip ? tooltipContent : undefined}>
{label}
</InlineFormLabel>
<input
......
import React, { memo } from 'react';
import React, { memo, FC } from 'react';
// Types
import { ExploreQueryFieldProps } from '@grafana/data';
......@@ -11,7 +11,7 @@ import { PromExploreExtraField } from './PromExploreExtraField';
export type Props = ExploreQueryFieldProps<PrometheusDatasource, PromQuery, PromOptions>;
export function PromExploreQueryEditor(props: Props) {
export const PromExploreQueryEditor: FC<Props> = (props: Props) => {
const { query, data, datasource, history, onChange, onRunQuery } = props;
function onChangeQueryStep(value: string) {
......@@ -55,6 +55,6 @@ export function PromExploreQueryEditor(props: Props) {
}
/>
);
}
};
export default memo(PromExploreQueryEditor);
......@@ -17,7 +17,8 @@ interface State {
}
export default class PromLink extends Component<Props, State> {
state: State = { href: null };
state: State = { href: '' };
async componentDidUpdate(prevProps: Props) {
const { panelData } = this.props;
......
......@@ -26,9 +26,9 @@ const INTERVAL_FACTOR_OPTIONS: Array<SelectableValue<number>> = _.map([1, 2, 3,
}));
interface State {
legendFormat: string;
legendFormat?: string;
formatOption: SelectableValue<string>;
interval: string;
interval?: string;
intervalFactorOption: SelectableValue<number>;
instant: boolean;
}
......
......@@ -184,7 +184,7 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
const result = isDataFrame(data.series[0]) ? data.series.map(toLegacyResponseData) : data.series;
const hints = datasource.getQueryHints(query, result);
const hint = hints && hints.length > 0 ? hints[0] : null;
const hint = hints.length > 0 ? hints[0] : null;
this.setState({ hint });
};
......@@ -250,7 +250,7 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
const { datasource, query, onChange, onRunQuery } = this.props;
const { hint } = this.state;
onChange(datasource.modifyQuery(query, hint.fix.action));
onChange(datasource.modifyQuery(query, hint!.fix!.action));
onRunQuery();
};
......@@ -277,7 +277,8 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
: metricsByPrefix;
// Hint for big disabled lookups
let hint: QueryHint;
let hint: QueryHint | null = null;
if (!datasource.lookupsDisabled && languageProvider.lookupsDisabled) {
hint = {
label: `Dynamic label lookup is disabled for datasources with more than ${lookupMetricsThreshold} metrics.`,
......
......@@ -9,7 +9,6 @@ exports[`PrometheusExploreExtraField should render component 1`] = `
className="gf-form"
>
<Component
tooltip={null}
width={5}
>
Prometheus Explore Extra Field
......
......@@ -78,7 +78,7 @@ export const PromSettings = (props: Props) => {
<div className="gf-form-group">
<div className="gf-form">
<Switch
checked={options.jsonData.disableMetricsLookup}
checked={options.jsonData.disableMetricsLookup ?? false}
label="Disable metrics lookup"
labelClass="width-14"
onChange={onUpdateDatasourceJsonDataOptionChecked(props, 'disableMetricsLookup')}
......
......@@ -26,7 +26,7 @@ import { filter, map, tap } from 'rxjs/operators';
import PrometheusMetricFindQuery from './metric_find_query';
import { ResultTransformer } from './result_transformer';
import PrometheusLanguageProvider from './language_provider';
import { getBackendSrv } from '@grafana/runtime';
import { getBackendSrv, BackendSrvRequest } from '@grafana/runtime';
import addLabelToQuery from './add_label_to_query';
import { getQueryHints } from './query_hints';
import { expandRecordingRules } from './language_utils';
......@@ -39,17 +39,6 @@ import TableModel from 'app/core/table_model';
export const ANNOTATION_QUERY_STEP_DEFAULT = '60s';
interface RequestOptions {
method?: string;
url?: string;
headers?: Record<string, string>;
transformRequest?: (data: any) => string;
data?: any;
withCredentials?: boolean;
silent?: boolean;
requestId?: string;
}
export interface PromDataQueryResponse {
data: {
status: string;
......@@ -92,7 +81,7 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
this.type = 'prometheus';
this.editorSrc = 'app/features/prometheus/partials/query.editor.html';
this.url = instanceSettings.url;
this.url = instanceSettings.url!;
this.basicAuth = instanceSettings.basicAuth;
this.withCredentials = instanceSettings.withCredentials;
this.interval = instanceSettings.jsonData.timeInterval || '15s';
......@@ -102,7 +91,7 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
this.resultTransformer = new ResultTransformer(templateSrv);
this.ruleMappings = {};
this.languageProvider = new PrometheusLanguageProvider(this);
this.lookupsDisabled = instanceSettings.jsonData.disableMetricsLookup;
this.lookupsDisabled = instanceSettings.jsonData.disableMetricsLookup ?? false;
this.customQueryParameters = new URLSearchParams(instanceSettings.jsonData.customQueryParameters);
}
......@@ -123,8 +112,8 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
}
}
_request(url: string, data: Record<string, string> = {}, options?: RequestOptions) {
options = defaults(options || {}, {
_request(url: string, data: Record<string, string> | null, overrides?: Partial<BackendSrvRequest>) {
const options: BackendSrvRequest = defaults(overrides || {}, {
url: this.url + url,
method: this.httpMethod,
headers: {},
......@@ -153,7 +142,7 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
options.headers.Authorization = this.basicAuth;
}
return getBackendSrv().datasourceRequest(options as Required<RequestOptions>);
return getBackendSrv().datasourceRequest(options);
}
// Use this for tab completion features, wont publish response to other components
......@@ -271,13 +260,10 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
let runningQueriesCount = queries.length;
const subQueries = queries.map((query, index) => {
const target = activeTargets[index];
let observable: Observable<any> = null;
if (query.instant) {
observable = from(this.performInstantQuery(query, end));
} else {
observable = from(this.performTimeSeriesQuery(query, query.start, query.end));
}
let observable = query.instant
? from(this.performInstantQuery(query, end))
: from(this.performTimeSeriesQuery(query, query.start, query.end));
return observable.pipe(
// Decrease the counter here. We assume that each request returns only single value and then completes
......@@ -301,13 +287,10 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
private panelsQuery(queries: PromQueryRequest[], activeTargets: PromQuery[], end: number, requestId: string) {
const observables: Array<Observable<Array<TableModel | TimeSeries>>> = queries.map((query, index) => {
const target = activeTargets[index];
let observable: Observable<any> = null;
if (query.instant) {
observable = from(this.performInstantQuery(query, end));
} else {
observable = from(this.performTimeSeriesQuery(query, query.start, query.end));
}
let observable = query.instant
? from(this.performInstantQuery(query, end))
: from(this.performTimeSeriesQuery(query, query.start, query.end));
return observable.pipe(
filter((response: any) => (response.cancelled ? false : true)),
......@@ -346,10 +329,10 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
const range = Math.ceil(end - start);
// options.interval is the dynamically calculated interval
let interval = kbn.interval_to_seconds(options.interval);
let interval: number = kbn.interval_to_seconds(options.interval);
// Minimum interval ("Min step"), if specified for the query or datasource. or same as interval otherwise
const minInterval = kbn.interval_to_seconds(
templateSrv.replace(target.interval, options.scopedVars) || options.interval
templateSrv.replace(target.interval || options.interval, options.scopedVars)
);
const intervalFactor = target.intervalFactor || 1;
// Adjust the interval to take into account any specified minimum and interval factor plus Prometheus limits
......@@ -576,7 +559,7 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
return [];
}
const step = Math.floor(query.step) * 1000;
const step = Math.floor(query.step ?? 15) * 1000;
response?.data?.data?.result?.forEach(series => {
const tags = Object.entries(series.metric)
......@@ -596,16 +579,17 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
});
const activeValues = series.values.filter((value: Record<number, string>) => parseFloat(value[1]) >= 1);
const activeValuesTimestamps = activeValues.map((value: number[]) => value[0]);
const activeValuesTimestamps: number[] = activeValues.map((value: number[]) => value[0]);
// Instead of creating singular annotation for each active event we group events into region if they are less
// then `step` apart.
let latestEvent: AnnotationEvent = null;
activeValuesTimestamps.forEach((timestamp: number) => {
let latestEvent: AnnotationEvent | null = null;
for (const timestamp of activeValuesTimestamps) {
// We already have event `open` and we have new event that is inside the `step` so we just update the end.
if (latestEvent && latestEvent.timeEnd + step >= timestamp) {
if (latestEvent && (latestEvent.timeEnd ?? 0) + step >= timestamp) {
latestEvent.timeEnd = timestamp;
return;
continue;
}
// Event exists but new one is outside of the `step` so we "finish" the current region.
......@@ -622,7 +606,8 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
tags,
text: self.resultTransformer.renderTemplate(textFormat, series.metric),
};
});
}
if (latestEvent) {
// finish up last point if we have one
latestEvent.timeEnd = activeValuesTimestamps[activeValuesTimestamps.length - 1];
......@@ -723,7 +708,7 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
getPrometheusTime(date: string | DateTime, roundUp: boolean) {
if (typeof date === 'string') {
date = dateMath.parse(date, roundUp);
date = dateMath.parse(date, roundUp)!;
}
return Math.ceil(date.valueOf() / 1000);
......
......@@ -54,9 +54,9 @@ function addMetricsMetadata(metric: string, metadata?: PromMetricsMetadata): Com
const PREFIX_DELIMITER_REGEX = /(="|!="|=~"|!~"|\{|\[|\(|\+|-|\/|\*|%|\^|\band\b|\bor\b|\bunless\b|==|>=|!=|<=|>|<|=|~|,)/;
export default class PromQlLanguageProvider extends LanguageProvider {
histogramMetrics?: string[];
histogramMetrics: string[];
timeRange?: { start: number; end: number };
metrics?: string[];
metrics: string[];
metricsMetadata?: PromMetricsMetadata;
startTask: Promise<any>;
datasource: PrometheusDatasource;
......@@ -87,7 +87,7 @@ export default class PromQlLanguageProvider extends LanguageProvider {
// Strip syntax chars so that typeahead suggestions can work on clean inputs
cleanText(s: string) {
const parts = s.split(PREFIX_DELIMITER_REGEX);
const last = parts.pop();
const last = parts.pop()!;
return last
.trimLeft()
.replace(/"$/, '')
......@@ -136,6 +136,12 @@ export default class PromQlLanguageProvider extends LanguageProvider {
{ prefix, text, value, labelKey, wrapperClasses }: TypeaheadInput,
context: { history: Array<HistoryItem<PromQuery>> } = { history: [] }
): Promise<TypeaheadOutput> => {
const emptyResult: TypeaheadOutput = { suggestions: [] };
if (!value) {
return emptyResult;
}
// Local text properties
const empty = value.document.text.length === 0;
const selectedLines = value.document.getTextsAtRange(value.selection);
......@@ -179,9 +185,7 @@ export default class PromQlLanguageProvider extends LanguageProvider {
return this.getTermCompletionItems();
}
return {
suggestions: [],
};
return emptyResult;
};
getBeginningCompletionItems = (context: { history: Array<HistoryItem<PromQuery>> }): TypeaheadOutput => {
......@@ -253,7 +257,12 @@ export default class PromQlLanguageProvider extends LanguageProvider {
// Stitch all query lines together to support multi-line queries
let queryOffset;
const queryText = value.document.getBlocks().reduce((text: string, block) => {
const blockText = block.getText();
if (!block) {
return text;
}
const blockText = block?.getText();
if (value.anchorBlock.key === block.key) {
// Newline characters are not accounted for but this is irrelevant
// for the purpose of extracting the selector string
......@@ -305,6 +314,10 @@ export default class PromQlLanguageProvider extends LanguageProvider {
labelKey,
value,
}: TypeaheadInput): Promise<TypeaheadOutput> => {
if (!value) {
return { suggestions: [] };
}
const suggestions: CompletionItemGroup[] = [];
const line = value.anchorBlock.getText();
const cursorOffset = value.selection.anchor.offset;
......@@ -346,7 +359,8 @@ export default class PromQlLanguageProvider extends LanguageProvider {
return { suggestions };
}
let context: string;
let context: string | undefined;
if ((text && isValueStart) || wrapperClasses.includes('attr-value')) {
// Label values
if (labelKey && labelValues[labelKey]) {
......
......@@ -119,19 +119,20 @@ export function expandRecordingRules(query: string, mapping: { [name: string]: s
// Regex that matches occurences of ){ or }{ or ]{ which is a sign of incorrecly added labels.
const invalidLabelsRegex = /(\)\{|\}\{|\]\{)/;
const correctlyExpandedQueryArray = queryArray.map(query => {
let expression = query;
if (expression.match(invalidLabelsRegex)) {
expression = addLabelsToExpression(expression, invalidLabelsRegex);
}
return expression;
return addLabelsToExpression(query, invalidLabelsRegex);
});
return correctlyExpandedQueryArray.join('');
}
function addLabelsToExpression(expr: string, invalidLabelsRegexp: RegExp) {
const match = expr.match(invalidLabelsRegexp);
if (!match) {
return expr;
}
// Split query into 2 parts - before the invalidLabelsRegex match and after.
const indexOfRegexMatch = expr.match(invalidLabelsRegexp).index;
const indexOfRegexMatch = match.index ?? 0;
const exprBeforeRegexMatch = expr.substr(0, indexOfRegexMatch + 1);
const exprAfterRegexMatch = expr.substr(indexOfRegexMatch + 1);
......
import _ from 'lodash';
import { TimeRange } from '@grafana/data';
import { TimeRange, MetricFindValue } from '@grafana/data';
import { PrometheusDatasource, PromDataQueryResponse } from './datasource';
import { PromQueryRequest } from './types';
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
......@@ -13,7 +13,7 @@ export default class PrometheusMetricFindQuery {
this.range = getTimeSrv().timeRange();
}
process() {
process(): Promise<MetricFindValue[]> {
const labelNamesRegex = /^label_names\(\)\s*$/;
const labelValuesRegex = /^label_values\((?:(.+),\s*)?([a-zA-Z_][a-zA-Z0-9_]*)\)\s*$/;
const metricNamesRegex = /^metrics\((.+)\)\s*$/;
......@@ -28,7 +28,7 @@ export default class PrometheusMetricFindQuery {
if (labelValuesQuery[1]) {
return this.labelValuesQuery(labelValuesQuery[2], labelValuesQuery[1]);
} else {
return this.labelValuesQuery(labelValuesQuery[2], null);
return this.labelValuesQuery(labelValuesQuery[2]);
}
}
......@@ -136,7 +136,7 @@ export default class PrometheusMetricFindQuery {
});
}
metricNameAndLabelsQuery(query: string) {
metricNameAndLabelsQuery(query: string): Promise<MetricFindValue[]> {
const start = this.datasource.getPrometheusTime(this.range.from, false);
const end = this.datasource.getPrometheusTime(this.range.to, true);
const params = new URLSearchParams({
......@@ -144,10 +144,11 @@ export default class PrometheusMetricFindQuery {
start: start.toString(),
end: end.toString(),
});
const url = `/api/v1/series?${params.toString()}`;
const url = `/api/v1/series?${params.toString()}`;
const self = this;
return this.datasource.metadataRequest(url).then((result: PromDataQueryResponse) => {
return this.datasource.metadataRequest(url).then((result: any) => {
return _.map(result.data.data, (metric: { [key: string]: string }) => {
return {
text: self.datasource.getOriginalMetricName(metric),
......
......@@ -3,11 +3,11 @@ import { PrometheusDatasource } from './datasource';
describe('getQueryHints()', () => {
it('returns no hints for no series', () => {
expect(getQueryHints('', [])).toEqual(null);
expect(getQueryHints('', [])).toEqual([]);
});
it('returns no hints for empty series', () => {
expect(getQueryHints('', [{ datapoints: [] }])).toEqual(null);
expect(getQueryHints('', [{ datapoints: [] }])).toEqual([]);
});
it('returns a rate hint for a counter metric', () => {
......@@ -59,7 +59,7 @@ describe('getQueryHints()', () => {
// Test substring match not triggering hint
hints = getQueryHints('foo_foo', series, datasource);
expect(hints).toBe(null);
expect(hints).toEqual([]);
});
it('returns no rate hint for a counter metric that already has a rate', () => {
......@@ -72,7 +72,7 @@ describe('getQueryHints()', () => {
},
];
const hints = getQueryHints('rate(metric_total[1m])', series);
expect(hints).toEqual(null);
expect(hints).toEqual([]);
});
it('returns no rate hint for a counter metric that already has an increase', () => {
......@@ -85,7 +85,7 @@ describe('getQueryHints()', () => {
},
];
const hints = getQueryHints('increase(metric_total[1m])', series);
expect(hints).toEqual(null);
expect(hints).toEqual([]);
});
it('returns a rate hint w/o action for a complex counter metric', () => {
......
......@@ -7,7 +7,7 @@ import { PrometheusDatasource } from './datasource';
*/
export const SUM_HINT_THRESHOLD_COUNT = 20;
export function getQueryHints(query: string, series?: any[], datasource?: PrometheusDatasource): QueryHint[] | null {
export function getQueryHints(query: string, series?: any[], datasource?: PrometheusDatasource): QueryHint[] {
const hints = [];
// ..._bucket metric needs a histogram_quantile()
......@@ -32,27 +32,32 @@ export function getQueryHints(query: string, series?: any[], datasource?: Promet
// Use metric metadata for exact types
const nameMatch = query.match(/\b(\w+_(total|sum|count))\b/);
let counterNameMetric = nameMatch ? nameMatch[1] : '';
const metricsMetadata = datasource?.languageProvider?.metricsMetadata;
const metricsMetadata = datasource?.languageProvider?.metricsMetadata ?? {};
const metricMetadataKeys = Object.keys(metricsMetadata);
let certain = false;
if (_.size(metricsMetadata) > 0) {
counterNameMetric = Object.keys(metricsMetadata).find(metricName => {
// Only considering first type information, could be non-deterministic
const metadata = metricsMetadata[metricName][0];
if (metadata.type.toLowerCase() === 'counter') {
const metricRegex = new RegExp(`\\b${metricName}\\b`);
if (query.match(metricRegex)) {
certain = true;
return true;
if (metricMetadataKeys.length > 0) {
counterNameMetric =
metricMetadataKeys.find(metricName => {
// Only considering first type information, could be non-deterministic
const metadata = metricsMetadata[metricName][0];
if (metadata.type.toLowerCase() === 'counter') {
const metricRegex = new RegExp(`\\b${metricName}\\b`);
if (query.match(metricRegex)) {
certain = true;
return true;
}
}
}
return false;
});
return false;
}) ?? '';
}
if (counterNameMetric) {
const simpleMetric = query.trim().match(/^\w+$/);
const verb = certain ? 'is' : 'looks like';
let label = `Metric ${counterNameMetric} ${verb} a counter.`;
let fix: QueryFix;
let fix: QueryFix | undefined;
if (simpleMetric) {
fix = {
label: 'Fix by adding rate().',
......@@ -60,10 +65,11 @@ export function getQueryHints(query: string, series?: any[], datasource?: Promet
type: 'ADD_RATE',
query,
},
} as QueryFix;
};
} else {
label = `${label} Try applying a rate() function.`;
}
hints.push({
type: 'APPLY_RATE',
label,
......@@ -119,5 +125,5 @@ export function getQueryHints(query: string, series?: any[], datasource?: Promet
}
}
return hints.length > 0 ? hints : null;
return hints;
}
......@@ -20,7 +20,7 @@ export class ResultTransformer {
),
];
} else if (prometheusResult && options.format === 'heatmap') {
let seriesList = [];
let seriesList: TimeSeries[] = [];
for (const metricData of prometheusResult) {
seriesList.push(this.transformMetricData(metricData, options, options.start, options.end));
}
......@@ -28,7 +28,7 @@ export class ResultTransformer {
seriesList = this.transformToHistogramOverTime(seriesList);
return seriesList;
} else if (prometheusResult) {
const seriesList = [];
const seriesList: TimeSeries[] = [];
for (const metricData of prometheusResult) {
if (response.data.data.resultType === 'matrix') {
seriesList.push(this.transformMetricData(metricData, options, options.start, options.end));
......@@ -41,7 +41,7 @@ export class ResultTransformer {
return [];
}
transformMetricData(metricData: any, options: any, start: number, end: number) {
transformMetricData(metricData: any, options: any, start: number, end: number): TimeSeries {
const dps = [];
const { name, labels, title } = this.createLabelInfo(metricData.metric, options);
......@@ -53,7 +53,8 @@ export class ResultTransformer {
}
for (const value of metricData.values) {
let dpValue = parseFloat(value[1]);
let dpValue: number | null = parseFloat(value[1]);
if (_.isNaN(dpValue)) {
dpValue = null;
}
......@@ -73,9 +74,8 @@ export class ResultTransformer {
return {
datapoints: dps,
query: options.query,
refId: options.refId,
target: name,
target: name ?? '',
tags: labels,
title,
meta: options.meta,
......@@ -147,11 +147,11 @@ export class ResultTransformer {
return table;
}
transformInstantMetricData(md: any, options: any) {
transformInstantMetricData(md: any, options: any): TimeSeries {
const dps = [];
const { name, labels } = this.createLabelInfo(md.metric, options);
dps.push([parseFloat(md.value[1]), md.value[0] * 1000]);
return { target: name, title: name, datapoints: dps, tags: labels, refId: options.refId, meta: options.meta };
return { target: name ?? '', title: name, datapoints: dps, tags: labels, refId: options.refId, meta: options.meta };
}
createLabelInfo(labels: { [key: string]: string }, options: any): { name?: string; labels: Labels; title?: string } {
......@@ -210,7 +210,7 @@ export class ResultTransformer {
for (let j = 0; j < topSeries.length; j++) {
const bottomPoint = bottomSeries[j] || [0];
topSeries[j][0] -= bottomPoint[0];
topSeries[j][0]! -= bottomPoint[0]!;
}
}
......
......@@ -139,7 +139,7 @@ export function useLoadOptions(datasource: ZipkinDatasource) {
const newTraces = traces.length
? fromPairs(
traces.map(trace => {
const rootSpan = trace.find(span => !span.parentId);
const rootSpan = trace.find(span => !span.parentId)!;
return [`${rootSpan.name} [${Math.floor(rootSpan.duration / 1000)} ms]`, rootSpan.traceId];
})
......@@ -186,7 +186,8 @@ export function useLoadOptions(datasource: ZipkinDatasource) {
function useMapToCascaderOptions(services: AsyncState<CascaderOption[]>, allOptions: OptionsState) {
return useMemo(() => {
let cascaderOptions: CascaderOption[];
let cascaderOptions: CascaderOption[] = [];
if (services.value && services.value.length) {
cascaderOptions = services.value.map(services => {
return {
......
......@@ -16,10 +16,9 @@ import { apiPrefix } from './constants';
import { ZipkinSpan } from './types';
import { transformResponse } from './utils/transforms';
export type ZipkinQuery = {
// At the moment this should be simply the trace ID to get
export interface ZipkinQuery extends DataQuery {
query: string;
} & DataQuery;
}
export class ZipkinDatasource extends DataSourceApi<ZipkinQuery> {
constructor(private instanceSettings: DataSourceInstanceSettings) {
......
......@@ -117,8 +117,8 @@ class AlertListPanel extends PanelCtrl {
params.dashboardId = this.dashboard.id;
}
params.from = dateMath.parse(this.dashboard.time.from).unix() * 1000;
params.to = dateMath.parse(this.dashboard.time.to).unix() * 1000;
params.from = dateMath.parse(this.dashboard.time.from)!.unix() * 1000;
params.to = dateMath.parse(this.dashboard.time.to)!.unix() * 1000;
return promiseToDigest(this.$scope)(
getBackendSrv()
......
......@@ -123,7 +123,7 @@ describe('Graph Panel Migrations', () => {
expect(result.dataLinks).toBeUndefined();
expect(fieldSource.defaults.links).toHaveLength(1);
const link = fieldSource.defaults.links[0];
const link = fieldSource.defaults.links![0];
expect(link.url).toEqual('THE DRILLDOWN URL');
});
});
......@@ -429,7 +429,8 @@ class GraphElement {
// Function for rendering panel
renderPanel() {
this.panelWidth = this.elem.width();
this.panelWidth = this.elem.width() ?? 0;
if (this.shouldAbortRender()) {
return;
}
......
......@@ -207,14 +207,18 @@ export default function GraphTooltip(this: any, elem: any, dashboard: any, scope
self.clear(plot);
return;
}
pos.pageX = elem.offset().left + pointOffset.left;
pos.pageY = elem.offset().top + elem.height() * pos.panelRelY;
const isVisible =
pos.pageY >= $(window).scrollTop() && pos.pageY <= $(window).innerHeight() + $(window).scrollTop();
const scrollTop = $(window).scrollTop() ?? 0;
const isVisible = pos.pageY >= scrollTop && pos.pageY <= $(window).innerHeight()! + scrollTop;
if (!isVisible) {
self.clear(plot);
return;
}
plot.setCrosshair(pos);
allSeriesMode = true;
......
......@@ -470,8 +470,8 @@ export class EventMarkers {
},
left,
top,
line.width(),
line.height()
line.width() ?? 1,
line.height() ?? 1
);
return drawableEvent;
......@@ -604,8 +604,8 @@ export class EventMarkers {
},
left,
top,
region.width(),
region.height()
region.width() ?? 1,
region.height() ?? 1
);
return drawableEvent;
......
......@@ -33,7 +33,7 @@ coreModule.directive('colorLegend', () => {
function render() {
const legendElem = $(elem).find('svg');
const legendWidth = Math.floor(legendElem.outerWidth());
const legendWidth = Math.floor(legendElem.outerWidth() ?? 10);
if (panel.color.mode === 'spectrum') {
const colorScheme: any = _.find(ctrl.colorSchemes, {
......@@ -102,7 +102,7 @@ function drawColorLegend(
const legend = d3.select(legendElem.get(0));
clearLegend(elem);
const legendWidth = Math.floor(legendElem.outerWidth()) - 30;
const legendWidth = Math.floor(legendElem.outerWidth() ?? 10) - 30;
const legendHeight = legendElem.attr('height');
const rangeStep = ((rangeTo - rangeFrom) / legendWidth) * LEGEND_SEGMENT_WIDTH;
......@@ -140,7 +140,7 @@ function drawOpacityLegend(
const legend = d3.select(legendElem.get(0));
clearLegend(elem);
const legendWidth = Math.floor(legendElem.outerWidth()) - 30;
const legendWidth = Math.floor(legendElem.outerWidth() ?? 30) - 30;
const legendHeight = legendElem.attr('height');
const rangeStep = ((rangeTo - rangeFrom) / legendWidth) * LEGEND_SEGMENT_WIDTH;
......@@ -214,7 +214,7 @@ function drawSimpleColorLegend(elem: JQuery, colorScale: any) {
const legendElem = $(elem).find('svg');
clearLegend(elem);
const legendWidth = Math.floor(legendElem.outerWidth());
const legendWidth = Math.floor(legendElem.outerWidth() ?? 30);
const legendHeight = legendElem.attr('height');
if (legendWidth) {
......@@ -242,7 +242,7 @@ function drawSimpleOpacityLegend(elem: JQuery, options: { colorScale: string; ex
clearLegend(elem);
const legend = d3.select(legendElem.get(0));
const legendWidth = Math.floor(legendElem.outerWidth());
const legendWidth = Math.floor(legendElem.outerWidth() ?? 30);
const legendHeight = legendElem.attr('height');
if (legendWidth) {
......
......@@ -19,7 +19,7 @@ import { ColumnRender, TableRenderModel, ColumnStyle } from './types';
import { ColumnOptionsCtrl } from './column_options';
export class TableRenderer {
formatters: any[];
formatters: any[] = [];
colorState: any;
constructor(
......
......@@ -2,7 +2,7 @@
echo -e "Collecting code stats (typescript errors & more)"
ERROR_COUNT_LIMIT=700
ERROR_COUNT_LIMIT=600
DIRECTIVES_LIMIT=172
CONTROLLERS_LIMIT=139
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment