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 { getVariableOptions } from './QueryEditor/utils';
|
||||
import { prepareAnnotation } from '../migrations';
|
||||
import { useInterpolatedQuery } from '../hooks/useInterpolatedQuery';
|
||||
|
||||
const severityOptions: Array<SelectableValue<number>> = [
|
||||
{ value: 0, label: 'Not classified' },
|
||||
@@ -27,6 +28,7 @@ type Props = ZabbixQueryEditorProps & {
|
||||
export const AnnotationQueryEditor = ({ annotation, onAnnotationChange, datasource }: Props) => {
|
||||
annotation = prepareAnnotation(annotation);
|
||||
const query = annotation.target;
|
||||
const interpolatedQuery = useInterpolatedQuery(datasource, query);
|
||||
|
||||
const loadGroupOptions = async () => {
|
||||
const groups = await datasource.zabbix.getAllGroups();
|
||||
@@ -44,8 +46,7 @@ export const AnnotationQueryEditor = ({ annotation, onAnnotationChange, datasour
|
||||
}, []);
|
||||
|
||||
const loadHostOptions = async (group: string) => {
|
||||
const groupFilter = datasource.replaceTemplateVars(group);
|
||||
const hosts = await datasource.zabbix.getAllHosts(groupFilter);
|
||||
const hosts = await datasource.zabbix.getAllHosts(group);
|
||||
let options: Array<SelectableValue<string>> = hosts?.map((host) => ({
|
||||
value: host.name,
|
||||
label: host.name,
|
||||
@@ -57,14 +58,12 @@ export const AnnotationQueryEditor = ({ annotation, onAnnotationChange, datasour
|
||||
};
|
||||
|
||||
const [{ loading: hostsLoading, value: hostOptions }, fetchHosts] = useAsyncFn(async () => {
|
||||
const options = await loadHostOptions(query.group.filter);
|
||||
const options = await loadHostOptions(interpolatedQuery.group.filter);
|
||||
return options;
|
||||
}, [query.group.filter]);
|
||||
}, [interpolatedQuery.group.filter]);
|
||||
|
||||
const loadAppOptions = async (group: string, host: string) => {
|
||||
const groupFilter = datasource.replaceTemplateVars(group);
|
||||
const hostFilter = datasource.replaceTemplateVars(host);
|
||||
const apps = await datasource.zabbix.getAllApps(groupFilter, hostFilter);
|
||||
const apps = await datasource.zabbix.getAllApps(group, host);
|
||||
let options: Array<SelectableValue<string>> = apps?.map((app) => ({
|
||||
value: app.name,
|
||||
label: app.name,
|
||||
@@ -75,13 +74,13 @@ export const AnnotationQueryEditor = ({ annotation, onAnnotationChange, datasour
|
||||
};
|
||||
|
||||
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;
|
||||
}, [query.group.filter, query.host.filter]);
|
||||
}, [interpolatedQuery.group.filter, interpolatedQuery.host.filter]);
|
||||
|
||||
// Update suggestions on every metric change
|
||||
const groupFilter = datasource.replaceTemplateVars(query.group?.filter);
|
||||
const hostFilter = datasource.replaceTemplateVars(query.host?.filter);
|
||||
const groupFilter = interpolatedQuery.group?.filter;
|
||||
const hostFilter = interpolatedQuery.host?.filter;
|
||||
|
||||
useEffect(() => {
|
||||
fetchGroups();
|
||||
|
||||
@@ -7,7 +7,22 @@ jest.mock('@grafana/runtime', () => ({
|
||||
config: {},
|
||||
}));
|
||||
|
||||
jest.mock('@grafana/ui', () => ({
|
||||
...jest.requireActual('@grafana/ui'),
|
||||
config: {},
|
||||
}));
|
||||
|
||||
describe('ConfigEditor', () => {
|
||||
beforeAll(() => {
|
||||
Object.defineProperty(HTMLCanvasElement.prototype, 'getContext', {
|
||||
value: () => ({
|
||||
measureText: () => ({ width: 0 }),
|
||||
font: '',
|
||||
textAlign: '',
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
describe('on initial render', () => {
|
||||
it('should not mutate the options object', () => {
|
||||
const options = Object.freeze({ ...getDefaultOptions() }); // freezing the options to prevent mutations
|
||||
@@ -27,7 +42,7 @@ describe('ConfigEditor', () => {
|
||||
const onOptionsChangeSpy = jest.fn();
|
||||
|
||||
expect(() => render(<ConfigEditor options={options} onOptionsChange={onOptionsChangeSpy} />)).not.toThrow();
|
||||
expect(onOptionsChangeSpy).toBeCalledTimes(1);
|
||||
expect(onOptionsChangeSpy).toHaveBeenCalledTimes(1);
|
||||
expect(onOptionsChangeSpy).toHaveBeenCalledWith({
|
||||
...getDefaultOptions(),
|
||||
jsonData: {
|
||||
@@ -51,7 +66,7 @@ describe('ConfigEditor', () => {
|
||||
const onOptionsChangeSpy = jest.fn();
|
||||
|
||||
expect(() => render(<ConfigEditor options={options} onOptionsChange={onOptionsChangeSpy} />)).not.toThrow();
|
||||
expect(onOptionsChangeSpy).toBeCalledTimes(1);
|
||||
expect(onOptionsChangeSpy).toHaveBeenCalledTimes(1);
|
||||
expect(onOptionsChangeSpy).toHaveBeenCalledWith({
|
||||
...getDefaultOptions(),
|
||||
jsonData: {
|
||||
|
||||
@@ -11,6 +11,7 @@ import { ZabbixDatasource } from '../../datasource';
|
||||
import { ZabbixMetricsQuery } from '../../types/query';
|
||||
import { ZBXItem, ZBXItemTag } from '../../types';
|
||||
import { itemTagToString } from '../../utils';
|
||||
import { useInterpolatedQuery } from '../../hooks/useInterpolatedQuery';
|
||||
|
||||
export interface Props {
|
||||
query: ZabbixMetricsQuery;
|
||||
@@ -19,6 +20,8 @@ export interface Props {
|
||||
}
|
||||
|
||||
export const MetricsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
const interpolatedQuery = useInterpolatedQuery(datasource, query);
|
||||
|
||||
const loadGroupOptions = async () => {
|
||||
const groups = await datasource.zabbix.getAllGroups();
|
||||
const options = groups?.map((group) => ({
|
||||
@@ -35,8 +38,7 @@ export const MetricsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
}, []);
|
||||
|
||||
const loadHostOptions = async (group: string) => {
|
||||
const groupFilter = datasource.replaceTemplateVars(group);
|
||||
const hosts = await datasource.zabbix.getAllHosts(groupFilter);
|
||||
const hosts = await datasource.zabbix.getAllHosts(group);
|
||||
let options: Array<SelectableValue<string>> = hosts?.map((host) => ({
|
||||
value: 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 options = await loadHostOptions(query.group.filter);
|
||||
const options = await loadHostOptions(interpolatedQuery.group.filter);
|
||||
return options;
|
||||
}, [query.group.filter]);
|
||||
}, [interpolatedQuery.group.filter]);
|
||||
|
||||
const loadAppOptions = async (group: string, host: string) => {
|
||||
const groupFilter = datasource.replaceTemplateVars(group);
|
||||
const hostFilter = datasource.replaceTemplateVars(host);
|
||||
const apps = await datasource.zabbix.getAllApps(groupFilter, hostFilter);
|
||||
const apps = await datasource.zabbix.getAllApps(group, host);
|
||||
let options: Array<SelectableValue<string>> = apps?.map((app) => ({
|
||||
value: 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 options = await loadAppOptions(query.group.filter, query.host.filter);
|
||||
const options = await loadAppOptions(interpolatedQuery.group.filter, interpolatedQuery.host.filter);
|
||||
return options;
|
||||
}, [query.group.filter, query.host.filter]);
|
||||
}, [interpolatedQuery.group.filter, interpolatedQuery.host.filter]);
|
||||
|
||||
const loadTagOptions = async (group: string, host: string) => {
|
||||
const tagsAvailable = await datasource.zabbix.isZabbix54OrHigher();
|
||||
@@ -76,9 +76,7 @@ export const MetricsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
return [];
|
||||
}
|
||||
|
||||
const groupFilter = datasource.replaceTemplateVars(group);
|
||||
const hostFilter = datasource.replaceTemplateVars(host);
|
||||
const items = await datasource.zabbix.getAllItems(groupFilter, hostFilter, null, null, {});
|
||||
const items = await datasource.zabbix.getAllItems(group, host, null, null, {});
|
||||
const tags: ZBXItemTag[] = _.flatten(items.map((item: ZBXItem) => item.tags || []));
|
||||
// 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 options = await loadTagOptions(query.group.filter, query.host.filter);
|
||||
const options = await loadTagOptions(interpolatedQuery.group.filter, interpolatedQuery.host.filter);
|
||||
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 groupFilter = datasource.replaceTemplateVars(group);
|
||||
const hostFilter = datasource.replaceTemplateVars(host);
|
||||
const appFilter = datasource.replaceTemplateVars(app);
|
||||
const tagFilter = datasource.replaceTemplateVars(itemTag);
|
||||
const options = {
|
||||
itemtype: 'num',
|
||||
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) => ({
|
||||
value: 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 options = await loadItemOptions(
|
||||
query.group.filter,
|
||||
query.host.filter,
|
||||
query.application.filter,
|
||||
query.itemTag.filter
|
||||
interpolatedQuery.group.filter,
|
||||
interpolatedQuery.host.filter,
|
||||
interpolatedQuery.application.filter,
|
||||
interpolatedQuery.itemTag.filter
|
||||
);
|
||||
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
|
||||
const groupFilter = datasource.replaceTemplateVars(query.group?.filter);
|
||||
const hostFilter = datasource.replaceTemplateVars(query.host?.filter);
|
||||
const appFilter = datasource.replaceTemplateVars(query.application?.filter);
|
||||
const tagFilter = datasource.replaceTemplateVars(query.itemTag?.filter);
|
||||
const groupFilter = interpolatedQuery.group?.filter;
|
||||
const hostFilter = interpolatedQuery.host?.filter;
|
||||
const appFilter = interpolatedQuery.application?.filter;
|
||||
const tagFilter = interpolatedQuery.itemTag?.filter;
|
||||
|
||||
useEffect(() => {
|
||||
fetchGroups();
|
||||
|
||||
@@ -9,6 +9,7 @@ import { MetricPicker } from '../../../components';
|
||||
import { getVariableOptions } from './utils';
|
||||
import { ZabbixDatasource } from '../../datasource';
|
||||
import { ZabbixMetricsQuery, ZabbixTagEvalType } from '../../types/query';
|
||||
import { useInterpolatedQuery } from '../../hooks/useInterpolatedQuery';
|
||||
|
||||
const showProblemsOptions: Array<SelectableValue<string>> = [
|
||||
{ label: 'Problems', value: 'problems' },
|
||||
@@ -37,6 +38,8 @@ export interface Props {
|
||||
}
|
||||
|
||||
export const ProblemsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
const interpolatedQuery = useInterpolatedQuery(datasource, query);
|
||||
|
||||
const loadGroupOptions = async () => {
|
||||
const groups = await datasource.zabbix.getAllGroups();
|
||||
const options = groups?.map((group) => ({
|
||||
@@ -53,8 +56,7 @@ export const ProblemsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
}, []);
|
||||
|
||||
const loadHostOptions = async (group: string) => {
|
||||
const groupFilter = datasource.replaceTemplateVars(group);
|
||||
const hosts = await datasource.zabbix.getAllHosts(groupFilter);
|
||||
const hosts = await datasource.zabbix.getAllHosts(group);
|
||||
let options: Array<SelectableValue<string>> = hosts?.map((host) => ({
|
||||
value: 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 options = await loadHostOptions(query.group.filter);
|
||||
const options = await loadHostOptions(interpolatedQuery.group.filter);
|
||||
return options;
|
||||
}, [query.group.filter]);
|
||||
}, [interpolatedQuery.group.filter]);
|
||||
|
||||
const loadAppOptions = async (group: string, host: string) => {
|
||||
const groupFilter = datasource.replaceTemplateVars(group);
|
||||
const hostFilter = datasource.replaceTemplateVars(host);
|
||||
const apps = await datasource.zabbix.getAllApps(groupFilter, hostFilter);
|
||||
const apps = await datasource.zabbix.getAllApps(group, host);
|
||||
let options: Array<SelectableValue<string>> = apps?.map((app) => ({
|
||||
value: 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 options = await loadAppOptions(query.group.filter, query.host.filter);
|
||||
const options = await loadAppOptions(interpolatedQuery.group.filter, interpolatedQuery.host.filter);
|
||||
return options;
|
||||
}, [query.group.filter, query.host.filter]);
|
||||
}, [interpolatedQuery.group.filter, interpolatedQuery.host.filter]);
|
||||
|
||||
const loadProxyOptions = async () => {
|
||||
const proxies = await datasource.zabbix.getProxies();
|
||||
@@ -104,8 +104,8 @@ export const ProblemsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
}, []);
|
||||
|
||||
// Update suggestions on every metric change
|
||||
const groupFilter = datasource.replaceTemplateVars(query.group?.filter);
|
||||
const hostFilter = datasource.replaceTemplateVars(query.host?.filter);
|
||||
const groupFilter = interpolatedQuery.group?.filter;
|
||||
const hostFilter = interpolatedQuery.host?.filter;
|
||||
|
||||
useEffect(() => {
|
||||
fetchGroups();
|
||||
|
||||
@@ -9,6 +9,7 @@ import { MetricPicker } from '../../../components';
|
||||
import { getVariableOptions } from './utils';
|
||||
import { ZabbixDatasource } from '../../datasource';
|
||||
import { ZabbixMetricsQuery } from '../../types/query';
|
||||
import { useInterpolatedQuery } from '../../hooks/useInterpolatedQuery';
|
||||
|
||||
export interface Props {
|
||||
query: ZabbixMetricsQuery;
|
||||
@@ -17,6 +18,8 @@ export interface Props {
|
||||
}
|
||||
|
||||
export const TextMetricsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
const interpolatedQuery = useInterpolatedQuery(datasource, query);
|
||||
|
||||
const loadGroupOptions = async () => {
|
||||
const groups = await datasource.zabbix.getAllGroups();
|
||||
const options = groups?.map((group) => ({
|
||||
@@ -33,8 +36,7 @@ export const TextMetricsQueryEditor = ({ query, datasource, onChange }: Props) =
|
||||
}, []);
|
||||
|
||||
const loadHostOptions = async (group: string) => {
|
||||
const groupFilter = datasource.replaceTemplateVars(group);
|
||||
const hosts = await datasource.zabbix.getAllHosts(groupFilter);
|
||||
const hosts = await datasource.zabbix.getAllHosts(group);
|
||||
let options: Array<SelectableValue<string>> = hosts?.map((host) => ({
|
||||
value: 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 options = await loadHostOptions(query.group.filter);
|
||||
const options = await loadHostOptions(interpolatedQuery.group.filter);
|
||||
return options;
|
||||
}, [query.group.filter]);
|
||||
}, [interpolatedQuery.group.filter]);
|
||||
|
||||
const loadAppOptions = async (group: string, host: string) => {
|
||||
const groupFilter = datasource.replaceTemplateVars(group);
|
||||
const hostFilter = datasource.replaceTemplateVars(host);
|
||||
const apps = await datasource.zabbix.getAllApps(groupFilter, hostFilter);
|
||||
const apps = await datasource.zabbix.getAllApps(group, host);
|
||||
let options: Array<SelectableValue<string>> = apps?.map((app) => ({
|
||||
value: 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 options = await loadAppOptions(query.group.filter, query.host.filter);
|
||||
const options = await loadAppOptions(interpolatedQuery.group.filter, interpolatedQuery.host.filter);
|
||||
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 groupFilter = datasource.replaceTemplateVars(group);
|
||||
const hostFilter = datasource.replaceTemplateVars(host);
|
||||
const appFilter = datasource.replaceTemplateVars(app);
|
||||
const tagFilter = datasource.replaceTemplateVars(itemTag);
|
||||
const options = {
|
||||
itemtype: 'text',
|
||||
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) => ({
|
||||
value: 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 options = await loadItemOptions(
|
||||
query.group.filter,
|
||||
query.host.filter,
|
||||
query.application.filter,
|
||||
query.itemTag.filter
|
||||
interpolatedQuery.group.filter,
|
||||
interpolatedQuery.host.filter,
|
||||
interpolatedQuery.application.filter,
|
||||
interpolatedQuery.itemTag.filter
|
||||
);
|
||||
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
|
||||
const groupFilter = datasource.replaceTemplateVars(query.group?.filter);
|
||||
const hostFilter = datasource.replaceTemplateVars(query.host?.filter);
|
||||
const appFilter = datasource.replaceTemplateVars(query.application?.filter);
|
||||
const tagFilter = datasource.replaceTemplateVars(query.itemTag?.filter);
|
||||
const groupFilter = interpolatedQuery.group?.filter;
|
||||
const hostFilter = interpolatedQuery.host?.filter;
|
||||
const appFilter = interpolatedQuery.application?.filter;
|
||||
const tagFilter = interpolatedQuery.itemTag?.filter;
|
||||
|
||||
useEffect(() => {
|
||||
fetchGroups();
|
||||
|
||||
@@ -11,6 +11,7 @@ import { itemTagToString } from '../../utils';
|
||||
import { ZabbixDatasource } from '../../datasource';
|
||||
import { ZabbixMetricsQuery } from '../../types/query';
|
||||
import { ZBXItem, ZBXItemTag } from '../../types';
|
||||
import { useInterpolatedQuery } from '../../hooks/useInterpolatedQuery';
|
||||
|
||||
const countByOptions: Array<SelectableValue<string>> = [
|
||||
{ value: '', label: 'All triggers' },
|
||||
@@ -34,6 +35,8 @@ export interface Props {
|
||||
}
|
||||
|
||||
export const TriggersQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
const interpolatedQuery = useInterpolatedQuery(datasource, query);
|
||||
|
||||
const loadGroupOptions = async () => {
|
||||
const groups = await datasource.zabbix.getAllGroups();
|
||||
const options = groups?.map((group) => ({
|
||||
@@ -50,8 +53,7 @@ export const TriggersQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
}, []);
|
||||
|
||||
const loadHostOptions = async (group: string) => {
|
||||
const groupFilter = datasource.replaceTemplateVars(group);
|
||||
const hosts = await datasource.zabbix.getAllHosts(groupFilter);
|
||||
const hosts = await datasource.zabbix.getAllHosts(group);
|
||||
let options: Array<SelectableValue<string>> = hosts?.map((host) => ({
|
||||
value: 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 options = await loadHostOptions(query.group.filter);
|
||||
const options = await loadHostOptions(interpolatedQuery.group.filter);
|
||||
return options;
|
||||
}, [query.group.filter]);
|
||||
}, [interpolatedQuery.group.filter]);
|
||||
|
||||
const loadAppOptions = async (group: string, host: string) => {
|
||||
const groupFilter = datasource.replaceTemplateVars(group);
|
||||
const hostFilter = datasource.replaceTemplateVars(host);
|
||||
const apps = await datasource.zabbix.getAllApps(groupFilter, hostFilter);
|
||||
const apps = await datasource.zabbix.getAllApps(group, host);
|
||||
let options: Array<SelectableValue<string>> = apps?.map((app) => ({
|
||||
value: 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 options = await loadAppOptions(query.group.filter, query.host.filter);
|
||||
const options = await loadAppOptions(interpolatedQuery.group.filter, interpolatedQuery.host.filter);
|
||||
return options;
|
||||
}, [query.group.filter, query.host.filter]);
|
||||
}, [interpolatedQuery.group.filter, interpolatedQuery.host.filter]);
|
||||
|
||||
const loadTagOptions = async (group: string, host: string) => {
|
||||
const tagsAvailable = await datasource.zabbix.isZabbix54OrHigher();
|
||||
if (!tagsAvailable) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const groupFilter = datasource.replaceTemplateVars(group);
|
||||
const hostFilter = datasource.replaceTemplateVars(host);
|
||||
const items = await datasource.zabbix.getAllItems(groupFilter, hostFilter, null, null, {});
|
||||
const items = await datasource.zabbix.getAllItems(group, host, null, null, {});
|
||||
const tags: ZBXItemTag[] = _.flatten(items.map((item: ZBXItem) => item.tags || []));
|
||||
|
||||
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 options = await loadTagOptions(query.group.filter, query.host.filter);
|
||||
const options = await loadTagOptions(interpolatedQuery.group.filter, interpolatedQuery.host.filter);
|
||||
return options;
|
||||
}, [query.group.filter, query.host.filter]);
|
||||
}, [interpolatedQuery.group.filter, interpolatedQuery.host.filter]);
|
||||
|
||||
const loadProxyOptions = async () => {
|
||||
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 groupFilter = datasource.replaceTemplateVars(group);
|
||||
const hostFilter = datasource.replaceTemplateVars(host);
|
||||
const appFilter = datasource.replaceTemplateVars(app);
|
||||
const tagFilter = datasource.replaceTemplateVars(itemTag);
|
||||
const options = {
|
||||
itemtype: 'num',
|
||||
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) => ({
|
||||
value: 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 options = await loadItemOptions(
|
||||
query.group.filter,
|
||||
query.host.filter,
|
||||
query.application.filter,
|
||||
query.itemTag.filter
|
||||
interpolatedQuery.group.filter,
|
||||
interpolatedQuery.host.filter,
|
||||
interpolatedQuery.application.filter,
|
||||
interpolatedQuery.itemTag.filter
|
||||
);
|
||||
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
|
||||
const groupFilter = datasource.replaceTemplateVars(query.group?.filter);
|
||||
const hostFilter = datasource.replaceTemplateVars(query.host?.filter);
|
||||
const appFilter = datasource.replaceTemplateVars(query.application?.filter);
|
||||
const tagFilter = datasource.replaceTemplateVars(query.itemTag?.filter);
|
||||
const groupFilter = interpolatedQuery.group?.filter;
|
||||
const hostFilter = interpolatedQuery.host?.filter;
|
||||
const appFilter = interpolatedQuery.application?.filter;
|
||||
const tagFilter = interpolatedQuery.itemTag?.filter;
|
||||
|
||||
useEffect(() => {
|
||||
fetchGroups();
|
||||
|
||||
@@ -9,6 +9,7 @@ import { MetricPicker } from '../../../components';
|
||||
import { getVariableOptions } from './utils';
|
||||
import { ZabbixDatasource } from '../../datasource';
|
||||
import { ZabbixMetricsQuery } from '../../types/query';
|
||||
import { useInterpolatedQuery } from '../../hooks/useInterpolatedQuery';
|
||||
|
||||
export interface Props {
|
||||
query: ZabbixMetricsQuery;
|
||||
@@ -17,6 +18,7 @@ export interface Props {
|
||||
}
|
||||
|
||||
export const UserMacrosQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
const interpolatedQuery = useInterpolatedQuery(datasource, query);
|
||||
const loadGroupOptions = async () => {
|
||||
const groups = await datasource.zabbix.getAllGroups();
|
||||
const options = groups?.map((group) => ({
|
||||
@@ -33,8 +35,7 @@ export const UserMacrosQueryEditor = ({ query, datasource, onChange }: Props) =>
|
||||
}, []);
|
||||
|
||||
const loadHostOptions = async (group: string) => {
|
||||
const groupFilter = datasource.replaceTemplateVars(group);
|
||||
const hosts = await datasource.zabbix.getAllHosts(groupFilter);
|
||||
const hosts = await datasource.zabbix.getAllHosts(group);
|
||||
let options: Array<SelectableValue<string>> = hosts?.map((host) => ({
|
||||
value: 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 options = await loadHostOptions(query.group.filter);
|
||||
const options = await loadHostOptions(interpolatedQuery.group.filter);
|
||||
return options;
|
||||
}, [query.group.filter]);
|
||||
}, [interpolatedQuery.group.filter]);
|
||||
|
||||
const loadMacrosOptions = async (group: string, host: string) => {
|
||||
const groupFilter = datasource.replaceTemplateVars(group);
|
||||
const hostFilter = datasource.replaceTemplateVars(host);
|
||||
const macros = await datasource.zabbix.getAllMacros(groupFilter, hostFilter);
|
||||
const macros = await datasource.zabbix.getAllMacros(group, host);
|
||||
let options: Array<SelectableValue<string>> = macros?.map((m) => ({
|
||||
value: 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 options = await loadMacrosOptions(query.group.filter, query.host.filter);
|
||||
const options = await loadMacrosOptions(interpolatedQuery.group.filter, interpolatedQuery.host.filter);
|
||||
return options;
|
||||
}, [query.group.filter, query.host.filter]);
|
||||
}, [interpolatedQuery.group.filter, interpolatedQuery.host.filter]);
|
||||
|
||||
// Update suggestions on every metric change
|
||||
const groupFilter = datasource.replaceTemplateVars(query.group?.filter);
|
||||
const hostFilter = datasource.replaceTemplateVars(query.host?.filter);
|
||||
const groupFilter = interpolatedQuery.group?.filter;
|
||||
const hostFilter = interpolatedQuery.host?.filter;
|
||||
|
||||
useEffect(() => {
|
||||
fetchGroups();
|
||||
|
||||
@@ -6,7 +6,6 @@ import * as utils from './utils';
|
||||
import * as migrations from './migrations';
|
||||
import * as metricFunctions from './metricFunctions';
|
||||
import * as c from './constants';
|
||||
import dataProcessor from './dataProcessor';
|
||||
import responseHandler from './responseHandler';
|
||||
import problemsHandler from './problemsHandler';
|
||||
import { Zabbix } from './zabbix/zabbix';
|
||||
@@ -18,28 +17,27 @@ import {
|
||||
BackendSrvRequest,
|
||||
getBackendSrv,
|
||||
getTemplateSrv,
|
||||
toDataQueryResponse,
|
||||
getDataSourceSrv,
|
||||
HealthCheckError,
|
||||
DataSourceWithBackend,
|
||||
TemplateSrv,
|
||||
} from '@grafana/runtime';
|
||||
import {
|
||||
DataFrame,
|
||||
dataFrameFromJSON,
|
||||
DataQueryRequest,
|
||||
DataQueryResponse,
|
||||
DataSourceApi,
|
||||
DataSourceInstanceSettings,
|
||||
FieldType,
|
||||
isDataFrame,
|
||||
LoadingState,
|
||||
ScopedVars,
|
||||
toDataFrame,
|
||||
} from '@grafana/data';
|
||||
import { AnnotationQueryEditor } from './components/AnnotationQueryEditor';
|
||||
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;
|
||||
basicAuth: any;
|
||||
withCredentials: any;
|
||||
@@ -59,9 +57,10 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
||||
instanceSettings: DataSourceInstanceSettings<ZabbixDSOptions>;
|
||||
zabbix: Zabbix;
|
||||
|
||||
replaceTemplateVars: (target: any, scopedVars?: any) => any;
|
||||
|
||||
constructor(instanceSettings: DataSourceInstanceSettings<ZabbixDSOptions>) {
|
||||
constructor(
|
||||
instanceSettings: DataSourceInstanceSettings<ZabbixDSOptions>,
|
||||
private readonly templateSrv: TemplateSrv = getTemplateSrv()
|
||||
) {
|
||||
super(instanceSettings);
|
||||
|
||||
this.instanceSettings = instanceSettings;
|
||||
@@ -72,10 +71,6 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
||||
prepareAnnotation: migrations.prepareAnnotation,
|
||||
};
|
||||
|
||||
// Use custom format for template variables
|
||||
const templateSrv = getTemplateSrv();
|
||||
this.replaceTemplateVars = _.partial(replaceTemplateVars, templateSrv);
|
||||
|
||||
// General data source settings
|
||||
this.datasourceId = instanceSettings.id;
|
||||
this.name = instanceSettings.name;
|
||||
@@ -120,13 +115,12 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
||||
////////////////////////
|
||||
// Datasource methods //
|
||||
////////////////////////
|
||||
|
||||
/**
|
||||
* Query panel data. Calls for each panel in dashboard.
|
||||
* @param {Object} request Contains time range, targets and other info.
|
||||
* @return {Object} Grafana metrics object with timeseries data for each target.
|
||||
*/
|
||||
query(request: DataQueryRequest<ZabbixMetricsQuery>) {
|
||||
query(request: DataQueryRequest<ZabbixMetricsQuery>): Observable<DataQueryResponse> {
|
||||
trackRequest(request);
|
||||
|
||||
// Migrate old targets
|
||||
@@ -144,104 +138,33 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
||||
return target;
|
||||
});
|
||||
|
||||
const backendResponsePromise = this.backendQuery({ ...request, targets: requestTargets });
|
||||
const dbConnectionResponsePromise = this.dbConnectionQuery({ ...request, targets: requestTargets });
|
||||
const frontendResponsePromise = this.frontendQuery({ ...request, targets: requestTargets });
|
||||
const annotationResposePromise = this.annotationRequest({ ...request, targets: requestTargets });
|
||||
const interpolatedTargets = this.interpolateVariablesInQueries(requestTargets, request.scopedVars);
|
||||
const backendResponse = super.query({ ...request, targets: interpolatedTargets.filter(this.isBackendTarget) });
|
||||
const dbConnectionResponsePromise = this.dbConnectionQuery({ ...request, targets: interpolatedTargets });
|
||||
const frontendResponsePromise = this.frontendQuery({ ...request, targets: interpolatedTargets });
|
||||
const annotationResposePromise = this.annotationRequest({ ...request, targets: interpolatedTargets });
|
||||
|
||||
return Promise.all([
|
||||
backendResponsePromise,
|
||||
dbConnectionResponsePromise,
|
||||
frontendResponsePromise,
|
||||
annotationResposePromise,
|
||||
]).then((rsp) => {
|
||||
// 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,
|
||||
};
|
||||
const applyMergeQueries = (queryResponse: DataQueryResponse) =>
|
||||
this.mergeQueries(queryResponse, dbConnectionResponsePromise, frontendResponsePromise, annotationResposePromise);
|
||||
const applyFEFuncs = (queryResponse: DataQueryResponse) =>
|
||||
this.applyFrontendFunctions(queryResponse, {
|
||||
...request,
|
||||
targets: interpolatedTargets.filter(this.isBackendTarget),
|
||||
});
|
||||
}
|
||||
|
||||
async backendQuery(request: DataQueryRequest<any>): Promise<DataQueryResponse> {
|
||||
const { intervalMs, maxDataPoints, range, requestId } = request;
|
||||
const targets = request.targets.filter(this.isBackendTarget);
|
||||
|
||||
// Add range variables
|
||||
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 backendResponse.pipe(
|
||||
map(applyFEFuncs),
|
||||
map(responseHandler.convertZabbixUnits),
|
||||
map(this.convertToWide),
|
||||
map(applyMergeQueries)
|
||||
);
|
||||
|
||||
// Return early if no queries exist
|
||||
if (!queries.length) {
|
||||
return Promise.resolve({ data: [] });
|
||||
}
|
||||
|
||||
const body: any = { queries };
|
||||
|
||||
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> {
|
||||
async frontendQuery(request: DataQueryRequest<ZabbixMetricsQuery>): Promise<DataQueryResponse> {
|
||||
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) => {
|
||||
// Don't request for hidden targets
|
||||
if (target.hide) {
|
||||
@@ -250,7 +173,6 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
||||
|
||||
// Add range variables
|
||||
request.scopedVars = Object.assign({}, request.scopedVars, utils.getRangeScopedVars(request.range));
|
||||
this.replaceTargetVariables(target, request);
|
||||
const timeRange = this.buildTimeRange(request, target);
|
||||
|
||||
if (target.queryType === c.MODE_TEXT) {
|
||||
@@ -262,7 +184,8 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
||||
return this.queryTextData(target, timeRange);
|
||||
} else if (target.queryType === c.MODE_ITSERVICE) {
|
||||
// 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) {
|
||||
// Triggers query
|
||||
return this.queryTriggersData(target, timeRange, request);
|
||||
@@ -311,7 +234,6 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
||||
|
||||
// Add range variables
|
||||
request.scopedVars = Object.assign({}, request.scopedVars, utils.getRangeScopedVars(request.range));
|
||||
this.replaceTargetVariables(target, request);
|
||||
const timeRange = this.buildTimeRange(request, 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);
|
||||
|
||||
// Apply Time-related functions (timeShift(), etc)
|
||||
const timeFunctions = bindFunctionDefs(target.functions, 'Time');
|
||||
const timeFunctions = utils.bindFunctionDefs(target.functions, 'Time');
|
||||
if (timeFunctions.length) {
|
||||
const [time_from, time_to] = utils.sequence(timeFunctions)([timeFrom, timeTo]);
|
||||
timeFrom = time_from;
|
||||
@@ -377,7 +299,7 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
||||
async queryNumericDataForItems(items, target: ZabbixMetricsQuery, timeRange, useTrends, request) {
|
||||
let history;
|
||||
request.valueType = this.getTrendValueType(target);
|
||||
request.consolidateBy = getConsolidateBy(target) || request.valueType;
|
||||
request.consolidateBy = utils.getConsolidateBy(target) || request.valueType;
|
||||
|
||||
if (useTrends) {
|
||||
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>) {
|
||||
for (let i = 0; i < response.data.length; i++) {
|
||||
const frame: DataFrame = response.data[i];
|
||||
const target = getRequestTarget(request, frame.refId);
|
||||
const target = utils.getRequestTarget(request, frame.refId);
|
||||
|
||||
// Apply alias functions
|
||||
const aliasFunctions = bindFunctionDefs(target.functions, 'Alias');
|
||||
const aliasFunctions = utils.bindFunctionDefs(target.functions, 'Alias');
|
||||
utils.sequence(aliasFunctions)(frame);
|
||||
}
|
||||
return response;
|
||||
@@ -489,7 +411,7 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
||||
queryItemIdData(target, timeRange, useTrends, options) {
|
||||
let itemids = target.itemids;
|
||||
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());
|
||||
|
||||
if (!itemids) {
|
||||
@@ -504,34 +426,26 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
||||
/**
|
||||
* 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
|
||||
if (target.hide || (!(target as any).itservice && !target.itServiceFilter) || !target.slaProperty) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let itServiceFilter;
|
||||
request.isOldVersion = (target as any).itservice && !target.itServiceFilter;
|
||||
|
||||
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) {
|
||||
let itservices = await this.zabbix.getITServices(target.itServiceFilter);
|
||||
if (isOldVersion) {
|
||||
itservices = _.filter(itservices, { serviceid: (target as any).itservice?.serviceid });
|
||||
}
|
||||
if (target.slaFilter !== undefined) {
|
||||
const slaFilter = this.replaceTemplateVars(target.slaFilter, request.scopedVars);
|
||||
const slas = await this.zabbix.getSLAs(slaFilter);
|
||||
const slas = await this.zabbix.getSLAs(target.slaFilter);
|
||||
const result = await this.zabbix.getSLI(itservices, slas, timeRange, target, request);
|
||||
// Apply alias functions
|
||||
const aliasFunctions = bindFunctionDefs(target.functions, 'Alias');
|
||||
const aliasFunctions = utils.bindFunctionDefs(target.functions, 'Alias');
|
||||
utils.sequence(aliasFunctions)(result);
|
||||
return result;
|
||||
}
|
||||
@@ -568,11 +482,10 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
||||
|
||||
const hostids = hosts?.map((h) => h.hostid);
|
||||
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);
|
||||
// replaceTemplateVars() builds regex-like string, so we should trim it.
|
||||
const tagsFilterStr = tagsFilter.replace('/^', '').replace('$/', '');
|
||||
// variable interpolation builds regex-like string, so we should trim it.
|
||||
const tagsFilterStr = target.tags.filter.replace('/^', '').replace('$/', '');
|
||||
const tags = utils.parseTags(tagsFilterStr);
|
||||
tags.forEach((tag) => {
|
||||
// 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([]);
|
||||
}
|
||||
|
||||
const options = getTriggersOptions(target, timeRange);
|
||||
const options = utils.getTriggersOptions(target, timeRange);
|
||||
const alerts = await this.zabbix.getHostICAlerts(hostids, appids, itemids, options);
|
||||
return responseHandler.handleTriggersResponse(alerts, groups, timeRange, target);
|
||||
}
|
||||
|
||||
async queryTriggersPCData(target: ZabbixMetricsQuery, timeRange, request) {
|
||||
const [timeFrom, timeTo] = timeRange;
|
||||
const tagsFilter = this.replaceTemplateVars(target.tags?.filter, request.scopedVars);
|
||||
// replaceTemplateVars() builds regex-like string, so we should trim it.
|
||||
const tagsFilterStr = tagsFilter.replace('/^', '').replace('$/', '');
|
||||
// variable interpolation builds regex-like string, so we should trim it.
|
||||
const tagsFilterStr = target.tags.filter.replace('/^', '').replace('$/', '');
|
||||
const tags = utils.parseTags(tagsFilterStr);
|
||||
tags.forEach((tag) => {
|
||||
// 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() : () => [];
|
||||
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.
|
||||
const tagsFilterStr = tagsFilter.replace('/^', '').replace('$/', '');
|
||||
const tagsFilterStr = target.tags.filter.replace('/^', '').replace('$/', '');
|
||||
const tags = utils.parseTags(tagsFilterStr);
|
||||
tags.forEach((tag) => {
|
||||
// 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.timeTo = timeTo;
|
||||
getProblemsPromise = this.zabbix.getProblemsHistory(
|
||||
groupFilter,
|
||||
hostFilter,
|
||||
appFilter,
|
||||
proxyFilter,
|
||||
target.group.filter,
|
||||
target.host.filter,
|
||||
target.application.filter,
|
||||
target.proxy.filter,
|
||||
problemsOptions
|
||||
);
|
||||
} 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();
|
||||
|
||||
@@ -754,7 +657,7 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
||||
})
|
||||
.then((problems) => problemsHandler.setMaintenanceStatus(problems))
|
||||
.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.addTriggerDataSource(problems, target))
|
||||
.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.
|
||||
*/
|
||||
async testDatasource() {
|
||||
const backendDS = new DataSourceWithBackend(this.instanceSettings);
|
||||
try {
|
||||
const testResult = await backendDS.testDatasource();
|
||||
const testResult = await super.testDatasource();
|
||||
return this.zabbix.testDataSource().then((dbConnectorStatus) => {
|
||||
let message = testResult.message;
|
||||
if (dbConnectorStatus) {
|
||||
@@ -839,7 +741,7 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -878,7 +780,7 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const groupFilter = this.replaceTemplateVars(annotation.group.filter, {});
|
||||
const hostFilter = this.replaceTemplateVars(annotation.host.filter, {});
|
||||
const appFilter = this.replaceTemplateVars(annotation.application.filter, {});
|
||||
const groupFilter = annotation.group.filter;
|
||||
const hostFilter = annotation.host.filter;
|
||||
const appFilter = annotation.application.filter;
|
||||
const proxyFilter = undefined;
|
||||
|
||||
return this.zabbix
|
||||
.getProblemsHistory(groupFilter, hostFilter, appFilter, proxyFilter, problemsOptions)
|
||||
.then((problems) => {
|
||||
// Filter triggers by description
|
||||
const problemName = this.replaceTemplateVars(annotation.trigger.filter, {});
|
||||
const problemName = annotation.trigger.filter;
|
||||
if (utils.isRegex(problemName)) {
|
||||
problems = _.filter(problems, (p) => {
|
||||
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) {
|
||||
if (target.options.useTrends === 'false') {
|
||||
return false;
|
||||
@@ -1030,108 +903,89 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
||||
isDBConnectionTarget = (target: any): boolean => {
|
||||
return this.enableDirectDBConnection && (target.queryType === c.MODE_METRICS || target.queryType === c.MODE_ITEMID);
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
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];
|
||||
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);
|
||||
}
|
||||
return consolidateBy;
|
||||
}
|
||||
|
||||
function formatMetric(metricObj) {
|
||||
if (annotationRes.data) {
|
||||
queryResponse.data = queryResponse.data.concat(annotationRes.data);
|
||||
}
|
||||
});
|
||||
return queryResponse;
|
||||
}
|
||||
|
||||
convertToWide(response: DataQueryResponse) {
|
||||
if (responseHandler.isConvertibleToWide(response.data)) {
|
||||
response.data = responseHandler.convertToWide(response.data);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
interpolateVariablesInQueries(queries: ZabbixMetricsQuery[], scopedVars: ScopedVars): ZabbixMetricsQuery[] {
|
||||
if (!queries || queries.length === 0) {
|
||||
return [];
|
||||
}
|
||||
return queries.map((query) => {
|
||||
// backwardsCompatibility
|
||||
const isOldVersion: boolean = (query as any).itservice && !query.itServiceFilter;
|
||||
return {
|
||||
text: metricObj.name,
|
||||
expandable: false,
|
||||
...query,
|
||||
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 { datasourceSrvMock, templateSrvMock } from '../../test-setup/mocks';
|
||||
import { replaceTemplateVars, ZabbixDatasource, zabbixTemplateFormat } from '../datasource';
|
||||
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(
|
||||
'@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: () => ({
|
||||
datasourceRequest: jest.fn().mockResolvedValue({ data: { result: '' } }),
|
||||
fetch: () => ({
|
||||
@@ -20,7 +60,8 @@ jest.mock(
|
||||
replace: jest.fn().mockImplementation((query) => query),
|
||||
}),
|
||||
reportInteraction: jest.fn(),
|
||||
}),
|
||||
};
|
||||
},
|
||||
{ virtual: true }
|
||||
);
|
||||
|
||||
@@ -28,6 +69,24 @@ jest.mock('../components/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', () => {
|
||||
let ctx: any = {};
|
||||
let consoleSpy: jest.SpyInstance;
|
||||
@@ -101,7 +160,7 @@ describe('ZabbixDatasource', () => {
|
||||
item: { filter: 'System information' },
|
||||
textFilter: '',
|
||||
useCaptureGroups: true,
|
||||
queryType: 2,
|
||||
queryType: '2',
|
||||
resultFormat: 'table',
|
||||
options: {
|
||||
skipEmptyValues: false,
|
||||
@@ -110,25 +169,18 @@ describe('ZabbixDatasource', () => {
|
||||
];
|
||||
});
|
||||
|
||||
it('should return data in table format', (done) => {
|
||||
ctx.ds.query(ctx.options).then((result) => {
|
||||
it('should return data in table format', async () => {
|
||||
const result = (await ctx.ds.frontendQuery(ctx.options)) as DataQueryResponse;
|
||||
expect(result.data.length).toBe(1);
|
||||
|
||||
let tableData = result.data[0];
|
||||
expect(tableData.columns).toEqual([
|
||||
{ text: 'Host' },
|
||||
{ text: 'Item' },
|
||||
{ text: 'Key' },
|
||||
{ text: 'Last value' },
|
||||
]);
|
||||
expect(tableData.columns).toEqual([{ text: 'Host' }, { text: 'Item' }, { text: 'Key' }, { text: 'Last value' }]);
|
||||
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) => {
|
||||
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];
|
||||
expect(tableData.rows[0][3]).toEqual('last');
|
||||
done();
|
||||
@@ -163,7 +215,7 @@ describe('ZabbixDatasource', () => {
|
||||
{ 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];
|
||||
expect(tableData.rows.length).toBe(1);
|
||||
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', () => {
|
||||
beforeEach(() => {
|
||||
ctx.ds.replaceTemplateVars = (str) => str;
|
||||
ctx.ds.zabbix = {
|
||||
getGroups: jest.fn().mockReturnValue(Promise.resolve([])),
|
||||
getHosts: jest.fn().mockReturnValue(Promise.resolve([])),
|
||||
getApps: 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) => {
|
||||
jest.spyOn(utils, 'parseLegacyVariableQuery').mockImplementation((query: string) => {
|
||||
let group = '';
|
||||
if (query === '*') {
|
||||
group = '/.*/';
|
||||
} else {
|
||||
group = query;
|
||||
}
|
||||
return {
|
||||
queryType: VariableQueryTypes.Group,
|
||||
group: group,
|
||||
};
|
||||
});
|
||||
|
||||
const tests = [
|
||||
{ query: '*', expect: '/.*/' },
|
||||
{ query: 'Backend', expect: 'Backend' },
|
||||
@@ -259,6 +276,14 @@ describe('ZabbixDatasource', () => {
|
||||
});
|
||||
|
||||
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 = [
|
||||
{ query: '*.*', expect: ['/.*/', '/.*/'] },
|
||||
{ query: '.', expect: ['', ''] },
|
||||
@@ -275,6 +300,15 @@ describe('ZabbixDatasource', () => {
|
||||
});
|
||||
|
||||
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 = [
|
||||
{ query: '*.*.*', expect: ['/.*/', '/.*/', '/.*/'] },
|
||||
{ query: '.*.', expect: ['', '/.*/', ''] },
|
||||
@@ -291,6 +325,16 @@ describe('ZabbixDatasource', () => {
|
||||
});
|
||||
|
||||
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 = [
|
||||
{ query: '*.*.*.*', expect: ['/.*/', '/.*/', '', undefined, '/.*/', { showDisabledItems: undefined }] },
|
||||
{ query: '.*.*.*', expect: ['', '/.*/', '', undefined, '/.*/', { showDisabledItems: undefined }] },
|
||||
@@ -320,6 +364,14 @@ describe('ZabbixDatasource', () => {
|
||||
});
|
||||
|
||||
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 = '*.*';
|
||||
|
||||
ctx.ds.metricFindQuery(query);
|
||||
@@ -329,7 +381,6 @@ describe('ZabbixDatasource', () => {
|
||||
|
||||
describe('When invoking metricFindQuery()', () => {
|
||||
beforeEach(() => {
|
||||
ctx.ds.replaceTemplateVars = (str) => str;
|
||||
ctx.ds.zabbix = {
|
||||
getGroups: jest.fn().mockReturnValue(Promise.resolve([{ name: 'Group1' }, { name: 'Group2' }])),
|
||||
getHosts: jest.fn().mockReturnValue(Promise.resolve([{ name: 'Host1' }, { name: 'Host2' }])),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import _ from 'lodash';
|
||||
import * as utils from '../utils';
|
||||
import { replaceTemplateVars, zabbixTemplateFormat } from '../utils';
|
||||
|
||||
describe('Utils', () => {
|
||||
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 {
|
||||
DataFrame,
|
||||
DataQueryRequest,
|
||||
FieldType,
|
||||
getValueFormats,
|
||||
MappingType,
|
||||
rangeUtil,
|
||||
ScopedVars,
|
||||
TIME_SERIES_TIME_FIELD_NAME,
|
||||
ValueMapping,
|
||||
} 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
|
||||
@@ -200,24 +206,12 @@ function isContainsBraces(query) {
|
||||
}
|
||||
|
||||
// Pattern for testing regex
|
||||
export const regexPattern = /^\/(.*)\/([gmi]*)$/m;
|
||||
const regexPattern = /^\/(.*)\/([gmi]*)$/m;
|
||||
|
||||
export function isRegex(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) {
|
||||
const msRange = range.to.diff(range.from);
|
||||
const sRange = Math.round(msRange / 1000);
|
||||
@@ -254,7 +248,7 @@ export function parseItemInterval(interval: string): number {
|
||||
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 parsedInterval = intervalPattern.exec(interval);
|
||||
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()))`
|
||||
* @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.
|
||||
*/
|
||||
@@ -543,3 +485,125 @@ export function swap<T>(list: T[], n: number, k: number): T[] {
|
||||
}
|
||||
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