Alerting: azure monitoring manual backports (#42528)

Co-authored-by: Peter Holmberg <peter.hlmbrg@gmail.com>
Co-authored-by: gillesdemey <gilles.de.mey@gmail.com>
Co-authored-by: Domas <domas.lapinskas@grafana.com>
This commit is contained in:
Gilles De Mey
2021-11-30 17:29:22 +01:00
committed by GitHub
co-authored by Peter Holmberg Domas
parent 7bcc44e44e
commit febe5c9304
7 changed files with 86 additions and 11 deletions
@@ -240,6 +240,15 @@ abstract class DataSourceApi<
*/
abstract testDatasource(): Promise<any>;
/**
* Override to skip executing a query
*
* @returns false if the query should be skipped
*
* @virtual
*/
filterQuery?(query: TQuery): boolean;
/**
* Get hints for query improvements
*/
@@ -19,6 +19,10 @@ jest.mock('@grafana/runtime', () => ({
getDataSourceSrv: () => {
return {
getInstanceSettings: () => ({ name: 'prometheus' }),
get: () =>
Promise.resolve({
filterQuery: () => true,
}),
};
},
}));
@@ -136,6 +136,7 @@ export class QueryRows extends PureComponent<Props, State> {
return {
...item,
refId: query.refId,
queryType: item.model.queryType ?? '',
model: {
...item.model,
...query,
@@ -24,7 +24,8 @@ export const VizWrapper: FC<Props> = ({ data, currentPanel, changePanel, onThres
});
const vizHeight = useVizHeight(data, currentPanel, options.frameIndex);
const styles = useStyles2(getStyles(vizHeight));
const [fieldConfig, setFieldConfig] = useState<FieldConfigSource>(defaultFieldConfig(thresholds));
const [fieldConfig, setFieldConfig] = useState<FieldConfigSource>(defaultFieldConfig(thresholds, data));
useEffect(() => {
setFieldConfig((fieldConfig) => ({
@@ -32,6 +33,7 @@ export const VizWrapper: FC<Props> = ({ data, currentPanel, changePanel, onThres
defaults: {
...fieldConfig.defaults,
thresholds: thresholds,
unit: defaultUnit(data),
custom: {
...fieldConfig.defaults.custom,
thresholdsStyle: {
@@ -40,7 +42,7 @@ export const VizWrapper: FC<Props> = ({ data, currentPanel, changePanel, onThres
},
},
}));
}, [thresholds, setFieldConfig]);
}, [thresholds, setFieldConfig, data]);
const context: PanelContext = useMemo(
() => ({
@@ -98,13 +100,19 @@ const getStyles = (visHeight: number) => (theme: GrafanaTheme2) => ({
`,
});
function defaultFieldConfig(thresholds: ThresholdsConfig): FieldConfigSource {
function defaultUnit(data: PanelData): string | undefined {
return data.series[0]?.fields.find((field) => field.type === 'number')?.config.unit;
}
function defaultFieldConfig(thresholds: ThresholdsConfig, data: PanelData): FieldConfigSource {
if (!thresholds) {
return { defaults: {}, overrides: [] };
}
return {
defaults: {
thresholds: thresholds,
unit: defaultUnit(data),
custom: {
thresholdsStyle: {
mode: 'line',
@@ -2,13 +2,14 @@ import {
ArrayVector,
DataFrame,
DataFrameJSON,
DataSourceApi,
Field,
FieldType,
getDefaultRelativeTimeRange,
LoadingState,
rangeUtil,
} from '@grafana/data';
import { FetchResponse } from '@grafana/runtime';
import { DataSourceSrv, FetchResponse } from '@grafana/runtime';
import { BackendSrv } from 'app/core/services/backend_srv';
import { AlertQuery } from 'app/types/unified-alerting-dto';
import { Observable, of, throwError } from 'rxjs';
@@ -28,7 +29,8 @@ describe('AlertingQueryRunner', () => {
const runner = new AlertingQueryRunner(
mockBackendSrv({
fetch: () => of(response),
})
}),
mockDataSourceSrv()
);
const data = runner.get();
@@ -82,7 +84,8 @@ describe('AlertingQueryRunner', () => {
const runner = new AlertingQueryRunner(
mockBackendSrv({
fetch: () => of(response),
})
}),
mockDataSourceSrv()
);
const data = runner.get();
@@ -110,7 +113,8 @@ describe('AlertingQueryRunner', () => {
const runner = new AlertingQueryRunner(
mockBackendSrv({
fetch: () => of(response).pipe(delay(210)),
})
}),
mockDataSourceSrv()
);
const data = runner.get();
@@ -162,7 +166,8 @@ describe('AlertingQueryRunner', () => {
const runner = new AlertingQueryRunner(
mockBackendSrv({
fetch: () => throwError(error),
})
}),
mockDataSourceSrv()
);
const data = runner.get();
@@ -178,6 +183,28 @@ describe('AlertingQueryRunner', () => {
expect(data.B.error).toEqual(error);
});
});
it('should not execute if a query fails filterQuery check', async () => {
const runner = new AlertingQueryRunner(
mockBackendSrv({
fetch: () => throwError(new Error("shouldn't happen")),
}),
mockDataSourceSrv({ filterQuery: () => false })
);
const data = runner.get();
runner.run([createQuery('A'), createQuery('B')]);
await expect(data.pipe(take(1))).toEmitValuesWith((values) => {
const [data] = values;
expect(data.A.state).toEqual(LoadingState.Done);
expect(data.A.series).toHaveLength(0);
expect(data.B.state).toEqual(LoadingState.Done);
expect(data.B.series).toHaveLength(0);
});
});
});
type MockBackendSrvConfig = {
@@ -191,6 +218,12 @@ const mockBackendSrv = ({ fetch }: MockBackendSrvConfig): BackendSrv => {
} as unknown) as BackendSrv;
};
const mockDataSourceSrv = (dsApi?: Partial<DataSourceApi>) => {
return ({
get: () => Promise.resolve(dsApi ?? {}),
} as unknown) as DataSourceSrv;
};
const expectDataFrameWithValues = ({ time, values }: { time: number[]; values: number[] }): DataFrame => {
return {
fields: [
@@ -11,7 +11,7 @@ import {
TimeRange,
withLoadingIndicator,
} from '@grafana/data';
import { FetchResponse, toDataQueryError } from '@grafana/runtime';
import { FetchResponse, getDataSourceSrv, toDataQueryError } from '@grafana/runtime';
import { BackendSrv, getBackendSrv } from 'app/core/services/backend_srv';
import { preProcessPanelData } from 'app/features/query/state/runRequest';
import { AlertQuery } from 'app/types/unified-alerting-dto';
@@ -32,7 +32,7 @@ export class AlertingQueryRunner {
private subscription?: Unsubscribable;
private lastResult: Record<string, PanelData>;
constructor(private backendSrv = getBackendSrv()) {
constructor(private backendSrv = getBackendSrv(), private dataSourceSrv = getDataSourceSrv()) {
this.subject = new ReplaySubject(1);
this.lastResult = {};
}
@@ -41,12 +41,24 @@ export class AlertingQueryRunner {
return this.subject.asObservable();
}
run(queries: AlertQuery[]) {
async run(queries: AlertQuery[]) {
if (queries.length === 0) {
const empty = initialState(queries, LoadingState.Done);
return this.subject.next(empty);
}
// do not execute if one more of the queries are not runnable,
// for example not completely configured
for (const query of queries) {
if (!isExpressionQuery(query.model)) {
const ds = await this.dataSourceSrv.get(query.datasourceUid);
if (ds.filterQuery && !ds.filterQuery(query.model)) {
const empty = initialState(queries, LoadingState.Done);
return this.subject.next(empty);
}
}
}
this.subscription = runRequest(this.backendSrv, queries).subscribe({
next: (dataPerQuery) => {
const nextResult = applyChange(dataPerQuery, (refId, data) => {
@@ -73,6 +73,14 @@ export default class Datasource extends DataSourceApi<AzureMonitorQuery, AzureDa
}
}
filterQuery(item: AzureMonitorQuery): boolean {
if (!item.queryType) {
return true;
}
const ds = this.pseudoDatasource[item.queryType];
return ds?.filterQuery?.(item) ?? true;
}
query(options: DataQueryRequest<AzureMonitorQuery>): Observable<DataQueryResponse> {
const byType = new Map<AzureQueryType, DataQueryRequest<AzureMonitorQuery>>();