Skip to content

Commit

Permalink
Support for new autocomplete endpoints
Browse files Browse the repository at this point in the history
Improved autocompleter endpoint detection. Only needed one request when
starting to edit a dashboard. No extra requests made on visualization
mode
  • Loading branch information
lpetrora committed Feb 29, 2024
1 parent e324ccc commit 45fc33c
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 18 deletions.
35 changes: 26 additions & 9 deletions src/DataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { replaceVariables } from 'utils';

import { MetricFindQuery, RequestSpec } from './RequestSpec';
import RestApiBackend from './backend/rest';
import { Backend } from './backend/types';
import { BACKEND_TYPE, Backend } from './backend/types';
import WebApiBackend from './backend/web';
import { Settings } from './settings';
import { Backend as BackendType, CmkQuery, DataSourceOptions, Edition, ResponseDataAutocomplete } from './types';
Expand All @@ -22,6 +22,7 @@ export class DataSource extends DataSourceApi<CmkQuery> {
webBackend: WebApiBackend;
restBackend: RestApiBackend;
settings: Settings;
autocompleteBackend: BACKEND_TYPE | null = null;

constructor(private instanceSettings: DataSourceInstanceSettings<DataSourceOptions>) {
super(instanceSettings);
Expand All @@ -30,14 +31,18 @@ export class DataSource extends DataSourceApi<CmkQuery> {
this.settings = new Settings(instanceSettings.jsonData);
}

protected async setAutocompleteBackend() {
this.autocompleteBackend = await this.getBackend().getAutocompleteBackend();
}

async query(dataQueryRequest: DataQueryRequest<CmkQuery>): Promise<DataQueryResponse> {
for (const target of dataQueryRequest.targets) {
target.requestSpec = replaceVariables(target.requestSpec, dataQueryRequest.scopedVars);
}
return this.getBackend().query(dataQueryRequest);
}

async metricFindQuery(query: MetricFindQuery, options?: any): Promise<MetricFindValue[]> {
async metricFindQuery(query: MetricFindQuery, options?: unknown): Promise<MetricFindValue[]> {
if (query.objectType === 'site') {
// rest-api site endpoint were added in 2.2.0 so we have to use the web-api here
// TODO: clean up (remove filterSites from Backend) with end of 2.1.0
Expand All @@ -51,8 +56,17 @@ export class DataSource extends DataSourceApi<CmkQuery> {
return this.getBackend().testDatasource();
}

async autocompleterRequest<T>(api_url: string, data: unknown): Promise<FetchResponse<WebApiResponse<T>>> {
return this.webBackend.autocompleterRequest(api_url, data);
async autocompleterRequest(
api_url: string,
data: unknown
): Promise<FetchResponse<WebApiResponse<ResponseDataAutocomplete>>> {
this.autocompleteBackend === null && (await this.setAutocompleteBackend());

if (this.autocompleteBackend === BACKEND_TYPE.WEB) {
return this.webBackend.autocompleterRequest(api_url, data);
}

return this.restBackend.autocompleterRequest(api_url, data);
}

async contextAutocomplete(
Expand All @@ -64,10 +78,13 @@ export class DataSource extends DataSourceApi<CmkQuery> {
if (ident === 'label' && this.getBackendType() === 'web') {
// we have a 2.1.0 version without werk #15074 so label autocompleter is a special edge case
// can be removed after we stop supporting 2.1.0
const response = await this.autocompleterRequest<Array<{ value: string }>>('ajax_autocomplete_labels.py', {
world: params.world,
search_label: prefix,
});
const response = await this.webBackend.autocompleterRequest<Array<{ value: string }>>(
'ajax_autocomplete_labels.py',
{
world: params.world,
search_label: prefix,
}
);
return response.data.result.map((val: { value: string }) => ({
value: val.value,
label: val.value,
Expand All @@ -78,7 +95,7 @@ export class DataSource extends DataSourceApi<CmkQuery> {
replaceVariables(partialRequestSpec),
this.getBackendType() === 'rest' ? 'latest' : '2.1.0'
);
const response = await this.autocompleterRequest<ResponseDataAutocomplete>('ajax_vs_autocomplete.py', {
const response = await this.autocompleterRequest('ajax_vs_autocomplete.py', {
ident,
value: prefix,
params: {
Expand Down
53 changes: 51 additions & 2 deletions src/backend/rest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ import { BackendSrvRequest, FetchError, FetchResponse, getBackendSrv } from '@gr
import { Aggregation, GraphType, MetricFindQuery } from 'RequestSpec';
import * as process from 'process';

import { CmkQuery } from '../types';
import { CmkQuery, ResponseDataAutocomplete } from '../types';
import { createCmkContext, replaceVariables, toLiveStatusQuery, updateMetricTitles, updateQuery } from '../utils';
import { Backend, DatasourceOptions } from './types';
import { WebApiResponse } from './../webapi';
import { BACKEND_TYPE, Backend, DatasourceOptions } from './types';
import { validateRequestSpec } from './validate';

type RestApiError = {
Expand Down Expand Up @@ -85,6 +86,14 @@ type RestApiHostResponse = RestApiLivestatusResponse<{ name: string }>;

type RestApiServiceResponse = RestApiLivestatusResponse<{ description: string }>;

type RestApiAutocompleteResponseEntry = {
id: string;
value: string;
};

type RestApiAutocompleteResponse = {
choices: [RestApiAutocompleteResponseEntry];
};
export default class RestApiBackend implements Backend {
datasource: DatasourceOptions;

Expand Down Expand Up @@ -362,4 +371,44 @@ export default class RestApiBackend implements Backend {
return new MutableDataFrame();
}
}

async getAutocompleteBackend(): Promise<BACKEND_TYPE> {
return this.api<RestApiAutocompleteResponse>({
url: `/objects/autocomplete/sites`,
method: 'POST',
data: { value: '', parameters: {} },
})
.then(() => {
return BACKEND_TYPE.REST;
})
.catch(() => {
return BACKEND_TYPE.WEB;
});
}

async autocompleterRequest(
api_url = '',
data: unknown
): Promise<FetchResponse<WebApiResponse<ResponseDataAutocomplete>>> {
const { ident, params: parameters, value } = data as { ident: string; value: unknown; params: unknown };

const response = await this.api<RestApiAutocompleteResponse>({
url: `/objects/autocomplete/${ident}`,
method: 'POST',
data: { value, parameters },
});

const choices = response?.data?.choices || [];

const new_data: WebApiResponse<ResponseDataAutocomplete> = {
result_code: 200,
severity: 'success',
result: {
choices: choices.map((element) => [element.id, element.value]),
},
};

const res: FetchResponse<WebApiResponse<ResponseDataAutocomplete>> = { ...response, data: new_data };
return res;
}
}
7 changes: 6 additions & 1 deletion src/backend/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,22 @@ import { DataQueryRequest, DataQueryResponse, MetricFindValue } from '@grafana/d
import { MetricFindQuery } from '../RequestSpec';
import { CmkQuery, Edition } from '../types';

export enum BACKEND_TYPE {
REST = 'rest',
WEB = 'web',
}

export interface Backend {
query: (options: DataQueryRequest<CmkQuery>) => Promise<DataQueryResponse>;
testDatasource: () => Promise<unknown>;
metricFindQuery: (query: MetricFindQuery) => Promise<MetricFindValue[]>;
listSites: () => Promise<MetricFindValue[]>;
getAutocompleteBackend: () => Promise<BACKEND_TYPE>;
}

export interface DatasourceOptions {
getBackend: () => Backend;
getEdition: () => Edition;
getUrl: () => string | undefined;

getUsername(): string;
}
6 changes: 5 additions & 1 deletion src/backend/web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
createWebApiRequestBody,
createWebApiRequestSpecification,
} from './../webapi';
import { Backend, DatasourceOptions } from './types';
import { BACKEND_TYPE, Backend, DatasourceOptions } from './types';
import { validateRequestSpec } from './validate';

export default class WebApiBackend implements Backend {
Expand Down Expand Up @@ -203,4 +203,8 @@ export default class WebApiBackend implements Backend {

return frame;
}

async getAutocompleteBackend(): Promise<BACKEND_TYPE> {
return BACKEND_TYPE.WEB;
}
}
15 changes: 12 additions & 3 deletions src/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,18 @@
"large": "img/checkmk_logo.svg"
},
"links": [
{ "name": "Documentation", "url": "https://docs.checkmk.com/latest/en/grafana.html" },
{ "name": "GitHub", "url": "https://github.com/Checkmk/grafana-checkmk-datasource" },
{ "name": "Checkmk", "url": "https://Checkmk.com" }
{
"name": "Documentation",
"url": "https://docs.checkmk.com/latest/en/grafana.html"
},
{
"name": "GitHub",
"url": "https://github.com/Checkmk/grafana-checkmk-datasource"
},
{
"name": "Checkmk",
"url": "https://Checkmk.com"
}
],
"screenshots": [
{
Expand Down
3 changes: 1 addition & 2 deletions src/ui/filters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React from 'react';

import { DataSource } from '../DataSource';
import { RequestSpec } from '../RequestSpec';
import { ResponseDataAutocomplete } from '../types';
import {
CheckMkSelect,
CheckMkSelectNegatable,
Expand Down Expand Up @@ -79,7 +78,7 @@ export const Filters = (props: FiltersProp): JSX.Element => {
} else {
return (async function () {
// TODO: would have expected that this is dependent on the site, but does not look like that?
const response = await datasource.autocompleterRequest<ResponseDataAutocomplete>('ajax_vs_autocomplete.py', {
const response = await datasource.autocompleterRequest('ajax_vs_autocomplete.py', {
ident: 'tag_groups_opt',
params: { group_id: context.groupId, strict: true },
value: prefix,
Expand Down

0 comments on commit 45fc33c

Please sign in to comment.