Build plugin with grafana toolkit (#1539)
* Use grafana toolkit template for building plugin * Fix linter and type errors * Update styles building * Fix sass deprecation warning * Remove empty js files produced by webpack building sass * Fix signing script * Replace classnames with cx * Fix data source config page * Use custom webpack config instead of overriding original one * Use gpx_ prefix for plugin executable * Remove unused configs * Roll back react hooks dependencies usage * Move plugin-specific ts config to root config file * Temporary do not use rst2html for function description tooltip * Remove unused code * remove unused dependencies * update react table dependency * Migrate tests to typescript * remove unused dependencies * Remove old webpack configs * Add sign target to makefile * Add magefile * Update CI test job * Update go packages * Update build instructions * Downgrade go version to 1.18 * Fix go version in ci * Fix metric picker * Add comment to webpack config * remove angular mocks * update bra config * Rename datasource-zabbix to datasource (fix mage build) * Add instructions for building backend with mage * Fix webpack targets * Fix ci backend tests * Add initial e2e tests * Fix e2e ci tests * Update docker compose for cypress tests * build grafana docker image * Fix docker stop task * CI: add Grafana compatibility check
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
import React, { FC } from 'react';
|
||||
import { locationService } from '@grafana/runtime';
|
||||
import { ExploreUrlState, TimeRange, urlUtil } from '@grafana/data';
|
||||
import { MODE_ITEMID, MODE_METRICS } from '../../datasource-zabbix/constants';
|
||||
import { MODE_ITEMID, MODE_METRICS } from '../../datasource/constants';
|
||||
import { ActionButton } from '../ActionButton/ActionButton';
|
||||
import { expandItemName } from '../../datasource-zabbix/utils';
|
||||
import { ProblemDTO } from '../../datasource-zabbix/types';
|
||||
import { expandItemName } from '../../datasource/utils';
|
||||
import { ProblemDTO } from '../../datasource/types';
|
||||
|
||||
interface Props {
|
||||
problem: ProblemDTO;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { css, cx } from '@emotion/css';
|
||||
import React, { FormEvent, useCallback, useEffect, useState, useRef } from 'react';
|
||||
import { ClickOutsideWrapper, Icon, Input, Spinner, useStyles2 } from '@grafana/ui';
|
||||
import { ClickOutsideWrapper, Input, Spinner, useStyles2 } from '@grafana/ui';
|
||||
import { MetricPickerMenu } from './MetricPickerMenu';
|
||||
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
|
||||
import { isRegex } from '../../datasource-zabbix/utils';
|
||||
import { isRegex } from '../../datasource/utils';
|
||||
|
||||
export interface Props {
|
||||
value: string;
|
||||
isLoading?: boolean;
|
||||
options: SelectableValue<string>[];
|
||||
options: Array<SelectableValue<string>>;
|
||||
width?: number;
|
||||
onChange: (value: string) => void;
|
||||
}
|
||||
@@ -18,7 +18,7 @@ export const MetricPicker = ({ value, options, isLoading, width, onChange }: Pro
|
||||
const [query, setQuery] = useState(value);
|
||||
const [filteredOptions, setFilteredOptions] = useState(options);
|
||||
const [selectedOptionIdx, setSelectedOptionIdx] = useState(-1);
|
||||
const [offset, setOffset] = useState({ vertical: 0, horizontal: 0 });
|
||||
const [offset] = useState({ vertical: 0, horizontal: 0 });
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const customStyles = useStyles2(getStyles);
|
||||
|
||||
@@ -50,7 +50,7 @@ export const MetricPicker = ({ value, options, isLoading, width, onChange }: Pro
|
||||
const newQuery = v?.currentTarget?.value;
|
||||
if (newQuery) {
|
||||
setQuery(newQuery);
|
||||
if (value != newQuery) {
|
||||
if (value !== newQuery) {
|
||||
const filtered = options.filter(
|
||||
(option) =>
|
||||
option.value?.toLowerCase().includes(newQuery.toLowerCase()) ||
|
||||
@@ -74,10 +74,7 @@ export const MetricPicker = ({ value, options, isLoading, width, onChange }: Pro
|
||||
};
|
||||
|
||||
const onBlurInternal = () => {
|
||||
if (!isOpen) {
|
||||
// Only call if menu isn't opened
|
||||
onChange(query);
|
||||
}
|
||||
onChange(query);
|
||||
};
|
||||
|
||||
const onKeyDown = (e: React.KeyboardEvent) => {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { CustomScrollbar, getSelectStyles, Icon, Tooltip, useStyles2, useTheme2
|
||||
import { MENU_MAX_HEIGHT } from './constants';
|
||||
|
||||
interface Props {
|
||||
options: SelectableValue<string>[];
|
||||
options: Array<SelectableValue<string>>;
|
||||
onSelect: (option: SelectableValue<string>) => void;
|
||||
offset: { vertical: number; horizontal: number };
|
||||
minWidth?: number;
|
||||
|
||||
@@ -10,7 +10,7 @@ import { MetricPicker } from '../../components';
|
||||
import { getVariableOptions } from './QueryEditor/utils';
|
||||
import { prepareAnnotation } from '../migrations';
|
||||
|
||||
const severityOptions: SelectableValue<number>[] = [
|
||||
const severityOptions: Array<SelectableValue<number>> = [
|
||||
{ value: 0, label: 'Not classified' },
|
||||
{ value: 1, label: 'Information' },
|
||||
{ value: 2, label: 'Warning' },
|
||||
@@ -46,7 +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);
|
||||
let options: SelectableValue<string>[] = hosts?.map((host) => ({
|
||||
let options: Array<SelectableValue<string>> = hosts?.map((host) => ({
|
||||
value: host.name,
|
||||
label: host.name,
|
||||
}));
|
||||
@@ -65,7 +65,7 @@ export const AnnotationQueryEditor = ({ annotation, onAnnotationChange, datasour
|
||||
const groupFilter = datasource.replaceTemplateVars(group);
|
||||
const hostFilter = datasource.replaceTemplateVars(host);
|
||||
const apps = await datasource.zabbix.getAllApps(groupFilter, hostFilter);
|
||||
let options: SelectableValue<string>[] = apps?.map((app) => ({
|
||||
let options: Array<SelectableValue<string>> = apps?.map((app) => ({
|
||||
value: app.name,
|
||||
label: app.name,
|
||||
}));
|
||||
@@ -44,23 +44,26 @@ export const ConfigEditor = (props: Props) => {
|
||||
if (options.jsonData.dbConnectionEnable) {
|
||||
if (!options.jsonData.dbConnectionDatasourceId) {
|
||||
const dsName = options.jsonData.dbConnectionDatasourceName;
|
||||
getDataSourceSrv().get(dsName)
|
||||
.then(ds => {
|
||||
if (ds) {
|
||||
const selectedDs = getDirectDBDatasources().find(dsOption => dsOption.id === ds.id);
|
||||
setSelectedDBDatasource({ label: selectedDs.name, value: selectedDs.id });
|
||||
setCurrentDSType(selectedDs.type);
|
||||
onOptionsChange({
|
||||
...options,
|
||||
jsonData: {
|
||||
...options.jsonData,
|
||||
dbConnectionDatasourceId: ds.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
getDataSourceSrv()
|
||||
.get(dsName)
|
||||
.then((ds) => {
|
||||
if (ds) {
|
||||
const selectedDs = getDirectDBDatasources().find((dsOption) => dsOption.id === ds.id);
|
||||
setSelectedDBDatasource({ label: selectedDs.name, value: selectedDs.id });
|
||||
setCurrentDSType(selectedDs.type);
|
||||
onOptionsChange({
|
||||
...options,
|
||||
jsonData: {
|
||||
...options.jsonData,
|
||||
dbConnectionDatasourceId: ds.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const selectedDs = getDirectDBDatasources().find(dsOption => dsOption.id === options.jsonData.dbConnectionDatasourceId);
|
||||
const selectedDs = getDirectDBDatasources().find(
|
||||
(dsOption) => dsOption.id === options.jsonData.dbConnectionDatasourceId
|
||||
);
|
||||
setSelectedDBDatasource({ label: selectedDs.name, value: selectedDs.id });
|
||||
setCurrentDSType(selectedDs.type);
|
||||
}
|
||||
@@ -89,7 +92,7 @@ export const ConfigEditor = (props: Props) => {
|
||||
/>
|
||||
</div>
|
||||
<div className="gf-form max-width-25">
|
||||
{options.secureJsonFields?.password ?
|
||||
{options.secureJsonFields?.password ? (
|
||||
<>
|
||||
<FormField
|
||||
labelWidth={7}
|
||||
@@ -100,7 +103,8 @@ export const ConfigEditor = (props: Props) => {
|
||||
placeholder="Configured"
|
||||
/>
|
||||
<Button onClick={resetSecureJsonField('password', options, onOptionsChange)}>Reset</Button>
|
||||
</> :
|
||||
</>
|
||||
) : (
|
||||
<FormField
|
||||
labelWidth={7}
|
||||
inputWidth={15}
|
||||
@@ -110,7 +114,7 @@ export const ConfigEditor = (props: Props) => {
|
||||
onChange={secureJsonDataChangeHandler('password', options, onOptionsChange)}
|
||||
required
|
||||
/>
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
<Switch
|
||||
label="Trends"
|
||||
@@ -118,34 +122,34 @@ export const ConfigEditor = (props: Props) => {
|
||||
checked={options.jsonData.trends}
|
||||
onChange={jsonDataSwitchHandler('trends', options, onOptionsChange)}
|
||||
/>
|
||||
{options.jsonData.trends &&
|
||||
<>
|
||||
<div className="gf-form">
|
||||
<FormField
|
||||
labelWidth={7}
|
||||
inputWidth={4}
|
||||
label="After"
|
||||
value={options.jsonData.trendsFrom || ''}
|
||||
placeholder="7d"
|
||||
onChange={jsonDataChangeHandler('trendsFrom', options, onOptionsChange)}
|
||||
tooltip="Time after which trends will be used.
|
||||
{options.jsonData.trends && (
|
||||
<>
|
||||
<div className="gf-form">
|
||||
<FormField
|
||||
labelWidth={7}
|
||||
inputWidth={4}
|
||||
label="After"
|
||||
value={options.jsonData.trendsFrom || ''}
|
||||
placeholder="7d"
|
||||
onChange={jsonDataChangeHandler('trendsFrom', options, onOptionsChange)}
|
||||
tooltip="Time after which trends will be used.
|
||||
Best practice is to set this value to your history storage period (7d, 30d, etc)."
|
||||
/>
|
||||
</div>
|
||||
<div className="gf-form">
|
||||
<FormField
|
||||
labelWidth={7}
|
||||
inputWidth={4}
|
||||
label="Range"
|
||||
value={options.jsonData.trendsRange || ''}
|
||||
placeholder="4d"
|
||||
onChange={jsonDataChangeHandler('trendsRange', options, onOptionsChange)}
|
||||
tooltip="Time range width after which trends will be used instead of history.
|
||||
/>
|
||||
</div>
|
||||
<div className="gf-form">
|
||||
<FormField
|
||||
labelWidth={7}
|
||||
inputWidth={4}
|
||||
label="Range"
|
||||
value={options.jsonData.trendsRange || ''}
|
||||
placeholder="4d"
|
||||
onChange={jsonDataChangeHandler('trendsRange', options, onOptionsChange)}
|
||||
tooltip="Time range width after which trends will be used instead of history.
|
||||
It's better to set this value in range of 4 to 7 days to prevent loading large amount of history data."
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="gf-form">
|
||||
<FormField
|
||||
labelWidth={7}
|
||||
@@ -183,33 +187,38 @@ export const ConfigEditor = (props: Props) => {
|
||||
checked={options.jsonData.dbConnectionEnable}
|
||||
onChange={jsonDataSwitchHandler('dbConnectionEnable', options, onOptionsChange)}
|
||||
/>
|
||||
{options.jsonData.dbConnectionEnable &&
|
||||
<>
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel width={9}>Data Source</InlineFormLabel>
|
||||
<Select
|
||||
width={32}
|
||||
options={getDirectDBDSOptions()}
|
||||
value={selectedDBDatasource}
|
||||
onChange={directDBDatasourceChanegeHandler(options, onOptionsChange, setSelectedDBDatasource, setCurrentDSType)}
|
||||
/>
|
||||
</div>
|
||||
{currentDSType === 'influxdb' &&
|
||||
<div className="gf-form">
|
||||
<FormField
|
||||
labelWidth={9}
|
||||
inputWidth={16}
|
||||
label="Retention Policy"
|
||||
value={options.jsonData.dbConnectionRetentionPolicy || ''}
|
||||
placeholder="Retention policy name"
|
||||
onChange={jsonDataChangeHandler('dbConnectionRetentionPolicy', options, onOptionsChange)}
|
||||
tooltip="Specify retention policy name for fetching long-term stored data (optional).
|
||||
{options.jsonData.dbConnectionEnable && (
|
||||
<>
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel width={9}>Data Source</InlineFormLabel>
|
||||
<Select
|
||||
width={32}
|
||||
options={getDirectDBDSOptions()}
|
||||
value={selectedDBDatasource}
|
||||
onChange={directDBDatasourceChanegeHandler(
|
||||
options,
|
||||
onOptionsChange,
|
||||
setSelectedDBDatasource,
|
||||
setCurrentDSType
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
{currentDSType === 'influxdb' && (
|
||||
<div className="gf-form">
|
||||
<FormField
|
||||
labelWidth={9}
|
||||
inputWidth={16}
|
||||
label="Retention Policy"
|
||||
value={options.jsonData.dbConnectionRetentionPolicy || ''}
|
||||
placeholder="Retention policy name"
|
||||
onChange={jsonDataChangeHandler('dbConnectionRetentionPolicy', options, onOptionsChange)}
|
||||
tooltip="Specify retention policy name for fetching long-term stored data (optional).
|
||||
Leave it blank if only default retention policy used."
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="gf-form-group">
|
||||
@@ -235,98 +244,102 @@ export const ConfigEditor = (props: Props) => {
|
||||
);
|
||||
};
|
||||
|
||||
const jsonDataChangeHandler = (
|
||||
key: keyof ZabbixDSOptions,
|
||||
value: DataSourceSettings<ZabbixDSOptions, ZabbixSecureJSONData>,
|
||||
onChange: Props['onOptionsChange']
|
||||
) => (
|
||||
event: React.SyntheticEvent<HTMLInputElement | HTMLSelectElement>
|
||||
) => {
|
||||
onChange({
|
||||
...value,
|
||||
jsonData: {
|
||||
...value.jsonData,
|
||||
[key]: event.currentTarget.value,
|
||||
},
|
||||
});
|
||||
};
|
||||
const jsonDataChangeHandler =
|
||||
(
|
||||
key: keyof ZabbixDSOptions,
|
||||
value: DataSourceSettings<ZabbixDSOptions, ZabbixSecureJSONData>,
|
||||
onChange: Props['onOptionsChange']
|
||||
) =>
|
||||
(event: React.SyntheticEvent<HTMLInputElement | HTMLSelectElement>) => {
|
||||
onChange({
|
||||
...value,
|
||||
jsonData: {
|
||||
...value.jsonData,
|
||||
[key]: event.currentTarget.value,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const jsonDataSwitchHandler = (
|
||||
key: keyof ZabbixDSOptions,
|
||||
value: DataSourceSettings<ZabbixDSOptions, ZabbixSecureJSONData>,
|
||||
onChange: Props['onOptionsChange']
|
||||
) => (
|
||||
event: React.SyntheticEvent<HTMLInputElement>
|
||||
) => {
|
||||
onChange({
|
||||
...value,
|
||||
jsonData: {
|
||||
...value.jsonData,
|
||||
[key]: (event.target as HTMLInputElement).checked,
|
||||
},
|
||||
});
|
||||
};
|
||||
const jsonDataSwitchHandler =
|
||||
(
|
||||
key: keyof ZabbixDSOptions,
|
||||
value: DataSourceSettings<ZabbixDSOptions, ZabbixSecureJSONData>,
|
||||
onChange: Props['onOptionsChange']
|
||||
) =>
|
||||
(event: React.SyntheticEvent<HTMLInputElement>) => {
|
||||
onChange({
|
||||
...value,
|
||||
jsonData: {
|
||||
...value.jsonData,
|
||||
[key]: (event.target as HTMLInputElement).checked,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const secureJsonDataChangeHandler = (
|
||||
key: keyof ZabbixDSOptions,
|
||||
value: DataSourceSettings<ZabbixDSOptions, ZabbixSecureJSONData>,
|
||||
onChange: Props['onOptionsChange']
|
||||
) => (
|
||||
event: React.SyntheticEvent<HTMLInputElement | HTMLSelectElement>
|
||||
) => {
|
||||
onChange({
|
||||
...value,
|
||||
secureJsonData: {
|
||||
...value.secureJsonData,
|
||||
[key]: event.currentTarget.value,
|
||||
},
|
||||
});
|
||||
};
|
||||
const secureJsonDataChangeHandler =
|
||||
(
|
||||
key: keyof ZabbixDSOptions,
|
||||
value: DataSourceSettings<ZabbixDSOptions, ZabbixSecureJSONData>,
|
||||
onChange: Props['onOptionsChange']
|
||||
) =>
|
||||
(event: React.SyntheticEvent<HTMLInputElement | HTMLSelectElement>) => {
|
||||
onChange({
|
||||
...value,
|
||||
secureJsonData: {
|
||||
...value.secureJsonData,
|
||||
[key]: event.currentTarget.value,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const resetSecureJsonField = (
|
||||
key: keyof ZabbixDSOptions,
|
||||
value: DataSourceSettings<ZabbixDSOptions, ZabbixSecureJSONData>,
|
||||
onChange: Props['onOptionsChange']
|
||||
) => (
|
||||
event: React.SyntheticEvent<HTMLButtonElement>
|
||||
) => {
|
||||
onChange({
|
||||
...value,
|
||||
secureJsonFields: {
|
||||
...value.secureJsonFields,
|
||||
[key]: false,
|
||||
},
|
||||
});
|
||||
};
|
||||
const resetSecureJsonField =
|
||||
(
|
||||
key: keyof ZabbixDSOptions,
|
||||
value: DataSourceSettings<ZabbixDSOptions, ZabbixSecureJSONData>,
|
||||
onChange: Props['onOptionsChange']
|
||||
) =>
|
||||
(event: React.SyntheticEvent<HTMLButtonElement>) => {
|
||||
onChange({
|
||||
...value,
|
||||
secureJsonFields: {
|
||||
...value.secureJsonFields,
|
||||
[key]: false,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const directDBDatasourceChanegeHandler = (
|
||||
options: DataSourceSettings<ZabbixDSOptions, ZabbixSecureJSONData>,
|
||||
onChange: Props['onOptionsChange'],
|
||||
setSelectedDS: React.Dispatch<any>,
|
||||
setSelectedDSType: React.Dispatch<any>,
|
||||
) => (
|
||||
value: SelectableValue<number>
|
||||
) => {
|
||||
const selectedDs = getDirectDBDatasources().find(dsOption => dsOption.id === value.value);
|
||||
setSelectedDS({ label: selectedDs.name, value: selectedDs.id });
|
||||
setSelectedDSType(selectedDs.type);
|
||||
onChange({
|
||||
...options,
|
||||
jsonData: {
|
||||
...options.jsonData,
|
||||
dbConnectionDatasourceId: value.value
|
||||
},
|
||||
});
|
||||
};
|
||||
const directDBDatasourceChanegeHandler =
|
||||
(
|
||||
options: DataSourceSettings<ZabbixDSOptions, ZabbixSecureJSONData>,
|
||||
onChange: Props['onOptionsChange'],
|
||||
setSelectedDS: React.Dispatch<any>,
|
||||
setSelectedDSType: React.Dispatch<any>
|
||||
) =>
|
||||
(value: SelectableValue<number>) => {
|
||||
const selectedDs = getDirectDBDatasources().find((dsOption) => dsOption.id === value.value);
|
||||
setSelectedDS({ label: selectedDs.name, value: selectedDs.id });
|
||||
setSelectedDSType(selectedDs.type);
|
||||
onChange({
|
||||
...options,
|
||||
jsonData: {
|
||||
...options.jsonData,
|
||||
dbConnectionDatasourceId: value.value,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const getDirectDBDatasources = () => {
|
||||
let dsList = (getDataSourceSrv() as any).getAll();
|
||||
dsList = dsList.filter(ds => SUPPORTED_SQL_DS.includes(ds.type));
|
||||
dsList = dsList.filter((ds) => SUPPORTED_SQL_DS.includes(ds.type));
|
||||
return dsList;
|
||||
};
|
||||
|
||||
const getDirectDBDSOptions = () => {
|
||||
const dsList = getDirectDBDatasources();
|
||||
const dsOpts: Array<SelectableValue<number>> = dsList.map(ds => ({ label: ds.name, value: ds.id, description: ds.type }));
|
||||
const dsOpts: Array<SelectableValue<number>> = dsList.map((ds) => ({
|
||||
label: ds.name,
|
||||
value: ds.id,
|
||||
description: ds.type,
|
||||
}));
|
||||
return dsOpts;
|
||||
};
|
||||
@@ -1,22 +1,7 @@
|
||||
import { css, cx } from '@emotion/css';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
|
||||
import {
|
||||
Button,
|
||||
ClickOutsideWrapper,
|
||||
ContextMenu,
|
||||
Dropdown,
|
||||
Icon,
|
||||
Input,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Portal,
|
||||
Segment,
|
||||
useStyles2,
|
||||
useTheme2,
|
||||
} from '@grafana/ui';
|
||||
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Button, ClickOutsideWrapper, Icon, Input, Menu, useStyles2, useTheme2 } from '@grafana/ui';
|
||||
import { FuncDef } from '../../types';
|
||||
import { getCategories } from '../../metricFunctions';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { Suspense } from 'react';
|
||||
import { Icon, Tooltip } from '@grafana/ui';
|
||||
import React from 'react';
|
||||
import { Icon } from '@grafana/ui';
|
||||
import { MetricFunc } from '../../types';
|
||||
|
||||
const DOCS_FUNC_REF_URL = 'https://alexanderzobnin.github.io/grafana-zabbix/reference/functions/';
|
||||
@@ -10,30 +10,7 @@ export interface FunctionEditorControlsProps {
|
||||
onRemove: (func: MetricFunc) => void;
|
||||
}
|
||||
|
||||
const FunctionDescription = React.lazy(async () => {
|
||||
// @ts-ignore
|
||||
const { default: rst2html } = await import(/* webpackChunkName: "rst2html" */ 'rst2html');
|
||||
return {
|
||||
default(props: { description?: string }) {
|
||||
return <div dangerouslySetInnerHTML={{ __html: rst2html(props.description ?? '') }} />;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const FunctionHelpButton = (props: { description?: string; name: string }) => {
|
||||
if (props.description) {
|
||||
let tooltip = (
|
||||
<Suspense fallback={<span>Loading description...</span>}>
|
||||
<FunctionDescription description={props.description} />
|
||||
</Suspense>
|
||||
);
|
||||
return (
|
||||
<Tooltip content={tooltip} placement={'bottom-end'}>
|
||||
<Icon className={props.description ? undefined : 'pointer'} name="question-circle" />
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Icon
|
||||
className="pointer"
|
||||
@@ -4,7 +4,7 @@ import { InlineField, InlineFieldRow, Select } from '@grafana/ui';
|
||||
import * as c from '../constants';
|
||||
import * as migrations from '../migrations';
|
||||
import { ZabbixDatasource } from '../datasource';
|
||||
import { MetricFunc, ShowProblemTypes, ZabbixDSOptions, ZabbixMetricsQuery, ZabbixQueryOptions } from '../types';
|
||||
import { ShowProblemTypes, ZabbixDSOptions, ZabbixMetricsQuery, ZabbixQueryOptions } from '../types';
|
||||
import { MetricsQueryEditor } from './QueryEditor/MetricsQueryEditor';
|
||||
import { QueryFunctionsEditor } from './QueryEditor/QueryFunctionsEditor';
|
||||
import { QueryOptionsEditor } from './QueryEditor/QueryOptionsEditor';
|
||||
@@ -136,10 +136,6 @@ export const QueryEditor = ({ query, datasource, onChange, onRunQuery }: ZabbixQ
|
||||
onChangeInternal({ ...query, options });
|
||||
};
|
||||
|
||||
const getSelectableValue = (value: string): SelectableValue<string> => {
|
||||
return { value, label: value };
|
||||
};
|
||||
|
||||
const renderMetricsEditor = () => {
|
||||
return (
|
||||
<>
|
||||
@@ -10,7 +10,7 @@ import { getVariableOptions } from './utils';
|
||||
import { ZabbixDatasource } from '../../datasource';
|
||||
import { ZabbixMetricsQuery } from '../../types';
|
||||
|
||||
const slaPropertyList: SelectableValue<string>[] = [
|
||||
const slaPropertyList: Array<SelectableValue<string>> = [
|
||||
{ label: 'Status', value: 'status' },
|
||||
{ label: 'SLA', value: 'sla' },
|
||||
{ label: 'OK time', value: 'okTime' },
|
||||
@@ -18,7 +18,7 @@ const slaPropertyList: SelectableValue<string>[] = [
|
||||
{ label: 'Down time', value: 'downtimeTime' },
|
||||
];
|
||||
|
||||
const slaIntervals: SelectableValue<string>[] = [
|
||||
const slaIntervals: Array<SelectableValue<string>> = [
|
||||
{ label: 'No interval', value: 'none' },
|
||||
{ label: 'Auto', value: 'auto' },
|
||||
{ label: '1 hour', value: '1h' },
|
||||
@@ -35,7 +35,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);
|
||||
let options: SelectableValue<string>[] = hosts?.map((host) => ({
|
||||
let options: Array<SelectableValue<string>> = hosts?.map((host) => ({
|
||||
value: host.name,
|
||||
label: host.name,
|
||||
}));
|
||||
@@ -54,7 +54,7 @@ export const MetricsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
const groupFilter = datasource.replaceTemplateVars(group);
|
||||
const hostFilter = datasource.replaceTemplateVars(host);
|
||||
const apps = await datasource.zabbix.getAllApps(groupFilter, hostFilter);
|
||||
let options: SelectableValue<string>[] = apps?.map((app) => ({
|
||||
let options: Array<SelectableValue<string>> = apps?.map((app) => ({
|
||||
value: app.name,
|
||||
label: app.name,
|
||||
}));
|
||||
@@ -78,7 +78,7 @@ export const MetricsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
showDisabledItems: query.options.showDisabledItems,
|
||||
};
|
||||
const items = await datasource.zabbix.getAllItems(groupFilter, hostFilter, appFilter, tagFilter, options);
|
||||
let itemOptions: SelectableValue<string>[] = items?.map((item) => ({
|
||||
let itemOptions: Array<SelectableValue<string>> = items?.map((item) => ({
|
||||
value: item.name,
|
||||
label: item.name,
|
||||
}));
|
||||
@@ -10,13 +10,13 @@ import { getVariableOptions } from './utils';
|
||||
import { ZabbixDatasource } from '../../datasource';
|
||||
import { ZabbixMetricsQuery } from '../../types';
|
||||
|
||||
const showProblemsOptions: SelectableValue<string>[] = [
|
||||
const showProblemsOptions: Array<SelectableValue<string>> = [
|
||||
{ label: 'Problems', value: 'problems' },
|
||||
{ label: 'Recent problems', value: 'recent' },
|
||||
{ label: 'History', value: 'history' },
|
||||
];
|
||||
|
||||
const severityOptions: SelectableValue<number>[] = [
|
||||
const severityOptions: Array<SelectableValue<number>> = [
|
||||
{ value: 0, label: 'Not classified' },
|
||||
{ value: 1, label: 'Information' },
|
||||
{ value: 2, label: 'Warning' },
|
||||
@@ -50,7 +50,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);
|
||||
let options: SelectableValue<string>[] = hosts?.map((host) => ({
|
||||
let options: Array<SelectableValue<string>> = hosts?.map((host) => ({
|
||||
value: host.name,
|
||||
label: host.name,
|
||||
}));
|
||||
@@ -69,7 +69,7 @@ export const ProblemsQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
const groupFilter = datasource.replaceTemplateVars(group);
|
||||
const hostFilter = datasource.replaceTemplateVars(host);
|
||||
const apps = await datasource.zabbix.getAllApps(groupFilter, hostFilter);
|
||||
let options: SelectableValue<string>[] = apps?.map((app) => ({
|
||||
let options: Array<SelectableValue<string>> = apps?.map((app) => ({
|
||||
value: app.name,
|
||||
label: app.name,
|
||||
}));
|
||||
@@ -34,7 +34,7 @@ export const QueryFunctionsEditor = ({ query, onChange }: Props) => {
|
||||
};
|
||||
|
||||
const onRemoveFunc = (func: MetricFunc) => {
|
||||
const functions = query.functions?.filter((f) => f != func);
|
||||
const functions = query.functions?.filter((f) => f !== func);
|
||||
onChange({ ...query, functions });
|
||||
};
|
||||
|
||||
@@ -14,13 +14,13 @@ import {
|
||||
import * as c from '../../constants';
|
||||
import { ZabbixQueryOptions } from '../../types';
|
||||
|
||||
const ackOptions: SelectableValue<number>[] = [
|
||||
const ackOptions: Array<SelectableValue<number>> = [
|
||||
{ label: 'all triggers', value: 2 },
|
||||
{ label: 'unacknowledged', value: 0 },
|
||||
{ label: 'acknowledged', value: 1 },
|
||||
];
|
||||
|
||||
const sortOptions: SelectableValue<string>[] = [
|
||||
const sortOptions: Array<SelectableValue<string>> = [
|
||||
{ label: 'Default', value: 'default' },
|
||||
{ label: 'Last change', value: 'lastchange' },
|
||||
{ label: 'Severity', value: 'severity' },
|
||||
@@ -35,7 +35,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);
|
||||
let options: SelectableValue<string>[] = hosts?.map((host) => ({
|
||||
let options: Array<SelectableValue<string>> = hosts?.map((host) => ({
|
||||
value: host.name,
|
||||
label: host.name,
|
||||
}));
|
||||
@@ -54,7 +54,7 @@ export const TextMetricsQueryEditor = ({ query, datasource, onChange }: Props) =
|
||||
const groupFilter = datasource.replaceTemplateVars(group);
|
||||
const hostFilter = datasource.replaceTemplateVars(host);
|
||||
const apps = await datasource.zabbix.getAllApps(groupFilter, hostFilter);
|
||||
let options: SelectableValue<string>[] = apps?.map((app) => ({
|
||||
let options: Array<SelectableValue<string>> = apps?.map((app) => ({
|
||||
value: app.name,
|
||||
label: app.name,
|
||||
}));
|
||||
@@ -78,7 +78,7 @@ export const TextMetricsQueryEditor = ({ query, datasource, onChange }: Props) =
|
||||
showDisabledItems: query.options.showDisabledItems,
|
||||
};
|
||||
const items = await datasource.zabbix.getAllItems(groupFilter, hostFilter, appFilter, tagFilter, options);
|
||||
let itemOptions: SelectableValue<string>[] = items?.map((item) => ({
|
||||
let itemOptions: Array<SelectableValue<string>> = items?.map((item) => ({
|
||||
value: item.name,
|
||||
label: item.name,
|
||||
}));
|
||||
@@ -10,7 +10,7 @@ import { getVariableOptions } from './utils';
|
||||
import { ZabbixDatasource } from '../../datasource';
|
||||
import { ZabbixMetricsQuery } from '../../types';
|
||||
|
||||
const severityOptions: SelectableValue<number>[] = [
|
||||
const severityOptions: Array<SelectableValue<number>> = [
|
||||
{ value: 0, label: 'Not classified' },
|
||||
{ value: 1, label: 'Information' },
|
||||
{ value: 2, label: 'Warning' },
|
||||
@@ -44,7 +44,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);
|
||||
let options: SelectableValue<string>[] = hosts?.map((host) => ({
|
||||
let options: Array<SelectableValue<string>> = hosts?.map((host) => ({
|
||||
value: host.name,
|
||||
label: host.name,
|
||||
}));
|
||||
@@ -63,7 +63,7 @@ export const TriggersQueryEditor = ({ query, datasource, onChange }: Props) => {
|
||||
const groupFilter = datasource.replaceTemplateVars(group);
|
||||
const hostFilter = datasource.replaceTemplateVars(host);
|
||||
const apps = await datasource.zabbix.getAllApps(groupFilter, hostFilter);
|
||||
let options: SelectableValue<string>[] = apps?.map((app) => ({
|
||||
let options: Array<SelectableValue<string>> = apps?.map((app) => ({
|
||||
value: app.name,
|
||||
label: app.name,
|
||||
}));
|
||||
@@ -69,7 +69,7 @@ function setAliasByRegex(alias: string, frame: DataFrame) {
|
||||
valueField.config.displayNameFromDS = extractText(valueField.config?.displayNameFromDS, alias);
|
||||
}
|
||||
frame.name = extractText(frame.name, alias);
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
console.error('Failed to apply RegExp:', error?.message || error);
|
||||
}
|
||||
return frame;
|
||||
@@ -79,7 +79,7 @@ function setAliasByRegex(alias: string, frame: DataFrame) {
|
||||
if (field.type !== FieldType.time) {
|
||||
try {
|
||||
field.config.displayNameFromDS = extractText(field.config?.displayNameFromDS, alias);
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
console.error('Failed to apply RegExp:', error?.message || error);
|
||||
}
|
||||
}
|
||||
@@ -48,11 +48,9 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
||||
|
||||
replaceTemplateVars: (target: any, scopedVars?: any) => any;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(instanceSettings: DataSourceInstanceSettings<ZabbixDSOptions>, private templateSrv) {
|
||||
constructor(instanceSettings: DataSourceInstanceSettings<ZabbixDSOptions>) {
|
||||
super(instanceSettings);
|
||||
|
||||
this.templateSrv = templateSrv;
|
||||
this.enableDebugLog = config.buildInfo.env === 'development';
|
||||
|
||||
this.annotations = {
|
||||
@@ -61,7 +59,8 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
||||
};
|
||||
|
||||
// Use custom format for template variables
|
||||
this.replaceTemplateVars = _.partial(replaceTemplateVars, this.templateSrv);
|
||||
const templateSrv = getTemplateSrv();
|
||||
this.replaceTemplateVars = _.partial(replaceTemplateVars, templateSrv);
|
||||
|
||||
// General data source settings
|
||||
this.datasourceId = instanceSettings.id;
|
||||
@@ -455,7 +454,8 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
||||
*/
|
||||
queryItemIdData(target, timeRange, useTrends, options) {
|
||||
let itemids = target.itemids;
|
||||
itemids = this.templateSrv.replace(itemids, options.scopedVars, zabbixItemIdsTemplateFormat);
|
||||
const templateSrv = getTemplateSrv();
|
||||
itemids = templateSrv.replace(itemids, options.scopedVars, zabbixItemIdsTemplateFormat);
|
||||
itemids = _.map(itemids.split(','), (itemid) => itemid.trim());
|
||||
|
||||
if (!itemids) {
|
||||
@@ -631,7 +631,7 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
||||
title: 'Success',
|
||||
message: message,
|
||||
};
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
if (error instanceof ZabbixAPIError) {
|
||||
return {
|
||||
status: 'error',
|
||||
@@ -824,6 +824,7 @@ 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) {
|
||||
@@ -836,15 +837,15 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
|
||||
}
|
||||
|
||||
if (target.itemids) {
|
||||
target.itemids = this.templateSrv.replace(target.itemids, options.scopedVars, zabbixItemIdsTemplateFormat);
|
||||
target.itemids = templateSrv.replace(target.itemids, options.scopedVars, zabbixItemIdsTemplateFormat);
|
||||
}
|
||||
|
||||
_.forEach(target.functions, (func) => {
|
||||
func.params = _.map(func.params, (param) => {
|
||||
if (typeof param === 'number') {
|
||||
return +this.templateSrv.replace(param.toString(), options.scopedVars);
|
||||
return +templateSrv.replace(param.toString(), options.scopedVars);
|
||||
} else {
|
||||
return this.templateSrv.replace(param, options.scopedVars);
|
||||
return templateSrv.replace(param, options.scopedVars);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -935,7 +936,7 @@ function zabbixItemIdsTemplateFormat(value) {
|
||||
* $variable -> a|b|c -> /a|b|c/
|
||||
* /$variable/ -> /a|b|c/ -> /a|b|c/
|
||||
*/
|
||||
function replaceTemplateVars(templateSrv, target, scopedVars) {
|
||||
export function replaceTemplateVars(templateSrv, target, scopedVars) {
|
||||
let replacedTarget = templateSrv.replace(target, scopedVars, zabbixTemplateFormat);
|
||||
if (target && target !== replacedTarget && !utils.isRegex(replacedTarget)) {
|
||||
replacedTarget = '/^' + replacedTarget + '$/';
|
||||
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.4 KiB |
@@ -1,16 +1,8 @@
|
||||
import { DataSourcePlugin } from '@grafana/data';
|
||||
import { loadPluginCss } from '@grafana/runtime';
|
||||
import { ZabbixDatasource } from './datasource';
|
||||
import { QueryEditor } from './components/QueryEditor';
|
||||
import { ZabbixVariableQueryEditor } from './components/VariableQueryEditor';
|
||||
import { ConfigEditor } from './components/ConfigEditor';
|
||||
import '../sass/grafana-zabbix.dark.scss';
|
||||
import '../sass/grafana-zabbix.light.scss';
|
||||
|
||||
loadPluginCss({
|
||||
dark: 'plugins/alexanderzobnin-zabbix-app/css/grafana-zabbix.dark.css',
|
||||
light: 'plugins/alexanderzobnin-zabbix-app/css/grafana-zabbix.light.css',
|
||||
});
|
||||
|
||||
export const plugin = new DataSourcePlugin(ZabbixDatasource)
|
||||
.setConfigEditor(ConfigEditor)
|
||||
@@ -2,14 +2,11 @@
|
||||
"type": "datasource",
|
||||
"name": "Zabbix",
|
||||
"id": "alexanderzobnin-zabbix-datasource",
|
||||
|
||||
"metrics": true,
|
||||
"annotations": true,
|
||||
|
||||
"backend": true,
|
||||
"alerting": true,
|
||||
"executable": "../zabbix-plugin",
|
||||
|
||||
"executable": "../gpx_zabbix-plugin",
|
||||
"includes": [
|
||||
{
|
||||
"type": "dashboard",
|
||||
@@ -27,11 +24,9 @@
|
||||
"path": "dashboards/zabbix_server_dashboard.json"
|
||||
}
|
||||
],
|
||||
|
||||
"queryOptions": {
|
||||
"maxDataPoints": true
|
||||
},
|
||||
|
||||
"info": {
|
||||
"author": {
|
||||
"name": "Alexander Zobnin",
|
||||
@@ -1,5 +1,5 @@
|
||||
import _ from 'lodash';
|
||||
import * as utils from '../datasource-zabbix/utils';
|
||||
import * as utils from './utils';
|
||||
import { DataFrame, Field, FieldType, ArrayVector } from '@grafana/data';
|
||||
import { ZBXProblem, ZBXTrigger, ProblemDTO, ZBXEvent } from './types';
|
||||
|
||||
@@ -1,24 +1,30 @@
|
||||
import mocks from '../../test-setup/mocks';
|
||||
import { ZabbixDatasource, zabbixTemplateFormat } from "../datasource";
|
||||
import _ from 'lodash';
|
||||
import { templateSrvMock, datasourceSrvMock } from '../../test-setup/mocks';
|
||||
import { replaceTemplateVars, ZabbixDatasource, zabbixTemplateFormat } from '../datasource';
|
||||
import { dateMath } from '@grafana/data';
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
getBackendSrv: () => ({
|
||||
datasourceRequest: jest.fn().mockResolvedValue({ data: { result: '' } }),
|
||||
fetch: () => ({
|
||||
toPromise: () => jest.fn().mockResolvedValue({ data: { result: '' } })
|
||||
jest.mock(
|
||||
'@grafana/runtime',
|
||||
() => ({
|
||||
getBackendSrv: () => ({
|
||||
datasourceRequest: jest.fn().mockResolvedValue({ data: { result: '' } }),
|
||||
fetch: () => ({
|
||||
toPromise: () => jest.fn().mockResolvedValue({ data: { result: '' } }),
|
||||
}),
|
||||
}),
|
||||
getTemplateSrv: () => ({
|
||||
replace: jest.fn().mockImplementation((query) => query),
|
||||
}),
|
||||
}),
|
||||
loadPluginCss: () => {
|
||||
},
|
||||
}), { virtual: true });
|
||||
{ virtual: true }
|
||||
);
|
||||
|
||||
jest.mock('../components/AnnotationQueryEditor', () => ({
|
||||
AnnotationQueryEditor: () => {},
|
||||
}));
|
||||
|
||||
describe('ZabbixDatasource', () => {
|
||||
let ctx = {};
|
||||
let ctx: any = {};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.instanceSettings = {
|
||||
@@ -29,82 +35,89 @@ describe('ZabbixDatasource', () => {
|
||||
trends: true,
|
||||
trendsFrom: '14d',
|
||||
trendsRange: '7d',
|
||||
dbConnectionEnable: false
|
||||
}
|
||||
dbConnectionEnable: false,
|
||||
},
|
||||
};
|
||||
|
||||
ctx.options = {
|
||||
targets: [
|
||||
{
|
||||
group: { filter: "" },
|
||||
host: { filter: "" },
|
||||
application: { filter: "" },
|
||||
item: { filter: "" }
|
||||
}
|
||||
group: { filter: '' },
|
||||
host: { filter: '' },
|
||||
application: { filter: '' },
|
||||
item: { filter: '' },
|
||||
},
|
||||
],
|
||||
range: {
|
||||
from: dateMath.parse('now-1h'),
|
||||
to: dateMath.parse('now')
|
||||
}
|
||||
to: dateMath.parse('now'),
|
||||
},
|
||||
};
|
||||
|
||||
ctx.templateSrv = mocks.templateSrvMock;
|
||||
ctx.datasourceSrv = mocks.datasourceSrvMock;
|
||||
ctx.datasourceSrv = datasourceSrvMock;
|
||||
|
||||
ctx.ds = new ZabbixDatasource(ctx.instanceSettings, ctx.templateSrv);
|
||||
ctx.ds = new ZabbixDatasource(ctx.instanceSettings);
|
||||
ctx.ds.templateSrv = templateSrvMock;
|
||||
});
|
||||
|
||||
describe('When querying text data', () => {
|
||||
beforeEach(() => {
|
||||
ctx.ds.replaceTemplateVars = (str) => str;
|
||||
ctx.ds.zabbix.zabbixAPI.getHistory = jest.fn().mockReturnValue(Promise.resolve([
|
||||
{ clock: "1500010200", itemid: "10100", ns: "900111000", value: "Linux first" },
|
||||
{ clock: "1500010300", itemid: "10100", ns: "900111000", value: "Linux 2nd" },
|
||||
{ clock: "1500010400", itemid: "10100", ns: "900111000", value: "Linux last" }
|
||||
]));
|
||||
ctx.ds.zabbix.zabbixAPI.getHistory = jest.fn().mockReturnValue(
|
||||
Promise.resolve([
|
||||
{ clock: '1500010200', itemid: '10100', ns: '900111000', value: 'Linux first' },
|
||||
{ clock: '1500010300', itemid: '10100', ns: '900111000', value: 'Linux 2nd' },
|
||||
{ clock: '1500010400', itemid: '10100', ns: '900111000', value: 'Linux last' },
|
||||
])
|
||||
);
|
||||
|
||||
ctx.ds.zabbix.getItemsFromTarget = jest.fn().mockReturnValue(Promise.resolve([
|
||||
ctx.ds.zabbix.getItemsFromTarget = jest.fn().mockReturnValue(
|
||||
Promise.resolve([
|
||||
{
|
||||
hosts: [{ hostid: '10001', name: 'Zabbix server' }],
|
||||
itemid: '10100',
|
||||
name: 'System information',
|
||||
key_: 'system.uname',
|
||||
},
|
||||
])
|
||||
);
|
||||
|
||||
ctx.options.targets = [
|
||||
{
|
||||
hosts: [{ hostid: "10001", name: "Zabbix server" }],
|
||||
itemid: "10100",
|
||||
name: "System information",
|
||||
key_: "system.uname",
|
||||
}
|
||||
]));
|
||||
|
||||
ctx.options.targets = [{
|
||||
group: { filter: "" },
|
||||
host: { filter: "Zabbix server" },
|
||||
application: { filter: "" },
|
||||
item: { filter: "System information" },
|
||||
textFilter: "",
|
||||
useCaptureGroups: true,
|
||||
queryType: 2,
|
||||
resultFormat: "table",
|
||||
options: {
|
||||
skipEmptyValues: false
|
||||
}
|
||||
}];
|
||||
group: { filter: '' },
|
||||
host: { filter: 'Zabbix server' },
|
||||
application: { filter: '' },
|
||||
item: { filter: 'System information' },
|
||||
textFilter: '',
|
||||
useCaptureGroups: true,
|
||||
queryType: 2,
|
||||
resultFormat: 'table',
|
||||
options: {
|
||||
skipEmptyValues: false,
|
||||
},
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
it('should return data in table format', (done) => {
|
||||
ctx.ds.query(ctx.options).then(result => {
|
||||
ctx.ds.query(ctx.options).then((result) => {
|
||||
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.rows).toEqual([
|
||||
['Zabbix server', 'System information', 'system.uname', 'Linux last']
|
||||
{ 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.options.targets[0].textFilter = 'Linux (.*)';
|
||||
ctx.ds.query(ctx.options).then((result) => {
|
||||
let tableData = result.data[0];
|
||||
expect(tableData.rows[0][3]).toEqual('last');
|
||||
done();
|
||||
@@ -112,26 +125,34 @@ describe('ZabbixDatasource', () => {
|
||||
});
|
||||
|
||||
it('should skip item when last value is empty', () => {
|
||||
ctx.ds.zabbix.getItemsFromTarget = jest.fn().mockReturnValue(Promise.resolve([
|
||||
{
|
||||
hosts: [{ hostid: "10001", name: "Zabbix server" }],
|
||||
itemid: "10100", name: "System information", key_: "system.uname"
|
||||
},
|
||||
{
|
||||
hosts: [{ hostid: "10002", name: "Server02" }],
|
||||
itemid: "90109", name: "System information", key_: "system.uname"
|
||||
}
|
||||
]));
|
||||
ctx.ds.zabbix.getItemsFromTarget = jest.fn().mockReturnValue(
|
||||
Promise.resolve([
|
||||
{
|
||||
hosts: [{ hostid: '10001', name: 'Zabbix server' }],
|
||||
itemid: '10100',
|
||||
name: 'System information',
|
||||
key_: 'system.uname',
|
||||
},
|
||||
{
|
||||
hosts: [{ hostid: '10002', name: 'Server02' }],
|
||||
itemid: '90109',
|
||||
name: 'System information',
|
||||
key_: 'system.uname',
|
||||
},
|
||||
])
|
||||
);
|
||||
|
||||
ctx.options.targets[0].options.skipEmptyValues = true;
|
||||
ctx.ds.zabbix.getHistory = jest.fn().mockReturnValue(Promise.resolve([
|
||||
{ clock: "1500010200", itemid: "10100", ns: "900111000", value: "Linux first" },
|
||||
{ clock: "1500010300", itemid: "10100", ns: "900111000", value: "Linux 2nd" },
|
||||
{ clock: "1500010400", itemid: "10100", ns: "900111000", value: "Linux last" },
|
||||
{ clock: "1500010200", itemid: "90109", ns: "900111000", value: "Non empty value" },
|
||||
{ clock: "1500010500", itemid: "90109", ns: "900111000", value: "" }
|
||||
]));
|
||||
return ctx.ds.query(ctx.options).then(result => {
|
||||
ctx.ds.zabbix.getHistory = jest.fn().mockReturnValue(
|
||||
Promise.resolve([
|
||||
{ clock: '1500010200', itemid: '10100', ns: '900111000', value: 'Linux first' },
|
||||
{ clock: '1500010300', itemid: '10100', ns: '900111000', value: 'Linux 2nd' },
|
||||
{ clock: '1500010400', itemid: '10100', ns: '900111000', value: 'Linux last' },
|
||||
{ clock: '1500010200', itemid: '90109', ns: '900111000', value: 'Non empty value' },
|
||||
{ clock: '1500010500', itemid: '90109', ns: '900111000', value: '' },
|
||||
])
|
||||
);
|
||||
return ctx.ds.query(ctx.options).then((result) => {
|
||||
let tableData = result.data[0];
|
||||
expect(tableData.rows.length).toBe(1);
|
||||
expect(tableData.rows[0][3]).toEqual('Linux last');
|
||||
@@ -140,11 +161,10 @@ describe('ZabbixDatasource', () => {
|
||||
});
|
||||
|
||||
describe('When replacing template variables', () => {
|
||||
|
||||
function testReplacingVariable(target, varValue, expectedResult, done) {
|
||||
ctx.ds.templateSrv.replace = () => {
|
||||
return zabbixTemplateFormat(varValue);
|
||||
};
|
||||
ctx.ds.replaceTemplateVars = _.partial(replaceTemplateVars, {
|
||||
replace: jest.fn((target) => zabbixTemplateFormat(varValue)),
|
||||
});
|
||||
|
||||
let result = ctx.ds.replaceTemplateVars(target);
|
||||
expect(result).toBe(expectedResult);
|
||||
@@ -198,7 +218,7 @@ describe('ZabbixDatasource', () => {
|
||||
getGroups: jest.fn().mockReturnValue(Promise.resolve([])),
|
||||
getHosts: jest.fn().mockReturnValue(Promise.resolve([])),
|
||||
getApps: jest.fn().mockReturnValue(Promise.resolve([])),
|
||||
getItems: jest.fn().mockReturnValue(Promise.resolve([]))
|
||||
getItems: jest.fn().mockReturnValue(Promise.resolve([])),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -218,7 +238,7 @@ describe('ZabbixDatasource', () => {
|
||||
});
|
||||
|
||||
it('should return empty list for empty query', (done) => {
|
||||
ctx.ds.metricFindQuery('').then(result => {
|
||||
ctx.ds.metricFindQuery('').then((result) => {
|
||||
expect(ctx.ds.zabbix.getGroups).toBeCalledTimes(0);
|
||||
ctx.ds.zabbix.getGroups.mockClear();
|
||||
|
||||
@@ -248,7 +268,7 @@ describe('ZabbixDatasource', () => {
|
||||
{ query: '*.*.*', expect: ['/.*/', '/.*/', '/.*/'] },
|
||||
{ query: '.*.', expect: ['', '/.*/', ''] },
|
||||
{ query: 'Backend.backend01.*', expect: ['Backend', 'backend01', '/.*/'] },
|
||||
{ query: 'Back*.*.', expect: ['Back*', '/.*/', ''] }
|
||||
{ query: 'Back*.*.', expect: ['Back*', '/.*/', ''] },
|
||||
];
|
||||
|
||||
for (const test of tests) {
|
||||
@@ -264,13 +284,18 @@ describe('ZabbixDatasource', () => {
|
||||
{ query: '*.*.*.*', expect: ['/.*/', '/.*/', '', null, '/.*/'] },
|
||||
{ query: '.*.*.*', expect: ['', '/.*/', '', null, '/.*/'] },
|
||||
{ query: 'Backend.backend01.*.*', expect: ['Backend', 'backend01', '', null, '/.*/'] },
|
||||
{ query: 'Back*.*.cpu.*', expect: ['Back*', '/.*/', 'cpu', null, '/.*/'] }
|
||||
{ query: 'Back*.*.cpu.*', expect: ['Back*', '/.*/', 'cpu', null, '/.*/'] },
|
||||
];
|
||||
|
||||
for (const test of tests) {
|
||||
ctx.ds.metricFindQuery(test.query);
|
||||
expect(ctx.ds.zabbix.getItems)
|
||||
.toBeCalledWith(test.expect[0], test.expect[1], test.expect[2], test.expect[3], test.expect[4]);
|
||||
expect(ctx.ds.zabbix.getItems).toBeCalledWith(
|
||||
test.expect[0],
|
||||
test.expect[1],
|
||||
test.expect[2],
|
||||
test.expect[3],
|
||||
test.expect[4]
|
||||
);
|
||||
ctx.ds.zabbix.getItems.mockClear();
|
||||
}
|
||||
done();
|
||||
@@ -3,14 +3,12 @@ import { compactQuery } from '../utils';
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
getDataSourceSrv: jest.fn(() => ({
|
||||
get: jest.fn().mockResolvedValue(
|
||||
{ id: 42, name: 'InfluxDB DS', meta: {} }
|
||||
),
|
||||
get: jest.fn().mockResolvedValue({ id: 42, name: 'InfluxDB DS', meta: {} }),
|
||||
})),
|
||||
}));
|
||||
|
||||
describe('InfluxDBConnector', () => {
|
||||
let ctx = {};
|
||||
let ctx: any = {};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.options = { datasourceName: 'InfluxDB DS', retentionPolicy: 'longterm' };
|
||||
@@ -20,7 +18,8 @@ describe('InfluxDBConnector', () => {
|
||||
itemids: ['123', '234'],
|
||||
range: { timeFrom: 15000, timeTill: 15100 },
|
||||
intervalSec: 5,
|
||||
table: 'history', aggFunction: 'MAX'
|
||||
table: 'history',
|
||||
aggFunction: 'MAX',
|
||||
};
|
||||
});
|
||||
|
||||
@@ -57,9 +56,7 @@ describe('InfluxDBConnector', () => {
|
||||
it('should query proper table depending on item type', () => {
|
||||
const { timeFrom, timeTill } = ctx.defaultQueryParams.range;
|
||||
const options = { intervalMs: 5000 };
|
||||
const items = [
|
||||
{ itemid: '123', value_type: 3 }
|
||||
];
|
||||
const items = [{ itemid: '123', value_type: 3 }];
|
||||
const expectedQuery = compactQuery(`SELECT MEAN("value")
|
||||
FROM "history_uint"
|
||||
WHERE ("itemid" = '123')
|
||||
@@ -97,9 +94,7 @@ describe('InfluxDBConnector', () => {
|
||||
ctx.influxDBConnector.retentionPolicy = '';
|
||||
const { timeFrom, timeTill } = ctx.defaultQueryParams.range;
|
||||
const options = { intervalMs: 5000 };
|
||||
const items = [
|
||||
{ itemid: '123', value_type: 3 }
|
||||
];
|
||||
const items = [{ itemid: '123', value_type: 3 }];
|
||||
const expectedQuery = compactQuery(`SELECT MEAN("value")
|
||||
FROM "history_uint"
|
||||
WHERE ("itemid" = '123')
|
||||
@@ -114,9 +109,7 @@ describe('InfluxDBConnector', () => {
|
||||
it('should use retention policy name for trends query if it was set', () => {
|
||||
const { timeFrom, timeTill } = ctx.defaultQueryParams.range;
|
||||
const options = { intervalMs: 5000 };
|
||||
const items = [
|
||||
{ itemid: '123', value_type: 3 }
|
||||
];
|
||||
const items = [{ itemid: '123', value_type: 3 }];
|
||||
const expectedQuery = compactQuery(`SELECT MEAN("value_avg")
|
||||
FROM "longterm"."history_uint"
|
||||
WHERE ("itemid" = '123')
|
||||
@@ -131,9 +124,7 @@ describe('InfluxDBConnector', () => {
|
||||
it('should use proper value column if retention policy set (trends used)', () => {
|
||||
const { timeFrom, timeTill } = ctx.defaultQueryParams.range;
|
||||
const options = { intervalMs: 5000, consolidateBy: 'max' };
|
||||
const items = [
|
||||
{ itemid: '123', value_type: 3 }
|
||||
];
|
||||
const items = [{ itemid: '123', value_type: 3 }];
|
||||
const expectedQuery = compactQuery(`SELECT MAX("value_max")
|
||||
FROM "longterm"."history_uint"
|
||||
WHERE ("itemid" = '123')
|
||||
@@ -2,15 +2,15 @@ import _ from 'lodash';
|
||||
import { migrateDSConfig, DS_CONFIG_SCHEMA } from '../migrations';
|
||||
|
||||
describe('Migrations', () => {
|
||||
let ctx = {};
|
||||
let ctx: any = {};
|
||||
|
||||
describe('When migrating datasource config', () => {
|
||||
beforeEach(() => {
|
||||
ctx.jsonData = {
|
||||
dbConnection: {
|
||||
enable: true,
|
||||
datasourceId: 1
|
||||
}
|
||||
datasourceId: 1,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -19,7 +19,7 @@ describe('Migrations', () => {
|
||||
expect(ctx.jsonData).toMatchObject({
|
||||
dbConnectionEnable: true,
|
||||
dbConnectionDatasourceId: 1,
|
||||
schema: DS_CONFIG_SCHEMA
|
||||
schema: DS_CONFIG_SCHEMA,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,13 +27,13 @@ describe('Migrations', () => {
|
||||
ctx.jsonData = {
|
||||
futureOptionOne: 'foo',
|
||||
futureOptionTwo: 'bar',
|
||||
schema: DS_CONFIG_SCHEMA
|
||||
schema: DS_CONFIG_SCHEMA,
|
||||
};
|
||||
migrateDSConfig(ctx.jsonData);
|
||||
expect(ctx.jsonData).toMatchObject({
|
||||
futureOptionOne: 'foo',
|
||||
futureOptionTwo: 'bar',
|
||||
schema: DS_CONFIG_SCHEMA
|
||||
schema: DS_CONFIG_SCHEMA,
|
||||
});
|
||||
expect(ctx.jsonData.dbConnectionEnable).toBeUndefined();
|
||||
expect(ctx.jsonData.dbConnectionDatasourceId).toBeUndefined();
|
||||
@@ -55,7 +55,7 @@ describe('Migrations', () => {
|
||||
disableReadOnlyUsersAck: true,
|
||||
dbConnectionEnable: true,
|
||||
dbConnectionDatasourceName: 'MySQL Zabbix',
|
||||
dbConnectionRetentionPolicy: 'one_year'
|
||||
dbConnectionRetentionPolicy: 'one_year',
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import _ from 'lodash';
|
||||
// eslint-disable-next-line
|
||||
import moment from 'moment';
|
||||
import * as c from './constants';
|
||||
import { VariableQuery, VariableQueryTypes, ZBXItemTag } from './types';
|
||||
@@ -507,12 +508,12 @@ export function isProblemsDataFrame(data: DataFrame): boolean {
|
||||
}
|
||||
|
||||
// Swap n and k elements.
|
||||
export function swap<T>(list: Array<T>, n: number, k: number): Array<T> {
|
||||
export function swap<T>(list: T[], n: number, k: number): T[] {
|
||||
if (list === null || list.length < 2 || k > list.length - 1 || k < 0 || n > list.length - 1 || n < 0) {
|
||||
return list;
|
||||
}
|
||||
|
||||
const newList: Array<T> = new Array(list.length);
|
||||
const newList: T[] = new Array(list.length);
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
if (i === n) {
|
||||
newList[i] = list[k];
|
||||
@@ -31,14 +31,6 @@ export const consolidateByTrendColumns = {
|
||||
sum: 'num*value_avg', // sum of sums inside the one-hour trend period
|
||||
};
|
||||
|
||||
export interface IDBConnector {
|
||||
getHistory(): any;
|
||||
|
||||
getTrends(): any;
|
||||
|
||||
testDataSource(): any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for external history database connectors. Subclasses should implement `getHistory()`, `getTrends()` and
|
||||
* `testDataSource()` methods, which describe how to fetch data from source other than Zabbix API.
|
||||
@@ -47,13 +39,13 @@ export class DBConnector {
|
||||
protected datasourceId: any;
|
||||
private datasourceName: any;
|
||||
protected datasourceTypeId: any;
|
||||
private datasourceTypeName: any;
|
||||
// private datasourceTypeName: any;
|
||||
|
||||
constructor(options) {
|
||||
this.datasourceId = options.datasourceId;
|
||||
this.datasourceName = options.datasourceName;
|
||||
this.datasourceTypeId = null;
|
||||
this.datasourceTypeName = null;
|
||||
// this.datasourceTypeName = null;
|
||||
}
|
||||
|
||||
static loadDatasource(dsId, dsName) {
|
||||
@@ -74,7 +66,7 @@ export class DBConnector {
|
||||
loadDBDataSource() {
|
||||
return DBConnector.loadDatasource(this.datasourceId, this.datasourceName).then((ds) => {
|
||||
this.datasourceTypeId = ds.meta.id;
|
||||
this.datasourceTypeName = ds.meta.name;
|
||||
// this.datasourceTypeName = ds.meta.name;
|
||||
if (!this.datasourceName) {
|
||||
this.datasourceName = ds.name;
|
||||
}
|
||||
@@ -2,12 +2,10 @@ import {
|
||||
ArrayVector,
|
||||
DataFrame,
|
||||
dataFrameToJSON,
|
||||
DataSourceApi,
|
||||
Field,
|
||||
FieldType,
|
||||
MutableDataFrame,
|
||||
TIME_SERIES_TIME_FIELD_NAME,
|
||||
toDataFrame,
|
||||
} from '@grafana/data';
|
||||
import _ from 'lodash';
|
||||
import { compactQuery } from '../../../utils';
|
||||
@@ -20,7 +20,7 @@ const roundInterval: (interval: number) => number = rangeUtil?.roundInterval ||
|
||||
*/
|
||||
export class ZabbixAPIConnector {
|
||||
backendAPIUrl: string;
|
||||
requestOptions: { basicAuth: any; withCredentials: boolean; };
|
||||
requestOptions: { basicAuth: any; withCredentials: boolean };
|
||||
getTrend: (items: any, timeFrom: any, timeTill: any) => Promise<any[]>;
|
||||
version: string;
|
||||
getVersionPromise: Promise<string>;
|
||||
@@ -32,7 +32,7 @@ export class ZabbixAPIConnector {
|
||||
|
||||
this.requestOptions = {
|
||||
basicAuth: basicAuth,
|
||||
withCredentials: withCredentials
|
||||
withCredentials: withCredentials,
|
||||
};
|
||||
|
||||
this.getTrend = this.getTrend_ZBXNEXT1193;
|
||||
@@ -58,7 +58,7 @@ export class ZabbixAPIConnector {
|
||||
url: this.backendAPIUrl,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
hideFromInspector: false,
|
||||
data: {
|
||||
@@ -90,7 +90,7 @@ export class ZabbixAPIConnector {
|
||||
initVersion(): Promise<string> {
|
||||
if (!this.getVersionPromise) {
|
||||
this.getVersionPromise = Promise.resolve(
|
||||
this.getVersion().then(version => {
|
||||
this.getVersion().then((version) => {
|
||||
if (version) {
|
||||
console.log(`Zabbix version detected: ${version}`);
|
||||
} else {
|
||||
@@ -122,7 +122,7 @@ export class ZabbixAPIConnector {
|
||||
const params: any = {
|
||||
eventids: eventid,
|
||||
message: message,
|
||||
action: action
|
||||
action: action,
|
||||
};
|
||||
|
||||
if (severity !== undefined) {
|
||||
@@ -136,7 +136,7 @@ export class ZabbixAPIConnector {
|
||||
const params = {
|
||||
output: ['name', 'groupid'],
|
||||
sortfield: 'name',
|
||||
real_hosts: true
|
||||
real_hosts: true,
|
||||
};
|
||||
|
||||
return this.request('hostgroup.get', params);
|
||||
@@ -145,7 +145,7 @@ export class ZabbixAPIConnector {
|
||||
getHosts(groupids) {
|
||||
const params: any = {
|
||||
output: ['hostid', 'name', 'host'],
|
||||
sortfield: 'name'
|
||||
sortfield: 'name',
|
||||
};
|
||||
if (groupids) {
|
||||
params.groupids = groupids;
|
||||
@@ -161,7 +161,7 @@ export class ZabbixAPIConnector {
|
||||
|
||||
const params = {
|
||||
output: 'extend',
|
||||
hostids: hostids
|
||||
hostids: hostids,
|
||||
};
|
||||
|
||||
return this.request('application.get', params);
|
||||
@@ -176,22 +176,11 @@ export class ZabbixAPIConnector {
|
||||
*/
|
||||
getItems(hostids, appids, itemtype) {
|
||||
const params: any = {
|
||||
output: [
|
||||
'itemid',
|
||||
'name',
|
||||
'key_',
|
||||
'value_type',
|
||||
'hostid',
|
||||
'status',
|
||||
'state',
|
||||
'units',
|
||||
'valuemapid',
|
||||
'delay'
|
||||
],
|
||||
output: ['itemid', 'name', 'key_', 'value_type', 'hostid', 'status', 'state', 'units', 'valuemapid', 'delay'],
|
||||
sortfield: 'name',
|
||||
webitems: true,
|
||||
filter: {},
|
||||
selectHosts: ['hostid', 'name', 'host']
|
||||
selectHosts: ['hostid', 'name', 'host'],
|
||||
};
|
||||
if (hostids) {
|
||||
params.hostids = hostids;
|
||||
@@ -212,41 +201,28 @@ export class ZabbixAPIConnector {
|
||||
params.selectTags = 'extend';
|
||||
}
|
||||
|
||||
return this.request('item.get', params)
|
||||
.then(utils.expandItems);
|
||||
return this.request('item.get', params).then(utils.expandItems);
|
||||
}
|
||||
|
||||
getItemsByIDs(itemids) {
|
||||
const params: any = {
|
||||
itemids: itemids,
|
||||
output: [
|
||||
'itemid',
|
||||
'name',
|
||||
'key_',
|
||||
'value_type',
|
||||
'hostid',
|
||||
'status',
|
||||
'state',
|
||||
'units',
|
||||
'valuemapid',
|
||||
'delay'
|
||||
],
|
||||
output: ['itemid', 'name', 'key_', 'value_type', 'hostid', 'status', 'state', 'units', 'valuemapid', 'delay'],
|
||||
webitems: true,
|
||||
selectHosts: ['hostid', 'name']
|
||||
selectHosts: ['hostid', 'name'],
|
||||
};
|
||||
|
||||
if (this.isZabbix54OrHigher()) {
|
||||
params.selectTags = 'extend';
|
||||
}
|
||||
|
||||
return this.request('item.get', params)
|
||||
.then(items => utils.expandItems(items));
|
||||
return this.request('item.get', params).then((items) => utils.expandItems(items));
|
||||
}
|
||||
|
||||
getMacros(hostids) {
|
||||
const params = {
|
||||
output: 'extend',
|
||||
hostids: hostids
|
||||
hostids: hostids,
|
||||
};
|
||||
|
||||
return this.request('usermacro.get', params);
|
||||
@@ -255,7 +231,7 @@ export class ZabbixAPIConnector {
|
||||
getGlobalMacros() {
|
||||
const params = {
|
||||
output: 'extend',
|
||||
globalmacro: true
|
||||
globalmacro: true,
|
||||
};
|
||||
|
||||
return this.request('usermacro.get', params);
|
||||
@@ -264,10 +240,9 @@ export class ZabbixAPIConnector {
|
||||
getLastValue(itemid) {
|
||||
const params = {
|
||||
output: ['lastvalue'],
|
||||
itemids: itemid
|
||||
itemids: itemid,
|
||||
};
|
||||
return this.request('item.get', params)
|
||||
.then(items => items.length ? items[0].lastvalue : null);
|
||||
return this.request('item.get', params).then((items) => (items.length ? items[0].lastvalue : null));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -279,7 +254,6 @@ export class ZabbixAPIConnector {
|
||||
* @return {Array} Array of Zabbix history objects
|
||||
*/
|
||||
getHistory(items, timeFrom, timeTill) {
|
||||
|
||||
// Group items by value type and perform request for each value type
|
||||
const grouped_items = _.groupBy(items, 'value_type');
|
||||
const promises = _.map(grouped_items, (items, value_type) => {
|
||||
@@ -290,7 +264,7 @@ export class ZabbixAPIConnector {
|
||||
itemids: itemids,
|
||||
sortfield: 'clock',
|
||||
sortorder: 'ASC',
|
||||
time_from: timeFrom
|
||||
time_from: timeFrom,
|
||||
};
|
||||
|
||||
// Relative queries (e.g. last hour) don't include an end time
|
||||
@@ -314,7 +288,6 @@ export class ZabbixAPIConnector {
|
||||
* @return {Array} Array of Zabbix trend objects
|
||||
*/
|
||||
getTrend_ZBXNEXT1193(items, timeFrom, timeTill) {
|
||||
|
||||
// Group items by value type and perform request for each value type
|
||||
const grouped_items = _.groupBy(items, 'value_type');
|
||||
const promises = _.map(grouped_items, (items, value_type) => {
|
||||
@@ -325,7 +298,7 @@ export class ZabbixAPIConnector {
|
||||
itemids: itemids,
|
||||
sortfield: 'clock',
|
||||
sortorder: 'ASC',
|
||||
time_from: timeFrom
|
||||
time_from: timeFrom,
|
||||
};
|
||||
|
||||
// Relative queries (e.g. last hour) don't include an end time
|
||||
@@ -344,13 +317,9 @@ export class ZabbixAPIConnector {
|
||||
const itemids = _.map(items, 'itemid');
|
||||
|
||||
const params: any = {
|
||||
output: [
|
||||
'itemid',
|
||||
'clock',
|
||||
value_type
|
||||
],
|
||||
output: ['itemid', 'clock', value_type],
|
||||
itemids: itemids,
|
||||
time_from: time_from
|
||||
time_from: time_from,
|
||||
};
|
||||
|
||||
// Relative queries (e.g. last hour) don't include an end time
|
||||
@@ -364,7 +333,7 @@ export class ZabbixAPIConnector {
|
||||
getITService(serviceids?) {
|
||||
const params = {
|
||||
output: 'extend',
|
||||
serviceids: serviceids
|
||||
serviceids: serviceids,
|
||||
};
|
||||
return this.request('service.get', params);
|
||||
}
|
||||
@@ -382,7 +351,7 @@ export class ZabbixAPIConnector {
|
||||
|
||||
const params: any = {
|
||||
serviceids,
|
||||
intervals
|
||||
intervals,
|
||||
};
|
||||
|
||||
return this.request('service.getsla', params);
|
||||
@@ -410,10 +379,10 @@ export class ZabbixAPIConnector {
|
||||
}
|
||||
const sla = slaObjects[0];
|
||||
|
||||
const periods = intervals.map(interval => ({
|
||||
period_from: interval.from,
|
||||
period_to: interval.to,
|
||||
}));
|
||||
// const periods = intervals.map(interval => ({
|
||||
// period_from: interval.from,
|
||||
// period_to: interval.to,
|
||||
// }));
|
||||
const sliParams: any = {
|
||||
slaid: sla.slaid,
|
||||
serviceids,
|
||||
@@ -430,7 +399,7 @@ export class ZabbixAPIConnector {
|
||||
const slaLikeResponse: any = {};
|
||||
sliResponse.serviceids.forEach((serviceid) => {
|
||||
slaLikeResponse[serviceid] = {
|
||||
sla: []
|
||||
sla: [],
|
||||
};
|
||||
});
|
||||
sliResponse.sli.forEach((sliItem, i) => {
|
||||
@@ -440,7 +409,7 @@ export class ZabbixAPIConnector {
|
||||
okTime: sli.uptime,
|
||||
sla: sli.sli,
|
||||
from: sliResponse.periods[i].period_from,
|
||||
to: sliResponse.periods[i].period_to
|
||||
to: sliResponse.periods[i].period_to,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -526,13 +495,13 @@ export class ZabbixAPIConnector {
|
||||
skipDependent: true,
|
||||
//only_true: true,
|
||||
filter: {
|
||||
value: 1
|
||||
value: 1,
|
||||
},
|
||||
selectGroups: ['groupid', 'name'],
|
||||
selectHosts: ['hostid', 'name', 'host', 'maintenance_status', 'proxy_hostid'],
|
||||
selectItems: ['itemid', 'name', 'key_', 'lastvalue'],
|
||||
selectLastEvent: 'extend',
|
||||
selectTags: 'extend'
|
||||
selectTags: 'extend',
|
||||
};
|
||||
|
||||
if (showTriggers === ShowProblemTypes.Problems) {
|
||||
@@ -617,7 +586,7 @@ export class ZabbixAPIConnector {
|
||||
select_acknowledges: 'extend',
|
||||
selectTags: 'extend',
|
||||
sortfield: 'clock',
|
||||
sortorder: 'DESC'
|
||||
sortorder: 'DESC',
|
||||
};
|
||||
|
||||
return this.request('event.get', params);
|
||||
@@ -626,13 +595,7 @@ export class ZabbixAPIConnector {
|
||||
getEventAlerts(eventids) {
|
||||
const params = {
|
||||
eventids: eventids,
|
||||
output: [
|
||||
'alertid',
|
||||
'eventid',
|
||||
'message',
|
||||
'clock',
|
||||
'error'
|
||||
],
|
||||
output: ['alertid', 'eventid', 'message', 'clock', 'error'],
|
||||
selectUsers: true,
|
||||
};
|
||||
|
||||
@@ -646,11 +609,10 @@ export class ZabbixAPIConnector {
|
||||
preservekeys: true,
|
||||
select_acknowledges: 'extend',
|
||||
sortfield: 'clock',
|
||||
sortorder: 'DESC'
|
||||
sortorder: 'DESC',
|
||||
};
|
||||
|
||||
return this.request('event.get', params)
|
||||
.then(events => {
|
||||
return this.request('event.get', params).then((events) => {
|
||||
return _.filter(events, (event) => event.acknowledges.length);
|
||||
});
|
||||
}
|
||||
@@ -668,7 +630,7 @@ export class ZabbixAPIConnector {
|
||||
// filter: {
|
||||
// value: 1
|
||||
// },
|
||||
selectLastEvent: 'extend'
|
||||
selectLastEvent: 'extend',
|
||||
};
|
||||
|
||||
if (timeFrom || timeTo) {
|
||||
@@ -693,7 +655,7 @@ export class ZabbixAPIConnector {
|
||||
skipDependent: true,
|
||||
selectLastEvent: 'extend',
|
||||
selectGroups: 'extend',
|
||||
selectHosts: ['hostid', 'host', 'name']
|
||||
selectHosts: ['hostid', 'host', 'name'],
|
||||
};
|
||||
|
||||
if (count && acknowledged !== 0 && acknowledged !== 1) {
|
||||
@@ -709,8 +671,7 @@ export class ZabbixAPIConnector {
|
||||
params.lastChangeTill = timeTo;
|
||||
}
|
||||
|
||||
return this.request('trigger.get', params)
|
||||
.then((triggers) => {
|
||||
return this.request('trigger.get', params).then((triggers) => {
|
||||
if (!count || acknowledged === 0 || acknowledged === 1) {
|
||||
triggers = filterTriggersByAcknowledge(triggers, acknowledged);
|
||||
if (count) {
|
||||
@@ -750,7 +711,7 @@ export class ZabbixAPIConnector {
|
||||
getValueMappings() {
|
||||
const params = {
|
||||
output: 'extend',
|
||||
selectMappings: "extend",
|
||||
selectMappings: 'extend',
|
||||
};
|
||||
|
||||
return this.request('valuemap.get', params);
|
||||
@@ -759,9 +720,9 @@ export class ZabbixAPIConnector {
|
||||
|
||||
function filterTriggersByAcknowledge(triggers, acknowledged) {
|
||||
if (acknowledged === 0) {
|
||||
return _.filter(triggers, (trigger) => trigger.lastEvent.acknowledged === "0");
|
||||
return _.filter(triggers, (trigger) => trigger.lastEvent.acknowledged === '0');
|
||||
} else if (acknowledged === 1) {
|
||||
return _.filter(triggers, (trigger) => trigger.lastEvent.acknowledged === "1");
|
||||
return _.filter(triggers, (trigger) => trigger.lastEvent.acknowledged === '1');
|
||||
} else {
|
||||
return triggers;
|
||||
}
|
||||
@@ -785,9 +746,8 @@ function buildSLAIntervals(timeRange, interval) {
|
||||
for (let i = timeFrom; i <= timeTo - interval; i += interval) {
|
||||
intervals.push({
|
||||
from: i,
|
||||
to: (i + interval)
|
||||
to: i + interval,
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return intervals;
|
||||
@@ -802,12 +762,12 @@ export class ZabbixAPIError {
|
||||
|
||||
constructor(error: JSONRPCError) {
|
||||
this.code = error.code || null;
|
||||
this.name = error.message || "";
|
||||
this.data = error.data || "";
|
||||
this.message = "Zabbix API Error: " + this.name + " " + this.data;
|
||||
this.name = error.message || '';
|
||||
this.data = error.data || '';
|
||||
this.message = 'Zabbix API Error: ' + this.name + ' ' + this.data;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.name + " " + this.data;
|
||||
return this.name + ' ' + this.data;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import _ from 'lodash';
|
||||
// eslint-disable-next-line
|
||||
import moment from 'moment';
|
||||
import semver from 'semver';
|
||||
import * as utils from '../utils';
|
||||
@@ -1,12 +1,9 @@
|
||||
import { AppPlugin } from '@grafana/data';
|
||||
import { loadPluginCss } from 'grafana/app/plugins/sdk';
|
||||
|
||||
import './sass/grafana-zabbix.dark.scss';
|
||||
import './sass/grafana-zabbix.light.scss';
|
||||
|
||||
loadPluginCss({
|
||||
dark: 'plugins/alexanderzobnin-zabbix-app/css/grafana-zabbix.dark.css',
|
||||
light: 'plugins/alexanderzobnin-zabbix-app/css/grafana-zabbix.light.css',
|
||||
dark: 'plugins/alexanderzobnin-zabbix-app/styles/dark.css',
|
||||
light: 'plugins/alexanderzobnin-zabbix-app/styles/light.css',
|
||||
});
|
||||
|
||||
export const plugin = new AppPlugin<{}>();
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import { BusEventBase, BusEventWithPayload, dateMath, PanelProps } from '@grafana/data';
|
||||
import { DataSourceRef, dateMath, PanelProps } from '@grafana/data';
|
||||
import { getDataSourceSrv } from '@grafana/runtime';
|
||||
import { useTheme2 } from '@grafana/ui';
|
||||
import { contextSrv } from 'grafana/app/core/core';
|
||||
import { ProblemsPanelOptions } from './types';
|
||||
import { ProblemDTO, ZabbixMetricsQuery, ZBXQueryUpdatedEvent, ZBXTag } from '../datasource-zabbix/types';
|
||||
import { APIExecuteScriptResponse } from '../datasource-zabbix/zabbix/connectors/zabbix_api/types';
|
||||
import { ProblemsPanelOptions, RTResized } from './types';
|
||||
import { ProblemDTO, ZabbixMetricsQuery, ZBXQueryUpdatedEvent, ZBXTag } from '../datasource/types';
|
||||
import { APIExecuteScriptResponse } from '../datasource/zabbix/connectors/zabbix_api/types';
|
||||
import ProblemList from './components/Problems/Problems';
|
||||
import { AckProblemData } from './components/AckModal';
|
||||
import AlertList from './components/AlertList/AlertList';
|
||||
@@ -18,7 +17,6 @@ interface ProblemsPanelProps extends PanelProps<ProblemsPanelOptions> {}
|
||||
export const ProblemsPanel = (props: ProblemsPanelProps): JSX.Element => {
|
||||
const { data, options, timeRange, onOptionsChange } = props;
|
||||
const { layout, showTriggers, triggerSeverity, sortProblems } = options;
|
||||
const theme = useTheme2();
|
||||
|
||||
const prepareProblems = () => {
|
||||
const problems: ProblemDTO[] = [];
|
||||
@@ -63,11 +61,8 @@ export const ProblemsPanel = (props: ProblemsPanelProps): JSX.Element => {
|
||||
|
||||
// Filter triggers by severity
|
||||
problemsList = problemsList.filter((problem) => {
|
||||
if (problem.severity) {
|
||||
return triggerSeverity[problem.severity].show;
|
||||
} else {
|
||||
return triggerSeverity[problem.priority].show;
|
||||
}
|
||||
const severity = problem.severity !== undefined ? Number(problem.severity) : Number(problem.priority);
|
||||
return triggerSeverity[severity].show;
|
||||
});
|
||||
|
||||
return problemsList;
|
||||
@@ -97,7 +92,7 @@ export const ProblemsPanel = (props: ProblemsPanelProps): JSX.Element => {
|
||||
|
||||
// Set tags if present
|
||||
if (trigger.tags && trigger.tags.length === 0) {
|
||||
trigger.tags = null;
|
||||
trigger.tags = undefined;
|
||||
}
|
||||
|
||||
// Handle multi-line description
|
||||
@@ -109,7 +104,7 @@ export const ProblemsPanel = (props: ProblemsPanelProps): JSX.Element => {
|
||||
return trigger;
|
||||
};
|
||||
|
||||
const parseTags = (tagStr: string) => {
|
||||
const parseTags = (tagStr: string): ZBXTag[] => {
|
||||
if (!tagStr) {
|
||||
return [];
|
||||
}
|
||||
@@ -126,18 +121,18 @@ export const ProblemsPanel = (props: ProblemsPanelProps): JSX.Element => {
|
||||
return _.map(tags, (tag) => `${tag.tag}:${tag.value}`).join(', ');
|
||||
};
|
||||
|
||||
const addTagFilter = (tag, datasource) => {
|
||||
const targets = data.request.targets;
|
||||
const addTagFilter = (tag: ZBXTag, datasource: DataSourceRef) => {
|
||||
const targets = data.request?.targets!;
|
||||
let updated = false;
|
||||
for (const target of targets) {
|
||||
if (target.datasource?.uid === datasource?.uid || target.datasource === datasource) {
|
||||
const tagFilter = (target as ZabbixMetricsQuery).tags.filter;
|
||||
const tagFilter = (target as ZabbixMetricsQuery).tags?.filter!;
|
||||
let targetTags = parseTags(tagFilter);
|
||||
const newTag = { tag: tag.tag, value: tag.value };
|
||||
targetTags.push(newTag);
|
||||
targetTags = _.uniqWith(targetTags, _.isEqual);
|
||||
const newFilter = tagsToString(targetTags);
|
||||
(target as ZabbixMetricsQuery).tags.filter = newFilter;
|
||||
(target as ZabbixMetricsQuery).tags!.filter = newFilter;
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
@@ -148,18 +143,18 @@ export const ProblemsPanel = (props: ProblemsPanelProps): JSX.Element => {
|
||||
}
|
||||
};
|
||||
|
||||
const removeTagFilter = (tag, datasource) => {
|
||||
const matchTag = (t) => t.tag === tag.tag && t.value === tag.value;
|
||||
const targets = data.request.targets;
|
||||
const removeTagFilter = (tag: ZBXTag, datasource: DataSourceRef) => {
|
||||
const matchTag = (t: ZBXTag) => t.tag === tag.tag && t.value === tag.value;
|
||||
const targets = data.request?.targets!;
|
||||
let updated = false;
|
||||
for (const target of targets) {
|
||||
if (target.datasource?.uid === datasource?.uid || target.datasource === datasource) {
|
||||
const tagFilter = (target as ZabbixMetricsQuery).tags.filter;
|
||||
const tagFilter = (target as ZabbixMetricsQuery).tags?.filter!;
|
||||
let targetTags = parseTags(tagFilter);
|
||||
_.remove(targetTags, matchTag);
|
||||
targetTags = _.uniqWith(targetTags, _.isEqual);
|
||||
const newFilter = tagsToString(targetTags);
|
||||
(target as ZabbixMetricsQuery).tags.filter = newFilter;
|
||||
(target as ZabbixMetricsQuery).tags!.filter = newFilter;
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
@@ -172,8 +167,8 @@ export const ProblemsPanel = (props: ProblemsPanelProps): JSX.Element => {
|
||||
|
||||
const getProblemEvents = async (problem: ProblemDTO) => {
|
||||
const triggerids = [problem.triggerid];
|
||||
const timeFrom = Math.ceil(dateMath.parse(timeRange.from).unix());
|
||||
const timeTo = Math.ceil(dateMath.parse(timeRange.to).unix());
|
||||
const timeFrom = Math.ceil(dateMath.parse(timeRange.from)!.unix());
|
||||
const timeTo = Math.ceil(dateMath.parse(timeRange.to)!.unix());
|
||||
const ds: any = await getDataSourceSrv().get(problem.datasource);
|
||||
return ds.zabbix.getEvents(triggerids, timeFrom, timeTo, [0, 1], PROBLEM_EVENTS_LIMIT);
|
||||
};
|
||||
@@ -216,11 +211,11 @@ export const ProblemsPanel = (props: ProblemsPanelProps): JSX.Element => {
|
||||
}
|
||||
};
|
||||
|
||||
const onColumnResize = (newResized) => {
|
||||
const onColumnResize = (newResized: RTResized) => {
|
||||
onOptionsChange({ ...options, resizedColumns: newResized });
|
||||
};
|
||||
|
||||
const onTagClick = (tag: ZBXTag, datasource: string, ctrlKey?: boolean, shiftKey?: boolean) => {
|
||||
const onTagClick = (tag: ZBXTag, datasource: DataSourceRef, ctrlKey?: boolean, shiftKey?: boolean) => {
|
||||
if (ctrlKey || shiftKey) {
|
||||
removeTagFilter(tag, datasource);
|
||||
} else {
|
||||
@@ -231,7 +226,7 @@ export const ProblemsPanel = (props: ProblemsPanelProps): JSX.Element => {
|
||||
const renderList = () => {
|
||||
const problems = prepareProblems();
|
||||
const fontSize = parseInt(options.fontSize.slice(0, options.fontSize.length - 1), 10);
|
||||
const fontSizeProp = fontSize && fontSize !== 100 ? fontSize : null;
|
||||
const fontSizeProp = fontSize && fontSize !== 100 ? fontSize : undefined;
|
||||
|
||||
return (
|
||||
<AlertList
|
||||
@@ -248,7 +243,7 @@ export const ProblemsPanel = (props: ProblemsPanelProps): JSX.Element => {
|
||||
const renderTable = () => {
|
||||
const problems = prepareProblems();
|
||||
const fontSize = parseInt(options.fontSize.slice(0, options.fontSize.length - 1), 10);
|
||||
const fontSizeProp = fontSize && fontSize !== 100 ? fontSize : null;
|
||||
const fontSizeProp = fontSize && fontSize !== 100 ? fontSize : undefined;
|
||||
|
||||
return (
|
||||
<ProblemList
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
ZBX_ACK_ACTION_ACK,
|
||||
ZBX_ACK_ACTION_CHANGE_SEVERITY,
|
||||
ZBX_ACK_ACTION_CLOSE,
|
||||
} from '../../datasource-zabbix/constants';
|
||||
} from '../../datasource/constants';
|
||||
import {
|
||||
Button,
|
||||
VerticalGroup,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { ProblemDTO } from '../../../datasource-zabbix/types';
|
||||
import { ProblemDTO } from '../../../datasource/types';
|
||||
|
||||
interface AlertAcknowledgesProps {
|
||||
problem: ProblemDTO;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { PureComponent, CSSProperties } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { cx } from '@emotion/css';
|
||||
import _ from 'lodash';
|
||||
// eslint-disable-next-line
|
||||
import moment from 'moment';
|
||||
import { isNewProblem, formatLastChange } from '../../utils';
|
||||
import { ProblemsPanelOptions, TriggerSeverity } from '../../types';
|
||||
@@ -8,7 +9,7 @@ import { AckProblemData, AckModal } from '../AckModal';
|
||||
import EventTag from '../EventTag';
|
||||
import AlertAcknowledges from './AlertAcknowledges';
|
||||
import AlertIcon from './AlertIcon';
|
||||
import { ProblemDTO, ZBXTag } from '../../../datasource-zabbix/types';
|
||||
import { ProblemDTO, ZBXTag } from '../../../datasource/types';
|
||||
import { ModalController } from '../../../components';
|
||||
import { DataSourceRef } from '@grafana/data';
|
||||
import { Tooltip } from '@grafana/ui';
|
||||
@@ -36,10 +37,10 @@ export default class AlertCard extends PureComponent<AlertCardProps> {
|
||||
render() {
|
||||
const { problem, panelOptions } = this.props;
|
||||
const showDatasourceName = panelOptions.targets && panelOptions.targets.length > 1;
|
||||
const cardClass = classNames('alert-rule-item', 'zbx-trigger-card', {
|
||||
const cardClass = cx('alert-rule-item', 'zbx-trigger-card', {
|
||||
'zbx-trigger-highlighted': panelOptions.highlightBackground,
|
||||
});
|
||||
const descriptionClass = classNames('alert-rule-item__text', {
|
||||
const descriptionClass = cx('alert-rule-item__text', {
|
||||
'zbx-description--newline': panelOptions.descriptionAtNewLine,
|
||||
});
|
||||
|
||||
@@ -155,7 +156,7 @@ export default class AlertCard extends PureComponent<AlertCardProps> {
|
||||
<span>{lastchange || 'last change unknown'}</span>
|
||||
<div className="trigger-info-block zbx-status-icons">
|
||||
{problem.url && (
|
||||
<a href={problem.url} target="_blank">
|
||||
<a href={problem.url} target="_blank" rel="noreferrer">
|
||||
<i className="fa fa-external-link"></i>
|
||||
</a>
|
||||
)}
|
||||
@@ -239,19 +240,23 @@ const DEFAULT_PROBLEM_COLOR = 'rgb(215, 0, 0)';
|
||||
function AlertStatus(props) {
|
||||
const { problem, okColor, problemColor, blink } = props;
|
||||
const status = problem.value === '0' ? 'RESOLVED' : 'PROBLEM';
|
||||
const color = problem.value === '0' ? okColor || DEFAULT_OK_COLOR : problemColor || DEFAULT_PROBLEM_COLOR;
|
||||
const className = classNames(
|
||||
const color: string = problem.value === '0' ? okColor || DEFAULT_OK_COLOR : problemColor || DEFAULT_PROBLEM_COLOR;
|
||||
const className = cx(
|
||||
'zbx-trigger-state',
|
||||
{ 'alert-state-critical': problem.value === '1' },
|
||||
{ 'alert-state-ok': problem.value === '0' },
|
||||
{ 'zabbix-trigger--blinked': blink }
|
||||
);
|
||||
return <span className={className}>{status}</span>;
|
||||
return (
|
||||
<span className={className} style={{ color: color }}>
|
||||
{status}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function AlertSeverity(props) {
|
||||
const { severityDesc, highlightBackground, blink } = props;
|
||||
const className = classNames('zbx-trigger-severity', { 'zabbix-trigger--blinked': blink });
|
||||
const className = cx('zbx-trigger-severity', { 'zabbix-trigger--blinked': blink });
|
||||
const style: CSSProperties = {};
|
||||
if (!highlightBackground) {
|
||||
style.color = severityDesc.color;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { FC } from 'react';
|
||||
import { cx, css } from '@emotion/css';
|
||||
import { GFHeartIcon } from '../../../components';
|
||||
import { ProblemDTO } from '../../../datasource-zabbix/types';
|
||||
import { ProblemDTO } from '../../../datasource/types';
|
||||
|
||||
interface Props {
|
||||
problem: ProblemDTO;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React, { PureComponent, CSSProperties } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import React, { PureComponent } from 'react';
|
||||
import { cx } from '@emotion/css';
|
||||
import { ProblemsPanelOptions } from '../../types';
|
||||
import { AckProblemData } from '../AckModal';
|
||||
import AlertCard from './AlertCard';
|
||||
import { ProblemDTO, ZBXTag } from '../../../datasource-zabbix/types';
|
||||
import { ProblemDTO, ZBXTag } from '../../../datasource/types';
|
||||
import { DataSourceRef } from '@grafana/data';
|
||||
|
||||
export interface AlertListProps {
|
||||
@@ -13,7 +13,7 @@ export interface AlertListProps {
|
||||
pageSize?: number;
|
||||
fontSize?: number;
|
||||
onProblemAck?: (problem: ProblemDTO, data: AckProblemData) => void;
|
||||
onTagClick?: (tag: ZBXTag, datasource: string, ctrlKey?: boolean, shiftKey?: boolean) => void;
|
||||
onTagClick?: (tag: ZBXTag, datasource: DataSourceRef, ctrlKey?: boolean, shiftKey?: boolean) => void;
|
||||
}
|
||||
|
||||
interface AlertListState {
|
||||
@@ -45,7 +45,7 @@ export default class AlertList extends PureComponent<AlertListProps, AlertListSt
|
||||
});
|
||||
};
|
||||
|
||||
handleTagClick = (tag: ZBXTag, datasource: string, ctrlKey?: boolean, shiftKey?: boolean) => {
|
||||
handleTagClick = (tag: ZBXTag, datasource: DataSourceRef, ctrlKey?: boolean, shiftKey?: boolean) => {
|
||||
if (this.props.onTagClick) {
|
||||
this.props.onTagClick(tag, datasource, ctrlKey, shiftKey);
|
||||
}
|
||||
@@ -60,7 +60,7 @@ export default class AlertList extends PureComponent<AlertListProps, AlertListSt
|
||||
const currentProblems = this.getCurrentProblems(this.state.page);
|
||||
let fontSize = parseInt(panelOptions.fontSize.slice(0, panelOptions.fontSize.length - 1), 10);
|
||||
fontSize = fontSize && fontSize !== 100 ? fontSize : null;
|
||||
const alertListClass = classNames('alert-rule-list', { [`font-size--${fontSize}`]: fontSize });
|
||||
const alertListClass = cx('alert-rule-list', { [`font-size--${fontSize}`]: !!fontSize });
|
||||
|
||||
return (
|
||||
<div className="triggers-panel-container" key="alertListContainer">
|
||||
@@ -117,7 +117,7 @@ class PaginationControl extends PureComponent<PaginationControlProps> {
|
||||
|
||||
const pageLinks = [];
|
||||
for (let i = startPage; i < endPage; i++) {
|
||||
const pageLinkClass = classNames('triggers-panel-page-link', 'pointer', { active: i === pageIndex });
|
||||
const pageLinkClass = cx('triggers-panel-page-link', 'pointer', { active: i === pageIndex });
|
||||
const value = i + 1;
|
||||
const pageLinkElem = (
|
||||
<li key={value.toString()}>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { DataSourceRef } from '@grafana/data';
|
||||
import { Tooltip } from '@grafana/ui';
|
||||
import { ZBXTag } from '../../datasource-zabbix/types';
|
||||
import { ZBXTag } from '../../datasource/types';
|
||||
|
||||
const TAG_COLORS = [
|
||||
'#E24D42',
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { PureComponent } from 'react';
|
||||
import { cx, css } from '@emotion/css';
|
||||
import { GrafanaTheme, SelectableValue } from '@grafana/data';
|
||||
import { Button, Spinner, Modal, Select, stylesFactory, withTheme, Themeable } from '@grafana/ui';
|
||||
import { ZBXScript, APIExecuteScriptResponse } from '../../datasource-zabbix/zabbix/connectors/zabbix_api/types';
|
||||
import { ZBXScript, APIExecuteScriptResponse } from '../../datasource/zabbix/connectors/zabbix_api/types';
|
||||
import { FAIcon } from '../../components';
|
||||
|
||||
interface Props extends Themeable {
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
import React, { FormEvent } from 'react';
|
||||
import {
|
||||
Button,
|
||||
ColorPicker,
|
||||
HorizontalGroup,
|
||||
InlineField,
|
||||
InlineFieldRow,
|
||||
InlineLabel,
|
||||
InlineSwitch,
|
||||
Input,
|
||||
VerticalGroup,
|
||||
} from '@grafana/ui';
|
||||
import { StandardEditorProps } from '@grafana/data';
|
||||
import { GFHeartIcon } from '../../components';
|
||||
import { ColorPicker, InlineField, InlineFieldRow, InlineLabel, InlineSwitch, Input, VerticalGroup } from '@grafana/ui';
|
||||
import { TriggerSeverity } from '../types';
|
||||
|
||||
type Props = StandardEditorProps<TriggerSeverity[]>;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
import { RTCell } from '../../types';
|
||||
import { ProblemDTO } from '../../../datasource-zabbix/types';
|
||||
import { ProblemDTO } from '../../../datasource/types';
|
||||
import { FAIcon } from '../../../components';
|
||||
import { useTheme, stylesFactory } from '@grafana/ui';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { ZBXAcknowledge } from '../../../datasource-zabbix/types';
|
||||
import { ZBXAcknowledge } from '../../../datasource/types';
|
||||
|
||||
interface AcknowledgesListProps {
|
||||
acknowledges: ZBXAcknowledge[];
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import React, { FC, PureComponent } from 'react';
|
||||
// eslint-disable-next-line
|
||||
import moment from 'moment';
|
||||
import { TimeRange, DataSourceRef } from '@grafana/data';
|
||||
import { Tooltip } from '@grafana/ui';
|
||||
import { getDataSourceSrv } from '@grafana/runtime';
|
||||
import * as utils from '../../../datasource-zabbix/utils';
|
||||
import { ProblemDTO, ZBXAlert, ZBXEvent, ZBXGroup, ZBXHost, ZBXTag } from '../../../datasource-zabbix/types';
|
||||
import { APIExecuteScriptResponse, ZBXScript } from '../../../datasource-zabbix/zabbix/connectors/zabbix_api/types';
|
||||
import * as utils from '../../../datasource/utils';
|
||||
import { ProblemDTO, ZBXAlert, ZBXEvent, ZBXGroup, ZBXHost, ZBXTag, ZBXItem } from '../../../datasource/types';
|
||||
import { APIExecuteScriptResponse, ZBXScript } from '../../../datasource/zabbix/connectors/zabbix_api/types';
|
||||
import { AckModal, AckProblemData } from '../AckModal';
|
||||
import EventTag from '../EventTag';
|
||||
import AcknowledgesList from './AcknowledgesList';
|
||||
@@ -13,7 +14,6 @@ import ProblemTimeline from './ProblemTimeline';
|
||||
import { AckButton, ExecScriptButton, ExploreButton, FAIcon, ModalController } from '../../../components';
|
||||
import { ExecScriptData, ExecScriptModal } from '../ExecScriptModal';
|
||||
import ProblemStatusBar from './ProblemStatusBar';
|
||||
import { ZBXItem } from '../../../datasource-zabbix/types';
|
||||
import { RTRow } from '../../types';
|
||||
|
||||
interface ProblemDetailsProps extends RTRow<ProblemDTO> {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Tooltip } from '@grafana/ui';
|
||||
import FAIcon from '../../../components/FAIcon/FAIcon';
|
||||
import { ZBXAlert, ProblemDTO } from '../../../datasource-zabbix/types';
|
||||
import { ZBXAlert, ProblemDTO } from '../../../datasource/types';
|
||||
|
||||
export interface ProblemStatusBarProps {
|
||||
problem: ProblemDTO;
|
||||
@@ -61,7 +61,7 @@ function ProblemStatusBarItem(props: ProblemStatusBarItemProps) {
|
||||
);
|
||||
}
|
||||
return link ? (
|
||||
<a href={link} target="_blank">
|
||||
<a href={link} target="_blank" rel="noreferrer">
|
||||
{item}
|
||||
</a>
|
||||
) : (
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import _ from 'lodash';
|
||||
// eslint-disable-next-line
|
||||
import moment from 'moment';
|
||||
import { ZBXEvent, ZBXAcknowledge } from '../../../datasource-zabbix/types';
|
||||
import { ZBXEvent, ZBXAcknowledge } from '../../../datasource/types';
|
||||
import { TimeRange } from '@grafana/data';
|
||||
|
||||
const DEFAULT_OK_COLOR = 'rgb(56, 189, 113)';
|
||||
@@ -370,7 +371,12 @@ class TimelineRegions extends PureComponent<TimelineRegionsProps> {
|
||||
return <rect key={`${event.eventid}-${index}`} className={className} {...attributes} />;
|
||||
});
|
||||
|
||||
return [firstItem, eventsIntervalItems];
|
||||
return (
|
||||
<>
|
||||
{firstItem}
|
||||
{eventsIntervalItems}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { cx } from '@emotion/css';
|
||||
import ReactTable from 'react-table-6';
|
||||
import classNames from 'classnames';
|
||||
import _ from 'lodash';
|
||||
// eslint-disable-next-line
|
||||
import moment from 'moment';
|
||||
import { isNewProblem } from '../../utils';
|
||||
import EventTag from '../EventTag';
|
||||
@@ -9,8 +10,8 @@ import { ProblemDetails } from './ProblemDetails';
|
||||
import { AckProblemData } from '../AckModal';
|
||||
import { FAIcon, GFHeartIcon } from '../../../components';
|
||||
import { ProblemsPanelOptions, RTCell, RTResized, TriggerSeverity } from '../../types';
|
||||
import { ProblemDTO, ZBXAlert, ZBXEvent, ZBXTag } from '../../../datasource-zabbix/types';
|
||||
import { APIExecuteScriptResponse, ZBXScript } from '../../../datasource-zabbix/zabbix/connectors/zabbix_api/types';
|
||||
import { ProblemDTO, ZBXAlert, ZBXEvent, ZBXTag } from '../../../datasource/types';
|
||||
import { APIExecuteScriptResponse, ZBXScript } from '../../../datasource/zabbix/connectors/zabbix_api/types';
|
||||
import { AckCell } from './AckCell';
|
||||
import { DataSourceRef, TimeRange } from '@grafana/data';
|
||||
|
||||
@@ -28,7 +29,7 @@ export interface ProblemListProps {
|
||||
getScripts: (problem: ProblemDTO) => Promise<ZBXScript[]>;
|
||||
onExecuteScript: (problem: ProblemDTO, scriptid: string) => Promise<APIExecuteScriptResponse>;
|
||||
onProblemAck?: (problem: ProblemDTO, data: AckProblemData) => void;
|
||||
onTagClick?: (tag: ZBXTag, datasource: string, ctrlKey?: boolean, shiftKey?: boolean) => void;
|
||||
onTagClick?: (tag: ZBXTag, datasource: DataSourceRef, ctrlKey?: boolean, shiftKey?: boolean) => void;
|
||||
onPageSizeChange?: (pageSize: number, pageIndex: number) => void;
|
||||
onColumnResize?: (newResized: RTResized) => void;
|
||||
}
|
||||
@@ -43,8 +44,9 @@ export default class ProblemList extends PureComponent<ProblemListProps, Problem
|
||||
rootWidth: number;
|
||||
rootRef: any;
|
||||
|
||||
constructor(props) {
|
||||
constructor(props: ProblemListProps) {
|
||||
super(props);
|
||||
this.rootWidth = 0;
|
||||
this.state = {
|
||||
expanded: {},
|
||||
expandedProblems: {},
|
||||
@@ -52,12 +54,12 @@ export default class ProblemList extends PureComponent<ProblemListProps, Problem
|
||||
};
|
||||
}
|
||||
|
||||
setRootRef = (ref) => {
|
||||
setRootRef = (ref: any) => {
|
||||
this.rootRef = ref;
|
||||
};
|
||||
|
||||
handleProblemAck = (problem: ProblemDTO, data: AckProblemData) => {
|
||||
return this.props.onProblemAck(problem, data);
|
||||
return this.props.onProblemAck!(problem, data);
|
||||
};
|
||||
|
||||
onExecuteScript = (problem: ProblemDTO, data: AckProblemData) => {};
|
||||
@@ -102,7 +104,7 @@ export default class ProblemList extends PureComponent<ProblemListProps, Problem
|
||||
});
|
||||
};
|
||||
|
||||
handleTagClick = (tag: ZBXTag, datasource: string, ctrlKey?: boolean, shiftKey?: boolean) => {
|
||||
handleTagClick = (tag: ZBXTag, datasource: DataSourceRef, ctrlKey?: boolean, shiftKey?: boolean) => {
|
||||
if (this.props.onTagClick) {
|
||||
this.props.onTagClick(tag, datasource, ctrlKey, shiftKey);
|
||||
}
|
||||
@@ -216,7 +218,7 @@ export default class ProblemList extends PureComponent<ProblemListProps, Problem
|
||||
const columns = this.buildColumns();
|
||||
this.rootWidth = this.rootRef && this.rootRef.clientWidth;
|
||||
const { pageSize, fontSize, panelOptions } = this.props;
|
||||
const panelClass = classNames('panel-problems', { [`font-size--${fontSize}`]: fontSize });
|
||||
const panelClass = cx('panel-problems', { [`font-size--${fontSize}`]: !!fontSize });
|
||||
let pageSizeOptions = [5, 10, 20, 25, 50, 100];
|
||||
if (pageSize) {
|
||||
pageSizeOptions.push(pageSize);
|
||||
@@ -330,7 +332,7 @@ function StatusIconCell(props: RTCell<ProblemDTO>, highlightNewerThan?: string)
|
||||
if (highlightNewerThan) {
|
||||
newProblem = isNewProblem(props.original, highlightNewerThan);
|
||||
}
|
||||
const className = classNames(
|
||||
const className = cx(
|
||||
'zbx-problem-status-icon',
|
||||
{ 'problem-status--new': newProblem },
|
||||
{ 'zbx-problem': props.value === '1' },
|
||||
@@ -348,7 +350,7 @@ function GroupCell(props: RTCell<ProblemDTO>) {
|
||||
}
|
||||
|
||||
function ProblemCell(props: RTCell<ProblemDTO>) {
|
||||
const comments = props.original.comments;
|
||||
// const comments = props.original.comments;
|
||||
return (
|
||||
<div>
|
||||
<span className="problem-description">{props.value}</span>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import _ from 'lodash';
|
||||
import { getNextRefIdChar } from './utils';
|
||||
import { ShowProblemTypes } from '../datasource-zabbix/types';
|
||||
import { ShowProblemTypes } from '../datasource/types';
|
||||
import { ProblemsPanelOptions } from './types';
|
||||
import { PanelModel } from '@grafana/data';
|
||||
|
||||
@@ -71,7 +71,7 @@ export function migratePanelSchema(panel) {
|
||||
}
|
||||
|
||||
if (schemaVersion < 7) {
|
||||
const updatedTargets = [];
|
||||
const updatedTargets: any[] = [];
|
||||
for (const targetKey in panel.targets) {
|
||||
const target = panel.targets[targetKey];
|
||||
if (!isEmptyTarget(target) && !isInvalidTarget(target, targetKey)) {
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import { PanelPlugin, StandardEditorProps } from '@grafana/data';
|
||||
import { PanelPlugin } from '@grafana/data';
|
||||
import { problemsPanelChangedHandler, problemsPanelMigrationHandler } from './migrations';
|
||||
import { ProblemsPanel } from './ProblemsPanel';
|
||||
import { defaultPanelOptions, ProblemsPanelOptions } from './types';
|
||||
import { ResetColumnsEditor } from './components/ResetColumnsEditor';
|
||||
import { ProblemColorEditor } from './components/ProblemColorEditor';
|
||||
import { loadPluginCss } from '@grafana/runtime';
|
||||
|
||||
loadPluginCss({
|
||||
dark: 'plugins/alexanderzobnin-zabbix-app/styles/dark.css',
|
||||
light: 'plugins/alexanderzobnin-zabbix-app/styles/light.css',
|
||||
});
|
||||
|
||||
export const plugin = new PanelPlugin<ProblemsPanelOptions, {}>(ProblemsPanel)
|
||||
.setPanelChangeHandler(problemsPanelChangedHandler)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { DataSourceRef } from '@grafana/data';
|
||||
import { CURRENT_SCHEMA_VERSION } from './migrations';
|
||||
|
||||
export interface ProblemsPanelOptions {
|
||||
@@ -26,7 +25,7 @@ export interface ProblemsPanelOptions {
|
||||
showEvents?: Number[];
|
||||
limit?: number;
|
||||
// View options
|
||||
fontSize?: string;
|
||||
fontSize: string;
|
||||
pageSize?: number;
|
||||
problemTimeline?: boolean;
|
||||
highlightBackground?: boolean;
|
||||
@@ -36,9 +35,9 @@ export interface ProblemsPanelOptions {
|
||||
lastChangeFormat?: string;
|
||||
resizedColumns?: RTResized;
|
||||
// Triggers severity and colors
|
||||
triggerSeverity?: TriggerSeverity[];
|
||||
okEventColor?: TriggerColor;
|
||||
ackEventColor?: TriggerColor;
|
||||
triggerSeverity: TriggerSeverity[];
|
||||
okEventColor: TriggerColor;
|
||||
ackEventColor: TriggerColor;
|
||||
markAckEvents?: boolean;
|
||||
}
|
||||
|
||||
@@ -70,7 +69,7 @@ export const defaultPanelOptions: Partial<ProblemsPanelOptions> = {
|
||||
descriptionAtNewLine: false,
|
||||
// Options
|
||||
sortProblems: 'lastchange',
|
||||
limit: null,
|
||||
limit: undefined,
|
||||
// View options
|
||||
layout: 'table',
|
||||
fontSize: '100%',
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { DataQuery } from '@grafana/data';
|
||||
import * as utils from '../datasource-zabbix/utils';
|
||||
import { ProblemDTO } from 'datasource-zabbix/types';
|
||||
import { DataQuery, dateMath } from '@grafana/data';
|
||||
import * as utils from '../datasource/utils';
|
||||
import { ProblemDTO } from 'datasource/types';
|
||||
|
||||
export function isNewProblem(problem: ProblemDTO, highlightNewerThan: string): boolean {
|
||||
try {
|
||||
const highlightIntervalMs = utils.parseInterval(highlightNewerThan);
|
||||
const durationSec = (Date.now() - problem.timestamp * 1000);
|
||||
const durationSec = Date.now() - problem.timestamp * 1000;
|
||||
return durationSec < highlightIntervalMs;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_TIME_FORMAT = "DD MMM YYYY HH:mm:ss";
|
||||
const DEFAULT_TIME_FORMAT = 'DD MMM YYYY HH:mm:ss';
|
||||
|
||||
export function formatLastChange(lastchangeUnix: number, customFormat?: string) {
|
||||
const timestamp = moment.unix(lastchangeUnix);
|
||||
const date = new Date(lastchangeUnix);
|
||||
const timestamp = dateMath.parse(date);
|
||||
const format = customFormat || DEFAULT_TIME_FORMAT;
|
||||
const lastchange = timestamp.format(format);
|
||||
const lastchange = timestamp!.format(format);
|
||||
return lastchange;
|
||||
}
|
||||
|
||||
export const getNextRefIdChar = (queries: DataQuery[]): string => {
|
||||
const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
|
||||
return _.find(letters, refId => {
|
||||
return _.every(queries, other => {
|
||||
const nextLetter = _.find(letters, (refId) => {
|
||||
return _.every(queries, (other) => {
|
||||
return other.refId !== refId;
|
||||
});
|
||||
});
|
||||
return nextLetter || 'A';
|
||||
};
|
||||
|
||||
@@ -576,7 +576,7 @@
|
||||
font-size: 1% * $i * 10;
|
||||
|
||||
& .rt-tr .rt-td.custom-expander i {
|
||||
font-size: 1.2rem * $i / 10;
|
||||
font-size: calc(1.2rem * $i / 10);
|
||||
}
|
||||
|
||||
.problem-details-container.show {
|
||||
@@ -594,7 +594,7 @@
|
||||
font-size: 1% * $i * 10;
|
||||
|
||||
& .rt-tr .rt-td.custom-expander i {
|
||||
font-size: 1.2rem * $i / 10;
|
||||
font-size: calc(1.2rem * $i / 10);
|
||||
}
|
||||
|
||||
.problem-details-container.show {
|
||||
@@ -4,22 +4,9 @@
|
||||
import { JSDOM } from 'jsdom';
|
||||
import { PanelCtrl, MetricsPanelCtrl } from './panelStub';
|
||||
|
||||
// Suppress messages
|
||||
console.log = () => {};
|
||||
|
||||
// Mock Grafana modules that are not available outside of the core project
|
||||
// Required for loading module.js
|
||||
jest.mock('angular', () => {
|
||||
return {
|
||||
module: function() {
|
||||
return {
|
||||
directive: function() {},
|
||||
service: function() {},
|
||||
factory: function() {}
|
||||
};
|
||||
}
|
||||
};
|
||||
}, {virtual: true});
|
||||
|
||||
jest.mock('grafana/app/features/templating/template_srv', () => {
|
||||
return {};
|
||||
}, {virtual: true});
|
||||
@@ -33,6 +20,9 @@ jest.mock('@grafana/runtime', () => {
|
||||
getBackendSrv: () => ({
|
||||
datasourceRequest: jest.fn().mockResolvedValue(),
|
||||
}),
|
||||
getTemplateSrv: () => ({
|
||||
replace: jest.fn().mockImplementation(query => query),
|
||||
}),
|
||||
};
|
||||
}, {virtual: true});
|
||||
|
||||
@@ -101,13 +91,8 @@ jest.mock('grafana/app/core/utils/kbn', () => {
|
||||
};
|
||||
}, {virtual: true});
|
||||
|
||||
// jest.mock('@grafana/ui', () => {
|
||||
// return {};
|
||||
// }, {virtual: true});
|
||||
|
||||
// Required for loading angularjs
|
||||
let dom = new JSDOM('<html><head><script></script></head><body></body></html>');
|
||||
// Setup jsdom
|
||||
let dom = new JSDOM('<html><head><script></script></head><body></body></html>');
|
||||
global.window = dom.window;
|
||||
global.document = global.window.document;
|
||||
global.Node = window.Node;
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
export let templateSrvMock = {
|
||||
replace: jest.fn().mockImplementation(query => query)
|
||||
};
|
||||
|
||||
export let backendSrvMock = {
|
||||
datasourceRequest: jest.fn()
|
||||
};
|
||||
|
||||
export let datasourceSrvMock = {
|
||||
loadDatasource: jest.fn(),
|
||||
getAll: jest.fn()
|
||||
};
|
||||
|
||||
export let timeSrvMock = {
|
||||
timeRange: jest.fn().mockReturnValue({ from: '', to: '' })
|
||||
};
|
||||
|
||||
const defaultExports = {
|
||||
templateSrvMock,
|
||||
backendSrvMock,
|
||||
datasourceSrvMock,
|
||||
timeSrvMock,
|
||||
};
|
||||
|
||||
export default defaultExports;
|
||||
25
src/test-setup/mocks.ts
Normal file
25
src/test-setup/mocks.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
export const templateSrvMock = {
|
||||
replace: jest.fn().mockImplementation((query) => query),
|
||||
};
|
||||
|
||||
export const backendSrvMock = {
|
||||
datasourceRequest: jest.fn(),
|
||||
};
|
||||
|
||||
export const datasourceSrvMock = {
|
||||
loadDatasource: jest.fn(),
|
||||
getAll: jest.fn(),
|
||||
};
|
||||
|
||||
export const timeSrvMock = {
|
||||
timeRange: jest.fn().mockReturnValue({ from: '', to: '' }),
|
||||
};
|
||||
|
||||
const defaultExports = {
|
||||
templateSrvMock,
|
||||
backendSrvMock,
|
||||
datasourceSrvMock,
|
||||
timeSrvMock,
|
||||
};
|
||||
|
||||
export default defaultExports;
|
||||
Reference in New Issue
Block a user