Skip to content

Commit 1769b37

Browse files
committed
fix label autcomplete with checkmk 2.2.0
1 parent 805aad4 commit 1769b37

File tree

3 files changed

+114
-99
lines changed

3 files changed

+114
-99
lines changed

src/DataSource.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { DataQueryRequest, DataQueryResponse, DataSourceApi, DataSourceInstanceSettings } from '@grafana/data';
22
import { FetchResponse } from '@grafana/runtime';
33

4+
import { RequestSpec } from './RequestSpec';
45
import RestApiBackend from './backend/rest';
56
import { Backend } from './backend/types';
67
import WebApiBackend from './backend/web';
7-
import { Backend as BackendType, CmkQuery, DataSourceOptions, Edition } from './types';
8+
import { Backend as BackendType, CmkQuery, DataSourceOptions, Edition, ResponseDataAutocomplete } from './types';
9+
import { createAutocompleteConfig } from './ui/autocomplete';
810
import { WebApiResponse, buildRequestBody } from './webapi';
911

1012
export class DataSource extends DataSourceApi<CmkQuery> {
@@ -34,6 +36,36 @@ export class DataSource extends DataSourceApi<CmkQuery> {
3436
});
3537
}
3638

39+
async contextAutocomplete(
40+
ident: string,
41+
partialRequestSpec: Partial<RequestSpec>,
42+
prefix: string,
43+
params: Record<string, string | boolean>
44+
): Promise<Array<{ value: string; label: string; isDisabled: boolean }>> {
45+
if (ident === 'label' && this.getBackendType() === 'web') {
46+
// we have a 2.1.0 version without werk #15074 so label autocompleter is a special edge case
47+
// can be removed after we stop supporting 2.1.0
48+
const response = await this.autocompleterRequest<Array<{ value: string }>>('ajax_autocomplete_labels.py', {
49+
world: params['world'],
50+
search_label: prefix,
51+
});
52+
return response.data.result.map((val: { value: string }) => ({
53+
value: val.value,
54+
label: val.value,
55+
isDisabled: false,
56+
}));
57+
}
58+
const response = await this.autocompleterRequest<ResponseDataAutocomplete>(
59+
'ajax_vs_autocomplete.py',
60+
createAutocompleteConfig(partialRequestSpec, ident, prefix, params)
61+
);
62+
return response.data.result.choices.map(([value, label]: [string, string]) => ({
63+
value,
64+
label,
65+
isDisabled: value === null,
66+
}));
67+
}
68+
3769
getUrl(): string | undefined {
3870
return this.instanceSettings.url;
3971
}

src/ui/QueryEditor.tsx

Lines changed: 11 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@ import { InlineFieldRow, VerticalGroup } from '@grafana/ui';
33
import React from 'react';
44

