diff --git a/src/DataSource.ts b/src/DataSource.ts index 84c421f..fdde4e1 100644 --- a/src/DataSource.ts +++ b/src/DataSource.ts @@ -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'; @@ -22,6 +22,7 @@ export class DataSource extends DataSourceApi { webBackend: WebApiBackend; restBackend: RestApiBackend; settings: Settings; + autocompleteBackend: BACKEND_TYPE | null = null; constructor(private instanceSettings: DataSourceInstanceSettings) { super(instanceSettings); @@ -30,6 +31,10 @@ export class DataSource extends DataSourceApi { this.settings = new Settings(instanceSettings.jsonData); } + protected async setAutocompleteBackend() { + this.autocompleteBackend = await this.getBackend().getAutocompleteBackend(); + } + async query(dataQueryRequest: DataQueryRequest): Promise { for (const target of dataQueryRequest.targets) { target.requestSpec = replaceVariables(target.requestSpec, dataQueryRequest.scopedVars); @@ -37,7 +42,7 @@ export class DataSource extends DataSourceApi { return this.getBackend().query(dataQueryRequest); } - async metricFindQuery(query: MetricFindQuery, options?: any): Promise { + async metricFindQuery(query: MetricFindQuery, options?: unknown): Promise { 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 @@ -51,8 +56,17 @@ export class DataSource extends DataSourceApi { return this.getBackend().testDatasource(); } - async autocompleterRequest(api_url: string, data: unknown): Promise>> { - return this.webBackend.autocompleterRequest(api_url, data); + async autocompleterRequest( + api_url: string, + data: unknown + ): Promise>> { + 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( @@ -64,10 +78,13 @@ export class DataSource extends DataSourceApi { 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>('ajax_autocomplete_labels.py', { - world: params.world, - search_label: prefix, - }); + const response = await this.webBackend.autocompleterRequest>( + 'ajax_autocomplete_labels.py', + { + world: params.world, + search_label: prefix, + } + ); return response.data.result.map((val: { value: string }) => ({ value: val.value, label: val.value, @@ -78,7 +95,7 @@ export class DataSource extends DataSourceApi { replaceVariables(partialRequestSpec), this.getBackendType() === 'rest' ? 'latest' : '2.1.0' ); - const response = await this.autocompleterRequest('ajax_vs_autocomplete.py', { + const response = await this.autocompleterRequest('ajax_vs_autocomplete.py', { ident, value: prefix, params: { diff --git a/src/backend/rest.ts b/src/backend/rest.ts index 4add6fe..4e349be 100644 --- a/src/backend/rest.ts +++ b/src/backend/rest.ts @@ -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 = { @@ -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; @@ -362,4 +371,44 @@ export default class RestApiBackend implements Backend { return new MutableDataFrame(); } } + + async getAutocompleteBackend(): Promise { + return this.api({ + 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>> { + const { ident, params: parameters, value } = data as { ident: string; value: unknown; params: unknown }; + + const response = await this.api({ + url: `/objects/autocomplete/${ident}`, + method: 'POST', + data: { value, parameters }, + }); + + const choices = response?.data?.choices || []; + + const new_data: WebApiResponse = { + result_code: 200, + severity: 'success', + result: { + choices: choices.map((element) => [element.id, element.value]), + }, + }; + + const res: FetchResponse> = { ...response, data: new_data }; + return res; + } } diff --git a/src/backend/types.ts b/src/backend/types.ts index 9f2944e..9582f9b 100644 --- a/src/backend/types.ts +++ b/src/backend/types.ts @@ -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) => Promise; testDatasource: () => Promise; metricFindQuery: (query: MetricFindQuery) => Promise; listSites: () => Promise; + getAutocompleteBackend: () => Promise; } export interface DatasourceOptions { getBackend: () => Backend; getEdition: () => Edition; getUrl: () => string | undefined; - getUsername(): string; } diff --git a/src/backend/web.ts b/src/backend/web.ts index abecd68..6028021 100644 --- a/src/backend/web.ts +++ b/src/backend/web.ts @@ -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 { @@ -203,4 +203,8 @@ export default class WebApiBackend implements Backend { return frame; } + + async getAutocompleteBackend(): Promise { + return BACKEND_TYPE.WEB; + } } diff --git a/src/plugin.json b/src/plugin.json index 9c0a2a7..3ca1a55 100644 --- a/src/plugin.json +++ b/src/plugin.json @@ -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": [ { diff --git a/src/ui/filters.tsx b/src/ui/filters.tsx index 51e9b75..c6e85aa 100644 --- a/src/ui/filters.tsx +++ b/src/ui/filters.tsx @@ -2,7 +2,6 @@ import React from 'react'; import { DataSource } from '../DataSource'; import { RequestSpec } from '../RequestSpec'; -import { ResponseDataAutocomplete } from '../types'; import { CheckMkSelect, CheckMkSelectNegatable, @@ -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('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,