Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add variable query support #141

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ Whether the alerts gathered should be inhibited.

![Parameters](https://raw.githubusercontent.com/camptocamp/grafana-prometheus-alertmanager-datasource/master/img/table.png)

# Variable Query Editor

Additionally, to the query editor options, the `Field` must be filled whose values are used for the variable.

![Parameters](https://raw.githubusercontent.com/camptocamp/grafana-prometheus-alertmanager-datasource/master/img/variablequeryeditor.png)

# Panels

## Stat
Expand Down
Binary file added img/variablequeryeditor.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
68 changes: 54 additions & 14 deletions src/DataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ import {
DataSourceApi,
DataSourceInstanceSettings,
FieldType,
MetricFindValue,
MutableDataFrame,
ScopedVars,
} from '@grafana/data';
import { getBackendSrv, getTemplateSrv } from '@grafana/runtime';
import { GenericOptions, CustomQuery, QueryRequest, defaultQuery } from './types';
import { lastValueFrom } from 'rxjs';

export class AlertmanagerDataSource extends DataSourceApi<CustomQuery, GenericOptions> {
url: string;
Expand All @@ -25,8 +28,8 @@ export class AlertmanagerDataSource extends DataSourceApi<CustomQuery, GenericOp
}
}

async query(options: QueryRequest): Promise<DataQueryResponse> {
const promises = options.targets.map((query) => {
async doQuery(queries: CustomQuery[], scopedVars?: ScopedVars): Promise<MutableDataFrame[]> {
const promises = queries.map((query) => {
query = { ...defaultQuery, ...query };
if (query.hide) {
return Promise.resolve(new MutableDataFrame());
Expand All @@ -43,31 +46,70 @@ export class AlertmanagerDataSource extends DataSourceApi<CustomQuery, GenericOp
params.push(`receiver=${query.receiver}`);
}
if (query.filters !== undefined && query.filters.length > 0) {
query.filters = getTemplateSrv().replace(query.filters, options.scopedVars, this.interpolateQueryExpr);
query.filters = getTemplateSrv().replace(query.filters, scopedVars, this.interpolateQueryExpr);
query.filters.split(',').forEach((value) => {
params.push(`filter=${encodeURIComponent(value)}`);
});
}

const request = this.doRequest({
return this.doRequest({
url: `${this.url}/api/v2/alerts?${params.join('&')}`,
method: 'GET',
}).then((request) => request.toPromise());

return request.then((data: any) => this.retrieveData(query, data));
})
.then((data) => lastValueFrom(data))
.then((data) => {
return this.retrieveData(query, data);
})
.catch(() => {
return new MutableDataFrame();
});
});

return Promise.all(promises).then((data) => {
return data;
});
}

async query(options: QueryRequest): Promise<DataQueryResponse> {
return this.doQuery(options.targets, options.scopedVars).then((data) => {
return { data };
});
}

async metricFindQuery(query: CustomQuery, options?: any): Promise<MetricFindValue[]> {
if (typeof query.field === undefined) {
return [];
}
const response = (await this.doQuery([query], options.scopedVars))[0];

let fieldIndex = -1;
response.fields.forEach((field, index) => {
if (field.name === query.field) {
fieldIndex = index;
}
});
if (fieldIndex === -1) {
return [];
}

const values = response.fields[fieldIndex].values.toArray().map((val) => {
return val.toString();
});
const unique = [...new Set(values)];
return unique.map((val) => {
return { text: val };
});
}

async testDatasource() {
return this.doRequest({
url: this.url,
method: 'GET',
}).then((response) =>
response.toPromise().then((data) => {
})
.then((response) => {
return lastValueFrom(response);
})
.then((data) => {
if (data !== undefined) {
if (data.ok) {
return { status: 'success', message: 'Datasource is working', title: 'Success' };
Expand All @@ -84,8 +126,7 @@ export class AlertmanagerDataSource extends DataSourceApi<CustomQuery, GenericOp
message: `Unknown error in datasource`,
title: 'Error',
};
})
);
});
}

async doRequest(options: any) {
Expand Down Expand Up @@ -114,11 +155,10 @@ export class AlertmanagerDataSource extends DataSourceApi<CustomQuery, GenericOp
});
}

const frame = new MutableDataFrame({
return new MutableDataFrame({
refId: refId,
fields: fields,
});
return frame;
}

parseAlertAttributes(alert: any, fields: any[]): string[] {
Expand Down Expand Up @@ -178,5 +218,5 @@ export function alertmanagerRegularEscape(value: any) {
}

export function alertmanagerSpecialRegexEscape(value: any) {
return typeof value === 'string' ? value.replace(/\\/g, '\\\\\\\\').replace(/[$^*{}\[\]\'+?()|]/g, '\\\\$&') : value;
return typeof value === 'string' ? value.replace(/\\/g, '\\\\\\\\').replace(/[$^*{}\[\]'+?()|]/g, '\\\\$&') : value;
}
41 changes: 33 additions & 8 deletions src/QueryEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,27 +27,36 @@ export class QueryEditor extends PureComponent<Props> {

onActiveChange = () => {
const { onChange, query, onRunQuery } = this.props;
query.active = !query.active;
onChange({ ...query });
let newQuery = { ...query };
newQuery.active = !query.active;
onChange(newQuery);
onRunQuery();
};

onSilencedChange = () => {
const { onChange, query, onRunQuery } = this.props;
query.silenced = !query.silenced;
onChange({ ...query });
let newQuery = { ...query };
newQuery.silenced = !query.silenced;
onChange(newQuery);
onRunQuery();
};

onInhibitedChange = () => {
const { onChange, query, onRunQuery } = this.props;
query.inhibited = !query.inhibited;
onChange({ ...query });
let newQuery = { ...query };
newQuery.inhibited = !query.inhibited;
onChange(newQuery);
onRunQuery();
};

render() {
const { receiver, filters, active, silenced, inhibited } = { ...defaultQuery, ...this.props.query };
onFieldChange = (event: ChangeEvent<HTMLInputElement>) => {
const { onChange, query, onRunQuery } = this.props;
onChange({ ...query, field: event.target.value });
onRunQuery();
};

baseRender(showField: boolean) {
let { receiver, filters, active, silenced, inhibited, field } = { ...defaultQuery, ...this.props.query };

return (
<>
Expand All @@ -70,6 +79,18 @@ export class QueryEditor extends PureComponent<Props> {
label="Filters (comma separated key=value)"
/>
</div>
{showField && (
<div className="gf-form">
<FormField
value={field}
inputWidth={10}
onChange={this.onFieldChange}
labelWidth={5}
label="Field"
tooltip="Variables are taken from the values of this field in the query result"
/>
</div>
)}
<div className="gf-form">
<Switch label="Active" checked={active} onChange={this.onActiveChange} />
</div>
Expand All @@ -83,4 +104,8 @@ export class QueryEditor extends PureComponent<Props> {
</>
);
}

render() {
return this.baseRender(false);
}
}
7 changes: 7 additions & 0 deletions src/VariableQueryEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { QueryEditor } from './QueryEditor';

export class CustomVariableQueryEditor extends QueryEditor {
render() {
return super.baseRender(true);
}
}
4 changes: 3 additions & 1 deletion src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ConfigEditor } from './ConfigEditor';
import { AlertmanagerDataSource } from './DataSource';
import { QueryEditor } from './QueryEditor';
import { CustomQuery, GenericOptions } from './types';
import { CustomVariableQueryEditor } from './VariableQueryEditor';

class GenericAnnotationsQueryCtrl {
static templateUrl = 'partials/annotations.editor.html';
Expand All @@ -11,4 +12,5 @@ class GenericAnnotationsQueryCtrl {
export const plugin = new DataSourcePlugin<AlertmanagerDataSource, CustomQuery, GenericOptions>(AlertmanagerDataSource)
.setAnnotationQueryCtrl(GenericAnnotationsQueryCtrl)
.setConfigEditor(ConfigEditor)
.setQueryEditor(QueryEditor);
.setQueryEditor(QueryEditor)
.setVariableQueryEditor(CustomVariableQueryEditor);
3 changes: 1 addition & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { DataQuery, DataQueryRequest, DataSourceJsonData } from '@grafana/data';

export interface DataSourceOptions extends DataSourceJsonData {}

export interface QueryRequest extends DataQueryRequest<CustomQuery> {
adhocFilters?: any[];
}
Expand All @@ -13,6 +11,7 @@ export interface CustomQuery extends DataQuery {
active: boolean;
silenced: boolean;
inhibited: boolean;
field: string;
}

export const defaultQuery: Partial<CustomQuery> = {
Expand Down