55
import { DataSource } from '../DataSource';
6-
import { Aggregation, GraphType, RequestSpec } from '../RequestSpec';
6+
import { Aggregation, GraphType } from '../RequestSpec';
77
import { CmkQuery, DataSourceOptions, ResponseDataAutocomplete } from '../types';
88
import { aggregationToPresentation, updateQuery } from '../utils';
9-
import { createAutocompleteConfig } from './autocomplete';
109
import {
1110
CheckMkSelect,
1211
CheckMkSelectNegatable,
@@ -18,33 +17,6 @@ import {
1817

1918
type Props = QueryEditorProps<DataSource, CmkQuery, DataSourceOptions>;
2019

21-
async function contextAutocomplete(
22-
datasource: DataSource,
23-
ident: string,
24-
partialRequestSpec: Partial<RequestSpec>,
25-
prefix: string,
26-
params: Record<string, string | boolean>
27-
) {
28-
const response = await datasource.autocompleterRequest<ResponseDataAutocomplete>(
29-
'ajax_vs_autocomplete.py',
30-
createAutocompleteConfig(partialRequestSpec, ident, prefix, params)
31-
);
32-
return response.data.result.choices.map(([value, label]: [string, string]) => ({
33-
value,
34-
label,
35-
isDisabled: value === null,
36-
}));
37-
}
38-
39-
async function labelAutocomplete(datasource: DataSource, prefix: string) {
40-
// TODO: would have expected that the site is used as context!
41-
const response = await datasource.autocompleterRequest<Array<{ value: string }>>('ajax_autocomplete_labels.py', {
42-
world: 'core',
43-
search_label: prefix,
44-
});
45-
return response.data.result.map((val: { value: string }) => ({ value: val.value, label: val.value }));
46-
}
47-
4820
export const QueryEditor = (props: Props): JSX.Element => {
4921
const { onChange, onRunQuery, datasource, query } = props;
5022
updateQuery(query);
@@ -100,29 +72,30 @@ export const QueryEditor = (props: Props): JSX.Element => {
10072
];
10173

10274
const siteAutocompleter = React.useCallback(
103-
(prefix: string) => {
104-
return contextAutocomplete(datasource, 'sites', {}, prefix, { strict: false });
75+
async (prefix: string) => {
76+
return await datasource.contextAutocomplete('sites', {}, prefix, { strict: false });
10577
},
10678
[datasource]
10779
);
10880
const hostAutocompleter = React.useCallback(
10981
(prefix: string) =>
110-
contextAutocomplete(datasource, 'monitored_hostname', { site: qSite }, prefix, { strict: 'with_source' }),
82+
datasource.contextAutocomplete('monitored_hostname', { site: qSite }, prefix, { strict: 'with_source' }),
11183
[datasource, qSite]
11284
);
11385
const hostLabelAutocompleter = React.useCallback(
114-
(prefix: string) => labelAutocomplete(datasource, prefix),
86+
// TODO: would have expected that the site is used as context!
87+
(prefix: string) => datasource.contextAutocomplete('label', {}, prefix, { world: 'core' }),
11588
[datasource]
11689
);
11790
const hostGroupAutocompleter = React.useCallback(
11891
(prefix: string) =>
119-
contextAutocomplete(datasource, 'allgroups', { site: qSite }, prefix, { group_type: 'host', strict: true }),
92+
datasource.contextAutocomplete('allgroups', { site: qSite }, prefix, { group_type: 'host', strict: true }),
12093
[datasource, qSite]
12194
);
12295
const hostTagAutocompleter = React.useCallback(
12396
(prefix: string, mode: 'groups' | 'choices', context: Record<string, unknown>) => {
12497
if (mode === 'groups') {
125-
return contextAutocomplete(datasource, 'tag_groups', { site: qSite, ...context }, prefix, { strict: true });
98+
return datasource.contextAutocomplete('tag_groups', { site: qSite, ...context }, prefix, { strict: true });
12699
} else {
127100
return (async function () {
128101
// TODO: would have expected that this is dependent on the site, but does not look like that?
@@ -143,14 +116,14 @@ export const QueryEditor = (props: Props): JSX.Element => {
143116

144117
const serviceAutocompleter = React.useCallback(
145118
(prefix: string) =>
146-
contextAutocomplete(datasource, 'monitored_service_description', { site: qSite, ...qHost }, prefix, {
119+
datasource.contextAutocomplete('monitored_service_description', { site: qSite, ...qHost }, prefix, {
147120
strict: true,
148121
}),
149122
[datasource, qSite, qHost]
150123
);
151124
const serviceGroupAutocompleter = React.useCallback(
152125
(prefix: string) =>
153-
contextAutocomplete(datasource, 'allgroups', { site: qSite, ...qHost }, prefix, {
126+
datasource.contextAutocomplete('allgroups', { site: qSite, ...qHost }, prefix, {
154127
group_type: 'service',
155128
strict: true,
156129
}),
@@ -176,8 +149,7 @@ export const QueryEditor = (props: Props): JSX.Element => {
176149
single_infos: ['host'],
177150
};
178151
}
179-
return contextAutocomplete(
180-
datasource,
152+
return datasource.contextAutocomplete(
181153
ident,
182154
{ site: qSite, ...qHost, ...qService, graph_type: qGraphType },
183155
prefix,

tests/unit/ui/QueryEditor.test.tsx

Lines changed: 70 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -7,38 +7,32 @@ import { defaultRequestSpec } from '../../../src/RequestSpec';
77
import { CmkQuery } from '../../../src/types';
88
import { QueryEditor } from '../../../src/ui/QueryEditor';
99

10-
const completions: Record<string, string[][]> = {
10+
const completions: Record<string, Array<{ value: string; label: string }>> = {
1111
sites: [
12-
['site_1', 'Site One'],
13-
['site_2', 'Site Two'],
12+
{ value: 'site_1', label: 'Site One' },
13+
{ value: 'site_2', label: 'Site Two' },
1414
],
1515
monitored_hostname: [
16-
['host_name_1', 'Hostname One'],
17-
['host_name_2', 'Hostname Two'],
16+
{ value: 'host_name_1', label: 'Hostname One' },
17+
{ value: 'host_name_2', label: 'Hostname Two' },
1818
],
1919
monitored_service_description: [
20-
['service_1', 'Service One'],
21-
['service_2', 'Service Two'],
20+
{ value: 'service_1', label: 'Service One' },
21+
{ value: 'service_2', label: 'Service Two' },
2222
],
2323
monitored_metrics: [
24-
['metric_1', 'Metric One'],
25-
['metric_2', 'Metric Two'],
24+
{ value: 'metric_1', label: 'Metric One' },
25+
{ value: 'metric_2', label: 'Metric Two' },
2626
],
2727
available_graphs: [
28-
['graph_1', 'Graph One'],
29-
['graph_2', 'Graph Two'],
28+
{ value: 'graph_1', label: 'Graph One' },
29+
{ value: 'graph_2', label: 'Graph Two' },
3030
],
3131
};
3232

3333
describe('QueryEditor RAW', () => {
34-
const autocompleterRequest = jest.fn(async (_, { ident }) => {
35-
return {
36-
data: {
37-
result: {
38-
choices: completions[ident],
39-
},
40-
},
41-
};
34+
const contextAutocomplete = jest.fn(async (ident, _partialRequestSpec, _prefix, _params) => {
35+
return completions[ident];
4236
});
4337

4438
const mockDatasource = {
@@ -47,7 +41,7 @@ describe('QueryEditor RAW', () => {
4741
edition: 'RAW',
4842
},
4943
},
50-
autocompleterRequest,
44+
contextAutocomplete,
5145
getEdition() {
5246
return 'RAW';
5347
},
@@ -75,9 +69,11 @@ describe('QueryEditor RAW', () => {
7569
async ({ label, attribute, autocomplete }) => {
7670
render(<QueryEditor datasource={mockDatasource} query={query} onRunQuery={onRunQuery} onChange={onChange} />);
7771

78-
expect(autocompleterRequest).toHaveBeenCalledWith(
79-
'ajax_vs_autocomplete.py',
80-
expect.objectContaining({ ident: autocomplete })
72+
expect(contextAutocomplete).toHaveBeenCalledWith(
73+
autocomplete,
74+
expect.anything(),
75+
expect.anything(),
76+
expect.anything()
8177
);
8278

8379
const input = screen.getByLabelText(label);
@@ -140,70 +136,85 @@ describe('QueryEditor RAW', () => {
140136
it('calls the autocompleters the minimal amount of times', async () => {
141137
render(<QueryEditor datasource={mockDatasource} query={query} onRunQuery={onRunQuery} onChange={onChange} />);
142138

143-
expect(autocompleterRequest).toHaveBeenCalledWith(
144-
'ajax_vs_autocomplete.py',
145-
expect.objectContaining({ ident: 'sites' })
146-
);
147-
expect(autocompleterRequest).toHaveBeenCalledWith(
148-
'ajax_vs_autocomplete.py',
149-
expect.objectContaining({ ident: 'monitored_hostname' })
139+
expect(contextAutocomplete).toHaveBeenCalledWith('sites', expect.anything(), expect.anything(), expect.anything());
140+
expect(contextAutocomplete).toHaveBeenCalledWith(
141+
'monitored_hostname',
142+
expect.anything(),
143+
expect.anything(),
144+
expect.anything()
150145
);
151-
expect(autocompleterRequest).toHaveBeenCalledWith(
152-
'ajax_vs_autocomplete.py',
153-
expect.objectContaining({ ident: 'monitored_service_description' })
146+
expect(contextAutocomplete).toHaveBeenCalledWith(
147+
'monitored_service_description',
148+
expect.anything(),
149+
expect.anything(),
150+
expect.anything()
154151
);
155-
expect(autocompleterRequest).toHaveBeenCalledWith(
156-
'ajax_vs_autocomplete.py',
157-
expect.objectContaining({ ident: 'available_graphs' })
152+
expect(contextAutocomplete).toHaveBeenCalledWith(
153+
'available_graphs',
154+
expect.anything(),
155+
expect.anything(),
156+
expect.anything()
158157
);
159-
autocompleterRequest.mockClear();
158+
contextAutocomplete.mockClear();
160159

161160
const siteInput = screen.getByLabelText('Site');
162161
await act(async () => {
163162
await selectEvent.select(siteInput, 'Site One', { container: document.body });
164163
});
165-
expect(autocompleterRequest).toHaveBeenCalledWith(
166-
'ajax_vs_autocomplete.py',
167-
expect.objectContaining({ ident: 'monitored_hostname' })
164+
expect(contextAutocomplete).toHaveBeenCalledWith(
165+
'monitored_hostname',
166+
expect.anything(),
167+
expect.anything(),
168+
expect.anything()
168169
);
169-
expect(autocompleterRequest).toHaveBeenCalledWith(
170-
'ajax_vs_autocomplete.py',
171-
expect.objectContaining({ ident: 'monitored_service_description' })
170+
expect(contextAutocomplete).toHaveBeenCalledWith(
171+
'monitored_service_description',
172+
expect.anything(),
173+
expect.anything(),
174+
expect.anything()
172175
);
173-
expect(autocompleterRequest).toHaveBeenCalledWith(
174-
'ajax_vs_autocomplete.py',
175-
expect.objectContaining({ ident: 'available_graphs' })
176+
expect(contextAutocomplete).toHaveBeenCalledWith(
177+
'available_graphs',
178+
expect.anything(),
179+
expect.anything(),
180+
expect.anything()
176181
);
177-
autocompleterRequest.mockClear();
182+
contextAutocomplete.mockClear();
178183

179184
const hostInput = screen.getByLabelText('Hostname');
180185
await act(async () => {
181186
await selectEvent.select(hostInput, 'Hostname One', { container: document.body });
182187
});
183-
expect(autocompleterRequest).toHaveBeenCalledWith(
184-
'ajax_vs_autocomplete.py',
185-
expect.objectContaining({ ident: 'monitored_service_description' })
188+
expect(contextAutocomplete).toHaveBeenCalledWith(
189+
'monitored_service_description',
190+
expect.anything(),
191+
expect.anything(),
192+
expect.anything()
186193
);
187-
expect(autocompleterRequest).toHaveBeenCalledWith(
188-
'ajax_vs_autocomplete.py',
189-
expect.objectContaining({ ident: 'available_graphs' })
194+
expect(contextAutocomplete).toHaveBeenCalledWith(
195+
'available_graphs',
196+
expect.anything(),
197+
expect.anything(),
198+
expect.anything()
190199
);
191-
autocompleterRequest.mockClear();
200+
contextAutocomplete.mockClear();
192201

193202
const serviceInput = screen.getByLabelText('Service');
194203
await act(async () => {
195204
await selectEvent.select(serviceInput, 'Service One', { container: document.body });
196205
});
197-
expect(autocompleterRequest).toHaveBeenCalledWith(
198-
'ajax_vs_autocomplete.py',
199-
expect.objectContaining({ ident: 'available_graphs' })
206+
expect(contextAutocomplete).toHaveBeenCalledWith(
207+
'available_graphs',
208+
expect.anything(),
209+
expect.anything(),
210+
expect.anything()
200211
);
201-
autocompleterRequest.mockClear();
212+
contextAutocomplete.mockClear();
202213

203214
const graphInput = screen.getByLabelText('Predefined graph');
204215
await act(async () => {
205216
await selectEvent.select(graphInput, 'Graph One', { container: document.body });
206217
});
207-
expect(autocompleterRequest).toHaveBeenCalledTimes(0);
218+
expect(contextAutocomplete).toHaveBeenCalledTimes(0);
208219
});
209220
});

0 commit comments

Comments
 (0)