Migrate from DatasourceAPI to DatasourceWithBackend (#2123)
This PR migrates the use of `DatasourceApi` to `DatasourceWithBackend`, with this a couple additional improvements were made: 1. Migrate to use `interpolateVariablesInQuery` everywhere instead of the custom `replaceTemplateVariables` we were using 2. Moves util functions out of `datasource.ts` and into the existing `utils.ts` <img width="1261" height="406" alt="Screenshot 2025-11-20 at 11 37 56 AM" src="https://github.com/user-attachments/assets/9e396cf2-eab0-49d1-958c-963a2e896eba" /> Now we can see the `query` calls being made to the backend: <img width="367" height="102" alt="Screenshot 2025-11-20 at 11 38 18 AM" src="https://github.com/user-attachments/assets/a5a9a337-7f19-4f7c-9d04-9d30c0216fb2" /> Tested: - By running queries from Explore and Dashboards (with and without variables) - By interacting with all the different Editors to make sure `ComboBox` was working as expected Next: Once this is merged, we will next be able to slowly move away from using the `ZabbixConnector` to make backend datasource calls. Fixes: [#131](https://github.com/orgs/grafana/projects/457/views/40?pane=issue&itemId=139450234&issue=grafana%7Coss-big-tent-squad%7C131)
This commit is contained in:
committed by
GitHub
parent
cc492b916d
commit
ce4a8d3e19
5
.changeset/tough-pugs-matter.md
Normal file
5
.changeset/tough-pugs-matter.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'grafana-zabbix': major
|
||||||
|
---
|
||||||
|
|
||||||
|
Migrates use of DatasourceApi to DatasourceWithBackend
|
||||||
@@ -9,6 +9,7 @@ import { QueryEditorRow } from './QueryEditor/QueryEditorRow';
|
|||||||
import { MetricPicker } from '../../components';
|
import { MetricPicker } from '../../components';
|
||||||
import { getVariableOptions } from './QueryEditor/utils';
|
import { getVariableOptions } from './QueryEditor/utils';
|
||||||
import { prepareAnnotation } from '../migrations';
|
import { prepareAnnotation } from '../migrations';
|
||||||
|
import { useInterpolatedQuery } from '../hooks/useInterpolatedQuery';
|
||||||
|
|
||||||
const severityOptions: Array<SelectableValue<number>> = [
|
const severityOptions: Array<SelectableValue<number>> = [
|
||||||
{ value: 0, label: 'Not classified' },
|
{ value: 0, label: 'Not classified' },
|
||||||
@@ -27,6 +28,7 @@ type Props = ZabbixQueryEditorProps & {
|
|||||||
export const AnnotationQueryEditor = ({ annotation, onAnnotationChange, datasource }: Props) => {
|
export const AnnotationQueryEditor = ({ annotation, onAnnotationChange, datasource }: Props) => {
|
||||||
annotation = prepareAnnotation(annotation);
|
annotation = prepareAnnotation(annotation);
|
||||||
const query = annotation.target;
|
const query = annotation.target;
|
||||||
|
const interpolatedQuery = useInterpolatedQuery(datasource, query);
|
||||||
|
|
||||||
const loadGroupOptions = async () => {
|
const loadGroupOptions = async () => {
|
||||||
const groups = await datasource.zabbix.getAllGroups();
|
const groups = await datasource.zabbix.getAllGroups();
|
||||||
@@ -44,8 +46,7 @@ export const AnnotationQueryEditor = ({ annotation, onAnnotationChange, datasour
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const loadHostOptions = async (group: string) => {
|
const loadHostOptions = async (group: string) => {
|
||||||
const groupFilter = datasource.replaceTemplateVars(group);
|
const hosts = await datasource.zabbix.getAllHosts(group);
|
||||||
const hosts = await datasource.zabbix.getAllHosts(groupFilter);
|
|
||||||
let options: Array<SelectableValue<string>> = hosts?.map((host) => ({
|
let options: Array<SelectableValue<string>> = hosts?.map((host) => ({
|
||||||
value: host.name,
|
value: host.name,
|
||||||
label: host.name,
|
label: host.name,
|
||||||
@@ -57,14 +58,12 @@ export const AnnotationQueryEditor = ({ annotation, onAnnotationChange, datasour
|
|||||||
};
|
};
|
||||||
|
|
||||||
const [{ loading: hostsLoading, value: hostOptions }, fetchHosts] = useAsyncFn(async () => {
|
const [{ loading: hostsLoading, value: hostOptions }, fetchHosts] = useAsyncFn(async () => {
|
||||||
const options = await loadHostOptions(query.group.filter);
|
const options = await loadHostOptions(interpolatedQuery.group.filter);
|
||||||
return options;
|
return options;
|
||||||
}, [query.group.filter]);
|
}, [interpolatedQuery.group.filter]);
|
||||||
|
|
||||||
const loadAppOptions = async (group: string, host: string) => {
|
const loadAppOptions = async (group: string, host: string) => {
|
||||||
const groupFilter = datasource.replaceTemplateVars(group);
|
const apps = await datasource.zabbix.getAllApps(group, host);
|
||||||
const hostFilter = datasource.replaceTemplateVars(host);
|
|
||||||
const apps = await datasource.zabbix.getAllApps(groupFilter, hostFilter);
|
|
||||||
let options: Array<SelectableValue<string>> = apps?.map((app) => ({
|
let options: Array<SelectableValue<string>> = apps?.map((app) => ({
|
||||||
value: app.name,
|
value: app.name,
|
||||||
label: app.name,
|
label: app.name,
|
||||||
@@ -75,13 +74,13 @@ export const AnnotationQueryEditor = ({ annotation, onAnnotationChange, datasour
|
|||||||
};
|
};
|
||||||
|
|
||||||
const [{ loading: appsLoading, value: appOptions }, fetchApps] = useAsyncFn(async () => {
|
const [{ loading: appsLoading, value: appOptions }, fetchApps] = useAsyncFn(async () => {
|
||||||
const options = await loadAppOptions(query.group.filter, query.host.filter);
|
const options = await loadAppOptions(interpolatedQuery.group.filter, interpolatedQuery.host.filter);
|
||||||
return options;
|
return options;
|
||||||
}, [query.group.filter, query.host.filter]);
|
}, [interpolatedQuery.group.filter, interpolatedQuery.host.filter]);
|
||||||
|
|
||||||
// Update suggestions on every metric change
|
// Update suggestions on every metric change
|
||||||
const groupFilter = datasource.replaceTemplateVars(query.group?.filter);
|
const groupFilter = interpolatedQuery.group?.filter;
|
||||||
const hostFilter = datasource.replaceTemplateVars(query.host?.filter);
|
const hostFilter = interpolatedQuery.host?.filter;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchGroups();
|
fetchGroups();
|
||||||
|
|||||||
@@ -7,7 +7,22 @@ jest.mock('@grafana/runtime', () => ({
|
|||||||
config: {},
|
config: {},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
jest.mock('@grafana/ui', () => ({
|
||||||
|
...jest.requireActual('@grafana/ui'),
|
||||||
|
config: {},
|
||||||
|
}));
|
||||||
|
|
||||||
describe('ConfigEditor', () => {
|
describe('ConfigEditor', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
Object.defineProperty(HTMLCanvasElement.prototype, 'getContext', {
|
||||||
|
value: () => ({
|
||||||
|
measureText: () => ({ width: 0 }),
|
||||||
|
font: '',
|
||||||
|
textAlign: '',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('on initial render', () => {
|
describe('on initial render', () => {
|
||||||
it('should not mutate the options object', () => {
|
it('should not mutate the options object', () => {
|
||||||
const options = Object.freeze({ ...getDefaultOptions() }); // freezing the options to prevent mutations
|
const options = Object.freeze({ ...getDefaultOptions() }); // freezing the options to prevent mutations
|
||||||
@@ -27,7 +42,7 @@ describe('ConfigEditor', () => {
|
|||||||
const onOptionsChangeSpy = jest.fn();
|
const onOptionsChangeSpy = jest.fn();
|
||||||
|
|
||||||
expect(() => render(<ConfigEditor options={options} onOptionsChange={onOptionsChangeSpy} />)).not.toThrow();
|
expect(() => render(<ConfigEditor options={options} onOptionsChange={onOptionsChangeSpy} />)).not.toThrow();
|
||||||
expect(onOptionsChangeSpy).toBeCalledTimes(1);
|
expect(onOptionsChangeSpy).toHaveBeenCalledTimes(1);
|
||||||
expect(onOptionsChangeSpy).toHaveBeenCalledWith({
|
expect(onOptionsChangeSpy).toHaveBeenCalledWith({
|
||||||
...getDefaultOptions(),
|
...getDefaultOptions(),
|
||||||
jsonData: {
|
jsonData: {
|
||||||
@@ -51,7 +66,7 @@ describe('ConfigEditor', () => {
|
|||||||
const onOptionsChangeSpy = jest.fn();
|
const onOptionsChangeSpy = jest.fn();
|
||||||
|
|
||||||
expect(() => render(<ConfigEditor options={options} onOptionsChange={onOptionsChangeSpy} />)).not.toThrow();
|
expect(() => render(<ConfigEditor options={options} onOptionsChange={onOptionsChangeSpy} />)).not.toThrow();
|
||||||
expect(onOptionsChangeSpy).toBeCalledTimes(1);
|
expect(onOptionsChangeSpy).toHaveBeenCalledTimes(1);
|
||||||
expect(onOptionsChangeSpy).toHaveBeenCalledWith({
|
expect(onOptionsChangeSpy).toHaveBeenCalledWith({
|
||||||
...getDefaultOptions(),
|
...getDefaultOptions(),
|
||||||
jsonData: {
|
jsonData: {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { ZabbixDatasource } from '../../datasource';
|
|||||||
import { ZabbixMetricsQuery } from '../../types/query';
|
import { ZabbixMetricsQuery } from '../../types/query';
|
||||||
import { ZBXItem, ZBXItemTag } from '../../types';
|
import { ZBXItem, ZBXItemTag } from '../../types';
|
||||||
import { itemTagToString } from '../../utils';
|
import { itemTagToString } from '../../utils';
|
||||||
|
import { useInterpolatedQuery } from '../../hooks/useInterpolatedQuery';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
query: ZabbixMetricsQuery;
|
query: ZabbixMetricsQuery;
|
||||||
@@ -19,6 +20,8 @@ export interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const MetricsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
export const MetricsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||||
|
const interpolatedQuery = useInterpolatedQuery(datasource, query);
|
||||||
|
|
||||||
const loadGroupOptions = async () => {
|
const loadGroupOptions = async () => {
|
||||||
const groups = await datasource.zabbix.getAllGroups();
|
const groups = await datasource.zabbix.getAllGroups();
|
||||||
const options = groups?.map((group) => ({
|
const options = groups?.map((group) => ({
|
||||||
@@ -35,8 +38,7 @@ export const MetricsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const loadHostOptions = async (group: string) => {
|
const loadHostOptions = async (group: string) => {
|
||||||
const groupFilter = datasource.replaceTemplateVars(group);
|
const hosts = await datasource.zabbix.getAllHosts(group);
|
||||||
const hosts = await datasource.zabbix.getAllHosts(groupFilter);
|
|
||||||
let options: Array<SelectableValue<string>> = hosts?.map((host) => ({
|
let options: Array<SelectableValue<string>> = hosts?.map((host) => ({
|
||||||
value: host.name,
|
value: host.name,
|
||||||
label: host.name,
|
label: host.name,
|
||||||
@@ -48,14 +50,12 @@ export const MetricsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const [{ loading: hostsLoading, value: hostOptions }, fetchHosts] = useAsyncFn(async () => {
|
const [{ loading: hostsLoading, value: hostOptions }, fetchHosts] = useAsyncFn(async () => {
|
||||||
const options = await loadHostOptions(query.group.filter);
|
const options = await loadHostOptions(interpolatedQuery.group.filter);
|
||||||
return options;
|
return options;
|
||||||
}, [query.group.filter]);
|
}, [interpolatedQuery.group.filter]);
|
||||||
|
|
||||||
const loadAppOptions = async (group: string, host: string) => {
|
const loadAppOptions = async (group: string, host: string) => {
|
||||||
const groupFilter = datasource.replaceTemplateVars(group);
|
const apps = await datasource.zabbix.getAllApps(group, host);
|
||||||
const hostFilter = datasource.replaceTemplateVars(host);
|
|
||||||
const apps = await datasource.zabbix.getAllApps(groupFilter, hostFilter);
|
|
||||||
let options: Array<SelectableValue<string>> = apps?.map((app) => ({
|
let options: Array<SelectableValue<string>> = apps?.map((app) => ({
|
||||||
value: app.name,
|
value: app.name,
|
||||||
label: app.name,
|
label: app.name,
|
||||||
@@ -66,9 +66,9 @@ export const MetricsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const [{ loading: appsLoading, value: appOptions }, fetchApps] = useAsyncFn(async () => {
|
const [{ loading: appsLoading, value: appOptions }, fetchApps] = useAsyncFn(async () => {
|
||||||
const options = await loadAppOptions(query.group.filter, query.host.filter);
|
const options = await loadAppOptions(interpolatedQuery.group.filter, interpolatedQuery.host.filter);
|
||||||
return options;
|
return options;
|
||||||
}, [query.group.filter, query.host.filter]);
|
}, [interpolatedQuery.group.filter, interpolatedQuery.host.filter]);
|
||||||
|
|
||||||
const loadTagOptions = async (group: string, host: string) => {
|
const loadTagOptions = async (group: string, host: string) => {
|
||||||
const tagsAvailable = await datasource.zabbix.isZabbix54OrHigher();
|
const tagsAvailable = await datasource.zabbix.isZabbix54OrHigher();
|
||||||
@@ -76,9 +76,7 @@ export const MetricsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const groupFilter = datasource.replaceTemplateVars(group);
|
const items = await datasource.zabbix.getAllItems(group, host, null, null, {});
|
||||||
const hostFilter = datasource.replaceTemplateVars(host);
|
|
||||||
const items = await datasource.zabbix.getAllItems(groupFilter, hostFilter, null, null, {});
|
|
||||||
const tags: ZBXItemTag[] = _.flatten(items.map((item: ZBXItem) => item.tags || []));
|
const tags: ZBXItemTag[] = _.flatten(items.map((item: ZBXItem) => item.tags || []));
|
||||||
// const tags: ZBXItemTag[] = await datasource.zabbix.getItemTags(groupFilter, hostFilter, null);
|
// const tags: ZBXItemTag[] = await datasource.zabbix.getItemTags(groupFilter, hostFilter, null);
|
||||||
|
|
||||||
@@ -93,20 +91,16 @@ export const MetricsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const [{ loading: tagsLoading, value: tagOptions }, fetchTags] = useAsyncFn(async () => {
|
const [{ loading: tagsLoading, value: tagOptions }, fetchTags] = useAsyncFn(async () => {
|
||||||
const options = await loadTagOptions(query.group.filter, query.host.filter);
|
const options = await loadTagOptions(interpolatedQuery.group.filter, interpolatedQuery.host.filter);
|
||||||
return options;
|
return options;
|
||||||
}, [query.group.filter, query.host.filter]);
|
}, [interpolatedQuery.group.filter, interpolatedQuery.host.filter]);
|
||||||
|
|
||||||
const loadItemOptions = async (group: string, host: string, app: string, itemTag: string) => {
|
const loadItemOptions = async (group: string, host: string, app: string, itemTag: string) => {
|
||||||
const groupFilter = datasource.replaceTemplateVars(group);
|
|
||||||
const hostFilter = datasource.replaceTemplateVars(host);
|
|
||||||
const appFilter = datasource.replaceTemplateVars(app);
|
|
||||||
const tagFilter = datasource.replaceTemplateVars(itemTag);
|
|
||||||
const options = {
|
const options = {
|
||||||
itemtype: 'num',
|
itemtype: 'num',
|
||||||
showDisabledItems: query.options.showDisabledItems,
|
showDisabledItems: query.options.showDisabledItems,
|
||||||
};
|
};
|
||||||
const items = await datasource.zabbix.getAllItems(groupFilter, hostFilter, appFilter, tagFilter, options);
|
const items = await datasource.zabbix.getAllItems(group, host, app, itemTag, options);
|
||||||
let itemOptions: Array<SelectableValue<string>> = items?.map((item) => ({
|
let itemOptions: Array<SelectableValue<string>> = items?.map((item) => ({
|
||||||
value: item.name,
|
value: item.name,
|
||||||
label: item.name,
|
label: item.name,
|
||||||
@@ -118,19 +112,24 @@ export const MetricsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
|
|
||||||
const [{ loading: itemsLoading, value: itemOptions }, fetchItems] = useAsyncFn(async () => {
|
const [{ loading: itemsLoading, value: itemOptions }, fetchItems] = useAsyncFn(async () => {
|
||||||
const options = await loadItemOptions(
|
const options = await loadItemOptions(
|
||||||
query.group.filter,
|
interpolatedQuery.group.filter,
|
||||||
query.host.filter,
|
interpolatedQuery.host.filter,
|
||||||
query.application.filter,
|
interpolatedQuery.application.filter,
|
||||||
query.itemTag.filter
|
interpolatedQuery.itemTag.filter
|
||||||
);
|
);
|
||||||
return options;
|
return options;
|
||||||
}, [query.group.filter, query.host.filter, query.application.filter, query.itemTag.filter]);
|
}, [
|
||||||
|
interpolatedQuery.group.filter,
|
||||||
|
interpolatedQuery.host.filter,
|
||||||
|
interpolatedQuery.application.filter,
|
||||||
|
interpolatedQuery.itemTag.filter,
|
||||||
|
]);
|
||||||
|
|
||||||
// Update suggestions on every metric change
|
// Update suggestions on every metric change
|
||||||
const groupFilter = datasource.replaceTemplateVars(query.group?.filter);
|
const groupFilter = interpolatedQuery.group?.filter;
|
||||||
const hostFilter = datasource.replaceTemplateVars(query.host?.filter);
|
const hostFilter = interpolatedQuery.host?.filter;
|
||||||
const appFilter = datasource.replaceTemplateVars(query.application?.filter);
|
const appFilter = interpolatedQuery.application?.filter;
|
||||||
const tagFilter = datasource.replaceTemplateVars(query.itemTag?.filter);
|
const tagFilter = interpolatedQuery.itemTag?.filter;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchGroups();
|
fetchGroups();
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { MetricPicker } from '../../../components';
|
|||||||
import { getVariableOptions } from './utils';
|
import { getVariableOptions } from './utils';
|
||||||
import { ZabbixDatasource } from '../../datasource';
|
import { ZabbixDatasource } from '../../datasource';
|
||||||
import { ZabbixMetricsQuery, ZabbixTagEvalType } from '../../types/query';
|
import { ZabbixMetricsQuery, ZabbixTagEvalType } from '../../types/query';
|
||||||
|
import { useInterpolatedQuery } from '../../hooks/useInterpolatedQuery';
|
||||||
|
|
||||||
const showProblemsOptions: Array<SelectableValue<string>> = [
|
const showProblemsOptions: Array<SelectableValue<string>> = [
|
||||||
{ label: 'Problems', value: 'problems' },
|
{ label: 'Problems', value: 'problems' },
|
||||||
@@ -37,6 +38,8 @@ export interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ProblemsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
export const ProblemsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||||
|
const interpolatedQuery = useInterpolatedQuery(datasource, query);
|
||||||
|
|
||||||
const loadGroupOptions = async () => {
|
const loadGroupOptions = async () => {
|
||||||
const groups = await datasource.zabbix.getAllGroups();
|
const groups = await datasource.zabbix.getAllGroups();
|
||||||
const options = groups?.map((group) => ({
|
const options = groups?.map((group) => ({
|
||||||
@@ -53,8 +56,7 @@ export const ProblemsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const loadHostOptions = async (group: string) => {
|
const loadHostOptions = async (group: string) => {
|
||||||
const groupFilter = datasource.replaceTemplateVars(group);
|
const hosts = await datasource.zabbix.getAllHosts(group);
|
||||||
const hosts = await datasource.zabbix.getAllHosts(groupFilter);
|
|
||||||
let options: Array<SelectableValue<string>> = hosts?.map((host) => ({
|
let options: Array<SelectableValue<string>> = hosts?.map((host) => ({
|
||||||
value: host.name,
|
value: host.name,
|
||||||
label: host.name,
|
label: host.name,
|
||||||
@@ -66,14 +68,12 @@ export const ProblemsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const [{ loading: hostsLoading, value: hostOptions }, fetchHosts] = useAsyncFn(async () => {
|
const [{ loading: hostsLoading, value: hostOptions }, fetchHosts] = useAsyncFn(async () => {
|
||||||
const options = await loadHostOptions(query.group.filter);
|
const options = await loadHostOptions(interpolatedQuery.group.filter);
|
||||||
return options;
|
return options;
|
||||||
}, [query.group.filter]);
|
}, [interpolatedQuery.group.filter]);
|
||||||
|
|
||||||
const loadAppOptions = async (group: string, host: string) => {
|
const loadAppOptions = async (group: string, host: string) => {
|
||||||
const groupFilter = datasource.replaceTemplateVars(group);
|
const apps = await datasource.zabbix.getAllApps(group, host);
|
||||||
const hostFilter = datasource.replaceTemplateVars(host);
|
|
||||||
const apps = await datasource.zabbix.getAllApps(groupFilter, hostFilter);
|
|
||||||
let options: Array<SelectableValue<string>> = apps?.map((app) => ({
|
let options: Array<SelectableValue<string>> = apps?.map((app) => ({
|
||||||
value: app.name,
|
value: app.name,
|
||||||
label: app.name,
|
label: app.name,
|
||||||
@@ -84,9 +84,9 @@ export const ProblemsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const [{ loading: appsLoading, value: appOptions }, fetchApps] = useAsyncFn(async () => {
|
const [{ loading: appsLoading, value: appOptions }, fetchApps] = useAsyncFn(async () => {
|
||||||
const options = await loadAppOptions(query.group.filter, query.host.filter);
|
const options = await loadAppOptions(interpolatedQuery.group.filter, interpolatedQuery.host.filter);
|
||||||
return options;
|
return options;
|
||||||
}, [query.group.filter, query.host.filter]);
|
}, [interpolatedQuery.group.filter, interpolatedQuery.host.filter]);
|
||||||
|
|
||||||
const loadProxyOptions = async () => {
|
const loadProxyOptions = async () => {
|
||||||
const proxies = await datasource.zabbix.getProxies();
|
const proxies = await datasource.zabbix.getProxies();
|
||||||
@@ -104,8 +104,8 @@ export const ProblemsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Update suggestions on every metric change
|
// Update suggestions on every metric change
|
||||||
const groupFilter = datasource.replaceTemplateVars(query.group?.filter);
|
const groupFilter = interpolatedQuery.group?.filter;
|
||||||
const hostFilter = datasource.replaceTemplateVars(query.host?.filter);
|
const hostFilter = interpolatedQuery.host?.filter;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchGroups();
|
fetchGroups();
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { MetricPicker } from '../../../components';
|
|||||||
import { getVariableOptions } from './utils';
|
import { getVariableOptions } from './utils';
|
||||||
import { ZabbixDatasource } from '../../datasource';
|
import { ZabbixDatasource } from '../../datasource';
|
||||||
import { ZabbixMetricsQuery } from '../../types/query';
|
import { ZabbixMetricsQuery } from '../../types/query';
|
||||||
|
import { useInterpolatedQuery } from '../../hooks/useInterpolatedQuery';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
query: ZabbixMetricsQuery;
|
query: ZabbixMetricsQuery;
|
||||||
@@ -17,6 +18,8 @@ export interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const TextMetricsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
export const TextMetricsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||||
|
const interpolatedQuery = useInterpolatedQuery(datasource, query);
|
||||||
|
|
||||||
const loadGroupOptions = async () => {
|
const loadGroupOptions = async () => {
|
||||||
const groups = await datasource.zabbix.getAllGroups();
|
const groups = await datasource.zabbix.getAllGroups();
|
||||||
const options = groups?.map((group) => ({
|
const options = groups?.map((group) => ({
|
||||||
@@ -33,8 +36,7 @@ export const TextMetricsQueryEditor = ({ query, datasource, onChange }: Props) =
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const loadHostOptions = async (group: string) => {
|
const loadHostOptions = async (group: string) => {
|
||||||
const groupFilter = datasource.replaceTemplateVars(group);
|
const hosts = await datasource.zabbix.getAllHosts(group);
|
||||||
const hosts = await datasource.zabbix.getAllHosts(groupFilter);
|
|
||||||
let options: Array<SelectableValue<string>> = hosts?.map((host) => ({
|
let options: Array<SelectableValue<string>> = hosts?.map((host) => ({
|
||||||
value: host.name,
|
value: host.name,
|
||||||
label: host.name,
|
label: host.name,
|
||||||
@@ -46,14 +48,12 @@ export const TextMetricsQueryEditor = ({ query, datasource, onChange }: Props) =
|
|||||||
};
|
};
|
||||||
|
|
||||||
const [{ loading: hostsLoading, value: hostOptions }, fetchHosts] = useAsyncFn(async () => {
|
const [{ loading: hostsLoading, value: hostOptions }, fetchHosts] = useAsyncFn(async () => {
|
||||||
const options = await loadHostOptions(query.group.filter);
|
const options = await loadHostOptions(interpolatedQuery.group.filter);
|
||||||
return options;
|
return options;
|
||||||
}, [query.group.filter]);
|
}, [interpolatedQuery.group.filter]);
|
||||||
|
|
||||||
const loadAppOptions = async (group: string, host: string) => {
|
const loadAppOptions = async (group: string, host: string) => {
|
||||||
const groupFilter = datasource.replaceTemplateVars(group);
|
const apps = await datasource.zabbix.getAllApps(group, host);
|
||||||
const hostFilter = datasource.replaceTemplateVars(host);
|
|
||||||
const apps = await datasource.zabbix.getAllApps(groupFilter, hostFilter);
|
|
||||||
let options: Array<SelectableValue<string>> = apps?.map((app) => ({
|
let options: Array<SelectableValue<string>> = apps?.map((app) => ({
|
||||||
value: app.name,
|
value: app.name,
|
||||||
label: app.name,
|
label: app.name,
|
||||||
@@ -64,20 +64,16 @@ export const TextMetricsQueryEditor = ({ query, datasource, onChange }: Props) =
|
|||||||
};
|
};
|
||||||
|
|
||||||
const [{ loading: appsLoading, value: appOptions }, fetchApps] = useAsyncFn(async () => {
|
const [{ loading: appsLoading, value: appOptions }, fetchApps] = useAsyncFn(async () => {
|
||||||
const options = await loadAppOptions(query.group.filter, query.host.filter);
|
const options = await loadAppOptions(interpolatedQuery.group.filter, interpolatedQuery.host.filter);
|
||||||
return options;
|
return options;
|
||||||
}, [query.group.filter, query.host.filter]);
|
}, [interpolatedQuery.group.filter, interpolatedQuery.host.filter]);
|
||||||
|
|
||||||
const loadItemOptions = async (group: string, host: string, app: string, itemTag: string) => {
|
const loadItemOptions = async (group: string, host: string, app: string, itemTag: string) => {
|
||||||
const groupFilter = datasource.replaceTemplateVars(group);
|
|
||||||
const hostFilter = datasource.replaceTemplateVars(host);
|
|
||||||
const appFilter = datasource.replaceTemplateVars(app);
|
|
||||||
const tagFilter = datasource.replaceTemplateVars(itemTag);
|
|
||||||
const options = {
|
const options = {
|
||||||
itemtype: 'text',
|
itemtype: 'text',
|
||||||
showDisabledItems: query.options.showDisabledItems,
|
showDisabledItems: query.options.showDisabledItems,
|
||||||
};
|
};
|
||||||
const items = await datasource.zabbix.getAllItems(groupFilter, hostFilter, appFilter, tagFilter, options);
|
const items = await datasource.zabbix.getAllItems(group, host, app, itemTag, options);
|
||||||
let itemOptions: Array<SelectableValue<string>> = items?.map((item) => ({
|
let itemOptions: Array<SelectableValue<string>> = items?.map((item) => ({
|
||||||
value: item.name,
|
value: item.name,
|
||||||
label: item.name,
|
label: item.name,
|
||||||
@@ -89,19 +85,24 @@ export const TextMetricsQueryEditor = ({ query, datasource, onChange }: Props) =
|
|||||||
|
|
||||||
const [{ loading: itemsLoading, value: itemOptions }, fetchItems] = useAsyncFn(async () => {
|
const [{ loading: itemsLoading, value: itemOptions }, fetchItems] = useAsyncFn(async () => {
|
||||||
const options = await loadItemOptions(
|
const options = await loadItemOptions(
|
||||||
query.group.filter,
|
interpolatedQuery.group.filter,
|
||||||
query.host.filter,
|
interpolatedQuery.host.filter,
|
||||||
query.application.filter,
|
interpolatedQuery.application.filter,
|
||||||
query.itemTag.filter
|
interpolatedQuery.itemTag.filter
|
||||||
);
|
);
|
||||||
return options;
|
return options;
|
||||||
}, [query.group.filter, query.host.filter, query.application.filter, query.itemTag.filter]);
|
}, [
|
||||||
|
interpolatedQuery.group.filter,
|
||||||
|
interpolatedQuery.host.filter,
|
||||||
|
interpolatedQuery.application.filter,
|
||||||
|
interpolatedQuery.itemTag.filter,
|
||||||
|
]);
|
||||||
|
|
||||||
// Update suggestions on every metric change
|
// Update suggestions on every metric change
|
||||||
const groupFilter = datasource.replaceTemplateVars(query.group?.filter);
|
const groupFilter = interpolatedQuery.group?.filter;
|
||||||
const hostFilter = datasource.replaceTemplateVars(query.host?.filter);
|
const hostFilter = interpolatedQuery.host?.filter;
|
||||||
const appFilter = datasource.replaceTemplateVars(query.application?.filter);
|
const appFilter = interpolatedQuery.application?.filter;
|
||||||
const tagFilter = datasource.replaceTemplateVars(query.itemTag?.filter);
|
const tagFilter = interpolatedQuery.itemTag?.filter;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchGroups();
|
fetchGroups();
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { itemTagToString } from '../../utils';
|
|||||||
import { ZabbixDatasource } from '../../datasource';
|
import { ZabbixDatasource } from '../../datasource';
|
||||||
import { ZabbixMetricsQuery } from '../../types/query';
|
import { ZabbixMetricsQuery } from '../../types/query';
|
||||||
import { ZBXItem, ZBXItemTag } from '../../types';
|
import { ZBXItem, ZBXItemTag } from '../../types';
|
||||||
|
import { useInterpolatedQuery } from '../../hooks/useInterpolatedQuery';
|
||||||
|
|
||||||
const countByOptions: Array<SelectableValue<string>> = [
|
const countByOptions: Array<SelectableValue<string>> = [
|
||||||
{ value: '', label: 'All triggers' },
|
{ value: '', label: 'All triggers' },
|
||||||
@@ -34,6 +35,8 @@ export interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const TriggersQueryEditor = ({ query, datasource, onChange }: Props) => {
|
export const TriggersQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||||
|
const interpolatedQuery = useInterpolatedQuery(datasource, query);
|
||||||
|
|
||||||
const loadGroupOptions = async () => {
|
const loadGroupOptions = async () => {
|
||||||
const groups = await datasource.zabbix.getAllGroups();
|
const groups = await datasource.zabbix.getAllGroups();
|
||||||
const options = groups?.map((group) => ({
|
const options = groups?.map((group) => ({
|
||||||
@@ -50,8 +53,7 @@ export const TriggersQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const loadHostOptions = async (group: string) => {
|
const loadHostOptions = async (group: string) => {
|
||||||
const groupFilter = datasource.replaceTemplateVars(group);
|
const hosts = await datasource.zabbix.getAllHosts(group);
|
||||||
const hosts = await datasource.zabbix.getAllHosts(groupFilter);
|
|
||||||
let options: Array<SelectableValue<string>> = hosts?.map((host) => ({
|
let options: Array<SelectableValue<string>> = hosts?.map((host) => ({
|
||||||
value: host.name,
|
value: host.name,
|
||||||
label: host.name,
|
label: host.name,
|
||||||
@@ -63,14 +65,12 @@ export const TriggersQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const [{ loading: hostsLoading, value: hostOptions }, fetchHosts] = useAsyncFn(async () => {
|
const [{ loading: hostsLoading, value: hostOptions }, fetchHosts] = useAsyncFn(async () => {
|
||||||
const options = await loadHostOptions(query.group.filter);
|
const options = await loadHostOptions(interpolatedQuery.group.filter);
|
||||||
return options;
|
return options;
|
||||||
}, [query.group.filter]);
|
}, [interpolatedQuery.group.filter]);
|
||||||
|
|
||||||
const loadAppOptions = async (group: string, host: string) => {
|
const loadAppOptions = async (group: string, host: string) => {
|
||||||
const groupFilter = datasource.replaceTemplateVars(group);
|
const apps = await datasource.zabbix.getAllApps(group, host);
|
||||||
const hostFilter = datasource.replaceTemplateVars(host);
|
|
||||||
const apps = await datasource.zabbix.getAllApps(groupFilter, hostFilter);
|
|
||||||
let options: Array<SelectableValue<string>> = apps?.map((app) => ({
|
let options: Array<SelectableValue<string>> = apps?.map((app) => ({
|
||||||
value: app.name,
|
value: app.name,
|
||||||
label: app.name,
|
label: app.name,
|
||||||
@@ -81,19 +81,16 @@ export const TriggersQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const [{ loading: appsLoading, value: appOptions }, fetchApps] = useAsyncFn(async () => {
|
const [{ loading: appsLoading, value: appOptions }, fetchApps] = useAsyncFn(async () => {
|
||||||
const options = await loadAppOptions(query.group.filter, query.host.filter);
|
const options = await loadAppOptions(interpolatedQuery.group.filter, interpolatedQuery.host.filter);
|
||||||
return options;
|
return options;
|
||||||
}, [query.group.filter, query.host.filter]);
|
}, [interpolatedQuery.group.filter, interpolatedQuery.host.filter]);
|
||||||
|
|
||||||
const loadTagOptions = async (group: string, host: string) => {
|
const loadTagOptions = async (group: string, host: string) => {
|
||||||
const tagsAvailable = await datasource.zabbix.isZabbix54OrHigher();
|
const tagsAvailable = await datasource.zabbix.isZabbix54OrHigher();
|
||||||
if (!tagsAvailable) {
|
if (!tagsAvailable) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
const items = await datasource.zabbix.getAllItems(group, host, null, null, {});
|
||||||
const groupFilter = datasource.replaceTemplateVars(group);
|
|
||||||
const hostFilter = datasource.replaceTemplateVars(host);
|
|
||||||
const items = await datasource.zabbix.getAllItems(groupFilter, hostFilter, null, null, {});
|
|
||||||
const tags: ZBXItemTag[] = _.flatten(items.map((item: ZBXItem) => item.tags || []));
|
const tags: ZBXItemTag[] = _.flatten(items.map((item: ZBXItem) => item.tags || []));
|
||||||
|
|
||||||
const tagList = _.uniqBy(tags, (t) => t.tag + t.value || '').map((t) => itemTagToString(t));
|
const tagList = _.uniqBy(tags, (t) => t.tag + t.value || '').map((t) => itemTagToString(t));
|
||||||
@@ -107,9 +104,9 @@ export const TriggersQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const [{ loading: tagsLoading, value: tagOptions }, fetchItemTags] = useAsyncFn(async () => {
|
const [{ loading: tagsLoading, value: tagOptions }, fetchItemTags] = useAsyncFn(async () => {
|
||||||
const options = await loadTagOptions(query.group.filter, query.host.filter);
|
const options = await loadTagOptions(interpolatedQuery.group.filter, interpolatedQuery.host.filter);
|
||||||
return options;
|
return options;
|
||||||
}, [query.group.filter, query.host.filter]);
|
}, [interpolatedQuery.group.filter, interpolatedQuery.host.filter]);
|
||||||
|
|
||||||
const loadProxyOptions = async () => {
|
const loadProxyOptions = async () => {
|
||||||
const proxies = await datasource.zabbix.getProxies();
|
const proxies = await datasource.zabbix.getProxies();
|
||||||
@@ -127,15 +124,11 @@ export const TriggersQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const loadItemOptions = async (group: string, host: string, app: string, itemTag: string) => {
|
const loadItemOptions = async (group: string, host: string, app: string, itemTag: string) => {
|
||||||
const groupFilter = datasource.replaceTemplateVars(group);
|
|
||||||
const hostFilter = datasource.replaceTemplateVars(host);
|
|
||||||
const appFilter = datasource.replaceTemplateVars(app);
|
|
||||||
const tagFilter = datasource.replaceTemplateVars(itemTag);
|
|
||||||
const options = {
|
const options = {
|
||||||
itemtype: 'num',
|
itemtype: 'num',
|
||||||
showDisabledItems: query.options.showDisabledItems,
|
showDisabledItems: query.options.showDisabledItems,
|
||||||
};
|
};
|
||||||
const items = await datasource.zabbix.getAllItems(groupFilter, hostFilter, appFilter, tagFilter, options);
|
const items = await datasource.zabbix.getAllItems(group, host, app, itemTag, options);
|
||||||
let itemOptions: Array<SelectableValue<string>> = items?.map((item) => ({
|
let itemOptions: Array<SelectableValue<string>> = items?.map((item) => ({
|
||||||
value: item.name,
|
value: item.name,
|
||||||
label: item.name,
|
label: item.name,
|
||||||
@@ -147,19 +140,24 @@ export const TriggersQueryEditor = ({ query, datasource, onChange }: Props) => {
|
|||||||
|
|
||||||
const [{ loading: itemsLoading, value: itemOptions }, fetchItems] = useAsyncFn(async () => {
|
const [{ loading: itemsLoading, value: itemOptions }, fetchItems] = useAsyncFn(async () => {
|
||||||
const options = await loadItemOptions(
|
const options = await loadItemOptions(
|
||||||
query.group.filter,
|
interpolatedQuery.group.filter,
|
||||||
query.host.filter,
|
interpolatedQuery.host.filter,
|
||||||
query.application.filter,
|
interpolatedQuery.application.filter,
|
||||||
query.itemTag.filter
|
interpolatedQuery.itemTag.filter
|
||||||
);
|
);
|
||||||
return options;
|
return options;
|
||||||
}, [query.group.filter, query.host.filter, query.application.filter, query.itemTag.filter]);
|
}, [
|
||||||
|
interpolatedQuery.group.filter,
|
||||||
|
interpolatedQuery.host.filter,
|
||||||
|
interpolatedQuery.application.filter,
|
||||||
|
interpolatedQuery.itemTag.filter,
|
||||||
|
]);
|
||||||
|
|
||||||
// Update suggestions on every metric change
|
// Update suggestions on every metric change
|
||||||
const groupFilter = datasource.replaceTemplateVars(query.group?.filter);
|
const groupFilter = interpolatedQuery.group?.filter;
|
||||||
const hostFilter = datasource.replaceTemplateVars(query.host?.filter);
|
const hostFilter = interpolatedQuery.host?.filter;
|
||||||
const appFilter = datasource.replaceTemplateVars(query.application?.filter);
|
const appFilter = interpolatedQuery.application?.filter;
|
||||||
const tagFilter = datasource.replaceTemplateVars(query.itemTag?.filter);
|
const tagFilter = interpolatedQuery.itemTag?.filter;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchGroups();
|
fetchGroups();
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { MetricPicker } from '../../../components';
|
|||||||
import { getVariableOptions } from './utils';
|
import { getVariableOptions } from './utils';
|
||||||
import { ZabbixDatasource } from '../../datasource';
|
import { ZabbixDatasource } from '../../datasource';
|
||||||
import { ZabbixMetricsQuery } from '../../types/query';
|
import { ZabbixMetricsQuery } from '../../types/query';
|
||||||
|
import { useInterpolatedQuery } from '../../hooks/useInterpolatedQuery';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
query: ZabbixMetricsQuery;
|
query: ZabbixMetricsQuery;
|
||||||
@@ -17,6 +18,7 @@ export interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const UserMacrosQueryEditor = ({ query, datasource, onChange }: Props) => {
|
export const UserMacrosQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||||
|
const interpolatedQuery = useInterpolatedQuery(datasource, query);
|
||||||
const loadGroupOptions = async () => {
|
const loadGroupOptions = async () => {
|
||||||
const groups = await datasource.zabbix.getAllGroups();
|
const groups = await datasource.zabbix.getAllGroups();
|
||||||
const options = groups?.map((group) => ({
|
const options = groups?.map((group) => ({
|
||||||
@@ -33,8 +35,7 @@ export const UserMacrosQueryEditor = ({ query, datasource, onChange }: Props) =>
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const loadHostOptions = async (group: string) => {
|
const loadHostOptions = async (group: string) => {
|
||||||
const groupFilter = datasource.replaceTemplateVars(group);
|
const hosts = await datasource.zabbix.getAllHosts(group);
|
||||||
const hosts = await datasource.zabbix.getAllHosts(groupFilter);
|
|
||||||
let options: Array<SelectableValue<string>> = hosts?.map((host) => ({
|
let options: Array<SelectableValue<string>> = hosts?.map((host) => ({
|
||||||
value: host.name,
|
value: host.name,
|
||||||
label: host.name,
|
label: host.name,
|
||||||
@@ -46,14 +47,12 @@ export const UserMacrosQueryEditor = ({ query, datasource, onChange }: Props) =>
|
|||||||
};
|
};
|
||||||
|
|
||||||
const [{ loading: hostsLoading, value: hostOptions }, fetchHosts] = useAsyncFn(async () => {
|
const [{ loading: hostsLoading, value: hostOptions }, fetchHosts] = useAsyncFn(async () => {
|
||||||
const options = await loadHostOptions(query.group.filter);
|
const options = await loadHostOptions(interpolatedQuery.group.filter);
|
||||||
return options;
|
return options;
|
||||||
}, [query.group.filter]);
|
}, [interpolatedQuery.group.filter]);
|
||||||
|
|
||||||
const loadMacrosOptions = async (group: string, host: string) => {
|
const loadMacrosOptions = async (group: string, host: string) => {
|
||||||
const groupFilter = datasource.replaceTemplateVars(group);
|
const macros = await datasource.zabbix.getAllMacros(group, host);
|
||||||
const hostFilter = datasource.replaceTemplateVars(host);
|
|
||||||
const macros = await datasource.zabbix.getAllMacros(groupFilter, hostFilter);
|
|
||||||
let options: Array<SelectableValue<string>> = macros?.map((m) => ({
|
let options: Array<SelectableValue<string>> = macros?.map((m) => ({
|
||||||
value: m.macro,
|
value: m.macro,
|
||||||
label: m.macro,
|
label: m.macro,
|
||||||
@@ -65,13 +64,13 @@ export const UserMacrosQueryEditor = ({ query, datasource, onChange }: Props) =>
|
|||||||
};
|
};
|
||||||
|
|
||||||
const [{ loading: macrosLoading, value: macrosOptions }, fetchmacros] = useAsyncFn(async () => {
|
const [{ loading: macrosLoading, value: macrosOptions }, fetchmacros] = useAsyncFn(async () => {
|
||||||
const options = await loadMacrosOptions(query.group.filter, query.host.filter);
|
const options = await loadMacrosOptions(interpolatedQuery.group.filter, interpolatedQuery.host.filter);
|
||||||
return options;
|
return options;
|
||||||
}, [query.group.filter, query.host.filter]);
|
}, [interpolatedQuery.group.filter, interpolatedQuery.host.filter]);
|
||||||
|
|
||||||
// Update suggestions on every metric change
|
// Update suggestions on every metric change
|
||||||
const groupFilter = datasource.replaceTemplateVars(query.group?.filter);
|
const groupFilter = interpolatedQuery.group?.filter;
|
||||||
const hostFilter = datasource.replaceTemplateVars(query.host?.filter);
|
const hostFilter = interpolatedQuery.host?.filter;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchGroups();
|
fetchGroups();
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import * as utils from './utils';
|
|||||||
import * as migrations from './migrations';
|
import * as migrations from './migrations';
|
||||||
import * as metricFunctions from './metricFunctions';
|
import * as metricFunctions from './metricFunctions';
|
||||||
import * as c from './constants';
|
import * as c from './constants';
|
||||||
import dataProcessor from './dataProcessor';
|
|
||||||
import responseHandler from './responseHandler';
|
import responseHandler from './responseHandler';
|
||||||
import problemsHandler from './problemsHandler';
|
import problemsHandler from './problemsHandler';
|
||||||
import { Zabbix } from './zabbix/zabbix';
|
import { Zabbix } from './zabbix/zabbix';
|
||||||
@@ -18,28 +17,27 @@ import {
|
|||||||
BackendSrvRequest,
|
BackendSrvRequest,
|
||||||
getBackendSrv,
|
getBackendSrv,
|
||||||
getTemplateSrv,
|
getTemplateSrv,
|
||||||
toDataQueryResponse,
|
|
||||||
getDataSourceSrv,
|
getDataSourceSrv,
|
||||||
HealthCheckError,
|
HealthCheckError,
|
||||||
DataSourceWithBackend,
|
DataSourceWithBackend,
|
||||||
|
TemplateSrv,
|
||||||
} from '@grafana/runtime';
|
} from '@grafana/runtime';
|
||||||
import {
|
import {
|
||||||
DataFrame,
|
DataFrame,
|
||||||
dataFrameFromJSON,
|
dataFrameFromJSON,
|
||||||
DataQueryRequest,
|
DataQueryRequest,
|
||||||
DataQueryResponse,
|
DataQueryResponse,
|
||||||
DataSourceApi,
|
|
||||||
DataSourceInstanceSettings,
|
DataSourceInstanceSettings,
|
||||||
FieldType,
|
FieldType,
|
||||||
isDataFrame,
|
isDataFrame,
|
||||||
LoadingState,
|
ScopedVars,
|
||||||
toDataFrame,
|
toDataFrame,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { AnnotationQueryEditor } from './components/AnnotationQueryEditor';
|
import { AnnotationQueryEditor } from './components/AnnotationQueryEditor';
|
||||||
import { trackRequest } from './tracking';
|
import { trackRequest } from './tracking';
|
||||||
import { lastValueFrom } from 'rxjs';
|
import { lastValueFrom, map, Observable } from 'rxjs';
|
||||||
|
|
||||||
export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDSOptions> {
|
export class ZabbixDatasource extends DataSourceWithBackend<ZabbixMetricsQuery, ZabbixDSOptions> {
|
||||||
name: string;
|
name: string;
|
||||||
basicAuth: any;
|
basicAuth: any;
|
||||||
withCredentials: any;
|
withCredentials: any;
|
||||||
@@ -59,9 +57,10 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
|||||||
instanceSettings: DataSourceInstanceSettings<ZabbixDSOptions>;
|
instanceSettings: DataSourceInstanceSettings<ZabbixDSOptions>;
|
||||||
zabbix: Zabbix;
|
zabbix: Zabbix;
|
||||||
|
|
||||||
replaceTemplateVars: (target: any, scopedVars?: any) => any;
|
constructor(
|
||||||
|
instanceSettings: DataSourceInstanceSettings<ZabbixDSOptions>,
|
||||||
constructor(instanceSettings: DataSourceInstanceSettings<ZabbixDSOptions>) {
|
private readonly templateSrv: TemplateSrv = getTemplateSrv()
|
||||||
|
) {
|
||||||
super(instanceSettings);
|
super(instanceSettings);
|
||||||
|
|
||||||
this.instanceSettings = instanceSettings;
|
this.instanceSettings = instanceSettings;
|
||||||
@@ -72,10 +71,6 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
|||||||
prepareAnnotation: migrations.prepareAnnotation,
|
prepareAnnotation: migrations.prepareAnnotation,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Use custom format for template variables
|
|
||||||
const templateSrv = getTemplateSrv();
|
|
||||||
this.replaceTemplateVars = _.partial(replaceTemplateVars, templateSrv);
|
|
||||||
|
|
||||||
// General data source settings
|
// General data source settings
|
||||||
this.datasourceId = instanceSettings.id;
|
this.datasourceId = instanceSettings.id;
|
||||||
this.name = instanceSettings.name;
|
this.name = instanceSettings.name;
|
||||||
@@ -120,13 +115,12 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
|||||||
////////////////////////
|
////////////////////////
|
||||||
// Datasource methods //
|
// Datasource methods //
|
||||||
////////////////////////
|
////////////////////////
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query panel data. Calls for each panel in dashboard.
|
* Query panel data. Calls for each panel in dashboard.
|
||||||
* @param {Object} request Contains time range, targets and other info.
|
* @param {Object} request Contains time range, targets and other info.
|
||||||
* @return {Object} Grafana metrics object with timeseries data for each target.
|
* @return {Object} Grafana metrics object with timeseries data for each target.
|
||||||
*/
|
*/
|
||||||
query(request: DataQueryRequest<ZabbixMetricsQuery>) {
|
query(request: DataQueryRequest<ZabbixMetricsQuery>): Observable<DataQueryResponse> {
|
||||||
trackRequest(request);
|
trackRequest(request);
|
||||||
|
|
||||||
// Migrate old targets
|
// Migrate old targets
|
||||||
@@ -144,104 +138,33 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
|||||||
return target;
|
return target;
|
||||||
});
|
});
|
||||||
|
|
||||||
const backendResponsePromise = this.backendQuery({ ...request, targets: requestTargets });
|
const interpolatedTargets = this.interpolateVariablesInQueries(requestTargets, request.scopedVars);
|
||||||
const dbConnectionResponsePromise = this.dbConnectionQuery({ ...request, targets: requestTargets });
|
const backendResponse = super.query({ ...request, targets: interpolatedTargets.filter(this.isBackendTarget) });
|
||||||
const frontendResponsePromise = this.frontendQuery({ ...request, targets: requestTargets });
|
const dbConnectionResponsePromise = this.dbConnectionQuery({ ...request, targets: interpolatedTargets });
|
||||||
const annotationResposePromise = this.annotationRequest({ ...request, targets: requestTargets });
|
const frontendResponsePromise = this.frontendQuery({ ...request, targets: interpolatedTargets });
|
||||||
|
const annotationResposePromise = this.annotationRequest({ ...request, targets: interpolatedTargets });
|
||||||
|
|
||||||
return Promise.all([
|
const applyMergeQueries = (queryResponse: DataQueryResponse) =>
|
||||||
backendResponsePromise,
|
this.mergeQueries(queryResponse, dbConnectionResponsePromise, frontendResponsePromise, annotationResposePromise);
|
||||||
dbConnectionResponsePromise,
|
const applyFEFuncs = (queryResponse: DataQueryResponse) =>
|
||||||
frontendResponsePromise,
|
this.applyFrontendFunctions(queryResponse, {
|
||||||
annotationResposePromise,
|
...request,
|
||||||
]).then((rsp) => {
|
targets: interpolatedTargets.filter(this.isBackendTarget),
|
||||||
// Merge backend and frontend queries results
|
|
||||||
const [backendRes, dbConnectionRes, frontendRes, annotationRes] = rsp;
|
|
||||||
if (dbConnectionRes.data) {
|
|
||||||
backendRes.data = backendRes.data.concat(dbConnectionRes.data);
|
|
||||||
}
|
|
||||||
if (frontendRes.data) {
|
|
||||||
backendRes.data = backendRes.data.concat(frontendRes.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (annotationRes.data) {
|
|
||||||
backendRes.data = backendRes.data.concat(annotationRes.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
data: backendRes.data,
|
|
||||||
state: LoadingState.Done,
|
|
||||||
key: request.requestId,
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
async backendQuery(request: DataQueryRequest<any>): Promise<DataQueryResponse> {
|
return backendResponse.pipe(
|
||||||
const { intervalMs, maxDataPoints, range, requestId } = request;
|
map(applyFEFuncs),
|
||||||
const targets = request.targets.filter(this.isBackendTarget);
|
map(responseHandler.convertZabbixUnits),
|
||||||
|
map(this.convertToWide),
|
||||||
// Add range variables
|
map(applyMergeQueries)
|
||||||
request.scopedVars = Object.assign({}, request.scopedVars, utils.getRangeScopedVars(request.range));
|
|
||||||
|
|
||||||
const queries = _.compact(
|
|
||||||
targets.map((query) => {
|
|
||||||
// Don't request for hidden targets
|
|
||||||
if (query.hide) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.replaceTargetVariables(query, request);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...query,
|
|
||||||
datasourceId: this.datasourceId,
|
|
||||||
intervalMs,
|
|
||||||
maxDataPoints,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Return early if no queries exist
|
|
||||||
if (!queries.length) {
|
|
||||||
return Promise.resolve({ data: [] });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const body: any = { queries };
|
async frontendQuery(request: DataQueryRequest<ZabbixMetricsQuery>): Promise<DataQueryResponse> {
|
||||||
|
|
||||||
if (range) {
|
|
||||||
body.range = range;
|
|
||||||
body.from = range.from.valueOf().toString();
|
|
||||||
body.to = range.to.valueOf().toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
let rsp: any;
|
|
||||||
try {
|
|
||||||
rsp = await lastValueFrom(
|
|
||||||
getBackendSrv().fetch({
|
|
||||||
url: '/api/ds/query',
|
|
||||||
method: 'POST',
|
|
||||||
data: body,
|
|
||||||
requestId,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
return toDataQueryResponse(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
const resp = toDataQueryResponse(rsp);
|
|
||||||
this.sortByRefId(resp);
|
|
||||||
this.applyFrontendFunctions(resp, request);
|
|
||||||
responseHandler.convertZabbixUnits(resp);
|
|
||||||
if (responseHandler.isConvertibleToWide(resp.data)) {
|
|
||||||
console.log('Converting response to the wide format');
|
|
||||||
resp.data = responseHandler.convertToWide(resp.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp;
|
|
||||||
}
|
|
||||||
|
|
||||||
async frontendQuery(request: DataQueryRequest<any>): Promise<DataQueryResponse> {
|
|
||||||
const frontendTargets = request.targets.filter((t) => !(this.isBackendTarget(t) || this.isDBConnectionTarget(t)));
|
const frontendTargets = request.targets.filter((t) => !(this.isBackendTarget(t) || this.isDBConnectionTarget(t)));
|
||||||
|
const oldVersionTargets = frontendTargets
|
||||||
|
.filter((target) => (target as any).itservice && !target.itServiceFilter)
|
||||||
|
.map((t) => t.refId);
|
||||||
const promises = _.map(frontendTargets, (target) => {
|
const promises = _.map(frontendTargets, (target) => {
|
||||||
// Don't request for hidden targets
|
// Don't request for hidden targets
|
||||||
if (target.hide) {
|
if (target.hide) {
|
||||||
@@ -250,7 +173,6 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
|||||||
|
|
||||||
// Add range variables
|
// Add range variables
|
||||||
request.scopedVars = Object.assign({}, request.scopedVars, utils.getRangeScopedVars(request.range));
|
request.scopedVars = Object.assign({}, request.scopedVars, utils.getRangeScopedVars(request.range));
|
||||||
this.replaceTargetVariables(target, request);
|
|
||||||
const timeRange = this.buildTimeRange(request, target);
|
const timeRange = this.buildTimeRange(request, target);
|
||||||
|
|
||||||
if (target.queryType === c.MODE_TEXT) {
|
if (target.queryType === c.MODE_TEXT) {
|
||||||
@@ -262,7 +184,8 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
|||||||
return this.queryTextData(target, timeRange);
|
return this.queryTextData(target, timeRange);
|
||||||
} else if (target.queryType === c.MODE_ITSERVICE) {
|
} else if (target.queryType === c.MODE_ITSERVICE) {
|
||||||
// IT services query
|
// IT services query
|
||||||
return this.queryITServiceData(target, timeRange, request);
|
const isOldVersion = oldVersionTargets.includes(target.refId);
|
||||||
|
return this.queryITServiceData(target, timeRange, request, isOldVersion);
|
||||||
} else if (target.queryType === c.MODE_TRIGGERS) {
|
} else if (target.queryType === c.MODE_TRIGGERS) {
|
||||||
// Triggers query
|
// Triggers query
|
||||||
return this.queryTriggersData(target, timeRange, request);
|
return this.queryTriggersData(target, timeRange, request);
|
||||||
@@ -311,7 +234,6 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
|||||||
|
|
||||||
// Add range variables
|
// Add range variables
|
||||||
request.scopedVars = Object.assign({}, request.scopedVars, utils.getRangeScopedVars(request.range));
|
request.scopedVars = Object.assign({}, request.scopedVars, utils.getRangeScopedVars(request.range));
|
||||||
this.replaceTargetVariables(target, request);
|
|
||||||
const timeRange = this.buildTimeRange(request, target);
|
const timeRange = this.buildTimeRange(request, target);
|
||||||
const useTrends = this.isUseTrends(timeRange, target);
|
const useTrends = this.isUseTrends(timeRange, target);
|
||||||
|
|
||||||
@@ -341,7 +263,7 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
|||||||
let timeTo = Math.ceil(dateMath.parse(request.range.to) / 1000);
|
let timeTo = Math.ceil(dateMath.parse(request.range.to) / 1000);
|
||||||
|
|
||||||
// Apply Time-related functions (timeShift(), etc)
|
// Apply Time-related functions (timeShift(), etc)
|
||||||
const timeFunctions = bindFunctionDefs(target.functions, 'Time');
|
const timeFunctions = utils.bindFunctionDefs(target.functions, 'Time');
|
||||||
if (timeFunctions.length) {
|
if (timeFunctions.length) {
|
||||||
const [time_from, time_to] = utils.sequence(timeFunctions)([timeFrom, timeTo]);
|
const [time_from, time_to] = utils.sequence(timeFunctions)([timeFrom, timeTo]);
|
||||||
timeFrom = time_from;
|
timeFrom = time_from;
|
||||||
@@ -377,7 +299,7 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
|||||||
async queryNumericDataForItems(items, target: ZabbixMetricsQuery, timeRange, useTrends, request) {
|
async queryNumericDataForItems(items, target: ZabbixMetricsQuery, timeRange, useTrends, request) {
|
||||||
let history;
|
let history;
|
||||||
request.valueType = this.getTrendValueType(target);
|
request.valueType = this.getTrendValueType(target);
|
||||||
request.consolidateBy = getConsolidateBy(target) || request.valueType;
|
request.consolidateBy = utils.getConsolidateBy(target) || request.valueType;
|
||||||
|
|
||||||
if (useTrends) {
|
if (useTrends) {
|
||||||
history = await this.zabbix.getTrends(items, timeRange, request);
|
history = await this.zabbix.getTrends(items, timeRange, request);
|
||||||
@@ -454,10 +376,10 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
|||||||
applyFrontendFunctions(response: DataQueryResponse, request: DataQueryRequest<any>) {
|
applyFrontendFunctions(response: DataQueryResponse, request: DataQueryRequest<any>) {
|
||||||
for (let i = 0; i < response.data.length; i++) {
|
for (let i = 0; i < response.data.length; i++) {
|
||||||
const frame: DataFrame = response.data[i];
|
const frame: DataFrame = response.data[i];
|
||||||
const target = getRequestTarget(request, frame.refId);
|
const target = utils.getRequestTarget(request, frame.refId);
|
||||||
|
|
||||||
// Apply alias functions
|
// Apply alias functions
|
||||||
const aliasFunctions = bindFunctionDefs(target.functions, 'Alias');
|
const aliasFunctions = utils.bindFunctionDefs(target.functions, 'Alias');
|
||||||
utils.sequence(aliasFunctions)(frame);
|
utils.sequence(aliasFunctions)(frame);
|
||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
@@ -489,7 +411,7 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
|||||||
queryItemIdData(target, timeRange, useTrends, options) {
|
queryItemIdData(target, timeRange, useTrends, options) {
|
||||||
let itemids = target.itemids;
|
let itemids = target.itemids;
|
||||||
const templateSrv = getTemplateSrv();
|
const templateSrv = getTemplateSrv();
|
||||||
itemids = templateSrv.replace(itemids, options.scopedVars, zabbixItemIdsTemplateFormat);
|
itemids = templateSrv.replace(itemids, options.scopedVars, utils.zabbixItemIdsTemplateFormat);
|
||||||
itemids = _.map(itemids.split(','), (itemid) => itemid.trim());
|
itemids = _.map(itemids.split(','), (itemid) => itemid.trim());
|
||||||
|
|
||||||
if (!itemids) {
|
if (!itemids) {
|
||||||
@@ -504,34 +426,26 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
|||||||
/**
|
/**
|
||||||
* Query target data for IT Services
|
* Query target data for IT Services
|
||||||
*/
|
*/
|
||||||
async queryITServiceData(target: ZabbixMetricsQuery, timeRange, request) {
|
async queryITServiceData(
|
||||||
|
target: ZabbixMetricsQuery,
|
||||||
|
timeRange: number[],
|
||||||
|
request: DataQueryRequest<ZabbixMetricsQuery>,
|
||||||
|
isOldVersion: boolean
|
||||||
|
) {
|
||||||
// Don't show undefined and hidden targets
|
// Don't show undefined and hidden targets
|
||||||
if (target.hide || (!(target as any).itservice && !target.itServiceFilter) || !target.slaProperty) {
|
if (target.hide || (!(target as any).itservice && !target.itServiceFilter) || !target.slaProperty) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
let itServiceFilter;
|
let itservices = await this.zabbix.getITServices(target.itServiceFilter);
|
||||||
request.isOldVersion = (target as any).itservice && !target.itServiceFilter;
|
if (isOldVersion) {
|
||||||
|
|
||||||
if (request.isOldVersion) {
|
|
||||||
// Backward compatibility
|
|
||||||
itServiceFilter = '/.*/';
|
|
||||||
} else {
|
|
||||||
itServiceFilter = this.replaceTemplateVars(target.itServiceFilter, request.scopedVars);
|
|
||||||
}
|
|
||||||
|
|
||||||
request.slaInterval = target.slaInterval;
|
|
||||||
|
|
||||||
let itservices = await this.zabbix.getITServices(itServiceFilter);
|
|
||||||
if (request.isOldVersion) {
|
|
||||||
itservices = _.filter(itservices, { serviceid: (target as any).itservice?.serviceid });
|
itservices = _.filter(itservices, { serviceid: (target as any).itservice?.serviceid });
|
||||||
}
|
}
|
||||||
if (target.slaFilter !== undefined) {
|
if (target.slaFilter !== undefined) {
|
||||||
const slaFilter = this.replaceTemplateVars(target.slaFilter, request.scopedVars);
|
const slas = await this.zabbix.getSLAs(target.slaFilter);
|
||||||
const slas = await this.zabbix.getSLAs(slaFilter);
|
|
||||||
const result = await this.zabbix.getSLI(itservices, slas, timeRange, target, request);
|
const result = await this.zabbix.getSLI(itservices, slas, timeRange, target, request);
|
||||||
// Apply alias functions
|
// Apply alias functions
|
||||||
const aliasFunctions = bindFunctionDefs(target.functions, 'Alias');
|
const aliasFunctions = utils.bindFunctionDefs(target.functions, 'Alias');
|
||||||
utils.sequence(aliasFunctions)(result);
|
utils.sequence(aliasFunctions)(result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -568,11 +482,10 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
|||||||
|
|
||||||
const hostids = hosts?.map((h) => h.hostid);
|
const hostids = hosts?.map((h) => h.hostid);
|
||||||
const appids = apps?.map((a) => a.applicationid);
|
const appids = apps?.map((a) => a.applicationid);
|
||||||
const options = getTriggersOptions(target, timeRange);
|
const options = utils.getTriggersOptions(target, timeRange);
|
||||||
|
|
||||||
const tagsFilter = this.replaceTemplateVars(target.tags?.filter, request.scopedVars);
|
// variable interpolation builds regex-like string, so we should trim it.
|
||||||
// replaceTemplateVars() builds regex-like string, so we should trim it.
|
const tagsFilterStr = target.tags.filter.replace('/^', '').replace('$/', '');
|
||||||
const tagsFilterStr = tagsFilter.replace('/^', '').replace('$/', '');
|
|
||||||
const tags = utils.parseTags(tagsFilterStr);
|
const tags = utils.parseTags(tagsFilterStr);
|
||||||
tags.forEach((tag) => {
|
tags.forEach((tag) => {
|
||||||
// Zabbix uses {"tag": "<tag>", "value": "<value>", "operator": "<operator>"} format, where 1 means Equal
|
// Zabbix uses {"tag": "<tag>", "value": "<value>", "operator": "<operator>"} format, where 1 means Equal
|
||||||
@@ -603,16 +516,15 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
|||||||
return Promise.resolve([]);
|
return Promise.resolve([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const options = getTriggersOptions(target, timeRange);
|
const options = utils.getTriggersOptions(target, timeRange);
|
||||||
const alerts = await this.zabbix.getHostICAlerts(hostids, appids, itemids, options);
|
const alerts = await this.zabbix.getHostICAlerts(hostids, appids, itemids, options);
|
||||||
return responseHandler.handleTriggersResponse(alerts, groups, timeRange, target);
|
return responseHandler.handleTriggersResponse(alerts, groups, timeRange, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
async queryTriggersPCData(target: ZabbixMetricsQuery, timeRange, request) {
|
async queryTriggersPCData(target: ZabbixMetricsQuery, timeRange, request) {
|
||||||
const [timeFrom, timeTo] = timeRange;
|
const [timeFrom, timeTo] = timeRange;
|
||||||
const tagsFilter = this.replaceTemplateVars(target.tags?.filter, request.scopedVars);
|
// variable interpolation builds regex-like string, so we should trim it.
|
||||||
// replaceTemplateVars() builds regex-like string, so we should trim it.
|
const tagsFilterStr = target.tags.filter.replace('/^', '').replace('$/', '');
|
||||||
const tagsFilterStr = tagsFilter.replace('/^', '').replace('$/', '');
|
|
||||||
const tags = utils.parseTags(tagsFilterStr);
|
const tags = utils.parseTags(tagsFilterStr);
|
||||||
tags.forEach((tag) => {
|
tags.forEach((tag) => {
|
||||||
// Zabbix uses {"tag": "<tag>", "value": "<value>", "operator": "<operator>"} format, where 1 means Equal
|
// Zabbix uses {"tag": "<tag>", "value": "<value>", "operator": "<operator>"} format, where 1 means Equal
|
||||||
@@ -682,23 +594,8 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
|||||||
const getProxiesPromise = showProxy ? this.zabbix.getProxies() : () => [];
|
const getProxiesPromise = showProxy ? this.zabbix.getProxies() : () => [];
|
||||||
showAckButton = !this.disableReadOnlyUsersAck || userIsEditor;
|
showAckButton = !this.disableReadOnlyUsersAck || userIsEditor;
|
||||||
|
|
||||||
// Replace template variables
|
|
||||||
const groupFilter = this.replaceTemplateVars(target.group?.filter, options.scopedVars);
|
|
||||||
const hostFilter = this.replaceTemplateVars(target.host?.filter, options.scopedVars);
|
|
||||||
const appFilter = this.replaceTemplateVars(target.application?.filter, options.scopedVars);
|
|
||||||
const proxyFilter = this.replaceTemplateVars(target.proxy?.filter, options.scopedVars);
|
|
||||||
|
|
||||||
const triggerFilter = this.replaceTemplateVars(target.trigger?.filter, options.scopedVars);
|
|
||||||
const tagsFilter = this.replaceTemplateVars(target.tags?.filter, options.scopedVars);
|
|
||||||
|
|
||||||
const replacedTarget = {
|
|
||||||
...target,
|
|
||||||
trigger: { filter: triggerFilter },
|
|
||||||
tags: { filter: tagsFilter },
|
|
||||||
};
|
|
||||||
|
|
||||||
// replaceTemplateVars() builds regex-like string, so we should trim it.
|
// replaceTemplateVars() builds regex-like string, so we should trim it.
|
||||||
const tagsFilterStr = tagsFilter.replace('/^', '').replace('$/', '');
|
const tagsFilterStr = target.tags.filter.replace('/^', '').replace('$/', '');
|
||||||
const tags = utils.parseTags(tagsFilterStr);
|
const tags = utils.parseTags(tagsFilterStr);
|
||||||
tags.forEach((tag) => {
|
tags.forEach((tag) => {
|
||||||
// Zabbix uses {"tag": "<tag>", "value": "<value>", "operator": "<operator>"} format, where 1 means Equal
|
// Zabbix uses {"tag": "<tag>", "value": "<value>", "operator": "<operator>"} format, where 1 means Equal
|
||||||
@@ -733,14 +630,20 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
|||||||
problemsOptions.timeFrom = timeFrom;
|
problemsOptions.timeFrom = timeFrom;
|
||||||
problemsOptions.timeTo = timeTo;
|
problemsOptions.timeTo = timeTo;
|
||||||
getProblemsPromise = this.zabbix.getProblemsHistory(
|
getProblemsPromise = this.zabbix.getProblemsHistory(
|
||||||
groupFilter,
|
target.group.filter,
|
||||||
hostFilter,
|
target.host.filter,
|
||||||
appFilter,
|
target.application.filter,
|
||||||
proxyFilter,
|
target.proxy.filter,
|
||||||
problemsOptions
|
problemsOptions
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
getProblemsPromise = this.zabbix.getProblems(groupFilter, hostFilter, appFilter, proxyFilter, problemsOptions);
|
getProblemsPromise = this.zabbix.getProblems(
|
||||||
|
target.group.filter,
|
||||||
|
target.host.filter,
|
||||||
|
target.application.filter,
|
||||||
|
target.proxy.filter,
|
||||||
|
problemsOptions
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const getUsersPromise = this.zabbix.getUsers();
|
const getUsersPromise = this.zabbix.getUsers();
|
||||||
|
|
||||||
@@ -754,7 +657,7 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
|||||||
})
|
})
|
||||||
.then((problems) => problemsHandler.setMaintenanceStatus(problems))
|
.then((problems) => problemsHandler.setMaintenanceStatus(problems))
|
||||||
.then((problems) => problemsHandler.setAckButtonStatus(problems, showAckButton))
|
.then((problems) => problemsHandler.setAckButtonStatus(problems, showAckButton))
|
||||||
.then((problems) => problemsHandler.filterTriggersPre(problems, replacedTarget))
|
.then((problems) => problemsHandler.filterTriggersPre(problems, target))
|
||||||
.then((problems) => problemsHandler.sortProblems(problems, target))
|
.then((problems) => problemsHandler.sortProblems(problems, target))
|
||||||
.then((problems) => problemsHandler.addTriggerDataSource(problems, target))
|
.then((problems) => problemsHandler.addTriggerDataSource(problems, target))
|
||||||
.then((problems) => problemsHandler.formatAcknowledges(problems, zabbixUsers))
|
.then((problems) => problemsHandler.formatAcknowledges(problems, zabbixUsers))
|
||||||
@@ -770,9 +673,8 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
|||||||
* Test connection to Zabbix API and external history DB.
|
* Test connection to Zabbix API and external history DB.
|
||||||
*/
|
*/
|
||||||
async testDatasource() {
|
async testDatasource() {
|
||||||
const backendDS = new DataSourceWithBackend(this.instanceSettings);
|
|
||||||
try {
|
try {
|
||||||
const testResult = await backendDS.testDatasource();
|
const testResult = await super.testDatasource();
|
||||||
return this.zabbix.testDataSource().then((dbConnectorStatus) => {
|
return this.zabbix.testDataSource().then((dbConnectorStatus) => {
|
||||||
let message = testResult.message;
|
let message = testResult.message;
|
||||||
if (dbConnectorStatus) {
|
if (dbConnectorStatus) {
|
||||||
@@ -839,7 +741,7 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const prop of ['group', 'host', 'application', 'itemTag', 'item']) {
|
for (const prop of ['group', 'host', 'application', 'itemTag', 'item']) {
|
||||||
queryModel[prop] = this.replaceTemplateVars(queryModel[prop], {});
|
queryModel[prop] = utils.replaceTemplateVars(this.templateSrv, queryModel[prop], {});
|
||||||
}
|
}
|
||||||
|
|
||||||
queryModel = queryModel as VariableQuery;
|
queryModel = queryModel as VariableQuery;
|
||||||
@@ -878,7 +780,7 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
|||||||
}
|
}
|
||||||
|
|
||||||
return resultPromise.then((metrics) => {
|
return resultPromise.then((metrics) => {
|
||||||
return _.map(metrics, formatMetric);
|
return _.map(metrics, utils.formatMetric);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -931,16 +833,16 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
|||||||
problemsOptions.severities = severities;
|
problemsOptions.severities = severities;
|
||||||
}
|
}
|
||||||
|
|
||||||
const groupFilter = this.replaceTemplateVars(annotation.group.filter, {});
|
const groupFilter = annotation.group.filter;
|
||||||
const hostFilter = this.replaceTemplateVars(annotation.host.filter, {});
|
const hostFilter = annotation.host.filter;
|
||||||
const appFilter = this.replaceTemplateVars(annotation.application.filter, {});
|
const appFilter = annotation.application.filter;
|
||||||
const proxyFilter = undefined;
|
const proxyFilter = undefined;
|
||||||
|
|
||||||
return this.zabbix
|
return this.zabbix
|
||||||
.getProblemsHistory(groupFilter, hostFilter, appFilter, proxyFilter, problemsOptions)
|
.getProblemsHistory(groupFilter, hostFilter, appFilter, proxyFilter, problemsOptions)
|
||||||
.then((problems) => {
|
.then((problems) => {
|
||||||
// Filter triggers by description
|
// Filter triggers by description
|
||||||
const problemName = this.replaceTemplateVars(annotation.trigger.filter, {});
|
const problemName = annotation.trigger.filter;
|
||||||
if (utils.isRegex(problemName)) {
|
if (utils.isRegex(problemName)) {
|
||||||
problems = _.filter(problems, (p) => {
|
problems = _.filter(problems, (p) => {
|
||||||
return utils.buildRegex(problemName).test(p.description);
|
return utils.buildRegex(problemName).test(p.description);
|
||||||
@@ -977,35 +879,6 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace template variables
|
|
||||||
replaceTargetVariables(target, options) {
|
|
||||||
const templateSrv = getTemplateSrv();
|
|
||||||
const parts = ['group', 'host', 'application', 'itemTag', 'item'];
|
|
||||||
_.forEach(parts, (p) => {
|
|
||||||
if (target[p] && target[p].filter) {
|
|
||||||
target[p].filter = this.replaceTemplateVars(target[p].filter, options.scopedVars);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (target.textFilter) {
|
|
||||||
target.textFilter = this.replaceTemplateVars(target.textFilter, options.scopedVars);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (target.itemids) {
|
|
||||||
target.itemids = templateSrv.replace(target.itemids, options.scopedVars, zabbixItemIdsTemplateFormat);
|
|
||||||
}
|
|
||||||
|
|
||||||
_.forEach(target.functions, (func) => {
|
|
||||||
func.params = _.map(func.params, (param) => {
|
|
||||||
if (typeof param === 'number') {
|
|
||||||
return +templateSrv.replace(param.toString(), options.scopedVars);
|
|
||||||
} else {
|
|
||||||
return templateSrv.replace(param, options.scopedVars);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
isUseTrends(timeRange, target: ZabbixMetricsQuery) {
|
isUseTrends(timeRange, target: ZabbixMetricsQuery) {
|
||||||
if (target.options.useTrends === 'false') {
|
if (target.options.useTrends === 'false') {
|
||||||
return false;
|
return false;
|
||||||
@@ -1030,108 +903,89 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
|||||||
isDBConnectionTarget = (target: any): boolean => {
|
isDBConnectionTarget = (target: any): boolean => {
|
||||||
return this.enableDirectDBConnection && (target.queryType === c.MODE_METRICS || target.queryType === c.MODE_ITEMID);
|
return this.enableDirectDBConnection && (target.queryType === c.MODE_METRICS || target.queryType === c.MODE_ITEMID);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mergeQueries(
|
||||||
|
queryResponse: DataQueryResponse,
|
||||||
|
dbConnectionResponsePromise: Promise<DataQueryResponse>,
|
||||||
|
frontendResponsePromise: Promise<DataQueryResponse>,
|
||||||
|
annotationResposePromise: Promise<DataQueryResponse>
|
||||||
|
): DataQueryResponse {
|
||||||
|
Promise.all([dbConnectionResponsePromise, frontendResponsePromise, annotationResposePromise]).then((resp) => {
|
||||||
|
const [dbConnectionRes, frontendRes, annotationRes] = resp;
|
||||||
|
if (dbConnectionRes.data) {
|
||||||
|
queryResponse.data = queryResponse.data.concat(dbConnectionRes.data);
|
||||||
|
}
|
||||||
|
if (frontendRes.data) {
|
||||||
|
queryResponse.data = queryResponse.data.concat(frontendRes.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
function bindFunctionDefs(functionDefs, category) {
|
if (annotationRes.data) {
|
||||||
const aggregationFunctions = _.map(metricFunctions.getCategories()[category], 'name');
|
queryResponse.data = queryResponse.data.concat(annotationRes.data);
|
||||||
const aggFuncDefs = _.filter(functionDefs, (func) => {
|
}
|
||||||
return _.includes(aggregationFunctions, func.def.name) && func.params.length > 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
return _.map(aggFuncDefs, (func) => {
|
|
||||||
const funcInstance = metricFunctions.createFuncInstance(func.def, func.params);
|
|
||||||
return funcInstance.bindFunction(dataProcessor.metricFunctions);
|
|
||||||
});
|
});
|
||||||
|
return queryResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getConsolidateBy(target) {
|
convertToWide(response: DataQueryResponse) {
|
||||||
let consolidateBy;
|
if (responseHandler.isConvertibleToWide(response.data)) {
|
||||||
const funcDef = _.find(target.functions, (func) => {
|
response.data = responseHandler.convertToWide(response.data);
|
||||||
return func.def.name === 'consolidateBy';
|
|
||||||
});
|
|
||||||
if (funcDef && funcDef.params && funcDef.params.length) {
|
|
||||||
consolidateBy = funcDef.params[0];
|
|
||||||
}
|
}
|
||||||
return consolidateBy;
|
return response;
|
||||||
}
|
}
|
||||||
|
interpolateVariablesInQueries(queries: ZabbixMetricsQuery[], scopedVars: ScopedVars): ZabbixMetricsQuery[] {
|
||||||
function formatMetric(metricObj) {
|
if (!queries || queries.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return queries.map((query) => {
|
||||||
|
// backwardsCompatibility
|
||||||
|
const isOldVersion: boolean = (query as any).itservice && !query.itServiceFilter;
|
||||||
return {
|
return {
|
||||||
text: metricObj.name,
|
...query,
|
||||||
expandable: false,
|
itServiceFilter: isOldVersion
|
||||||
|
? '/.*/'
|
||||||
|
: utils.replaceTemplateVars(this.templateSrv, query.itServiceFilter, scopedVars),
|
||||||
|
slaFilter: utils.replaceTemplateVars(this.templateSrv, query.slaFilter, scopedVars),
|
||||||
|
itemids: utils.replaceTemplateVars(
|
||||||
|
this.templateSrv,
|
||||||
|
query.itemids,
|
||||||
|
scopedVars,
|
||||||
|
utils.zabbixItemIdsTemplateFormat
|
||||||
|
),
|
||||||
|
textFilter: utils.replaceTemplateVars(this.templateSrv, query.textFilter, scopedVars),
|
||||||
|
functions: utils.replaceVariablesInFuncParams(this.templateSrv, query.functions, scopedVars),
|
||||||
|
tags: {
|
||||||
|
...query.tags,
|
||||||
|
filter: utils.replaceTemplateVars(this.templateSrv, query.tags?.filter, scopedVars),
|
||||||
|
},
|
||||||
|
group: {
|
||||||
|
...query.group,
|
||||||
|
filter: utils.replaceTemplateVars(this.templateSrv, query.group?.filter, scopedVars),
|
||||||
|
},
|
||||||
|
host: {
|
||||||
|
...query.host,
|
||||||
|
filter: utils.replaceTemplateVars(this.templateSrv, query.host?.filter, scopedVars),
|
||||||
|
},
|
||||||
|
application: {
|
||||||
|
...query.application,
|
||||||
|
filter: utils.replaceTemplateVars(this.templateSrv, query.application?.filter, scopedVars),
|
||||||
|
},
|
||||||
|
proxy: {
|
||||||
|
...query.proxy,
|
||||||
|
filter: utils.replaceTemplateVars(this.templateSrv, query.proxy?.filter, scopedVars),
|
||||||
|
},
|
||||||
|
trigger: {
|
||||||
|
...query.trigger,
|
||||||
|
filter: utils.replaceTemplateVars(this.templateSrv, query.trigger?.filter, scopedVars),
|
||||||
|
},
|
||||||
|
itemTag: {
|
||||||
|
...query.itemTag,
|
||||||
|
filter: utils.replaceTemplateVars(this.templateSrv, query.itemTag?.filter, scopedVars),
|
||||||
|
},
|
||||||
|
item: {
|
||||||
|
...query.item,
|
||||||
|
filter: utils.replaceTemplateVars(this.templateSrv, query.item?.filter, scopedVars),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom formatter for template variables.
|
|
||||||
* Default Grafana "regex" formatter returns
|
|
||||||
* value1|value2
|
|
||||||
* This formatter returns
|
|
||||||
* (value1|value2)
|
|
||||||
* This format needed for using in complex regex with
|
|
||||||
* template variables, for example
|
|
||||||
* /CPU $cpu_item.*time/ where $cpu_item is system,user,iowait
|
|
||||||
*/
|
|
||||||
export function zabbixTemplateFormat(value) {
|
|
||||||
if (typeof value === 'string') {
|
|
||||||
return utils.escapeRegex(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
const escapedValues = _.map(value, utils.escapeRegex);
|
|
||||||
return '(' + escapedValues.join('|') + ')';
|
|
||||||
}
|
|
||||||
|
|
||||||
function zabbixItemIdsTemplateFormat(value) {
|
|
||||||
if (typeof value === 'string') {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
return value.join(',');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If template variables are used in request, replace it using regex format
|
|
||||||
* and wrap with '/' for proper multi-value work. Example:
|
|
||||||
* $variable selected as a, b, c
|
|
||||||
* We use filter $variable
|
|
||||||
* $variable -> a|b|c -> /a|b|c/
|
|
||||||
* /$variable/ -> /a|b|c/ -> /a|b|c/
|
|
||||||
*/
|
|
||||||
export function replaceTemplateVars(templateSrv, target, scopedVars) {
|
|
||||||
let replacedTarget = templateSrv.replace(target, scopedVars, zabbixTemplateFormat);
|
|
||||||
if (target && target !== replacedTarget && !utils.isRegex(replacedTarget)) {
|
|
||||||
replacedTarget = '/^' + replacedTarget + '$/';
|
|
||||||
}
|
|
||||||
return replacedTarget;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function base64StringToArrowTable(text: string) {
|
|
||||||
const b64 = atob(text);
|
|
||||||
const arr = Uint8Array.from(b64, (c) => {
|
|
||||||
return c.charCodeAt(0);
|
|
||||||
});
|
});
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRequestTarget(request: DataQueryRequest<any>, refId: string): any {
|
|
||||||
for (let i = 0; i < request.targets.length; i++) {
|
|
||||||
const target = request.targets[i];
|
|
||||||
if (target.refId === refId) {
|
|
||||||
return target;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const getTriggersOptions = (target: ZabbixMetricsQuery, timeRange) => {
|
|
||||||
const [timeFrom, timeTo] = timeRange;
|
|
||||||
const options: any = {
|
|
||||||
minSeverity: target.options?.minSeverity,
|
|
||||||
acknowledged: target.options?.acknowledged,
|
|
||||||
count: target.options?.count,
|
|
||||||
};
|
|
||||||
if (target.options?.useTimeRange) {
|
|
||||||
options.timeFrom = timeFrom;
|
|
||||||
options.timeTo = timeTo;
|
|
||||||
}
|
|
||||||
return options;
|
|
||||||
};
|
|
||||||
|
|||||||
22
src/datasource/hooks/useInterpolatedQuery.ts
Normal file
22
src/datasource/hooks/useInterpolatedQuery.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
|
import { ScopedVars } from '@grafana/data';
|
||||||
|
import { ZabbixDatasource } from '../datasource';
|
||||||
|
import { ZabbixMetricsQuery } from '../types/query';
|
||||||
|
|
||||||
|
const EMPTY_SCOPED_VARS: ScopedVars = {};
|
||||||
|
|
||||||
|
export const useInterpolatedQuery = (
|
||||||
|
datasource: ZabbixDatasource,
|
||||||
|
query: ZabbixMetricsQuery,
|
||||||
|
scopedVars?: ScopedVars
|
||||||
|
): ZabbixMetricsQuery => {
|
||||||
|
const [interpolatedQuery, setInterpolatedQuery] = useState<ZabbixMetricsQuery>(query);
|
||||||
|
const resolvedScopedVars = useMemo(() => scopedVars ?? EMPTY_SCOPED_VARS, [scopedVars]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const replacedQuery = datasource.interpolateVariablesInQueries([query], resolvedScopedVars)[0];
|
||||||
|
setInterpolatedQuery(replacedQuery);
|
||||||
|
}, [datasource, query, resolvedScopedVars]);
|
||||||
|
|
||||||
|
return interpolatedQuery;
|
||||||
|
};
|
||||||
@@ -1,12 +1,52 @@
|
|||||||
import { dateMath } from '@grafana/data';
|
import { DataQueryResponse, dateMath } from '@grafana/data';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { datasourceSrvMock, templateSrvMock } from '../../test-setup/mocks';
|
import { datasourceSrvMock, templateSrvMock } from '../../test-setup/mocks';
|
||||||
import { replaceTemplateVars, ZabbixDatasource, zabbixTemplateFormat } from '../datasource';
|
|
||||||
import { VariableQueryTypes } from '../types';
|
import { VariableQueryTypes } from '../types';
|
||||||
|
import { ZabbixDatasource } from 'datasource/datasource';
|
||||||
|
// firstValueFrom removed - tests call frontendQuery directly for text queries
|
||||||
|
import * as utils from '../utils';
|
||||||
|
|
||||||
jest.mock(
|
jest.mock(
|
||||||
'@grafana/runtime',
|
'@grafana/runtime',
|
||||||
() => ({
|
() => {
|
||||||
|
const actual = jest.requireActual('@grafana/runtime');
|
||||||
|
// Provide a custom query implementation that resolves backend + frontend + db + annotations
|
||||||
|
// so tests relying on merged results receive expected data.
|
||||||
|
if (actual && actual.DataSourceWithBackend && actual.DataSourceWithBackend.prototype) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
actual.DataSourceWithBackend.prototype.query = function (request: any) {
|
||||||
|
const that: any = this;
|
||||||
|
const { from } = require('rxjs');
|
||||||
|
|
||||||
|
const backendResponse = Promise.resolve({ data: [] });
|
||||||
|
const dbPromise = that.dbConnectionQuery ? that.dbConnectionQuery(request) : Promise.resolve({ data: [] });
|
||||||
|
const fePromise = that.frontendQuery ? that.frontendQuery(request) : Promise.resolve({ data: [] });
|
||||||
|
const annPromise = that.annotationRequest ? that.annotationRequest(request) : Promise.resolve({ data: [] });
|
||||||
|
|
||||||
|
return from(
|
||||||
|
Promise.all([backendResponse, dbPromise, fePromise, annPromise]).then(([backend, db, fe, ann]) => {
|
||||||
|
const data: any[] = [];
|
||||||
|
if (backend && backend.data) {
|
||||||
|
data.push(...backend.data);
|
||||||
|
}
|
||||||
|
if (db && db.data) {
|
||||||
|
data.push(...db.data);
|
||||||
|
}
|
||||||
|
if (fe && fe.data) {
|
||||||
|
data.push(...fe.data);
|
||||||
|
}
|
||||||
|
if (ann && ann.data) {
|
||||||
|
data.push(...ann.data);
|
||||||
|
}
|
||||||
|
return { data };
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
getBackendSrv: () => ({
|
getBackendSrv: () => ({
|
||||||
datasourceRequest: jest.fn().mockResolvedValue({ data: { result: '' } }),
|
datasourceRequest: jest.fn().mockResolvedValue({ data: { result: '' } }),
|
||||||
fetch: () => ({
|
fetch: () => ({
|
||||||
@@ -20,7 +60,8 @@ jest.mock(
|
|||||||
replace: jest.fn().mockImplementation((query) => query),
|
replace: jest.fn().mockImplementation((query) => query),
|
||||||
}),
|
}),
|
||||||
reportInteraction: jest.fn(),
|
reportInteraction: jest.fn(),
|
||||||
}),
|
};
|
||||||
|
},
|
||||||
{ virtual: true }
|
{ virtual: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -28,6 +69,24 @@ jest.mock('../components/AnnotationQueryEditor', () => ({
|
|||||||
AnnotationQueryEditor: () => {},
|
AnnotationQueryEditor: () => {},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
jest.mock(
|
||||||
|
'../utils',
|
||||||
|
() => (
|
||||||
|
jest.requireActual('../utils'),
|
||||||
|
{
|
||||||
|
replaceVariablesInFuncParams: jest.fn(),
|
||||||
|
parseInterval: jest.fn(),
|
||||||
|
replaceTemplateVars: jest.fn().mockImplementation((templateSrv, prop) => prop),
|
||||||
|
getRangeScopedVars: jest.fn(),
|
||||||
|
bindFunctionDefs: jest.fn().mockResolvedValue([]),
|
||||||
|
parseLegacyVariableQuery: jest.fn(),
|
||||||
|
formatMetric: jest.fn().mockImplementation((metric) => {
|
||||||
|
return { text: metric.name, expandable: false };
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
describe('ZabbixDatasource', () => {
|
describe('ZabbixDatasource', () => {
|
||||||
let ctx: any = {};
|
let ctx: any = {};
|
||||||
let consoleSpy: jest.SpyInstance;
|
let consoleSpy: jest.SpyInstance;
|
||||||
@@ -101,7 +160,7 @@ describe('ZabbixDatasource', () => {
|
|||||||
item: { filter: 'System information' },
|
item: { filter: 'System information' },
|
||||||
textFilter: '',
|
textFilter: '',
|
||||||
useCaptureGroups: true,
|
useCaptureGroups: true,
|
||||||
queryType: 2,
|
queryType: '2',
|
||||||
resultFormat: 'table',
|
resultFormat: 'table',
|
||||||
options: {
|
options: {
|
||||||
skipEmptyValues: false,
|
skipEmptyValues: false,
|
||||||
@@ -110,25 +169,18 @@ describe('ZabbixDatasource', () => {
|
|||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return data in table format', (done) => {
|
it('should return data in table format', async () => {
|
||||||
ctx.ds.query(ctx.options).then((result) => {
|
const result = (await ctx.ds.frontendQuery(ctx.options)) as DataQueryResponse;
|
||||||
expect(result.data.length).toBe(1);
|
expect(result.data.length).toBe(1);
|
||||||
|
|
||||||
let tableData = result.data[0];
|
let tableData = result.data[0];
|
||||||
expect(tableData.columns).toEqual([
|
expect(tableData.columns).toEqual([{ text: 'Host' }, { text: 'Item' }, { text: 'Key' }, { text: 'Last value' }]);
|
||||||
{ text: 'Host' },
|
|
||||||
{ text: 'Item' },
|
|
||||||
{ text: 'Key' },
|
|
||||||
{ text: 'Last value' },
|
|
||||||
]);
|
|
||||||
expect(tableData.rows).toEqual([['Zabbix server', 'System information', 'system.uname', 'Linux last']]);
|
expect(tableData.rows).toEqual([['Zabbix server', 'System information', 'system.uname', 'Linux last']]);
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should extract value if regex with capture group is used', (done) => {
|
it('should extract value if regex with capture group is used', (done) => {
|
||||||
ctx.options.targets[0].textFilter = 'Linux (.*)';
|
ctx.options.targets[0].textFilter = 'Linux (.*)';
|
||||||
ctx.ds.query(ctx.options).then((result) => {
|
ctx.ds.frontendQuery(ctx.options).then((result) => {
|
||||||
let tableData = result.data[0];
|
let tableData = result.data[0];
|
||||||
expect(tableData.rows[0][3]).toEqual('last');
|
expect(tableData.rows[0][3]).toEqual('last');
|
||||||
done();
|
done();
|
||||||
@@ -163,7 +215,7 @@ describe('ZabbixDatasource', () => {
|
|||||||
{ clock: '1500010500', itemid: '90109', ns: '900111000', value: '' },
|
{ clock: '1500010500', itemid: '90109', ns: '900111000', value: '' },
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
return ctx.ds.query(ctx.options).then((result) => {
|
return ctx.ds.frontendQuery(ctx.options).then((result) => {
|
||||||
let tableData = result.data[0];
|
let tableData = result.data[0];
|
||||||
expect(tableData.rows.length).toBe(1);
|
expect(tableData.rows.length).toBe(1);
|
||||||
expect(tableData.rows[0][3]).toEqual('Linux last');
|
expect(tableData.rows[0][3]).toEqual('Linux last');
|
||||||
@@ -171,69 +223,34 @@ describe('ZabbixDatasource', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('When replacing template variables', () => {
|
|
||||||
function testReplacingVariable(target, varValue, expectedResult, done) {
|
|
||||||
ctx.ds.replaceTemplateVars = _.partial(replaceTemplateVars, {
|
|
||||||
replace: jest.fn((target) => zabbixTemplateFormat(varValue)),
|
|
||||||
});
|
|
||||||
|
|
||||||
let result = ctx.ds.replaceTemplateVars(target);
|
|
||||||
expect(result).toBe(expectedResult);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Alphanumerics, spaces, dots, dashes and underscores
|
|
||||||
* are allowed in Zabbix host name.
|
|
||||||
* 'AaBbCc0123 .-_'
|
|
||||||
*/
|
|
||||||
it('should return properly escaped regex', (done) => {
|
|
||||||
let target = '$host';
|
|
||||||
let template_var_value = 'AaBbCc0123 .-_';
|
|
||||||
let expected_result = '/^AaBbCc0123 \\.-_$/';
|
|
||||||
|
|
||||||
testReplacingVariable(target, template_var_value, expected_result, done);
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Single-value variable
|
|
||||||
* $host = backend01
|
|
||||||
* $host => /^backend01|backend01$/
|
|
||||||
*/
|
|
||||||
it('should return proper regex for single value', (done) => {
|
|
||||||
let target = '$host';
|
|
||||||
let template_var_value = 'backend01';
|
|
||||||
let expected_result = '/^backend01$/';
|
|
||||||
|
|
||||||
testReplacingVariable(target, template_var_value, expected_result, done);
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Multi-value variable
|
|
||||||
* $host = [backend01, backend02]
|
|
||||||
* $host => /^(backend01|backend01)$/
|
|
||||||
*/
|
|
||||||
it('should return proper regex for multi-value', (done) => {
|
|
||||||
let target = '$host';
|
|
||||||
let template_var_value = ['backend01', 'backend02'];
|
|
||||||
let expected_result = '/^(backend01|backend02)$/';
|
|
||||||
|
|
||||||
testReplacingVariable(target, template_var_value, expected_result, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('When invoking metricFindQuery() with legacy query', () => {
|
describe('When invoking metricFindQuery() with legacy query', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.ds.replaceTemplateVars = (str) => str;
|
|
||||||
ctx.ds.zabbix = {
|
ctx.ds.zabbix = {
|
||||||
getGroups: jest.fn().mockReturnValue(Promise.resolve([])),
|
getGroups: jest.fn().mockReturnValue(Promise.resolve([])),
|
||||||
getHosts: jest.fn().mockReturnValue(Promise.resolve([])),
|
getHosts: jest.fn().mockReturnValue(Promise.resolve([])),
|
||||||
getApps: jest.fn().mockReturnValue(Promise.resolve([])),
|
getApps: jest.fn().mockReturnValue(Promise.resolve([])),
|
||||||
getItems: jest.fn().mockReturnValue(Promise.resolve([])),
|
getItems: jest.fn().mockReturnValue(Promise.resolve([])),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
jest.spyOn(utils, 'replaceTemplateVars').mockImplementation(({}, prop: string, {}) => {
|
||||||
|
return prop;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return groups', (done) => {
|
it('should return groups', (done) => {
|
||||||
|
jest.spyOn(utils, 'parseLegacyVariableQuery').mockImplementation((query: string) => {
|
||||||
|
let group = '';
|
||||||
|
if (query === '*') {
|
||||||
|
group = '/.*/';
|
||||||
|
} else {
|
||||||
|
group = query;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
queryType: VariableQueryTypes.Group,
|
||||||
|
group: group,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const tests = [
|
const tests = [
|
||||||
{ query: '*', expect: '/.*/' },
|
{ query: '*', expect: '/.*/' },
|
||||||
{ query: 'Backend', expect: 'Backend' },
|
{ query: 'Backend', expect: 'Backend' },
|
||||||
@@ -259,6 +276,14 @@ describe('ZabbixDatasource', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return hosts', (done) => {
|
it('should return hosts', (done) => {
|
||||||
|
jest.spyOn(utils, 'parseLegacyVariableQuery').mockImplementation((query: string) => {
|
||||||
|
let splits = query.split('.');
|
||||||
|
return {
|
||||||
|
queryType: VariableQueryTypes.Host,
|
||||||
|
group: splits[0] === '*' ? '/.*/' : splits[0],
|
||||||
|
host: splits[1] === '*' ? '/.*/' : splits[1],
|
||||||
|
};
|
||||||
|
});
|
||||||
const tests = [
|
const tests = [
|
||||||
{ query: '*.*', expect: ['/.*/', '/.*/'] },
|
{ query: '*.*', expect: ['/.*/', '/.*/'] },
|
||||||
{ query: '.', expect: ['', ''] },
|
{ query: '.', expect: ['', ''] },
|
||||||
@@ -275,6 +300,15 @@ describe('ZabbixDatasource', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return applications', (done) => {
|
it('should return applications', (done) => {
|
||||||
|
jest.spyOn(utils, 'parseLegacyVariableQuery').mockImplementation((query: string) => {
|
||||||
|
let splits = query.split('.');
|
||||||
|
return {
|
||||||
|
queryType: VariableQueryTypes.Application,
|
||||||
|
group: splits[0] === '*' ? '/.*/' : splits[0],
|
||||||
|
host: splits[1] === '*' ? '/.*/' : splits[1],
|
||||||
|
application: splits[2] === '*' ? '/.*/' : splits[2],
|
||||||
|
};
|
||||||
|
});
|
||||||
const tests = [
|
const tests = [
|
||||||
{ query: '*.*.*', expect: ['/.*/', '/.*/', '/.*/'] },
|
{ query: '*.*.*', expect: ['/.*/', '/.*/', '/.*/'] },
|
||||||
{ query: '.*.', expect: ['', '/.*/', ''] },
|
{ query: '.*.', expect: ['', '/.*/', ''] },
|
||||||
@@ -291,6 +325,16 @@ describe('ZabbixDatasource', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return items', (done) => {
|
it('should return items', (done) => {
|
||||||
|
jest.spyOn(utils, 'parseLegacyVariableQuery').mockImplementation((query: string) => {
|
||||||
|
let splits = query.split('.');
|
||||||
|
return {
|
||||||
|
queryType: VariableQueryTypes.Item,
|
||||||
|
group: splits[0] === '*' ? '/.*/' : splits[0],
|
||||||
|
host: splits[1] === '*' ? '/.*/' : splits[1],
|
||||||
|
application: splits[2] === '*' ? '' : splits[2],
|
||||||
|
item: splits[3] === '*' ? '/.*/' : splits[3],
|
||||||
|
};
|
||||||
|
});
|
||||||
const tests = [
|
const tests = [
|
||||||
{ query: '*.*.*.*', expect: ['/.*/', '/.*/', '', undefined, '/.*/', { showDisabledItems: undefined }] },
|
{ query: '*.*.*.*', expect: ['/.*/', '/.*/', '', undefined, '/.*/', { showDisabledItems: undefined }] },
|
||||||
{ query: '.*.*.*', expect: ['', '/.*/', '', undefined, '/.*/', { showDisabledItems: undefined }] },
|
{ query: '.*.*.*', expect: ['', '/.*/', '', undefined, '/.*/', { showDisabledItems: undefined }] },
|
||||||
@@ -320,6 +364,14 @@ describe('ZabbixDatasource', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should invoke method with proper arguments', (done) => {
|
it('should invoke method with proper arguments', (done) => {
|
||||||
|
jest.spyOn(utils, 'parseLegacyVariableQuery').mockImplementation((query: string) => {
|
||||||
|
let splits = query.split('.');
|
||||||
|
return {
|
||||||
|
queryType: VariableQueryTypes.Host,
|
||||||
|
group: splits[0] === '*' ? '/.*/' : splits[0],
|
||||||
|
host: splits[1] === '*' ? '/.*/' : splits[1],
|
||||||
|
};
|
||||||
|
});
|
||||||
let query = '*.*';
|
let query = '*.*';
|
||||||
|
|
||||||
ctx.ds.metricFindQuery(query);
|
ctx.ds.metricFindQuery(query);
|
||||||
@@ -329,7 +381,6 @@ describe('ZabbixDatasource', () => {
|
|||||||
|
|
||||||
describe('When invoking metricFindQuery()', () => {
|
describe('When invoking metricFindQuery()', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.ds.replaceTemplateVars = (str) => str;
|
|
||||||
ctx.ds.zabbix = {
|
ctx.ds.zabbix = {
|
||||||
getGroups: jest.fn().mockReturnValue(Promise.resolve([{ name: 'Group1' }, { name: 'Group2' }])),
|
getGroups: jest.fn().mockReturnValue(Promise.resolve([{ name: 'Group1' }, { name: 'Group2' }])),
|
||||||
getHosts: jest.fn().mockReturnValue(Promise.resolve([{ name: 'Host1' }, { name: 'Host2' }])),
|
getHosts: jest.fn().mockReturnValue(Promise.resolve([{ name: 'Host1' }, { name: 'Host2' }])),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import * as utils from '../utils';
|
import * as utils from '../utils';
|
||||||
|
import { replaceTemplateVars, zabbixTemplateFormat } from '../utils';
|
||||||
|
|
||||||
describe('Utils', () => {
|
describe('Utils', () => {
|
||||||
describe('expandItemName()', () => {
|
describe('expandItemName()', () => {
|
||||||
@@ -174,4 +175,90 @@ describe('Utils', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('replaceTemplateVars()', () => {
|
||||||
|
function testReplacingVariable(target, varValue, expectedResult, done) {
|
||||||
|
const templateSrv = {
|
||||||
|
replace: jest.fn((target) => zabbixTemplateFormat(varValue)),
|
||||||
|
getVariables: jest.fn(),
|
||||||
|
containsTemplate: jest.fn(),
|
||||||
|
updateTimeRange: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = replaceTemplateVars(templateSrv, target, {});
|
||||||
|
expect(result).toBe(expectedResult);
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Alphanumerics, spaces, dots, dashes and underscores
|
||||||
|
* are allowed in Zabbix host name.
|
||||||
|
* 'AaBbCc0123 .-_'
|
||||||
|
*/
|
||||||
|
it('should return properly escaped regex', (done) => {
|
||||||
|
let target = '$host';
|
||||||
|
let template_var_value = 'AaBbCc0123 .-_';
|
||||||
|
let expected_result = '/^AaBbCc0123 \\.-_$/';
|
||||||
|
|
||||||
|
testReplacingVariable(target, template_var_value, expected_result, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Single-value variable
|
||||||
|
* $host = backend01
|
||||||
|
* $host => /^backend01|backend01$/
|
||||||
|
*/
|
||||||
|
it('should return proper regex for single value', (done) => {
|
||||||
|
let target = '$host';
|
||||||
|
let template_var_value = 'backend01';
|
||||||
|
let expected_result = '/^backend01$/';
|
||||||
|
|
||||||
|
testReplacingVariable(target, template_var_value, expected_result, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Multi-value variable
|
||||||
|
* $host = [backend01, backend02]
|
||||||
|
* $host => /^(backend01|backend01)$/
|
||||||
|
*/
|
||||||
|
it('should return proper regex for multi-value', (done) => {
|
||||||
|
let target = '$host';
|
||||||
|
let template_var_value = ['backend01', 'backend02'];
|
||||||
|
let expected_result = '/^(backend01|backend02)$/';
|
||||||
|
|
||||||
|
testReplacingVariable(target, template_var_value, expected_result, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('replaceVariablesInFuncParams()', () => {
|
||||||
|
it('should interpolate numeric and string params with templateSrv', () => {
|
||||||
|
const replaceMock = jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation((value) => (value === '42' ? '100' : value.replace('$var', 'result')));
|
||||||
|
const templateSrv = { replace: replaceMock };
|
||||||
|
const scopedVars = { some: 'var' } as any;
|
||||||
|
const functions = [
|
||||||
|
{
|
||||||
|
def: { name: 'test' },
|
||||||
|
params: [42, '$var'],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const [fn] = utils.replaceVariablesInFuncParams(templateSrv as any, functions as any, scopedVars);
|
||||||
|
|
||||||
|
expect(replaceMock).toHaveBeenCalledWith('42', scopedVars);
|
||||||
|
expect(replaceMock).toHaveBeenCalledWith('$var', scopedVars);
|
||||||
|
expect(fn.params).toEqual([100, 'result']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep params undefined when function has none', () => {
|
||||||
|
const templateSrv = { replace: jest.fn() };
|
||||||
|
const functions = [{ def: { name: 'noop' } }];
|
||||||
|
|
||||||
|
const [fn] = utils.replaceVariablesInFuncParams(templateSrv as any, functions as any, {} as any);
|
||||||
|
|
||||||
|
expect(fn.params).toBeUndefined();
|
||||||
|
expect(templateSrv.replace).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,13 +5,19 @@ import * as c from './constants';
|
|||||||
import { VariableQuery, VariableQueryTypes, ZBXItemTag } from './types';
|
import { VariableQuery, VariableQueryTypes, ZBXItemTag } from './types';
|
||||||
import {
|
import {
|
||||||
DataFrame,
|
DataFrame,
|
||||||
|
DataQueryRequest,
|
||||||
FieldType,
|
FieldType,
|
||||||
getValueFormats,
|
getValueFormats,
|
||||||
MappingType,
|
MappingType,
|
||||||
rangeUtil,
|
rangeUtil,
|
||||||
|
ScopedVars,
|
||||||
TIME_SERIES_TIME_FIELD_NAME,
|
TIME_SERIES_TIME_FIELD_NAME,
|
||||||
ValueMapping,
|
ValueMapping,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
|
import * as metricFunctions from './metricFunctions';
|
||||||
|
import dataProcessor from './dataProcessor';
|
||||||
|
import { MetricFunc, ZabbixMetricsQuery } from './types/query';
|
||||||
|
import { TemplateSrv } from '@grafana/runtime';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This regex matches 3 types of variable reference with an optional format specifier
|
* This regex matches 3 types of variable reference with an optional format specifier
|
||||||
@@ -200,24 +206,12 @@ function isContainsBraces(query) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Pattern for testing regex
|
// Pattern for testing regex
|
||||||
export const regexPattern = /^\/(.*)\/([gmi]*)$/m;
|
const regexPattern = /^\/(.*)\/([gmi]*)$/m;
|
||||||
|
|
||||||
export function isRegex(str) {
|
export function isRegex(str) {
|
||||||
return regexPattern.test(str);
|
return regexPattern.test(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isTemplateVariable(str, templateVariables) {
|
|
||||||
const variablePattern = /^\$\w+/;
|
|
||||||
if (variablePattern.test(str)) {
|
|
||||||
const variables = _.map(templateVariables, (variable) => {
|
|
||||||
return '$' + variable.name;
|
|
||||||
});
|
|
||||||
return _.includes(variables, str);
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getRangeScopedVars(range) {
|
export function getRangeScopedVars(range) {
|
||||||
const msRange = range.to.diff(range.from);
|
const msRange = range.to.diff(range.from);
|
||||||
const sRange = Math.round(msRange / 1000);
|
const sRange = Math.round(msRange / 1000);
|
||||||
@@ -254,7 +248,7 @@ export function parseItemInterval(interval: string): number {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeZabbixInterval(interval: string): string {
|
function normalizeZabbixInterval(interval: string): string {
|
||||||
const intervalPattern = /(^[\d]+)(y|M|w|d|h|m|s)?/g;
|
const intervalPattern = /(^[\d]+)(y|M|w|d|h|m|s)?/g;
|
||||||
const parsedInterval = intervalPattern.exec(interval);
|
const parsedInterval = intervalPattern.exec(interval);
|
||||||
if (!parsedInterval || !interval || (parsedInterval.length > 2 && !parsedInterval[2])) {
|
if (!parsedInterval || !interval || (parsedInterval.length > 2 && !parsedInterval[2])) {
|
||||||
@@ -324,40 +318,6 @@ export function formatAcknowledges(acknowledges) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function convertToZabbixAPIUrl(url) {
|
|
||||||
const zabbixAPIUrlPattern = /.*api_jsonrpc.php$/;
|
|
||||||
const trimSlashPattern = /(.*?)[\/]*$/;
|
|
||||||
if (url.match(zabbixAPIUrlPattern)) {
|
|
||||||
return url;
|
|
||||||
} else {
|
|
||||||
return url.replace(trimSlashPattern, '$1');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wrap function to prevent multiple calls
|
|
||||||
* when waiting for result.
|
|
||||||
*/
|
|
||||||
export function callOnce(func, promiseKeeper) {
|
|
||||||
return function () {
|
|
||||||
if (!promiseKeeper) {
|
|
||||||
promiseKeeper = Promise.resolve(
|
|
||||||
func
|
|
||||||
.apply(this, arguments)
|
|
||||||
.then((result) => {
|
|
||||||
promiseKeeper = null;
|
|
||||||
return result;
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
promiseKeeper = null;
|
|
||||||
throw err;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return promiseKeeper;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply function one by one: `sequence([a(), b(), c()]) = c(b(a()))`
|
* Apply function one by one: `sequence([a(), b(), c()]) = c(b(a()))`
|
||||||
* @param {*} funcsArray functions to apply
|
* @param {*} funcsArray functions to apply
|
||||||
@@ -371,24 +331,6 @@ export function sequence(funcsArray) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const versionPattern = /^(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:-([0-9A-Za-z\.]+))?/;
|
|
||||||
|
|
||||||
export function isValidVersion(version) {
|
|
||||||
return versionPattern.exec(version);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseVersion(version: string) {
|
|
||||||
const match = versionPattern.exec(version);
|
|
||||||
if (!match) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const major = Number(match[1]);
|
|
||||||
const minor = Number(match[2] || 0);
|
|
||||||
const patch = Number(match[3] || 0);
|
|
||||||
const meta = match[4];
|
|
||||||
return { major, minor, patch, meta };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replaces any space-like symbols (tabs, new lines, spaces) by single whitespace.
|
* Replaces any space-like symbols (tabs, new lines, spaces) by single whitespace.
|
||||||
*/
|
*/
|
||||||
@@ -543,3 +485,125 @@ export function swap<T>(list: T[], n: number, k: number): T[] {
|
|||||||
}
|
}
|
||||||
return newList;
|
return newList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function bindFunctionDefs(functionDefs, category) {
|
||||||
|
const aggregationFunctions = _.map(metricFunctions.getCategories()[category], 'name');
|
||||||
|
const aggFuncDefs = _.filter(functionDefs, (func) => {
|
||||||
|
return _.includes(aggregationFunctions, func.def.name) && func.params.length > 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
return _.map(aggFuncDefs, (func) => {
|
||||||
|
const funcInstance = metricFunctions.createFuncInstance(func.def, func.params);
|
||||||
|
return funcInstance.bindFunction(dataProcessor.metricFunctions);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getConsolidateBy(target) {
|
||||||
|
let consolidateBy;
|
||||||
|
const funcDef = _.find(target.functions, (func) => {
|
||||||
|
return func.def.name === 'consolidateBy';
|
||||||
|
});
|
||||||
|
if (funcDef && funcDef.params && funcDef.params.length) {
|
||||||
|
consolidateBy = funcDef.params[0];
|
||||||
|
}
|
||||||
|
return consolidateBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatMetric(metricObj) {
|
||||||
|
return {
|
||||||
|
text: metricObj.name,
|
||||||
|
expandable: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom formatter for template variables.
|
||||||
|
* Default Grafana "regex" formatter returns
|
||||||
|
* value1|value2
|
||||||
|
* This formatter returns
|
||||||
|
* (value1|value2)
|
||||||
|
* This format needed for using in complex regex with
|
||||||
|
* template variables, for example
|
||||||
|
* /CPU $cpu_item.*time/ where $cpu_item is system,user,iowait
|
||||||
|
*/
|
||||||
|
export function zabbixTemplateFormat(value) {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
return escapeRegex(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const escapedValues = _.map(value, escapeRegex);
|
||||||
|
return '(' + escapedValues.join('|') + ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function zabbixItemIdsTemplateFormat(value) {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return value.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If template variables are used in request, replace it using regex format
|
||||||
|
* and wrap with '/' for proper multi-value work. Example:
|
||||||
|
* $variable selected as a, b, c
|
||||||
|
* We use filter $variable
|
||||||
|
* $variable -> a|b|c -> /a|b|c/
|
||||||
|
* /$variable/ -> /a|b|c/ -> /a|b|c/
|
||||||
|
*/
|
||||||
|
export function replaceTemplateVars(
|
||||||
|
templateSrv: TemplateSrv,
|
||||||
|
target: string,
|
||||||
|
scopedVars: ScopedVars,
|
||||||
|
format: any = zabbixTemplateFormat
|
||||||
|
) {
|
||||||
|
let replacedTarget = templateSrv.replace(target, scopedVars, format);
|
||||||
|
if (target && target !== replacedTarget && !isRegex(replacedTarget)) {
|
||||||
|
replacedTarget = '/^' + replacedTarget + '$/';
|
||||||
|
}
|
||||||
|
return replacedTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function replaceVariablesInFuncParams(
|
||||||
|
templateSrv: TemplateSrv,
|
||||||
|
functions: MetricFunc[],
|
||||||
|
scopedVars: ScopedVars
|
||||||
|
) {
|
||||||
|
return functions?.map((func) => {
|
||||||
|
const interpolatedParams = func?.params?.map((param) => {
|
||||||
|
if (typeof param === 'number') {
|
||||||
|
return +templateSrv.replace(param.toString(), scopedVars);
|
||||||
|
} else {
|
||||||
|
return templateSrv.replace(param, scopedVars);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...func,
|
||||||
|
params: interpolatedParams,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRequestTarget(request: DataQueryRequest<any>, refId: string): any {
|
||||||
|
for (let i = 0; i < request.targets.length; i++) {
|
||||||
|
const target = request.targets[i];
|
||||||
|
if (target.refId === refId) {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getTriggersOptions = (target: ZabbixMetricsQuery, timeRange) => {
|
||||||
|
const [timeFrom, timeTo] = timeRange;
|
||||||
|
const options: any = {
|
||||||
|
minSeverity: target.options?.minSeverity,
|
||||||
|
acknowledged: target.options?.acknowledged,
|
||||||
|
count: target.options?.count,
|
||||||
|
};
|
||||||
|
if (target.options?.useTimeRange) {
|
||||||
|
options.timeFrom = timeFrom;
|
||||||
|
options.timeTo = timeTo;
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user