From 44660476cac9b735a8180c60e96e510fda93c90c Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Thu, 7 May 2020 11:33:15 +0300 Subject: [PATCH 01/33] datasource: convert to TS --- .../{datasource.js => datasource.ts} | 142 ++++++++++-------- src/datasource-zabbix/utils.ts | 5 +- 2 files changed, 84 insertions(+), 63 deletions(-) rename src/datasource-zabbix/{datasource.js => datasource.ts} (83%) diff --git a/src/datasource-zabbix/datasource.js b/src/datasource-zabbix/datasource.ts similarity index 83% rename from src/datasource-zabbix/datasource.js rename to src/datasource-zabbix/datasource.ts index cd70e96..e4a0bb6 100644 --- a/src/datasource-zabbix/datasource.js +++ b/src/datasource-zabbix/datasource.ts @@ -14,9 +14,33 @@ import { VariableQueryTypes } from './types'; const DEFAULT_ZABBIX_VERSION = 3; export class ZabbixDatasource { + name: string; + url: string; + basicAuth: any; + withCredentials: any; + + username: string; + password: string; + trends: boolean; + trendsFrom: string; + trendsRange: string; + cacheTTL: any; + alertingEnabled: boolean; + addThresholds: boolean; + alertingMinSeverity: string; + disableReadOnlyUsersAck: boolean; + zabbixVersion: string; + enableDirectDBConnection: boolean; + dbConnectionDatasourceId: number; + dbConnectionDatasourceName: string; + dbConnectionRetentionPolicy: string; + enableDebugLog: boolean; + zabbix: any; + + replaceTemplateVars: (templateSrv: any, target: any, scopedVars?: any) => any; /** @ngInject */ - constructor(instanceSettings, templateSrv, zabbixAlertingSrv) { + constructor(instanceSettings, private templateSrv, private zabbixAlertingSrv) { this.templateSrv = templateSrv; this.zabbixAlertingSrv = zabbixAlertingSrv; @@ -43,7 +67,7 @@ export class ZabbixDatasource { this.trendsRange = jsonData.trendsRange || '4d'; // Set cache update interval - var ttl = jsonData.cacheTTL || '1h'; + const ttl = jsonData.cacheTTL || '1h'; this.cacheTTL = utils.parseInterval(ttl); // Alerting options @@ -61,7 +85,7 @@ export class ZabbixDatasource { this.dbConnectionDatasourceName = jsonData.dbConnectionDatasourceName; this.dbConnectionRetentionPolicy = jsonData.dbConnectionRetentionPolicy; - let zabbixOptions = { + const zabbixOptions = { url: this.url, username: this.username, password: this.password, @@ -103,7 +127,7 @@ export class ZabbixDatasource { } // Create request for each target - let promises = _.map(options.targets, t => { + const promises = _.map(options.targets, t => { // Don't request for hidden targets if (t.hide) { return []; @@ -123,15 +147,15 @@ export class ZabbixDatasource { this.replaceTargetVariables(target, options); // Apply Time-related functions (timeShift(), etc) - let timeFunctions = bindFunctionDefs(target.functions, 'Time'); + const timeFunctions = bindFunctionDefs(target.functions, 'Time'); if (timeFunctions.length) { const [time_from, time_to] = utils.sequence(timeFunctions)([timeFrom, timeTo]); timeFrom = time_from; timeTo = time_to; } - let timeRange = [timeFrom, timeTo]; + const timeRange = [timeFrom, timeTo]; - let useTrends = this.isUseTrends(timeRange); + const useTrends = this.isUseTrends(timeRange); // Metrics or Text query if (!target.queryType || target.queryType === c.MODE_METRICS || target.queryType === c.MODE_TEXT) { @@ -175,7 +199,7 @@ export class ZabbixDatasource { */ queryNumericData(target, timeRange, useTrends, options) { let queryStart, queryEnd; - let getItemOptions = { + const getItemOptions = { itemtype: 'num' }; return this.zabbix.getItemsFromTarget(target, getItemOptions) @@ -185,7 +209,7 @@ export class ZabbixDatasource { }).then(result => { queryEnd = new Date().getTime(); if (this.enableDebugLog) { - console.debug(`Datasource::Performance Query Time (${this.name}): ${queryEnd - queryStart}`); + console.log(`Datasource::Performance Query Time (${this.name}): ${queryEnd - queryStart}`); } return result; }); @@ -212,18 +236,18 @@ export class ZabbixDatasource { getTrendValueType(target) { // Find trendValue() function and get specified trend value - var trendFunctions = _.map(metricFunctions.getCategories()['Trends'], 'name'); - var trendValueFunc = _.find(target.functions, func => { + const trendFunctions = _.map(metricFunctions.getCategories()['Trends'], 'name'); + const trendValueFunc = _.find(target.functions, func => { return _.includes(trendFunctions, func.def.name); }); return trendValueFunc ? trendValueFunc.params[0] : "avg"; } applyDataProcessingFunctions(timeseries_data, target) { - let transformFunctions = bindFunctionDefs(target.functions, 'Transform'); - let aggregationFunctions = bindFunctionDefs(target.functions, 'Aggregate'); - let filterFunctions = bindFunctionDefs(target.functions, 'Filter'); - let aliasFunctions = bindFunctionDefs(target.functions, 'Alias'); + const transformFunctions = bindFunctionDefs(target.functions, 'Transform'); + const aggregationFunctions = bindFunctionDefs(target.functions, 'Aggregate'); + const filterFunctions = bindFunctionDefs(target.functions, 'Filter'); + const aliasFunctions = bindFunctionDefs(target.functions, 'Alias'); // Apply transformation functions timeseries_data = _.cloneDeep(_.map(timeseries_data, timeseries => { @@ -241,8 +265,8 @@ export class ZabbixDatasource { let dp = _.map(timeseries_data, 'datapoints'); dp = utils.sequence(aggregationFunctions)(dp); - let aggFuncNames = _.map(metricFunctions.getCategories()['Aggregate'], 'name'); - let lastAgg = _.findLast(target.functions, func => { + const aggFuncNames = _.map(metricFunctions.getCategories()['Aggregate'], 'name'); + const lastAgg = _.findLast(target.functions, func => { return _.includes(aggFuncNames, func.def.name); }); @@ -264,11 +288,11 @@ export class ZabbixDatasource { applyTimeShiftFunction(timeseries_data, target) { // Find timeShift() function and get specified interval - let timeShiftFunc = _.find(target.functions, (func) => { + const timeShiftFunc = _.find(target.functions, (func) => { return func.def.name === 'timeShift'; }); if (timeShiftFunc) { - let shift = timeShiftFunc.params[0]; + const shift = timeShiftFunc.params[0]; _.forEach(timeseries_data, (series) => { series.datapoints = dataProcessor.unShiftTimeSeries(shift, series.datapoints); }); @@ -279,7 +303,7 @@ export class ZabbixDatasource { * Query target data for Text */ queryTextData(target, timeRange) { - let options = { + const options = { itemtype: 'text' }; return this.zabbix.getItemsFromTarget(target, options) @@ -332,14 +356,14 @@ export class ZabbixDatasource { } queryTriggersData(target, timeRange) { - let [timeFrom, timeTo] = timeRange; + const [timeFrom, timeTo] = timeRange; return this.zabbix.getHostsFromTarget(target) .then(results => { - let [hosts, apps] = results; + const [hosts, apps] = results; if (hosts.length) { - let hostids = _.map(hosts, 'hostid'); - let appids = _.map(apps, 'applicationid'); - let options = { + const hostids = _.map(hosts, 'hostid'); + const appids = _.map(apps, 'applicationid'); + const options = { minSeverity: target.triggers.minSeverity, acknowledged: target.triggers.acknowledged, count: target.triggers.count, @@ -480,16 +504,16 @@ export class ZabbixDatasource { const timeRange = options.range || options.rangeRaw; const timeFrom = Math.ceil(dateMath.parse(timeRange.from) / 1000); const timeTo = Math.ceil(dateMath.parse(timeRange.to) / 1000); - var annotation = options.annotation; - var showOkEvents = annotation.showOkEvents ? c.SHOW_ALL_EVENTS : c.SHOW_OK_EVENTS; + const annotation = options.annotation; + const showOkEvents = annotation.showOkEvents ? c.SHOW_ALL_EVENTS : c.SHOW_OK_EVENTS; // Show all triggers - let triggersOptions = { + const triggersOptions = { showTriggers: c.SHOW_ALL_TRIGGERS, hideHostsInMaintenance: false }; - var getTriggers = this.zabbix.getTriggers(this.replaceTemplateVars(annotation.group, {}), + const getTriggers = this.zabbix.getTriggers(this.replaceTemplateVars(annotation.group, {}), this.replaceTemplateVars(annotation.host, {}), this.replaceTemplateVars(annotation.application, {}), triggersOptions); @@ -497,7 +521,7 @@ export class ZabbixDatasource { return getTriggers.then(triggers => { // Filter triggers by description - let triggerName = this.replaceTemplateVars(annotation.trigger, {}); + const triggerName = this.replaceTemplateVars(annotation.trigger, {}); if (utils.isRegex(triggerName)) { triggers = _.filter(triggers, trigger => { return utils.buildRegex(triggerName).test(trigger.description); @@ -513,11 +537,11 @@ export class ZabbixDatasource { return Number(trigger.priority) >= Number(annotation.minseverity); }); - var objectids = _.map(triggers, 'triggerid'); + const objectids = _.map(triggers, 'triggerid'); return this.zabbix .getEvents(objectids, timeFrom, timeTo, showOkEvents) .then(events => { - var indexedTriggers = _.keyBy(triggers, 'triggerid'); + const indexedTriggers = _.keyBy(triggers, 'triggerid'); // Hide acknowledged events if option enabled if (annotation.hideAcknowledged) { @@ -533,10 +557,10 @@ export class ZabbixDatasource { } // Show event type (OK or Problem) - let title = Number(event.value) ? 'Problem' : 'OK'; + const title = Number(event.value) ? 'Problem' : 'OK'; - let formattedAcknowledges = utils.formatAcknowledges(event.acknowledges); - let eventName = event.name || indexedTriggers[event.objectid].description; + const formattedAcknowledges = utils.formatAcknowledges(event.acknowledges); + const eventName = event.name || indexedTriggers[event.objectid].description; return { annotation: annotation, time: event.clock * 1000, @@ -555,8 +579,8 @@ export class ZabbixDatasource { * or empty object if no related triggers are finded. */ alertQuery(options) { - let enabled_targets = filterEnabledTargets(options.targets); - let getPanelItems = _.map(enabled_targets, t => { + const enabled_targets = filterEnabledTargets(options.targets); + const getPanelItems = _.map(enabled_targets, t => { let target = _.cloneDeep(t); target = migrations.migrate(target); this.replaceTargetVariables(target, options); @@ -565,8 +589,8 @@ export class ZabbixDatasource { return Promise.all(getPanelItems) .then(results => { - let items = _.flatten(results); - let itemids = _.map(items, 'itemid'); + const items = _.flatten(results); + const itemids = _.map(items, 'itemid'); if (itemids.length === 0) { return []; @@ -584,12 +608,12 @@ export class ZabbixDatasource { let state = 'ok'; - let firedTriggers = _.filter(triggers, {value: '1'}); + const firedTriggers = _.filter(triggers, {value: '1'}); if (firedTriggers.length) { state = 'alerting'; } - let thresholds = _.map(triggers, trigger => { + const thresholds = _.map(triggers, trigger => { return getTriggerThreshold(trigger.expression); }); @@ -603,7 +627,7 @@ export class ZabbixDatasource { // Replace template variables replaceTargetVariables(target, options) { - let parts = ['group', 'host', 'application', 'item']; + const parts = ['group', 'host', 'application', 'item']; _.forEach(parts, p => { if (target[p] && target[p].filter) { target[p].filter = this.replaceTemplateVars(target[p].filter, options.scopedVars); @@ -623,10 +647,10 @@ export class ZabbixDatasource { } isUseTrends(timeRange) { - let [timeFrom, timeTo] = timeRange; - let useTrendsFrom = Math.ceil(dateMath.parse('now-' + this.trendsFrom) / 1000); - let useTrendsRange = Math.ceil(utils.parseInterval(this.trendsRange) / 1000); - let useTrends = this.trends && ( + const [timeFrom, timeTo] = timeRange; + const useTrendsFrom = Math.ceil(dateMath.parse('now-' + this.trendsFrom) / 1000); + const useTrendsRange = Math.ceil(utils.parseInterval(this.trendsRange) / 1000); + const useTrends = this.trends && ( (timeFrom < useTrendsFrom) || (timeTo - timeFrom > useTrendsRange) ); @@ -635,20 +659,20 @@ export class ZabbixDatasource { } function bindFunctionDefs(functionDefs, category) { - var aggregationFunctions = _.map(metricFunctions.getCategories()[category], 'name'); - var aggFuncDefs = _.filter(functionDefs, function(func) { + const aggregationFunctions = _.map(metricFunctions.getCategories()[category], 'name'); + const aggFuncDefs = _.filter(functionDefs, func => { return _.includes(aggregationFunctions, func.def.name); }); - return _.map(aggFuncDefs, function(func) { - var funcInstance = metricFunctions.createFuncInstance(func.def, func.params); + return _.map(aggFuncDefs, func => { + const funcInstance = metricFunctions.createFuncInstance(func.def, func.params); return funcInstance.bindFunction(dataProcessor.metricFunctions); }); } function getConsolidateBy(target) { let consolidateBy; - let funcDef = _.find(target.functions, func => { + const funcDef = _.find(target.functions, func => { return func.def.name === 'consolidateBy'; }); if (funcDef && funcDef.params && funcDef.params.length) { @@ -658,8 +682,8 @@ function getConsolidateBy(target) { } function downsampleSeries(timeseries_data, options) { - let defaultAgg = dataProcessor.aggregationFunctions['avg']; - let consolidateByFunc = dataProcessor.aggregationFunctions[options.consolidateBy] || defaultAgg; + const defaultAgg = dataProcessor.aggregationFunctions['avg']; + const consolidateByFunc = dataProcessor.aggregationFunctions[options.consolidateBy] || defaultAgg; return _.map(timeseries_data, timeseries => { if (timeseries.datapoints.length > options.maxDataPoints) { timeseries.datapoints = dataProcessor @@ -691,7 +715,7 @@ export function zabbixTemplateFormat(value) { return utils.escapeRegex(value); } - var escapedValues = _.map(value, utils.escapeRegex); + const escapedValues = _.map(value, utils.escapeRegex); return '(' + escapedValues.join('|') + ')'; } @@ -711,7 +735,7 @@ function zabbixItemIdsTemplateFormat(value) { * /$variable/ -> /a|b|c/ -> /a|b|c/ */ function replaceTemplateVars(templateSrv, target, scopedVars) { - var replacedTarget = templateSrv.replace(target, scopedVars, zabbixTemplateFormat); + let replacedTarget = templateSrv.replace(target, scopedVars, zabbixTemplateFormat); if (target !== replacedTarget && !utils.isRegex(replacedTarget)) { replacedTarget = '/^' + replacedTarget + '$/'; } @@ -725,8 +749,8 @@ function filterEnabledTargets(targets) { } function getTriggerThreshold(expression) { - let thresholdPattern = /.*[<>=]{1,2}([\d\.]+)/; - let finded_thresholds = expression.match(thresholdPattern); + const thresholdPattern = /.*[<>=]{1,2}([\d\.]+)/; + const finded_thresholds = expression.match(thresholdPattern); if (finded_thresholds && finded_thresholds.length >= 2) { let threshold = finded_thresholds[1]; threshold = Number(threshold); @@ -735,7 +759,3 @@ function getTriggerThreshold(expression) { return null; } } - -// Fix for backward compatibility with lodash 2.4 -if (!_.includes) {_.includes = _.contains;} -if (!_.keyBy) {_.keyBy = _.indexBy;} diff --git a/src/datasource-zabbix/utils.ts b/src/datasource-zabbix/utils.ts index 44bc5a5..5eb68c4 100644 --- a/src/datasource-zabbix/utils.ts +++ b/src/datasource-zabbix/utils.ts @@ -222,10 +222,11 @@ export function escapeRegex(value) { return value.replace(/[\\^$*+?.()|[\]{}\/]/g, '\\$&'); } -export function parseInterval(interval) { +export function parseInterval(interval: string): number { const intervalPattern = /(^[\d]+)(y|M|w|d|h|m|s)/g; const momentInterval: any[] = intervalPattern.exec(interval); - return moment.duration(Number(momentInterval[1]), momentInterval[2]).valueOf(); + const duration = moment.duration(Number(momentInterval[1]), momentInterval[2]); + return (duration.valueOf() as number); } export function parseTimeShiftInterval(interval) { From e7cd31c75ca8f076fff3573c06c0d626d25c5e84 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Thu, 7 May 2020 11:49:51 +0300 Subject: [PATCH 02/33] convert metricFunctions to TS --- ...{metricFunctions.js => metricFunctions.ts} | 41 ++++++++++--------- src/datasource-zabbix/utils.ts | 7 ++++ 2 files changed, 28 insertions(+), 20 deletions(-) rename src/datasource-zabbix/{metricFunctions.js => metricFunctions.ts} (92%) diff --git a/src/datasource-zabbix/metricFunctions.js b/src/datasource-zabbix/metricFunctions.ts similarity index 92% rename from src/datasource-zabbix/metricFunctions.js rename to src/datasource-zabbix/metricFunctions.ts index 150e03a..fdad893 100644 --- a/src/datasource-zabbix/metricFunctions.js +++ b/src/datasource-zabbix/metricFunctions.ts @@ -1,8 +1,8 @@ import _ from 'lodash'; -import $ from 'jquery'; +import { isNumeric } from './utils'; -var index = []; -var categories = { +const index = []; +const categories = { Transform: [], Aggregate: [], Filter: [], @@ -298,11 +298,15 @@ addFuncDef({ defaultParams: ['avg'], }); -_.each(categories, function(funcList, catName) { +_.each(categories, (funcList, catName) => { categories[catName] = _.sortBy(funcList, 'name'); }); class FuncInstance { + def: any; + params: any; + text: string; + constructor(funcDef, params) { this.def = funcDef; @@ -318,13 +322,13 @@ class FuncInstance { } bindFunction(metricFunctions) { - var func = metricFunctions[this.def.name]; + const func = metricFunctions[this.def.name]; if (func) { // Bind function arguments - var bindedFunc = func; - var param; - for (var i = 0; i < this.params.length; i++) { + let bindedFunc = func; + let param; + for (let i = 0; i < this.params.length; i++) { param = this.params[i]; // Convert numeric params @@ -341,23 +345,21 @@ class FuncInstance { } render(metricExp) { - var str = this.def.name + '('; - var parameters = _.map(this.params, function(value, index) { - - var paramType = this.def.params[index].type; + const str = this.def.name + '('; + const parameters = _.map(this.params, (value, index) => { + const paramType = this.def.params[index].type; if (paramType === 'int' || paramType === 'float' || paramType === 'value_or_series' || paramType === 'boolean') { return value; - } - else if (paramType === 'int_or_interval' && $.isNumeric(value)) { + } else if (paramType === 'int_or_interval' && isNumeric(value)) { return value; } return "'" + value + "'"; - }, this); + }); if (metricExp) { parameters.unshift(metricExp); @@ -378,16 +380,15 @@ class FuncInstance { // handle optional parameters // if string contains ',' and next param is optional, split and update both if (this._hasMultipleParamsInString(strValue, index)) { - _.each(strValue.split(','), function(partVal, idx) { + _.each(strValue.split(','), (partVal, idx) => { this.updateParam(partVal.trim(), idx); - }, this); + }); return; } if (strValue === '' && this.def.params[index].optional) { this.params.splice(index, 1); - } - else { + }else { this.params[index] = strValue; } @@ -400,7 +401,7 @@ class FuncInstance { return; } - var text = this.def.name + '('; + let text = this.def.name + '('; text += this.params.join(', '); text += ')'; this.text = text; diff --git a/src/datasource-zabbix/utils.ts b/src/datasource-zabbix/utils.ts index 5eb68c4..93c99e5 100644 --- a/src/datasource-zabbix/utils.ts +++ b/src/datasource-zabbix/utils.ts @@ -345,6 +345,13 @@ export function getArrayDepth(a, level = 0) { return level + 1; } +/** + * Checks whether its argument represents a numeric value. + */ +export function isNumeric(n: any): boolean { + return !isNaN(parseFloat(n)) && isFinite(n); +} + // Fix for backward compatibility with lodash 2.4 if (!_.includes) { _.includes = (_ as any).contains; From ffb18620947f728ba0556cfa1cb71014cd93ea1b Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Thu, 7 May 2020 11:56:41 +0300 Subject: [PATCH 03/33] convert timeseries.js to TS --- .../{timeseries.js => timeseries.ts} | 93 +++++++++---------- src/datasource-zabbix/utils.ts | 5 - 2 files changed, 44 insertions(+), 54 deletions(-) rename src/datasource-zabbix/{timeseries.js => timeseries.ts} (87%) diff --git a/src/datasource-zabbix/timeseries.js b/src/datasource-zabbix/timeseries.ts similarity index 87% rename from src/datasource-zabbix/timeseries.js rename to src/datasource-zabbix/timeseries.ts index 109fa41..c40ee91 100644 --- a/src/datasource-zabbix/timeseries.js +++ b/src/datasource-zabbix/timeseries.ts @@ -20,35 +20,30 @@ const POINT_TIMESTAMP = 1; * Downsample time series by using given function (avg, min, max). */ function downsample(datapoints, time_to, ms_interval, func) { - var downsampledSeries = []; - var timeWindow = { + const downsampledSeries = []; + const timeWindow = { from: time_to * 1000 - ms_interval, to: time_to * 1000 }; - var points_sum = 0; - var points_num = 0; - var value_avg = 0; - var frame = []; + let points_sum = 0; + let points_num = 0; + let value_avg = 0; + let frame = []; - for (var i = datapoints.length - 1; i >= 0; i -= 1) { + for (let i = datapoints.length - 1; i >= 0; i -= 1) { if (timeWindow.from < datapoints[i][1] && datapoints[i][1] <= timeWindow.to) { points_sum += datapoints[i][0]; points_num++; frame.push(datapoints[i][0]); - } - else { + } else { value_avg = points_num ? points_sum / points_num : 0; if (func === "max") { downsampledSeries.push([_.max(frame), timeWindow.to]); - } - else if (func === "min") { + } else if (func === "min") { downsampledSeries.push([_.min(frame), timeWindow.to]); - } - - // avg by default - else { + } else { downsampledSeries.push([value_avg, timeWindow.to]); } @@ -72,25 +67,25 @@ function downsample(datapoints, time_to, ms_interval, func) { * datapoints: [[, ], ...] */ function groupBy(datapoints, interval, groupByCallback) { - var ms_interval = utils.parseInterval(interval); + const ms_interval = utils.parseInterval(interval); // Calculate frame timestamps - var frames = _.groupBy(datapoints, function (point) { + const frames = _.groupBy(datapoints, point => { // Calculate time for group of points return Math.floor(point[1] / ms_interval) * ms_interval; }); // frame: { '': [[, ], ...] } // return [{ '': }, { '': }, ...] - var grouped = _.mapValues(frames, function (frame) { - var points = _.map(frame, function (point) { + const grouped = _.mapValues(frames, frame => { + const points = _.map(frame, point => { return point[0]; }); return groupByCallback(points); }); // Convert points to Grafana format - return sortByTime(_.map(grouped, function (value, timestamp) { + return sortByTime(_.map(grouped, (value, timestamp) => { return [Number(value), Number(timestamp)]; })); } @@ -104,15 +99,15 @@ export function groupBy_perf(datapoints, interval, groupByCallback) { return groupByRange(datapoints, groupByCallback); } - let ms_interval = utils.parseInterval(interval); - let grouped_series = []; + const ms_interval = utils.parseInterval(interval); + const grouped_series = []; let frame_values = []; let frame_value; let frame_ts = datapoints.length ? getPointTimeFrame(datapoints[0][POINT_TIMESTAMP], ms_interval) : 0; let point_frame_ts = frame_ts; let point; - for (let i=0; i < datapoints.length; i++) { + for (let i = 0; i < datapoints.length; i++) { point = datapoints[i]; point_frame_ts = getPointTimeFrame(point[POINT_TIMESTAMP], ms_interval); if (point_frame_ts === frame_ts) { @@ -142,7 +137,7 @@ export function groupByRange(datapoints, groupByCallback) { const frame_start = datapoints[0][POINT_TIMESTAMP]; const frame_end = datapoints[datapoints.length - 1][POINT_TIMESTAMP]; let point; - for (let i=0; i < datapoints.length; i++) { + for (let i = 0; i < datapoints.length; i++) { point = datapoints[i]; frame_values.push(point[POINT_VALUE]); } @@ -157,30 +152,30 @@ export function groupByRange(datapoints, groupByCallback) { function sumSeries(timeseries) { // Calculate new points for interpolation - var new_timestamps = _.uniq(_.map(_.flatten(timeseries, true), function (point) { + let new_timestamps = _.uniq(_.map(_.flatten(timeseries), point => { return point[1]; })); new_timestamps = _.sortBy(new_timestamps); - var interpolated_timeseries = _.map(timeseries, function (series) { + const interpolated_timeseries = _.map(timeseries, series => { series = fillZeroes(series, new_timestamps); - var timestamps = _.map(series, function (point) { + const timestamps = _.map(series, point => { return point[1]; }); - var new_points = _.map(_.difference(new_timestamps, timestamps), function (timestamp) { + const new_points = _.map(_.difference(new_timestamps, timestamps), timestamp => { return [null, timestamp]; }); - var new_series = series.concat(new_points); + const new_series = series.concat(new_points); return sortByTime(new_series); }); _.each(interpolated_timeseries, interpolateSeries); - var new_timeseries = []; - var sum; - for (var i = new_timestamps.length - 1; i >= 0; i--) { + const new_timeseries = []; + let sum; + for (let i = new_timestamps.length - 1; i >= 0; i--) { sum = 0; - for (var j = interpolated_timeseries.length - 1; j >= 0; j--) { + for (let j = interpolated_timeseries.length - 1; j >= 0; j--) { sum += interpolated_timeseries[j][i][0]; } new_timeseries.push([sum, new_timestamps[i]]); @@ -225,9 +220,9 @@ function offset(datapoints, delta) { * @param {*} datapoints */ function delta(datapoints) { - let newSeries = []; + const newSeries = []; let deltaValue; - for (var i = 1; i < datapoints.length; i++) { + for (let i = 1; i < datapoints.length; i++) { deltaValue = datapoints[i][0] - datapoints[i - 1][0]; newSeries.push([deltaValue, datapoints[i][1]]); } @@ -239,7 +234,7 @@ function delta(datapoints) { * @param {*} datapoints */ function rate(datapoints) { - let newSeries = []; + const newSeries = []; let point, point_prev; let valueDelta = 0; let timeDelta = 0; @@ -261,7 +256,7 @@ function rate(datapoints) { } function simpleMovingAverage(datapoints, n) { - let sma = []; + const sma = []; let w_sum; let w_avg = null; let w_count = 0; @@ -352,7 +347,7 @@ function expMovingAverage(datapoints, n) { } function PERCENTILE(n, values) { - var sorted = _.sortBy(values); + const sorted = _.sortBy(values); return sorted[Math.floor(sorted.length * n / 100)]; } @@ -361,7 +356,7 @@ function COUNT(values) { } function SUM(values) { - var sum = null; + let sum = null; for (let i = 0; i < values.length; i++) { if (values[i] !== null) { sum += values[i]; @@ -371,7 +366,7 @@ function SUM(values) { } function AVERAGE(values) { - let values_non_null = getNonNullValues(values); + const values_non_null = getNonNullValues(values); if (values_non_null.length === 0) { return null; } @@ -379,7 +374,7 @@ function AVERAGE(values) { } function getNonNullValues(values) { - let values_non_null = []; + const values_non_null = []; for (let i = 0; i < values.length; i++) { if (values[i] !== null) { values_non_null.push(values[i]); @@ -397,7 +392,7 @@ function MAX(values) { } function MEDIAN(values) { - var sorted = _.sortBy(values); + const sorted = _.sortBy(values); return sorted[Math.floor(sorted.length / 2)]; } @@ -418,7 +413,7 @@ function getPointTimeFrame(timestamp, ms_interval) { } function sortByTime(series) { - return _.sortBy(series, function (point) { + return _.sortBy(series, point => { return point[1]; }); } @@ -432,8 +427,8 @@ function sortByTime(series) { * @param {*} timestamps */ function fillZeroes(series, timestamps) { - let prepend = []; - let append = []; + const prepend = []; + const append = []; let new_point; for (let i = 0; i < timestamps.length; i++) { if (timestamps[i] < series[0][POINT_TIMESTAMP]) { @@ -451,10 +446,10 @@ function fillZeroes(series, timestamps) { * Interpolate series with gaps */ function interpolateSeries(series) { - var left, right; + let left, right; // Interpolate series - for (var i = series.length - 1; i >= 0; i--) { + for (let i = series.length - 1; i >= 0; i--) { if (!series[i][0]) { left = findNearestLeft(series, i); right = findNearestRight(series, i); @@ -479,7 +474,7 @@ function linearInterpolation(timestamp, left, right) { } function findNearestRight(series, pointIndex) { - for (var i = pointIndex; i < series.length; i++) { + for (let i = pointIndex; i < series.length; i++) { if (series[i][0] !== null) { return series[i]; } @@ -488,7 +483,7 @@ function findNearestRight(series, pointIndex) { } function findNearestLeft(series, pointIndex) { - for (var i = pointIndex; i > 0; i--) { + for (let i = pointIndex; i > 0; i--) { if (series[i][0] !== null) { return series[i]; } diff --git a/src/datasource-zabbix/utils.ts b/src/datasource-zabbix/utils.ts index 93c99e5..69784f5 100644 --- a/src/datasource-zabbix/utils.ts +++ b/src/datasource-zabbix/utils.ts @@ -351,8 +351,3 @@ export function getArrayDepth(a, level = 0) { export function isNumeric(n: any): boolean { return !isNaN(parseFloat(n)) && isFinite(n); } - -// Fix for backward compatibility with lodash 2.4 -if (!_.includes) { - _.includes = (_ as any).contains; -} From 64f64ff71fe3441518034123893eee537c8c0d2a Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Thu, 7 May 2020 11:57:22 +0300 Subject: [PATCH 04/33] remove lodash backward compatibility fixes --- src/datasource-zabbix/responseHandler.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/datasource-zabbix/responseHandler.ts b/src/datasource-zabbix/responseHandler.ts index ccb6576..cb46c65 100644 --- a/src/datasource-zabbix/responseHandler.ts +++ b/src/datasource-zabbix/responseHandler.ts @@ -258,6 +258,3 @@ export default { handleTriggersResponse, sortTimeseries }; - -// Fix for backward compatibility with lodash 2.4 -if (!_.uniqBy) {_.uniqBy = _.uniq;} From 29d902326aabfca37ae3eef21d872fb78b98de73 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Thu, 7 May 2020 13:38:55 +0300 Subject: [PATCH 05/33] Move problems query to the data source --- src/datasource-zabbix/constants.ts | 1 + src/datasource-zabbix/datasource.ts | 80 +++++++++- .../partials/query.editor.html | 14 +- src/datasource-zabbix/problemsHandler.ts | 137 ++++++++++++++++++ src/datasource-zabbix/query.controller.js | 9 +- src/datasource-zabbix/utils.ts | 16 ++ 6 files changed, 246 insertions(+), 11 deletions(-) create mode 100644 src/datasource-zabbix/problemsHandler.ts diff --git a/src/datasource-zabbix/constants.ts b/src/datasource-zabbix/constants.ts index 79d2edc..6917eff 100644 --- a/src/datasource-zabbix/constants.ts +++ b/src/datasource-zabbix/constants.ts @@ -8,6 +8,7 @@ export const MODE_ITSERVICE = 1; export const MODE_TEXT = 2; export const MODE_ITEMID = 3; export const MODE_TRIGGERS = 4; +export const MODE_PROBLEMS = 5; // Triggers severity export const SEV_NOT_CLASSIFIED = 0; diff --git a/src/datasource-zabbix/datasource.ts b/src/datasource-zabbix/datasource.ts index e4a0bb6..178c290 100644 --- a/src/datasource-zabbix/datasource.ts +++ b/src/datasource-zabbix/datasource.ts @@ -1,5 +1,6 @@ import _ from 'lodash'; import config from 'grafana/app/core/config'; +import { contextSrv } from 'grafana/app/core/core'; import * as dateMath from 'grafana/app/core/utils/datemath'; import * as utils from './utils'; import * as migrations from './migrations'; @@ -7,9 +8,11 @@ import * as metricFunctions from './metricFunctions'; import * as c from './constants'; import dataProcessor from './dataProcessor'; import responseHandler from './responseHandler'; +import problemsHandler from './problemsHandler'; import { Zabbix } from './zabbix/zabbix'; import { ZabbixAPIError } from './zabbix/connectors/zabbix_api/zabbixAPICore'; import { VariableQueryTypes } from './types'; +import { getDataSourceSrv } from '@grafana/runtime'; const DEFAULT_ZABBIX_VERSION = 3; @@ -37,7 +40,7 @@ export class ZabbixDatasource { enableDebugLog: boolean; zabbix: any; - replaceTemplateVars: (templateSrv: any, target: any, scopedVars?: any) => any; + replaceTemplateVars: (target: any, scopedVars?: any) => any; /** @ngInject */ constructor(instanceSettings, private templateSrv, private zabbixAlertingSrv) { @@ -181,6 +184,9 @@ export class ZabbixDatasource { } else if (target.queryType === c.MODE_TRIGGERS) { // Triggers query return this.queryTriggersData(target, timeRange); + } else if (target.queryType === c.MODE_PROBLEMS) { + // Problems query + return this.queryProblems(target, timeRange, options); } else { return []; } @@ -384,6 +390,78 @@ export class ZabbixDatasource { }); } + queryProblems(target, timeRange, options) { + const [timeFrom, timeTo] = timeRange; + const userIsEditor = contextSrv.isEditor || contextSrv.isGrafanaAdmin; + + let proxies; + let showAckButton = true; + + // const showEvents = this.panel.showEvents.value; + const showEvents = target.showEvents?.value || 1; + // const showProxy = this.panel.hostProxy; + const showProxy = target.hostProxy; + + const getProxiesPromise = showProxy ? this.zabbix.getProxies() : () => []; + showAckButton = !this.disableReadOnlyUsersAck || userIsEditor; + + // Replace template variables + const groupFilter = this.replaceTemplateVars(target.group?.filter, options.scopedVars); + const hostFilter = this.replaceTemplateVars(target.host?.filter, options.scopedVars); + const appFilter = this.replaceTemplateVars(target.application?.filter, options.scopedVars); + const proxyFilter = this.replaceTemplateVars(target.proxy?.filter, options.scopedVars); + + const triggerFilter = this.replaceTemplateVars(target.trigger?.filter, options.scopedVars); + const tagsFilter = this.replaceTemplateVars(target.tags?.filter, options.scopedVars); + + const replacedTarget = { + ...target, + trigger: { filter: triggerFilter }, + tags: { filter: tagsFilter }, + }; + + const triggersOptions: any = { + showTriggers: showEvents + }; + + if (showEvents !== 1) { + triggersOptions.timeFrom = timeFrom; + triggersOptions.timeTo = timeTo; + } + + const problemsPromises = Promise.all([ + this.zabbix.getTriggers(groupFilter, hostFilter, appFilter, triggersOptions, proxyFilter), + getProxiesPromise + ]) + .then(([triggers, sourceProxies]) => { + proxies = _.keyBy(sourceProxies, 'proxyid'); + const eventids = _.compact(triggers.map(trigger => { + return trigger.lastEvent.eventid; + })); + return Promise.all([ + this.zabbix.getExtendedEventData(eventids), + Promise.resolve(triggers) + ]); + }) + .then(([events, triggers]) => { + problemsHandler.addEventTags(events, triggers); + problemsHandler.addAcknowledges(events, triggers); + return triggers; + }) + .then(triggers => problemsHandler.setMaintenanceStatus(triggers)) + .then(triggers => problemsHandler.setAckButtonStatus(triggers, showAckButton)) + .then(triggers => problemsHandler.filterTriggersPre(triggers, replacedTarget)) + .then(triggers => problemsHandler.addTriggerDataSource(triggers, target)) + .then(triggers => problemsHandler.addTriggerHostProxy(triggers, proxies)); + + return problemsPromises.then(problems => { + const problemsDataFrame = problemsHandler.toDataFrame(problems); + console.log(problems); + console.log(problemsDataFrame); + return problemsDataFrame; + }); + } + /** * Test connection to Zabbix API and external history DB. */ diff --git a/src/datasource-zabbix/partials/query.editor.html b/src/datasource-zabbix/partials/query.editor.html index c713b74..14487b6 100644 --- a/src/datasource-zabbix/partials/query.editor.html +++ b/src/datasource-zabbix/partials/query.editor.html @@ -54,7 +54,7 @@ -
+
@@ -91,7 +91,7 @@
-
+
@@ -124,7 +124,7 @@ }">
-
+
-