From 7842f468d4ba89f7a444989e3bbb91bf790f3986 Mon Sep 17 00:00:00 2001 From: Tom Zhang Date: Thu, 14 Jul 2016 11:10:48 +0930 Subject: [PATCH 01/64] add acknowledge trigger event function. --- src/datasource-zabbix/zabbixAPI.service.js | 8 +++++ src/panel-triggers/module.html | 17 ++++++++++ src/panel-triggers/module.js | 38 +++++++++++++++++++++- 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/datasource-zabbix/zabbixAPI.service.js b/src/datasource-zabbix/zabbixAPI.service.js index df8bcf4..f8dd3f1 100644 --- a/src/datasource-zabbix/zabbixAPI.service.js +++ b/src/datasource-zabbix/zabbixAPI.service.js @@ -47,6 +47,7 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) { }, // Handle API errors function(error) { + console.log('Zabbix error: '+error.data); if (isNotAuthorized(error.data)) { return self.loginOnce().then( function() { @@ -113,6 +114,13 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) { //////////////////////////////// // Zabbix API method wrappers // //////////////////////////////// + acknowledgeEvent(eventid,message){ + var params = { + eventids:eventid, + message:message + }; + return this.request('event.acknowledge',params); + } getGroups() { var params = { diff --git a/src/panel-triggers/module.html b/src/panel-triggers/module.html index fddc004..e16046e 100644 --- a/src/panel-triggers/module.html +++ b/src/panel-triggers/module.html @@ -94,6 +94,15 @@ {{ack.message}} + + + {{trigger.newAct.time}} + + + {{trigger.newAct.user}} + + + @@ -127,6 +136,14 @@ bs-tooltip="'Acknowledges ({{trigger.acknowledges.length}})'"> + + + + + diff --git a/src/panel-triggers/module.js b/src/panel-triggers/module.js index 7de6390..1d952c9 100644 --- a/src/panel-triggers/module.js +++ b/src/panel-triggers/module.js @@ -59,10 +59,11 @@ var defaultTimeFormat = "DD MMM YYYY HH:mm:ss"; class TriggerPanelCtrl extends MetricsPanelCtrl { /** @ngInject */ - constructor($scope, $injector, $q, $element, datasourceSrv, templateSrv) { + constructor($scope, $injector, $q, $element, datasourceSrv, templateSrv,contextSrv) { super($scope, $injector); this.datasourceSrv = datasourceSrv; this.templateSrv = templateSrv; + this.contextSrv = contextSrv; this.triggerStatusMap = triggerStatusMap; this.defaultTimeFormat = defaultTimeFormat; @@ -227,6 +228,41 @@ class TriggerPanelCtrl extends MetricsPanelCtrl { switchAcknowledges(trigger) { trigger.showAcknowledges = !trigger.showAcknowledges; } + addAcknowledgeMessage(trigger){ + trigger.showAcknowledges = true; + trigger.newAct={ + time:new Date(), + user:this.contextSrv.user.name+'(Grafana)', + eventid:trigger.lastEvent.eventid, + message:'' + }; + } + acknowledgeTrigger($event,trigger,newAct){ + if($event.keyCode!=13) return; + if(newAct.message.trim() === ""){ + delete trigger.newAct; + trigger.showAcknowledges = false; + return; + } + this.datasourceSrv.get(this.panel.datasource).then(datasource => { + var zabbix = datasource.zabbixAPI; + zabbix.acknowledgeEvent(newAct.eventid,newAct.user+': '+newAct.message) + .then(rs=>{ + zabbix.getAcknowledges(rs.eventids).then(events => { + _.each(events, event => { + trigger.acknowledges = _.map(event.acknowledges, ack => { + var time = new Date(+ack.clock * 1000); + ack.time = time.toLocaleString(); + ack.user = ack.alias + ' (' + ack.name + ' ' + ack.surname + ')'; + return ack; + }); + }); + delete trigger.newAct; + console.log('event id '+ rs.eventids.join() + ' new message added: ' + ack.message ); + }); + }); + }); + } } TriggerPanelCtrl.templateUrl = 'panel-triggers/module.html'; From 24969cd560c8f9caec43830646f932d49283748d Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sun, 31 Jul 2016 11:17:22 +0300 Subject: [PATCH 02/64] Acknowledges: use tooltip instead inner table. --- src/datasource-zabbix/zabbixAPI.service.js | 11 +- src/panel-triggers/ack-tooltip.directive.js | 113 ++++++++++++++++++++ src/panel-triggers/module.html | 66 +++--------- src/panel-triggers/module.js | 59 ++++------ src/panel-triggers/sass/panel_triggers.scss | 34 ++++++ 5 files changed, 185 insertions(+), 98 deletions(-) create mode 100644 src/panel-triggers/ack-tooltip.directive.js diff --git a/src/datasource-zabbix/zabbixAPI.service.js b/src/datasource-zabbix/zabbixAPI.service.js index f8dd3f1..523f8a1 100644 --- a/src/datasource-zabbix/zabbixAPI.service.js +++ b/src/datasource-zabbix/zabbixAPI.service.js @@ -47,7 +47,6 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) { }, // Handle API errors function(error) { - console.log('Zabbix error: '+error.data); if (isNotAuthorized(error.data)) { return self.loginOnce().then( function() { @@ -114,12 +113,14 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) { //////////////////////////////// // Zabbix API method wrappers // //////////////////////////////// - acknowledgeEvent(eventid,message){ + + acknowledgeEvent(eventid, message) { var params = { - eventids:eventid, - message:message + eventids: eventid, + message: message }; - return this.request('event.acknowledge',params); + + return this.request('event.acknowledge', params); } getGroups() { diff --git a/src/panel-triggers/ack-tooltip.directive.js b/src/panel-triggers/ack-tooltip.directive.js new file mode 100644 index 0000000..3622db4 --- /dev/null +++ b/src/panel-triggers/ack-tooltip.directive.js @@ -0,0 +1,113 @@ +import angular from 'angular'; +import $ from 'jquery'; +import Drop from 'tether-drop'; + +/** @ngInject */ +angular + .module('grafana.directives') + .directive('ackTooltip', function($sanitize, $compile) { + let buttonTemplate = ''; + + return { + scope: { + ack: "=", + trigger: "=", + onAck: "=", + context: "=" + }, + link: function(scope, element) { + let acknowledges = scope.ack; + let $button = $(buttonTemplate); + $button.appendTo(element); + + $button.click(function() { + let tooltip = '
'; + + if (acknowledges && acknowledges.length) { + tooltip += '' + + '' + + '' + + '' + + ''; + for (let ack of acknowledges) { + tooltip += '' + + '' + + ''; + } + tooltip += '
TimeUserComments
' + ack.time + '' + ack.user + '' + ack.message + '
'; + } else { + tooltip += 'Add acknowledge'; + } + + let addAckButtonTemplate = '
' + + '
'; + tooltip += addAckButtonTemplate; + tooltip += '
'; + + let drop = new Drop({ + target: element[0], + content: tooltip, + position: "bottom left", + classes: 'drop-popover ack-tooltip', + openOn: 'hover', + hoverCloseDelay: 500, + tetherOptions: { + constraints: [{to: 'window', pin: true, attachment: "both"}] + } + }); + + drop.open(); + drop.on('close', closeDrop); + + $('#add-acknowledge-btn').on('click', onAddAckButtonClick); + + function onAddAckButtonClick() { + let inputTemplate = '
' + + '' + + '' + + '
'; + + let $input = $(inputTemplate); + let $addAckButton = $('.ack-tooltip .ack-add-button'); + $addAckButton.replaceWith($input); + $('.ack-tooltip #cancel-ack-button').on('click', onAckCancelButtonClick); + $('.ack-tooltip #send-ack-button').on('click', onAckSendlButtonClick); + } + + function onAckCancelButtonClick() { + $('.ack-tooltip .ack-input-group').replaceWith(addAckButtonTemplate); + $('#add-acknowledge-btn').on('click', onAddAckButtonClick); + } + + function onAckSendlButtonClick() { + let message = $('.ack-tooltip #ack-message')[0].value; + let onAck = scope.onAck.bind(scope.context); + onAck(scope.trigger, message).then(() => { + closeDrop(); + }); + } + + function closeDrop() { + setTimeout(function() { + drop.destroy(); + }); + } + + }); + + $compile(element.contents())(scope); + } + }; + }); diff --git a/src/panel-triggers/module.html b/src/panel-triggers/module.html index e16046e..5c28e5f 100644 --- a/src/panel-triggers/module.html +++ b/src/panel-triggers/module.html @@ -31,21 +31,25 @@ +
{{trigger.host}}
+
{{ctrl.triggerStatusMap[trigger.value]}}
+
{{trigger.severity}}
+
{{trigger.description}} @@ -68,52 +72,16 @@ {{trigger.comments}}
- - -
-
- - - - - - - - - - - - - - - - - - - - -
TimeUserComments
- {{ack.time}} - - {{ack.user}} - - {{ack.message}} -
- {{trigger.newAct.time}} - - {{trigger.newAct.user}} -
-
-
+ {{trigger.lastchange}} + {{trigger.age}} + @@ -130,20 +98,12 @@ - - - - - - - - + + diff --git a/src/panel-triggers/module.js b/src/panel-triggers/module.js index 1d952c9..51718b5 100644 --- a/src/panel-triggers/module.js +++ b/src/panel-triggers/module.js @@ -16,6 +16,7 @@ import moment from 'moment'; import * as utils from '../datasource-zabbix/utils'; import {MetricsPanelCtrl} from 'app/plugins/sdk'; import {triggerPanelEditor} from './editor'; +import './ack-tooltip.directive'; import './css/panel_triggers.css!'; var defaultSeverity = [ @@ -59,7 +60,7 @@ var defaultTimeFormat = "DD MMM YYYY HH:mm:ss"; class TriggerPanelCtrl extends MetricsPanelCtrl { /** @ngInject */ - constructor($scope, $injector, $q, $element, datasourceSrv, templateSrv,contextSrv) { + constructor($scope, $injector, $q, $element, datasourceSrv, templateSrv, contextSrv) { super($scope, $injector); this.datasourceSrv = datasourceSrv; this.templateSrv = templateSrv; @@ -123,11 +124,11 @@ class TriggerPanelCtrl extends MetricsPanelCtrl { showEvents) .then(triggers => { return _.map(triggers, trigger => { - var triggerObj = trigger; + let triggerObj = trigger; // Format last change and age trigger.lastchangeUnix = Number(trigger.lastchange); - var timestamp = moment.unix(trigger.lastchangeUnix); + let timestamp = moment.unix(trigger.lastchangeUnix); if (self.panel.customLastChangeFormat) { // User defined format triggerObj.lastchange = timestamp.format(self.panel.lastChangeFormat); @@ -172,8 +173,12 @@ class TriggerPanelCtrl extends MetricsPanelCtrl { if (event) { trigger.acknowledges = _.map(event.acknowledges, ack => { - var time = new Date(+ack.clock * 1000); - ack.time = time.toLocaleString(); + let timestamp = moment.unix(ack.clock); + if (self.panel.customLastChangeFormat) { + ack.time = timestamp.format(self.panel.lastChangeFormat); + } else { + ack.time = timestamp.format(self.defaultTimeFormat); + } ack.user = ack.alias + ' (' + ack.name + ' ' + ack.surname + ')'; return ack; }); @@ -225,41 +230,15 @@ class TriggerPanelCtrl extends MetricsPanelCtrl { trigger.showComment = !trigger.showComment; } - switchAcknowledges(trigger) { - trigger.showAcknowledges = !trigger.showAcknowledges; - } - addAcknowledgeMessage(trigger){ - trigger.showAcknowledges = true; - trigger.newAct={ - time:new Date(), - user:this.contextSrv.user.name+'(Grafana)', - eventid:trigger.lastEvent.eventid, - message:'' - }; - } - acknowledgeTrigger($event,trigger,newAct){ - if($event.keyCode!=13) return; - if(newAct.message.trim() === ""){ - delete trigger.newAct; - trigger.showAcknowledges = false; - return; - } - this.datasourceSrv.get(this.panel.datasource).then(datasource => { - var zabbix = datasource.zabbixAPI; - zabbix.acknowledgeEvent(newAct.eventid,newAct.user+': '+newAct.message) - .then(rs=>{ - zabbix.getAcknowledges(rs.eventids).then(events => { - _.each(events, event => { - trigger.acknowledges = _.map(event.acknowledges, ack => { - var time = new Date(+ack.clock * 1000); - ack.time = time.toLocaleString(); - ack.user = ack.alias + ' (' + ack.name + ' ' + ack.surname + ')'; - return ack; - }); - }); - delete trigger.newAct; - console.log('event id '+ rs.eventids.join() + ' new message added: ' + ack.message ); - }); + acknowledgeTrigger(trigger, message) { + let self = this; + let eventid = trigger.lastEvent.eventid; + let grafana_user = this.contextSrv.user.name; + let ack_message = grafana_user + ' (Grafana): ' + message; + return this.datasourceSrv.get(this.panel.datasource).then(datasource => { + let zabbix = datasource.zabbixAPI; + return zabbix.acknowledgeEvent(eventid, ack_message).then(() => { + self.refresh(); }); }); } diff --git a/src/panel-triggers/sass/panel_triggers.scss b/src/panel-triggers/sass/panel_triggers.scss index 478f7dd..156c5dc 100644 --- a/src/panel-triggers/sass/panel_triggers.scss +++ b/src/panel-triggers/sass/panel_triggers.scss @@ -106,3 +106,37 @@ $grafanaListAccent: lighten($dark-2, 2%); height: 0px; line-height: 0px; } + +.ack-tooltip { + .drop-content { + // Rewrite tooltip width + max-width: 70rem !important; + min-width: 30rem !important; + } + + .ack-comments { + width: 60%; + } + + .ack-add-button { + padding-top: 1rem; + } + + table td, th { + padding-right: 1rem; + } + + .ack-input-group { + padding-top: 1rem; + + input { + border: 1px solid; + border-radius: 2px; + width: 50%; + } + + button { + margin-left: 1rem; + } + } +} From e75a714e22c3d324f960a98827a2b3423cee7715 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sun, 31 Jul 2016 17:12:53 +0300 Subject: [PATCH 03/64] Trigger panel: added host technical name, fixes #192. --- src/panel-triggers/editor.html | 22 ++++++++++++++++++++-- src/panel-triggers/module.html | 11 +++++++++++ src/panel-triggers/module.js | 1 + 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/panel-triggers/editor.html b/src/panel-triggers/editor.html index 069da01..f0ac480 100644 --- a/src/panel-triggers/editor.html +++ b/src/panel-triggers/editor.html @@ -150,7 +150,7 @@ Show fields
  • - +
  • +
  • + + + +
  • + +
    + +
    +
      +
    • +   +
    • -
    • +
    +
    • diff --git a/src/panel-triggers/module.html b/src/panel-triggers/module.html index 5c28e5f..23c3ce0 100644 --- a/src/panel-triggers/module.html +++ b/src/panel-triggers/module.html @@ -9,6 +9,11 @@ Host
    + +
    + Technical Name +
    +
    Status
    @@ -38,6 +43,12 @@ + +
    + {{trigger.hostTechName}} +
    + +
    {{ctrl.triggerStatusMap[trigger.value]}} diff --git a/src/panel-triggers/module.js b/src/panel-triggers/module.js index 51718b5..b8bb307 100644 --- a/src/panel-triggers/module.js +++ b/src/panel-triggers/module.js @@ -140,6 +140,7 @@ class TriggerPanelCtrl extends MetricsPanelCtrl { // Set host that the trigger belongs if (trigger.hosts.length) { triggerObj.host = trigger.hosts[0].name; + triggerObj.hostTechName = trigger.hosts[0].host; } // Set color From 38aa982c019ac5ef358a6854fda9189f5e1b4b90 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sun, 31 Jul 2016 17:40:11 +0300 Subject: [PATCH 04/64] Trigger panel: show acknowledged triggers in different color, fixes #126. --- src/panel-triggers/editor.html | 28 +++++++++++++++++++++++++++- src/panel-triggers/module.js | 6 ++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/panel-triggers/editor.html b/src/panel-triggers/editor.html index f0ac480..ea3333c 100644 --- a/src/panel-triggers/editor.html +++ b/src/panel-triggers/editor.html @@ -291,7 +291,7 @@
    -
    +
    +
    +
      +
    • + Acknowledged color +
    • +
    • + + +
    • +
    • + + + +
    • +
    +
    +
    diff --git a/src/panel-triggers/module.js b/src/panel-triggers/module.js index b8bb307..32ca76f 100644 --- a/src/panel-triggers/module.js +++ b/src/panel-triggers/module.js @@ -48,6 +48,7 @@ var panelDefaults = { showEvents: { text: 'Problems', value: '1' }, triggerSeverity: defaultSeverity, okEventColor: 'rgba(0, 245, 153, 0.45)', + ackEventColor: 'rgba(0, 0, 0, 0)' }; var triggerStatusMap = { @@ -183,6 +184,11 @@ class TriggerPanelCtrl extends MetricsPanelCtrl { ack.user = ack.alias + ' (' + ack.name + ' ' + ack.surname + ')'; return ack; }); + + // Mark acknowledged triggers with different color + if (self.panel.markAckEvents && trigger.acknowledges.length) { + trigger.color = self.panel.ackEventColor; + } } }); From a22ad010a666279180240d30137179700ffe2ec8 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sat, 13 Aug 2016 10:00:39 +0300 Subject: [PATCH 05/64] Use history for text data queries, fixes #250. --- src/datasource-zabbix/datasource.js | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/datasource-zabbix/datasource.js b/src/datasource-zabbix/datasource.js index 2f5d792..2cdfc4c 100644 --- a/src/datasource-zabbix/datasource.js +++ b/src/datasource-zabbix/datasource.js @@ -224,16 +224,15 @@ export class ZabbixAPIDatasource { .build(groupFilter, hostFilter, appFilter, itemFilter, 'text') .then(items => { if (items.length) { - var textItemsPromises = _.map(items, item => { - return self.zabbixAPI.getLastValue(item.itemid); - }); - return self.q.all(textItemsPromises) - .then(result => { - return _.map(result, (lastvalue, index) => { - var extractedValue; + return self.zabbixAPI.getHistory(items, timeFrom, timeTo) + .then(history => { + return self.queryProcessor.convertHistory(history, items, false, (point) => { + let extractedValue = point.value; + + // Regex-based extractor if (target.textFilter) { - var text_extract_pattern = new RegExp(self.replaceTemplateVars(target.textFilter, options.scopedVars)); - extractedValue = text_extract_pattern.exec(lastvalue); + let text_extract_pattern = new RegExp(self.replaceTemplateVars(target.textFilter, options.scopedVars)); + extractedValue = text_extract_pattern.exec(point.value); if (extractedValue) { if (target.useCaptureGroups) { extractedValue = extractedValue[1]; @@ -241,13 +240,12 @@ export class ZabbixAPIDatasource { extractedValue = extractedValue[0]; } } - } else { - extractedValue = lastvalue; } - return { - target: items[index].name, - datapoints: [[extractedValue, timeTo * 1000]] - }; + + return [ + extractedValue, + point.clock * 1000 + ]; }); }); } else { From ca5bd4754476bfd9c209c66933dbaef3d040a7f8 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sat, 13 Aug 2016 15:52:10 +0300 Subject: [PATCH 06/64] Removed unused logging. --- src/datasource-zabbix/datasource.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/datasource-zabbix/datasource.js b/src/datasource-zabbix/datasource.js index 2cdfc4c..daaeb24 100644 --- a/src/datasource-zabbix/datasource.js +++ b/src/datasource-zabbix/datasource.js @@ -49,8 +49,6 @@ export class ZabbixAPIDatasource { // Use custom format for template variables this.replaceTemplateVars = _.partial(replaceTemplateVars, this.templateSrv); - - console.log(this.zabbixCache); } //////////////////////// From f3f308d7e2aeb81742645efa9e8750033e332dc2 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sat, 13 Aug 2016 17:47:45 +0300 Subject: [PATCH 07/64] Datasource refactor. --- src/datasource-zabbix/datasource.js | 203 +++++++++++++--------------- 1 file changed, 96 insertions(+), 107 deletions(-) diff --git a/src/datasource-zabbix/datasource.js b/src/datasource-zabbix/datasource.js index daaeb24..e076560 100644 --- a/src/datasource-zabbix/datasource.js +++ b/src/datasource-zabbix/datasource.js @@ -61,17 +61,18 @@ export class ZabbixAPIDatasource { * @return {Object} Grafana metrics object with timeseries data for each target. */ query(options) { - var self = this; - - // get from & to in seconds var timeFrom = Math.ceil(dateMath.parse(options.range.from) / 1000); var timeTo = Math.ceil(dateMath.parse(options.range.to) / 1000); + var useTrendsFrom = Math.ceil(dateMath.parse('now-' + this.trendsFrom) / 1000); - var useTrends = (timeFrom < useTrendsFrom) && this.trends; + var useTrends = (timeFrom <= useTrendsFrom) && this.trends; // Create request for each target var promises = _.map(options.targets, target => { + // Prevent changes of original object + target = _.cloneDeep(target); + if (target.mode !== 1) { // Migrate old targets @@ -83,21 +84,20 @@ export class ZabbixAPIDatasource { } // Replace templated variables - var groupFilter = this.replaceTemplateVars(target.group.filter, options.scopedVars); - var hostFilter = this.replaceTemplateVars(target.host.filter, options.scopedVars); - var appFilter = this.replaceTemplateVars(target.application.filter, options.scopedVars); - var itemFilter = this.replaceTemplateVars(target.item.filter, options.scopedVars); + target.group.filter = this.replaceTemplateVars(target.group.filter, options.scopedVars); + target.host.filter = this.replaceTemplateVars(target.host.filter, options.scopedVars); + target.application.filter = this.replaceTemplateVars(target.application.filter, options.scopedVars); + target.item.filter = this.replaceTemplateVars(target.item.filter, options.scopedVars); + target.textFilter = this.replaceTemplateVars(target.textFilter, options.scopedVars); // Query numeric data if (!target.mode || target.mode === 0) { - return self.queryNumericData(target, groupFilter, hostFilter, appFilter, itemFilter, - timeFrom, timeTo, useTrends, options, self); + return this.queryNumericData(target, timeFrom, timeTo, useTrends); } // Query text data else if (target.mode === 2) { - return self.queryTextData(target, groupFilter, hostFilter, appFilter, itemFilter, - timeFrom, timeTo, options, self); + return this.queryTextData(target, timeFrom, timeTo); } } @@ -111,11 +111,11 @@ export class ZabbixAPIDatasource { return this.zabbixAPI .getSLA(target.itservice.serviceid, timeFrom, timeTo) .then(slaObject => { - return self.queryProcessor + return this.queryProcessor .handleSLAResponse(target.itservice, target.slaProperty, slaObject); }); } - }, this); + }); // Data for panel (all targets) return this.q.all(_.flatten(promises)) @@ -134,10 +134,13 @@ export class ZabbixAPIDatasource { }); } - queryNumericData(target, groupFilter, hostFilter, appFilter, itemFilter, timeFrom, timeTo, useTrends, options, self) { + queryNumericData(target, timeFrom, timeTo, useTrends) { // Build query in asynchronous manner - return self.queryProcessor - .build(groupFilter, hostFilter, appFilter, itemFilter, 'num') + return this.queryProcessor.build(target.group.filter, + target.host.filter, + target.application.filter, + target.item.filter, + 'num') .then(items => { // Add hostname for items from multiple hosts var addHostName = utils.isRegex(target.host.filter); @@ -153,51 +156,43 @@ export class ZabbixAPIDatasource { }); var valueType = trendValueFunc ? trendValueFunc.params[0] : "avg"; - getHistory = self.zabbixAPI + getHistory = this.zabbixAPI .getTrend(items, timeFrom, timeTo) .then(history => { - return self.queryProcessor.handleTrends(history, items, addHostName, valueType); + return this.queryProcessor.handleTrends(history, items, addHostName, valueType); }); } // Use history else { - getHistory = self.zabbixCache + getHistory = this.zabbixCache .getHistory(items, timeFrom, timeTo) .then(history => { - return self.queryProcessor.handleHistory(history, items, addHostName); + return this.queryProcessor.handleHistory(history, items, addHostName); }); } return getHistory.then(timeseries_data => { + let transformFunctions = bindFunctionDefs(target.functions, 'Transform'); + let aggregationFunctions = bindFunctionDefs(target.functions, 'Aggregate'); + let aliasFunctions = bindFunctionDefs(target.functions, 'Alias'); // Apply transformation functions timeseries_data = _.map(timeseries_data, timeseries => { - - // Filter only transformation functions - var transformFunctions = bindFunctionDefs(target.functions, 'Transform', DataProcessor); - - // Timeseries processing - var dp = timeseries.datapoints; - for (var i = 0; i < transformFunctions.length; i++) { - dp = transformFunctions[i](dp); - } - timeseries.datapoints = dp; - + timeseries.datapoints = sequence(transformFunctions)(timeseries.datapoints); return timeseries; }); // Apply aggregations - var aggregationFunctions = bindFunctionDefs(target.functions, 'Aggregate', DataProcessor); - var dp = _.map(timeseries_data, 'datapoints'); if (aggregationFunctions.length) { - for (var i = 0; i < aggregationFunctions.length; i++) { - dp = aggregationFunctions[i](dp); - } - var lastAgg = _.findLast(target.functions, func => { - return _.contains( - _.map(metricFunctions.getCategories()['Aggregate'], 'name'), func.def.name); + let dp = _.map(timeseries_data, 'datapoints'); + dp = sequence(aggregationFunctions)(dp); + + let aggFuncNames = _.map(metricFunctions.getCategories()['Aggregate'], 'name'); + let lastAgg = _.findLast(target.functions, func => { + return _.contains(aggFuncNames, func.def.name); }); + timeseries_data = [ { target: lastAgg.text, @@ -207,47 +202,36 @@ export class ZabbixAPIDatasource { } // Apply alias functions - var aliasFunctions = bindFunctionDefs(target.functions, 'Alias', DataProcessor); - for (var j = 0; j < aliasFunctions.length; j++) { - _.each(timeseries_data, aliasFunctions[j]); - } + _.each(timeseries_data, sequence(aliasFunctions)); return timeseries_data; }); }); } - queryTextData(target, groupFilter, hostFilter, appFilter, itemFilter, timeFrom, timeTo, options, self) { - return self.queryProcessor - .build(groupFilter, hostFilter, appFilter, itemFilter, 'text') + queryTextData(target, timeFrom, timeTo) { + return this.queryProcessor.build(target.group.filter, + target.host.filter, + target.application.filter, + target.item.filter, + 'text') .then(items => { if (items.length) { - return self.zabbixAPI.getHistory(items, timeFrom, timeTo) + return this.zabbixAPI.getHistory(items, timeFrom, timeTo) .then(history => { - return self.queryProcessor.convertHistory(history, items, false, (point) => { - let extractedValue = point.value; + return this.queryProcessor.convertHistory(history, items, false, (point) => { + let value = point.value; // Regex-based extractor if (target.textFilter) { - let text_extract_pattern = new RegExp(self.replaceTemplateVars(target.textFilter, options.scopedVars)); - extractedValue = text_extract_pattern.exec(point.value); - if (extractedValue) { - if (target.useCaptureGroups) { - extractedValue = extractedValue[1]; - } else { - extractedValue = extractedValue[0]; - } - } + value = extractText(point.value, target.textFilter, target.useCaptureGroups); } - return [ - extractedValue, - point.clock * 1000 - ]; + return [value, point.clock * 1000]; }); }); } else { - return self.q.when([]); + return this.q.when([]); } }); } @@ -304,12 +288,12 @@ export class ZabbixAPIDatasource { * of metrics in "{metric1,metcic2,...,metricN}" format. */ metricFindQuery(query) { - // Split query. Query structure: - // group.host.app.item - var self = this; - var parts = []; - _.each(query.split('.'), function (part) { - part = self.replaceTemplateVars(part, {}); + let result; + let parts = []; + + // Split query. Query structure: group.host.app.item + _.each(query.split('.'), part => { + part = this.replaceTemplateVars(part, {}); // Replace wildcard to regex if (part === '*') { @@ -317,7 +301,7 @@ export class ZabbixAPIDatasource { } parts.push(part); }); - var template = _.object(['group', 'host', 'app', 'item'], parts); + let template = _.object(['group', 'host', 'app', 'item'], parts); // Get items if (parts.length === 4) { @@ -325,40 +309,23 @@ export class ZabbixAPIDatasource { if (template.app === '/.*/') { template.app = ''; } - return this.queryProcessor - .getItems(template.group, template.host, template.app) - .then(items => { - return _.map(items, formatMetric); - }); - } - // Get applications - else if (parts.length === 3) { - return this.queryProcessor - .getApps(template.group, template.host) - .then(apps => { - return _.map(apps, formatMetric); - }); - } - // Get hosts - else if (parts.length === 2) { - return this.queryProcessor - .getHosts(template.group) - .then(hosts => { - return _.map(hosts, formatMetric); - }); - } - // Get groups - else if (parts.length === 1) { - return this.zabbixCache - .getGroups(template.group) - .then(groups => { - return _.map(groups, formatMetric); - }); - } - // Return empty object for invalid request - else { - return this.q.when([]); + result = this.queryProcessor.getItems(template.group, template.host, template.app); + } else if (parts.length === 3) { + // Get applications + result = this.queryProcessor.getApps(template.group, template.host); + } else if (parts.length === 2) { + // Get hosts + result = this.queryProcessor.getHosts(template.group); + } else if (parts.length === 1) { + // Get groups + result = this.zabbixCache.getGroups(template.group); + } else { + result = this.q.when([]); } + + return result.then(metrics => { + return _.map(metrics, formatMetric); + }); } ///////////////// @@ -438,8 +405,7 @@ export class ZabbixAPIDatasource { } -function bindFunctionDefs(functionDefs, category, DataProcessor) { - 'use strict'; +function bindFunctionDefs(functionDefs, category) { var aggregationFunctions = _.map(metricFunctions.getCategories()[category], 'name'); var aggFuncDefs = _.filter(functionDefs, function(func) { return _.contains(aggregationFunctions, func.def.name); @@ -452,7 +418,6 @@ function bindFunctionDefs(functionDefs, category, DataProcessor) { } function formatMetric(metricObj) { - 'use strict'; return { text: metricObj.name, expandable: false @@ -492,3 +457,27 @@ function replaceTemplateVars(templateSrv, target, scopedVars) { } return replacedTarget; } + +function extractText(str, pattern, useCaptureGroups) { + let extractPattern = new RegExp(pattern); + let extractedValue = extractPattern.exec(str); + if (extractedValue) { + if (useCaptureGroups) { + extractedValue = extractedValue[1]; + } else { + extractedValue = extractedValue[0]; + } + } + return extractedValue; +} + +// Apply function one by one: +// sequence([a(), b(), c()]) = c(b(a())); +function sequence(funcsArray) { + return function(result) { + for (var i = 0; i < funcsArray.length; i++) { + result = funcsArray[i].call(this, result); + } + return result; + }; +} From 2882a48f258a13222d855f28fbdb3325eef4b094 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sat, 13 Aug 2016 20:28:24 +0300 Subject: [PATCH 08/64] Support template variables in functions, fixes #205. --- src/datasource-zabbix/datasource.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/datasource-zabbix/datasource.js b/src/datasource-zabbix/datasource.js index e076560..932920d 100644 --- a/src/datasource-zabbix/datasource.js +++ b/src/datasource-zabbix/datasource.js @@ -90,6 +90,12 @@ export class ZabbixAPIDatasource { target.item.filter = this.replaceTemplateVars(target.item.filter, options.scopedVars); target.textFilter = this.replaceTemplateVars(target.textFilter, options.scopedVars); + _.forEach(target.functions, func => { + func.params = _.map(func.params, param => { + return this.templateSrv.replace(param, options.scopedVars); + }); + }); + // Query numeric data if (!target.mode || target.mode === 0) { return this.queryNumericData(target, timeFrom, timeTo, useTrends); @@ -417,6 +423,13 @@ function bindFunctionDefs(functionDefs, category) { }); } +function filterFunctionDefs(funcs, category) { + let filteredFuncs = _.map(metricFunctions.getCategories()[category]); + return _.filter(funcs, func => { + return _.contains(filteredFuncs, func.def.name); + }); +} + function formatMetric(metricObj) { return { text: metricObj.name, From d247fc8a99ceeaf02b48c4f4dc412488cea662de Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sun, 14 Aug 2016 11:58:23 +0300 Subject: [PATCH 09/64] Initial unit tests (using Mocha and Chai asserts). --- .jshintrc | 9 +++- Gruntfile.js | 39 +++++++++++++++-- package.json | 13 ++++-- .../specs/datasource_specs.js | 40 +++++++++++++++++ src/datasource-zabbix/specs/test-main.js | 43 +++++++++++++++++++ 5 files changed, 136 insertions(+), 8 deletions(-) create mode 100644 src/datasource-zabbix/specs/datasource_specs.js create mode 100644 src/datasource-zabbix/specs/test-main.js diff --git a/.jshintrc b/.jshintrc index fe86382..300d6de 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,13 @@ "define": true, "require": true, "Chromath": false, - "setImmediate": true + "setImmediate": true, + "expect": true, + "it": true, + "describe": true, + "sinon": true, + "module": true, + "beforeEach": true, + "inject": true } } diff --git a/Gruntfile.js b/Gruntfile.js index 646705d..7c5c08d 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -40,11 +40,13 @@ module.exports = function(grunt) { babel: { options: { - sourceMap: true, - presets: ["es2015"], - plugins: ['transform-es2015-modules-systemjs', "transform-es2015-for-of"], + presets: ["es2015"] }, dist: { + options: { + sourceMap: true, + plugins: ['transform-es2015-modules-systemjs', "transform-es2015-for-of"] + }, files: [{ cwd: 'src', expand: true, @@ -57,6 +59,34 @@ module.exports = function(grunt) { dest: 'dist/' }] }, + distTestNoSystemJs: { + files: [{ + cwd: 'src', + expand: true, + src: ['**/*.js'], + dest: 'dist/test' + }] + }, + distTestsSpecsNoSystemJs: { + files: [{ + expand: true, + cwd: 'specs', + src: ['**/*.js'], + dest: 'dist/test/specs' + }] + } + }, + + mochaTest: { + test: { + options: { + reporter: 'spec' + }, + src: [ + 'dist/test/datasource-zabbix/specs/test-main.js', + 'dist/test/datasource-zabbix/specs/*_specs.js' + ] + } }, sass: { @@ -77,6 +107,7 @@ module.exports = function(grunt) { 'copy:src_to_dist', 'copy:pluginDef', 'babel', - 'sass' + 'sass', + 'mochaTest' ]); }; diff --git a/package.json b/package.json index b4b96f1..d433b0e 100644 --- a/package.json +++ b/package.json @@ -23,16 +23,23 @@ "grunt-contrib-copy": "~0.8.2", "grunt-contrib-watch": "^0.6.1", "grunt-contrib-uglify": "~0.11.0", + "grunt-mocha-test": "~0.12.7", "grunt-systemjs-builder": "^0.2.5", "load-grunt-tasks": "~3.2.0", "grunt-execute": "~0.2.2", - "grunt-contrib-clean": "~0.6.0" + "grunt-contrib-clean": "~0.6.0", + "prunk": "~1.2.1", + "jsdom": "~3.1.2", + "q": "~1.4.1", + "chai": "~3.5.0", + "moment": "~2.14.1" }, "dependencies": { "babel-plugin-transform-es2015-modules-systemjs": "^6.5.0", - "babel-plugin-transform-es2015-for-of": "^6.5.0", + "babel-plugin-transform-es2015-for-of": "^6.6.0", "babel-preset-es2015": "^6.5.0", - "lodash": "~4.0.0" + "lodash": "~4.0.0", + "mocha": "^2.4.5" }, "homepage": "http://grafana-zabbix.org" } diff --git a/src/datasource-zabbix/specs/datasource_specs.js b/src/datasource-zabbix/specs/datasource_specs.js new file mode 100644 index 0000000..9f8faff --- /dev/null +++ b/src/datasource-zabbix/specs/datasource_specs.js @@ -0,0 +1,40 @@ +import {Datasource} from "../module"; +import Q from "q"; + +describe('ZabbixDatasource', function() { + var ctx = {}; + + beforeEach(function() { + ctx.instanceSettings = { + jsonData: { + username: 'zabbix', + password: 'zabbix', + trends: false + } + }; + ctx.$q = Q; + ctx.templateSrv = {}; + ctx.alertSrv = {}; + ctx.zabbixAPIService = function() {}; + ctx.ZabbixCachingProxy = function() {}; + ctx.QueryProcessor = function() {}; + ctx.ds = new Datasource(ctx.instanceSettings, ctx.$q, ctx.templateSrv, ctx.alertSrv, + ctx.zabbixAPIService, ctx.ZabbixCachingProxy, ctx.QueryProcessor); + }); + + describe('When querying data', function() { + + it('should return an empty array when no targets are set', function(done) { + var options = { + targets: [], + range: {from: null, to: null} + }; + ctx.ds.query(options).then(function(result) { + expect(result.data).to.have.length(0); + done(); + }); + }); + + }); + +}); diff --git a/src/datasource-zabbix/specs/test-main.js b/src/datasource-zabbix/specs/test-main.js new file mode 100644 index 0000000..cc846f1 --- /dev/null +++ b/src/datasource-zabbix/specs/test-main.js @@ -0,0 +1,43 @@ +// JSHint options +/* globals global: false */ + +import prunk from 'prunk'; +import {jsdom} from 'jsdom'; +import chai from 'chai'; + +// Mock angular module +var angularMocks = { + module: function() { + return { + directive: function() {}, + service: function() {}, + factory: function() {} + }; + } +}; + +var datemathMock = { + parse: function() {} +}; + +// Mock Grafana modules that are not available outside of the core project +// Required for loading module.js +prunk.mock('./css/query-editor.css!', 'no css, dude.'); +prunk.mock('app/plugins/sdk', { + QueryCtrl: null +}); +prunk.mock('app/core/utils/datemath', datemathMock); +prunk.mock('angular', angularMocks); +prunk.mock('jquery', 'module not found'); + +// Setup jsdom +// Required for loading angularjs +global.document = jsdom(''); +global.window = global.document.parentWindow; +global.navigator = window.navigator = {}; +global.Node = window.Node; + +// Setup Chai +chai.should(); +global.assert = chai.assert; +global.expect = chai.expect; From e64ae9837823f12ac98318b701863e77b0cbf542 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sun, 14 Aug 2016 14:53:40 +0300 Subject: [PATCH 10/64] Add trend using tests. Use sinon for checking functions calls. --- package.json | 6 +- .../specs/datasource_specs.js | 56 ++++++++- .../specs/modules/datemath.js | 111 ++++++++++++++++++ src/datasource-zabbix/specs/test-main.js | 8 +- 4 files changed, 176 insertions(+), 5 deletions(-) create mode 100644 src/datasource-zabbix/specs/modules/datemath.js diff --git a/package.json b/package.json index d433b0e..67de01a 100644 --- a/package.json +++ b/package.json @@ -32,14 +32,16 @@ "jsdom": "~3.1.2", "q": "~1.4.1", "chai": "~3.5.0", + "sinon-chai": "~2.8.0", "moment": "~2.14.1" }, "dependencies": { "babel-plugin-transform-es2015-modules-systemjs": "^6.5.0", "babel-plugin-transform-es2015-for-of": "^6.6.0", "babel-preset-es2015": "^6.5.0", - "lodash": "~4.0.0", - "mocha": "^2.4.5" + "lodash": "^2.4.1", + "mocha": "^2.4.5", + "sinon": "~1.16.1" }, "homepage": "http://grafana-zabbix.org" } diff --git a/src/datasource-zabbix/specs/datasource_specs.js b/src/datasource-zabbix/specs/datasource_specs.js index 9f8faff..4b292d8 100644 --- a/src/datasource-zabbix/specs/datasource_specs.js +++ b/src/datasource-zabbix/specs/datasource_specs.js @@ -1,15 +1,19 @@ import {Datasource} from "../module"; import Q from "q"; +import sinon from 'sinon'; +import _ from 'lodash'; describe('ZabbixDatasource', function() { var ctx = {}; + var defined = sinon.match.defined; beforeEach(function() { ctx.instanceSettings = { jsonData: { username: 'zabbix', password: 'zabbix', - trends: false + trends: true, + trendsFrom: '7d' } }; ctx.$q = Q; @@ -18,16 +22,32 @@ describe('ZabbixDatasource', function() { ctx.zabbixAPIService = function() {}; ctx.ZabbixCachingProxy = function() {}; ctx.QueryProcessor = function() {}; + ctx.ds = new Datasource(ctx.instanceSettings, ctx.$q, ctx.templateSrv, ctx.alertSrv, ctx.zabbixAPIService, ctx.ZabbixCachingProxy, ctx.QueryProcessor); + + ctx.ds.replaceTemplateVars = function(str) { + return str; + }; }); describe('When querying data', function() { + ctx.options = { + targets: [ + { + group: {filter: ""}, + host: {filter: ""}, + application: {filter: ""}, + item: {filter: ""} + } + ], + range: {from: 'now-7d', to: 'now'} + }; it('should return an empty array when no targets are set', function(done) { var options = { targets: [], - range: {from: null, to: null} + range: {from: 'now-6h', to: 'now'} }; ctx.ds.query(options).then(function(result) { expect(result.data).to.have.length(0); @@ -35,6 +55,38 @@ describe('ZabbixDatasource', function() { }); }); + it('should use trends if it enabled and time more than trendsFrom', function(done) { + var ranges = ['now-7d', 'now-168h', 'now-1M', 'now-1y']; + + _.forEach(ranges, range => { + ctx.options.range.from = range; + ctx.ds.queryNumericData = sinon.spy(); + ctx.ds.query(ctx.options); + + // Check that useTrends options is true + expect(ctx.ds.queryNumericData) + .to.have.been.calledWith(defined, defined, defined, true); + }); + + done(); + }); + + it('shouldnt use trends if it enabled and time less than trendsFrom', function(done) { + var ranges = ['now-6d', 'now-167h', 'now-1h', 'now-30m', 'now-30s']; + + _.forEach(ranges, range => { + ctx.options.range.from = range; + ctx.ds.queryNumericData = sinon.spy(); + ctx.ds.query(ctx.options); + + // Check that useTrends options is false + expect(ctx.ds.queryNumericData) + .to.have.been.calledWith(defined, defined, defined, false); + }); + + done(); + }); + }); }); diff --git a/src/datasource-zabbix/specs/modules/datemath.js b/src/datasource-zabbix/specs/modules/datemath.js new file mode 100644 index 0000000..fd97ab8 --- /dev/null +++ b/src/datasource-zabbix/specs/modules/datemath.js @@ -0,0 +1,111 @@ +import _ from 'lodash'; +import moment from 'moment'; + +var units = ['y', 'M', 'w', 'd', 'h', 'm', 's']; + +export function parse(text, roundUp) { + if (!text) { return undefined; } + if (moment.isMoment(text)) { return text; } + if (_.isDate(text)) { return moment(text); } + + var time; + var mathString = ''; + var index; + var parseString; + + if (text.substring(0, 3) === 'now') { + time = moment(); + mathString = text.substring('now'.length); + } else { + index = text.indexOf('||'); + if (index === -1) { + parseString = text; + mathString = ''; // nothing else + } else { + parseString = text.substring(0, index); + mathString = text.substring(index + 2); + } + // We're going to just require ISO8601 timestamps, k? + time = moment(parseString, moment.ISO_8601); + } + + if (!mathString.length) { + return time; + } + + return parseDateMath(mathString, time, roundUp); +} + +export function isValid(text) { + var date = parse(text); + if (!date) { + return false; + } + + if (moment.isMoment(date)) { + return date.isValid(); + } + + return false; +} + +export function parseDateMath(mathString, time, roundUp) { + var dateTime = time; + var i = 0; + var len = mathString.length; + + while (i < len) { + var c = mathString.charAt(i++); + var type; + var num; + var unit; + + if (c === '/') { + type = 0; + } else if (c === '+') { + type = 1; + } else if (c === '-') { + type = 2; + } else { + return undefined; + } + + if (isNaN(mathString.charAt(i))) { + num = 1; + } else if (mathString.length === 2) { + num = mathString.charAt(i); + } else { + var numFrom = i; + while (!isNaN(mathString.charAt(i))) { + i++; + if (i > 10) { return undefined; } + } + num = parseInt(mathString.substring(numFrom, i), 10); + } + + if (type === 0) { + // rounding is only allowed on whole, single, units (eg M or 1M, not 0.5M or 2M) + if (num !== 1) { + return undefined; + } + } + unit = mathString.charAt(i++); + + if (!_.contains(units, unit)) { + return undefined; + } else { + if (type === 0) { + if (roundUp) { + dateTime.endOf(unit); + } else { + dateTime.startOf(unit); + } + } else if (type === 1) { + dateTime.add(num, unit); + } else if (type === 2) { + dateTime.subtract(num, unit); + } + } + } + return dateTime; +} diff --git a/src/datasource-zabbix/specs/test-main.js b/src/datasource-zabbix/specs/test-main.js index cc846f1..23fa7c1 100644 --- a/src/datasource-zabbix/specs/test-main.js +++ b/src/datasource-zabbix/specs/test-main.js @@ -4,6 +4,9 @@ import prunk from 'prunk'; import {jsdom} from 'jsdom'; import chai from 'chai'; +import sinon from 'sinon'; +import sinonChai from 'sinon-chai'; +import * as dateMath from './modules/datemath'; // Mock angular module var angularMocks = { @@ -17,7 +20,9 @@ var angularMocks = { }; var datemathMock = { - parse: function() {} + parse: dateMath.parse, + parseDateMath: dateMath.parseDateMath, + isValid: dateMath.isValid }; // Mock Grafana modules that are not available outside of the core project @@ -39,5 +44,6 @@ global.Node = window.Node; // Setup Chai chai.should(); +chai.use(sinonChai); global.assert = chai.assert; global.expect = chai.expect; From 0e5cd2efbad0fed721386a59b1e390d48e31229b Mon Sep 17 00:00:00 2001 From: Tom Zhang Date: Wed, 24 Aug 2016 13:08:46 +0930 Subject: [PATCH 11/64] enable expanding MACRO in trigger descriptions --- src/datasource-zabbix/zabbixAPI.service.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/datasource-zabbix/zabbixAPI.service.js b/src/datasource-zabbix/zabbixAPI.service.js index df8bcf4..fda4a29 100644 --- a/src/datasource-zabbix/zabbixAPI.service.js +++ b/src/datasource-zabbix/zabbixAPI.service.js @@ -327,6 +327,7 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) { applicationids: applicationids, expandDescription: true, expandData: true, + expandComment: true, monitored: true, skipDependent: true, //only_true: true, From 614ee123b1c4e831fca6d953b49d2a967aae025d Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Thu, 15 Sep 2016 12:34:31 +0300 Subject: [PATCH 12/64] Update contribution guidelines. --- .github/CONTRIBUTING.md | 19 +++++++++++++++++++ CONTRIBUTING.md | 1 - 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 .github/CONTRIBUTING.md delete mode 100644 CONTRIBUTING.md diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..e2c2034 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,19 @@ +##Contributing to Grafana-Zabbix + +#### I'm submitting a ... +- [ ] Bug report +- [ ] Feature request +- [ ] Question / Support request + +#### For bug report please include this information: +- What Grafana version are you using? +- What Zabbix version are you using? +- What zabbix plugin version are you using? +- What OS are you running grafana on? +- What did you do? +- What was the expected result? +- What happened instead? + +**IMPORTANT** If it relates to metric data visualization would be great to get: +- An image or text representation of your metric query +- The raw query and response for the network request (check this in chrome dev tools network tab, here you can see metric requests and other request, please include the request body and request response) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index be343e6..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1 +0,0 @@ -# Contributing to Grafana-Zabbix \ No newline at end of file From a997dc9ac374ee50b8f2a670650eaef8e1d665dd Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Thu, 15 Sep 2016 12:37:06 +0300 Subject: [PATCH 13/64] Add issue template. --- .github/{CONTRIBUTING.md => ISSUE_TEMPLATE.md} | 2 -- 1 file changed, 2 deletions(-) rename .github/{CONTRIBUTING.md => ISSUE_TEMPLATE.md} (95%) diff --git a/.github/CONTRIBUTING.md b/.github/ISSUE_TEMPLATE.md similarity index 95% rename from .github/CONTRIBUTING.md rename to .github/ISSUE_TEMPLATE.md index e2c2034..1e848bf 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,5 +1,3 @@ -##Contributing to Grafana-Zabbix - #### I'm submitting a ... - [ ] Bug report - [ ] Feature request From 5a1a2278798813ebc549f92b547454c7f57caaa4 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Thu, 15 Sep 2016 12:48:05 +0300 Subject: [PATCH 14/64] Write contribution guidelines. --- .github/CONTRIBUTING.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/CONTRIBUTING.md diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..4242942 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,15 @@ +## How to contribute to Grafana-Zabbix + +#### **Did you find a bug?** + +* **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/alexanderzobnin/grafana-zabbix/issues). + +* If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/alexanderzobnin/grafana-zabbix/issues/new). Be sure to include a **title and clear description**, as much relevant information as possible. + +#### **Did you write a patch that fixes a bug?** + +* Open a new GitHub pull request with the patch. + +* Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable. + +Thanks! :heart: :heart: :heart: From 74503655bd55956af37e52227b80f1e8ced3fa7e Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Thu, 15 Sep 2016 22:14:26 +0300 Subject: [PATCH 15/64] Fixed missed lodash indexBy() after upgrade to 4.x. --- src/datasource-zabbix/datasource.js | 2 +- src/datasource-zabbix/queryProcessor.service.js | 2 +- src/datasource-zabbix/zabbixCache.service.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/datasource-zabbix/datasource.js b/src/datasource-zabbix/datasource.js index 932920d..7f04e80 100644 --- a/src/datasource-zabbix/datasource.js +++ b/src/datasource-zabbix/datasource.js @@ -378,7 +378,7 @@ export class ZabbixAPIDatasource { return self.zabbixAPI .getEvents(objectids, timeFrom, timeTo, showOkEvents) .then(events => { - var indexedTriggers = _.indexBy(triggers, 'triggerid'); + var indexedTriggers = _.groupBy(triggers, 'triggerid'); // Hide acknowledged events if option enabled if (annotation.hideAcknowledged) { diff --git a/src/datasource-zabbix/queryProcessor.service.js b/src/datasource-zabbix/queryProcessor.service.js index 557b2f1..6931468 100644 --- a/src/datasource-zabbix/queryProcessor.service.js +++ b/src/datasource-zabbix/queryProcessor.service.js @@ -230,7 +230,7 @@ angular.module('grafana.services').factory('QueryProcessor', function($q) { // Group history by itemid var grouped_history = _.groupBy(history, 'itemid'); - var hosts = _.indexBy(_.flatten(_.map(items, 'hosts')), 'hostid'); + var hosts = _.groupBy(_.flatten(_.map(items, 'hosts')), 'hostid'); return _.map(grouped_history, function(hist, itemid) { var item = _.find(items, {'itemid': itemid}); diff --git a/src/datasource-zabbix/zabbixCache.service.js b/src/datasource-zabbix/zabbixCache.service.js index e4c9868..9970568 100644 --- a/src/datasource-zabbix/zabbixCache.service.js +++ b/src/datasource-zabbix/zabbixCache.service.js @@ -117,7 +117,7 @@ angular.module('grafana.services').factory('ZabbixCachingProxy', function($q, $i var deferred = this.$q.defer(); var historyStorage = this.storage.history; var full_history; - var expired = _.filter(_.indexBy(items, 'itemid'), function(item, itemid) { + var expired = _.filter(_.groupBy(items, 'itemid'), function(item, itemid) { return !historyStorage[itemid]; }); if (expired.length) { From 70503a0647bdaab5e4252f0d874241f77b3bb976 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sat, 17 Sep 2016 21:25:26 +0300 Subject: [PATCH 16/64] Fixed lodash migration issues after upgrade to 4.x. Moved: _.contains -> _.includes _.object -> _.zipObject _.first - replaced by native slice() --- src/datasource-zabbix/datasource.js | 10 +++++----- src/datasource-zabbix/utils.js | 2 +- src/panel-triggers/module.js | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/datasource-zabbix/datasource.js b/src/datasource-zabbix/datasource.js index 7f04e80..2e914a4 100644 --- a/src/datasource-zabbix/datasource.js +++ b/src/datasource-zabbix/datasource.js @@ -158,7 +158,7 @@ export class ZabbixAPIDatasource { // Find trendValue() function and get specified trend value var trendFunctions = _.map(metricFunctions.getCategories()['Trends'], 'name'); var trendValueFunc = _.find(target.functions, func => { - return _.contains(trendFunctions, func.def.name); + return _.includes(trendFunctions, func.def.name); }); var valueType = trendValueFunc ? trendValueFunc.params[0] : "avg"; @@ -196,7 +196,7 @@ export class ZabbixAPIDatasource { let aggFuncNames = _.map(metricFunctions.getCategories()['Aggregate'], 'name'); let lastAgg = _.findLast(target.functions, func => { - return _.contains(aggFuncNames, func.def.name); + return _.includes(aggFuncNames, func.def.name); }); timeseries_data = [ @@ -307,7 +307,7 @@ export class ZabbixAPIDatasource { } parts.push(part); }); - let template = _.object(['group', 'host', 'app', 'item'], parts); + let template = _.zipObject(['group', 'host', 'app', 'item'], parts); // Get items if (parts.length === 4) { @@ -414,7 +414,7 @@ export class ZabbixAPIDatasource { function bindFunctionDefs(functionDefs, category) { var aggregationFunctions = _.map(metricFunctions.getCategories()[category], 'name'); var aggFuncDefs = _.filter(functionDefs, function(func) { - return _.contains(aggregationFunctions, func.def.name); + return _.includes(aggregationFunctions, func.def.name); }); return _.map(aggFuncDefs, function(func) { @@ -426,7 +426,7 @@ function bindFunctionDefs(functionDefs, category) { function filterFunctionDefs(funcs, category) { let filteredFuncs = _.map(metricFunctions.getCategories()[category]); return _.filter(funcs, func => { - return _.contains(filteredFuncs, func.def.name); + return _.includes(filteredFuncs, func.def.name); }); } diff --git a/src/datasource-zabbix/utils.js b/src/datasource-zabbix/utils.js index 5925da0..1f05572 100644 --- a/src/datasource-zabbix/utils.js +++ b/src/datasource-zabbix/utils.js @@ -35,7 +35,7 @@ export function isTemplateVariable(str, templateVariables) { var variables = _.map(templateVariables, variable => { return '$' + variable.name; }); - return _.contains(variables, str); + return _.includes(variables, str); } else { return false; } diff --git a/src/panel-triggers/module.js b/src/panel-triggers/module.js index 32ca76f..ce412f5 100644 --- a/src/panel-triggers/module.js +++ b/src/panel-triggers/module.js @@ -222,7 +222,7 @@ class TriggerPanelCtrl extends MetricsPanelCtrl { } // Limit triggers number - self.triggerList = _.first(triggerList, self.panel.limit); + self.triggerList = triggerList.slice(0, self.panel.limit); // Notify panel that request is finished self.setTimeQueryEnd(); From 8df8e66cbd86b216876e8b1fcb9e8157d9efb6d3 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sat, 24 Sep 2016 10:07:17 +0300 Subject: [PATCH 17/64] Bump version to 3.1.0-pre1. --- src/plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugin.json b/src/plugin.json index e44aaf2..9a30f99 100644 --- a/src/plugin.json +++ b/src/plugin.json @@ -31,7 +31,7 @@ {"name": "Metric Editor", "path": "img/screenshot-metric_editor.png"}, {"name": "Triggers", "path": "img/screenshot-triggers.png"} ], - "version": "3.0.0", + "version": "3.1.0-pre1", "updated": "2016-07-03" }, From f44fce8d75aff301cbb53a853c6bee79a275afb5 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sat, 24 Sep 2016 10:33:47 +0300 Subject: [PATCH 18/64] Add delta() series transform function, closes #172. --- src/datasource-zabbix/DataProcessor.js | 11 +++++++++++ src/datasource-zabbix/metricFunctions.js | 7 +++++++ 2 files changed, 18 insertions(+) diff --git a/src/datasource-zabbix/DataProcessor.js b/src/datasource-zabbix/DataProcessor.js index 0dad30d..020132f 100644 --- a/src/datasource-zabbix/DataProcessor.js +++ b/src/datasource-zabbix/DataProcessor.js @@ -151,6 +151,16 @@ export default class DataProcessor { }); } + static delta(datapoints) { + let newSeries = []; + let deltaValue; + for (var i = 1; i < datapoints.length; i++) { + deltaValue = datapoints[i][0] - datapoints[i - 1][0]; + newSeries.push([deltaValue, datapoints[i][1]]); + } + return newSeries; + } + static groupByWrapper(interval, groupFunc, datapoints) { var groupByCallback = DataProcessor.aggregationFunctions[groupFunc]; return DataProcessor.groupBy(interval, groupByCallback, datapoints); @@ -181,6 +191,7 @@ export default class DataProcessor { return { groupBy: this.groupByWrapper, scale: this.scale, + delta: this.delta, aggregateBy: this.aggregateByWrapper, average: _.partial(this.aggregateWrapper, this.AVERAGE), min: _.partial(this.aggregateWrapper, this.MIN), diff --git a/src/datasource-zabbix/metricFunctions.js b/src/datasource-zabbix/metricFunctions.js index 5616bc2..ac332ef 100644 --- a/src/datasource-zabbix/metricFunctions.js +++ b/src/datasource-zabbix/metricFunctions.js @@ -39,6 +39,13 @@ addFuncDef({ defaultParams: [100], }); +addFuncDef({ + name: 'delta', + category: 'Transform', + params: [], + defaultParams: [], +}); + addFuncDef({ name: 'sumSeries', category: 'Aggregate', From 3a713846b6343a9acb215b5ab3c73f1d3cb2de1d Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sat, 24 Sep 2016 10:41:50 +0300 Subject: [PATCH 19/64] Add docs for delta() function. --- docs/sources/reference/functions.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/sources/reference/functions.md b/docs/sources/reference/functions.md index 089edac..10db01f 100644 --- a/docs/sources/reference/functions.md +++ b/docs/sources/reference/functions.md @@ -30,6 +30,13 @@ scale(100) scale(0.01) ``` +### delta +``` +delta() +``` +Convert absolute values to delta, for example, bits to bits/sec. + + Aggregate --------- From dfbcce9c8f5a37107bf822b92f625394883caee9 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sat, 24 Sep 2016 11:08:22 +0300 Subject: [PATCH 20/64] Fix connection errors handling, fixes #236. --- src/datasource-zabbix/zabbixAPI.service.js | 17 +++++++----- .../zabbixAPICore.service.js | 26 +++++++++++-------- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/datasource-zabbix/zabbixAPI.service.js b/src/datasource-zabbix/zabbixAPI.service.js index 523f8a1..8cc2d8e 100644 --- a/src/datasource-zabbix/zabbixAPI.service.js +++ b/src/datasource-zabbix/zabbixAPI.service.js @@ -41,12 +41,12 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) { request(method, params) { var self = this; - return this.zabbixAPICore.request(this.url, method, params, this.requestOptions, this.auth) - .then(function(result) { + return this.zabbixAPICore + .request(this.url, method, params, this.requestOptions, this.auth) + .then((result) => { return result; - }, - // Handle API errors - function(error) { + }, (error) => { + // Handle API errors if (isNotAuthorized(error.data)) { return self.loginOnce().then( function() { @@ -56,15 +56,18 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) { function(error) { self.alertAPIError(error.data); }); + } else { + this.alertSrv.set("Connection Error", error.data, 'error', 5000); } }); } - alertAPIError(message) { + alertAPIError(message, timeout = 5000) { this.alertSrv.set( "Zabbix API Error", message, - 'error' + 'error', + timeout ); } diff --git a/src/datasource-zabbix/zabbixAPICore.service.js b/src/datasource-zabbix/zabbixAPICore.service.js index a1623e6..2a7473c 100644 --- a/src/datasource-zabbix/zabbixAPICore.service.js +++ b/src/datasource-zabbix/zabbixAPICore.service.js @@ -51,19 +51,23 @@ class ZabbixAPICoreService { requestOptions.headers.Authorization = options.basicAuth; } - this.backendSrv.datasourceRequest(requestOptions).then(function (response) { - // General connection issues - if (!response.data) { - deferred.reject(response); - } + this.backendSrv.datasourceRequest(requestOptions) + .then((response) => { + // General connection issues + if (!response.data) { + deferred.reject(response); + } - // Handle Zabbix API errors - else if (response.data.error) { - deferred.reject(response.data.error); - } + // Handle Zabbix API errors + else if (response.data.error) { + deferred.reject(response.data.error); + } + + deferred.resolve(response.data.result); + }, (error) => { + deferred.reject(error.err); + }); - deferred.resolve(response.data.result); - }); return deferred.promise; } From aa882d752ecc1335e5b00985e32264ee97dccd3a Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sat, 24 Sep 2016 12:44:22 +0300 Subject: [PATCH 21/64] Add top N and bottom N filter functions, closes #241. --- docs/sources/reference/functions.md | 32 ++++++++++++++++++++++++ src/datasource-zabbix/DataProcessor.js | 18 +++++++++++++ src/datasource-zabbix/datasource.js | 12 ++++++++- src/datasource-zabbix/metricFunctions.js | 21 ++++++++++++++++ 4 files changed, 82 insertions(+), 1 deletion(-) diff --git a/docs/sources/reference/functions.md b/docs/sources/reference/functions.md index 10db01f..c620ca7 100644 --- a/docs/sources/reference/functions.md +++ b/docs/sources/reference/functions.md @@ -78,6 +78,38 @@ max(interval) ``` **Deprecated**, use `aggregateBy(interval, max)` instead. + +Filter +--------- + +### top + +``` +top(N, value) +``` + +Returns top N series, sorted by _value_, which can be one of: _avg_, _min_, _max_, _median_. + +Examples: +``` +top(10, avg) +top(5, max) +``` + +### bottom + +``` +bottom(N, value) +``` + +Returns bottom N series, sorted by _value_, which can be one of: _avg_, _min_, _max_, _median_. + +Examples: +``` +bottom(5, avg) +``` + + ## Trends ### trendValue diff --git a/src/datasource-zabbix/DataProcessor.js b/src/datasource-zabbix/DataProcessor.js index 020132f..cb6c4c2 100644 --- a/src/datasource-zabbix/DataProcessor.js +++ b/src/datasource-zabbix/DataProcessor.js @@ -116,6 +116,22 @@ export default class DataProcessor { return sortByTime(new_timeseries); } + static limit(order, n, orderByFunc, timeseries) { + let orderByCallback = DataProcessor.aggregationFunctions[orderByFunc]; + let sortByIteratee = (ts) => { + let values = _.map(ts.datapoints, (point) => { + return point[0]; + }); + return orderByCallback(values); + }; + let sortedTimeseries = _.sortBy(timeseries, sortByIteratee); + if (order === 'bottom') { + return sortedTimeseries.slice(0, n); + } else { + return sortedTimeseries.slice(-n); + } + } + static AVERAGE(values) { var sum = 0; _.each(values, function(value) { @@ -198,6 +214,8 @@ export default class DataProcessor { max: _.partial(this.aggregateWrapper, this.MAX), median: _.partial(this.aggregateWrapper, this.MEDIAN), sumSeries: this.sumSeries, + top: _.partial(this.limit, 'top'), + bottom: _.partial(this.limit, 'bottom'), setAlias: this.setAlias, }; } diff --git a/src/datasource-zabbix/datasource.js b/src/datasource-zabbix/datasource.js index 2e914a4..c9db4bb 100644 --- a/src/datasource-zabbix/datasource.js +++ b/src/datasource-zabbix/datasource.js @@ -92,7 +92,11 @@ export class ZabbixAPIDatasource { _.forEach(target.functions, func => { func.params = _.map(func.params, param => { - return this.templateSrv.replace(param, options.scopedVars); + if (typeof param === 'number') { + return +this.templateSrv.replace(param.toString(), options.scopedVars); + } else { + return this.templateSrv.replace(param, options.scopedVars); + } }); }); @@ -181,6 +185,7 @@ export class ZabbixAPIDatasource { return getHistory.then(timeseries_data => { let transformFunctions = bindFunctionDefs(target.functions, 'Transform'); let aggregationFunctions = bindFunctionDefs(target.functions, 'Aggregate'); + let filterFunctions = bindFunctionDefs(target.functions, 'Filter'); let aliasFunctions = bindFunctionDefs(target.functions, 'Alias'); // Apply transformation functions @@ -189,6 +194,11 @@ export class ZabbixAPIDatasource { return timeseries; }); + // Apply filter functions + if (filterFunctions.length) { + timeseries_data = sequence(filterFunctions)(timeseries_data); + } + // Apply aggregations if (aggregationFunctions.length) { let dp = _.map(timeseries_data, 'datapoints'); diff --git a/src/datasource-zabbix/metricFunctions.js b/src/datasource-zabbix/metricFunctions.js index ac332ef..5d467f4 100644 --- a/src/datasource-zabbix/metricFunctions.js +++ b/src/datasource-zabbix/metricFunctions.js @@ -5,6 +5,7 @@ var index = []; var categories = { Transform: [], Aggregate: [], + Filter: [], Trends: [], Alias: [] }; @@ -99,6 +100,26 @@ addFuncDef({ defaultParams: ['1m', 'avg'], }); +addFuncDef({ + name: 'top', + category: 'Filter', + params: [ + { name: 'number', type: 'int' }, + { name: 'value', type: 'string', options: ['avg', 'min', 'max', 'median'] } + ], + defaultParams: [5, 'avg'], +}); + +addFuncDef({ + name: 'bottom', + category: 'Filter', + params: [ + { name: 'number', type: 'int' }, + { name: 'value', type: 'string', options: ['avg', 'min', 'max', 'median'] } + ], + defaultParams: [5, 'avg'], +}); + addFuncDef({ name: 'trendValue', category: 'Trends', From dc72bc2780c3a07d43ac8a528aab805c01567d27 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sat, 24 Sep 2016 14:02:20 +0300 Subject: [PATCH 22/64] Fixed undefined host names after lodash upgrade. --- src/datasource-zabbix/queryProcessor.service.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/datasource-zabbix/queryProcessor.service.js b/src/datasource-zabbix/queryProcessor.service.js index 6931468..5f8e8e9 100644 --- a/src/datasource-zabbix/queryProcessor.service.js +++ b/src/datasource-zabbix/queryProcessor.service.js @@ -230,13 +230,13 @@ angular.module('grafana.services').factory('QueryProcessor', function($q) { // Group history by itemid var grouped_history = _.groupBy(history, 'itemid'); - var hosts = _.groupBy(_.flatten(_.map(items, 'hosts')), 'hostid'); + var hosts = _.flatten(_.map(items, 'hosts')); return _.map(grouped_history, function(hist, itemid) { var item = _.find(items, {'itemid': itemid}); var alias = item.name; if (_.keys(hosts).length > 1 || addHostName) { - var host = hosts[item.hostid]; + var host = _.find(hosts, {'hostid': item.hostid}); alias = host.name + ": " + alias; } return { From 0338026c96f88151aaf961fef9aa5dafdaf8301d Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sat, 24 Sep 2016 14:28:24 +0300 Subject: [PATCH 23/64] Update license file. --- LICENSE | 201 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d7b33b6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2015-2016 Alexander Zobnin + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. From 2e84881ce633025f4d4cdf09565202bb9f9ca296 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sat, 24 Sep 2016 14:29:13 +0300 Subject: [PATCH 24/64] Delete LICENSE --- LICENSE | 201 -------------------------------------------------------- 1 file changed, 201 deletions(-) delete mode 100644 LICENSE diff --git a/LICENSE b/LICENSE deleted file mode 100644 index d7b33b6..0000000 --- a/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2015-2016 Alexander Zobnin - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. From 59db866d8d881fb8084c096fd5e232827c87895c Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sat, 24 Sep 2016 14:31:35 +0300 Subject: [PATCH 25/64] Add license from GitHub template template. --- LICENSE | 201 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. From 248a6cd15871bc4c740013ea67ed53afb39ef92f Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sat, 24 Sep 2016 14:32:11 +0300 Subject: [PATCH 26/64] Update LICENSE --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 8dada3e..d7b33b6 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright {yyyy} {name of copyright owner} + Copyright 2015-2016 Alexander Zobnin Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From 3bde99e0d69ae202c031180b0b71efff16a07aa7 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sat, 24 Sep 2016 14:34:15 +0300 Subject: [PATCH 27/64] Delete LICENSE.md --- LICENSE.md | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 LICENSE.md diff --git a/LICENSE.md b/LICENSE.md deleted file mode 100644 index ee0bee2..0000000 --- a/LICENSE.md +++ /dev/null @@ -1,13 +0,0 @@ -Copyright 2015-2016 Alexander Zobnin - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. \ No newline at end of file From 22f3e9861c2e90939768a07c36375ea94fc91a12 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Mon, 26 Sep 2016 22:16:09 +0300 Subject: [PATCH 28/64] Bump version to 3.1.0 --- src/plugin.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugin.json b/src/plugin.json index 9a30f99..9cf9542 100644 --- a/src/plugin.json +++ b/src/plugin.json @@ -31,8 +31,8 @@ {"name": "Metric Editor", "path": "img/screenshot-metric_editor.png"}, {"name": "Triggers", "path": "img/screenshot-triggers.png"} ], - "version": "3.1.0-pre1", - "updated": "2016-07-03" + "version": "3.1.0", + "updated": "2016-09-26" }, "includes": [ From 07ad717df43b85fd4ebe0bdfbfd097be1bec4c54 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Tue, 27 Sep 2016 20:15:05 +0300 Subject: [PATCH 29/64] Fix backward compatibility with lodash 2.4, fixes #279. --- src/datasource-zabbix/datasource.js | 5 +++++ src/datasource-zabbix/utils.js | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/datasource-zabbix/datasource.js b/src/datasource-zabbix/datasource.js index c9db4bb..cd1e2de 100644 --- a/src/datasource-zabbix/datasource.js +++ b/src/datasource-zabbix/datasource.js @@ -504,3 +504,8 @@ function sequence(funcsArray) { return result; }; } + +// Fix for backward compatibility with lodash 2.4 +if (!_.includes) { + _.includes = _.contains; +} diff --git a/src/datasource-zabbix/utils.js b/src/datasource-zabbix/utils.js index 1f05572..fbebd5b 100644 --- a/src/datasource-zabbix/utils.js +++ b/src/datasource-zabbix/utils.js @@ -93,3 +93,8 @@ export function convertToZabbixAPIUrl(url) { return url.replace(trimSlashPattern, "$1"); } } + +// Fix for backward compatibility with lodash 2.4 +if (!_.includes) { + _.includes = _.contains; +} From e10b831a918cca4681313803c6fdb7b4e7b76e45 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Tue, 27 Sep 2016 20:19:35 +0300 Subject: [PATCH 30/64] Added jshint and jscs checks to Grunt tasks. --- Gruntfile.js | 27 ++++++++++++++++++++++++++- package.json | 3 +++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index 646705d..a26d125 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -68,6 +68,29 @@ module.exports = function(grunt) { 'dist/panel-triggers/css/panel_triggers.css' : 'src/panel-triggers/sass/panel_triggers.scss', } } + }, + + jshint: { + source: { + files: { + src: ['src/**/*.js'], + } + }, + options: { + jshintrc: true, + reporter: require('jshint-stylish'), + ignores: [ + 'node_modules/*', + 'dist/*', + ] + } + }, + + jscs: { + src: ['src/**/*.js'], + options: { + config: ".jscs.json", + }, } }); @@ -76,7 +99,9 @@ module.exports = function(grunt) { 'clean', 'copy:src_to_dist', 'copy:pluginDef', + 'sass', 'babel', - 'sass' + 'jshint', + 'jscs' ]); }; diff --git a/package.json b/package.json index b4b96f1..560e34b 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,9 @@ "babel-plugin-transform-es2015-modules-systemjs": "^6.5.0", "babel-plugin-transform-es2015-for-of": "^6.5.0", "babel-preset-es2015": "^6.5.0", + "grunt-contrib-jshint": "^1.0.0", + "grunt-jscs": "^2.8.0", + "jshint-stylish": "^2.1.0", "lodash": "~4.0.0" }, "homepage": "http://grafana-zabbix.org" From 65308375f4de9844eb1662a3faf5ed51348725b7 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Tue, 27 Sep 2016 20:28:18 +0300 Subject: [PATCH 31/64] Fixed linter warnings. --- src/datasource-zabbix/datasource.js | 12 +++--------- src/datasource-zabbix/query.controller.js | 1 + src/datasource-zabbix/queryProcessor.service.js | 6 ------ src/datasource-zabbix/utils.js | 1 - src/datasource-zabbix/zabbixCache.service.js | 1 - 5 files changed, 4 insertions(+), 17 deletions(-) diff --git a/src/datasource-zabbix/datasource.js b/src/datasource-zabbix/datasource.js index cd1e2de..ee7d960 100644 --- a/src/datasource-zabbix/datasource.js +++ b/src/datasource-zabbix/datasource.js @@ -433,13 +433,6 @@ function bindFunctionDefs(functionDefs, category) { }); } -function filterFunctionDefs(funcs, category) { - let filteredFuncs = _.map(metricFunctions.getCategories()[category]); - return _.filter(funcs, func => { - return _.includes(filteredFuncs, func.def.name); - }); -} - function formatMetric(metricObj) { return { text: metricObj.name, @@ -457,7 +450,7 @@ function formatMetric(metricObj) { * template variables, for example * /CPU $cpu_item.*time/ where $cpu_item is system,user,iowait */ -function zabbixTemplateFormat(value, variable) { +function zabbixTemplateFormat(value) { if (typeof value === 'string') { return utils.escapeRegex(value); } @@ -466,7 +459,8 @@ function zabbixTemplateFormat(value, variable) { return '(' + escapedValues.join('|') + ')'; } -/** If template variables are used in request, replace it using regex format +/** + * If template variables are used in request, replace it using regex format * and wrap with '/' for proper multi-value work. Example: * $variable selected as a, b, c * We use filter $variable diff --git a/src/datasource-zabbix/query.controller.js b/src/datasource-zabbix/query.controller.js index ca271cc..5536d48 100644 --- a/src/datasource-zabbix/query.controller.js +++ b/src/datasource-zabbix/query.controller.js @@ -1,4 +1,5 @@ import {QueryCtrl} from 'app/plugins/sdk'; +import angular from 'angular'; import _ from 'lodash'; import * as utils from './utils'; import * as metricFunctions from './metricFunctions'; diff --git a/src/datasource-zabbix/queryProcessor.service.js b/src/datasource-zabbix/queryProcessor.service.js index 5f8e8e9..959645b 100644 --- a/src/datasource-zabbix/queryProcessor.service.js +++ b/src/datasource-zabbix/queryProcessor.service.js @@ -335,12 +335,6 @@ function getByFilter(list, filter) { } } -function getFromIndex(index, objids) { - return _.map(objids, function(id) { - return index[id]; - }); -} - function convertHistoryPoint(point) { // Value must be a number for properly work return [ diff --git a/src/datasource-zabbix/utils.js b/src/datasource-zabbix/utils.js index fbebd5b..01e0932 100644 --- a/src/datasource-zabbix/utils.js +++ b/src/datasource-zabbix/utils.js @@ -1,7 +1,6 @@ import _ from 'lodash'; import moment from 'moment'; - /** * Expand Zabbix item name * diff --git a/src/datasource-zabbix/zabbixCache.service.js b/src/datasource-zabbix/zabbixCache.service.js index 9970568..7107654 100644 --- a/src/datasource-zabbix/zabbixCache.service.js +++ b/src/datasource-zabbix/zabbixCache.service.js @@ -1,6 +1,5 @@ import angular from 'angular'; import _ from 'lodash'; -import * as utils from './utils'; // Use factory() instead service() for multiple datasources support. // Each datasource instance must initialize its own cache. From c6659478416315e420701d5b6a2c6f888682f528 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Tue, 27 Sep 2016 20:32:21 +0300 Subject: [PATCH 32/64] Run jshint and jscs tasks before babel transpiler. --- Gruntfile.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index a26d125..f3ca1e0 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -99,9 +99,9 @@ module.exports = function(grunt) { 'clean', 'copy:src_to_dist', 'copy:pluginDef', - 'sass', - 'babel', 'jshint', - 'jscs' + 'jscs', + 'sass', + 'babel' ]); }; From 28f95c62aa22a2bbf55e86fc2710e93bd396282a Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Tue, 27 Sep 2016 20:53:56 +0300 Subject: [PATCH 33/64] Bump version to 3.1.1 --- src/plugin.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugin.json b/src/plugin.json index 9cf9542..d0232e7 100644 --- a/src/plugin.json +++ b/src/plugin.json @@ -31,8 +31,8 @@ {"name": "Metric Editor", "path": "img/screenshot-metric_editor.png"}, {"name": "Triggers", "path": "img/screenshot-triggers.png"} ], - "version": "3.1.0", - "updated": "2016-09-26" + "version": "3.1.1", + "updated": "2016-09-27" }, "includes": [ From 61d1fede117b419dbd73fa28031056c7eb4d0927 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Wed, 28 Sep 2016 16:17:59 +0300 Subject: [PATCH 34/64] Fix license url in docs. --- docs/sources/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/index.md b/docs/sources/index.md index bd9ed6b..0ace143 100644 --- a/docs/sources/index.md +++ b/docs/sources/index.md @@ -22,7 +22,7 @@ a number of ways to get help: - [Gitter room](https://gitter.im/alexanderzobnin/grafana-zabbix) - [Twitter](https://twitter.com/AlexanderZobnin) -Or you can just send me [email](mailto:alexanderzobnin@gmail.com). +Or you can send me [email](mailto:alexanderzobnin@gmail.com). ## Support Project I develop this project in my free time, but if you really find it helpful and promising, you can @@ -34,5 +34,5 @@ Triggers panel was sponsored by [Core IT Project](http://coreit.fr/)). By utilizing this software, you agree to the terms of the included license. Grafana-Zabbix plugin is licensed under the Apache 2.0 agreement. See -[LICENSE](https://github.com/alexanderzobnin/grafana-zabbix/blob/master/LICENSE.md) for the full +[LICENSE](https://github.com/alexanderzobnin/grafana-zabbix/blob/master/LICENSE) for the full license terms. From e68adb0aecd8ccfcccaf014a7eb06e06688e6b71 Mon Sep 17 00:00:00 2001 From: Tom Zhang Date: Fri, 30 Sep 2016 13:36:13 +0930 Subject: [PATCH 35/64] not show host name when only 1 host selected fix #261 --- src/datasource-zabbix/queryProcessor.service.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/datasource-zabbix/queryProcessor.service.js b/src/datasource-zabbix/queryProcessor.service.js index 959645b..d151ff0 100644 --- a/src/datasource-zabbix/queryProcessor.service.js +++ b/src/datasource-zabbix/queryProcessor.service.js @@ -230,12 +230,12 @@ angular.module('grafana.services').factory('QueryProcessor', function($q) { // Group history by itemid var grouped_history = _.groupBy(history, 'itemid'); - var hosts = _.flatten(_.map(items, 'hosts')); + var hosts = _.uniq(_.flatten(_.map(items, 'hosts')),'hostid'); //uniq is needed to deduplicate return _.map(grouped_history, function(hist, itemid) { var item = _.find(items, {'itemid': itemid}); var alias = item.name; - if (_.keys(hosts).length > 1 || addHostName) { + if (_.keys(hosts).length > 1 && addHostName) { //only when actual multi hosts selected var host = _.find(hosts, {'hostid': item.hostid}); alias = host.name + ": " + alias; } From 62509caeb9e5f0f5009cecb0e859ff8952bba890 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Mon, 7 Nov 2016 21:03:42 +0300 Subject: [PATCH 36/64] Fixed annotations bug with time filtering, fixes #244. --- src/datasource-zabbix/datasource.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datasource-zabbix/datasource.js b/src/datasource-zabbix/datasource.js index ee7d960..5a1321b 100644 --- a/src/datasource-zabbix/datasource.js +++ b/src/datasource-zabbix/datasource.js @@ -365,7 +365,7 @@ export class ZabbixAPIDatasource { return buildQuery.then(query => { return self.zabbixAPI .getTriggers(query.groupids, query.hostids, query.applicationids, - showTriggers, timeFrom, timeTo) + showTriggers) .then(triggers => { // Filter triggers by description From ca97d392e9e43dd9ab5cbf0435869d7402a7aa62 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Mon, 7 Nov 2016 21:14:40 +0300 Subject: [PATCH 37/64] Fixed annotation popup bug after lodash upgrade. --- src/datasource-zabbix/datasource.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/datasource-zabbix/datasource.js b/src/datasource-zabbix/datasource.js index 5a1321b..eea0db2 100644 --- a/src/datasource-zabbix/datasource.js +++ b/src/datasource-zabbix/datasource.js @@ -388,7 +388,7 @@ export class ZabbixAPIDatasource { return self.zabbixAPI .getEvents(objectids, timeFrom, timeTo, showOkEvents) .then(events => { - var indexedTriggers = _.groupBy(triggers, 'triggerid'); + var indexedTriggers = _.keyBy(triggers, 'triggerid'); // Hide acknowledged events if option enabled if (annotation.hideAcknowledged) { @@ -500,6 +500,5 @@ function sequence(funcsArray) { } // Fix for backward compatibility with lodash 2.4 -if (!_.includes) { - _.includes = _.contains; -} +if (!_.includes) {_.includes = _.contains;} +if (!_.keyBy) {_.keyBy = _.indexBy;} From 912bf825e7736aca3e5769e3eb0ccd6bbd178824 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Mon, 7 Nov 2016 21:15:31 +0300 Subject: [PATCH 38/64] Fix for backward compatibility with lodash 2.4 (indexBy() => keyBy()). --- src/datasource-zabbix/zabbixCache.service.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/datasource-zabbix/zabbixCache.service.js b/src/datasource-zabbix/zabbixCache.service.js index 7107654..e2a9a00 100644 --- a/src/datasource-zabbix/zabbixCache.service.js +++ b/src/datasource-zabbix/zabbixCache.service.js @@ -116,7 +116,7 @@ angular.module('grafana.services').factory('ZabbixCachingProxy', function($q, $i var deferred = this.$q.defer(); var historyStorage = this.storage.history; var full_history; - var expired = _.filter(_.groupBy(items, 'itemid'), function(item, itemid) { + var expired = _.filter(_.keyBy(items, 'itemid'), function(item, itemid) { return !historyStorage[itemid]; }); if (expired.length) { @@ -235,3 +235,6 @@ String.prototype.getHash = function() { } return hash; }; + +// Fix for backward compatibility with lodash 2.4 +if (!_.keyBy) {_.keyBy = _.indexBy;} From 77e16b70a5178ef4c6e5042503a1a8b830b3de4e Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Mon, 7 Nov 2016 21:19:29 +0300 Subject: [PATCH 39/64] Refactor: removed trick with self and this. --- src/datasource-zabbix/datasource.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/datasource-zabbix/datasource.js b/src/datasource-zabbix/datasource.js index eea0db2..0b2eed4 100644 --- a/src/datasource-zabbix/datasource.js +++ b/src/datasource-zabbix/datasource.js @@ -257,10 +257,9 @@ export class ZabbixAPIDatasource { * @return {object} Connection status and Zabbix API version */ testDatasource() { - var self = this; return this.zabbixAPI.getVersion() .then(version => { - return self.zabbixAPI.login() + return this.zabbixAPI.login() .then(auth => { if (auth) { return { @@ -361,11 +360,10 @@ export class ZabbixAPIDatasource { .buildTriggerQuery(this.replaceTemplateVars(annotation.group, {}), this.replaceTemplateVars(annotation.host, {}), this.replaceTemplateVars(annotation.application, {})); - var self = this; + return buildQuery.then(query => { - return self.zabbixAPI - .getTriggers(query.groupids, query.hostids, query.applicationids, - showTriggers) + return this.zabbixAPI + .getTriggers(query.groupids, query.hostids, query.applicationids, showTriggers) .then(triggers => { // Filter triggers by description @@ -385,7 +383,7 @@ export class ZabbixAPIDatasource { }); var objectids = _.map(triggers, 'triggerid'); - return self.zabbixAPI + return this.zabbixAPI .getEvents(objectids, timeFrom, timeTo, showOkEvents) .then(events => { var indexedTriggers = _.keyBy(triggers, 'triggerid'); From 2330a3ae081ccdadac652196bd53908fd9b1ea05 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Mon, 7 Nov 2016 23:30:58 +0300 Subject: [PATCH 40/64] Annotations: show host name as colored tag. --- src/datasource-zabbix/datasource.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/datasource-zabbix/datasource.js b/src/datasource-zabbix/datasource.js index 0b2eed4..f2176c2 100644 --- a/src/datasource-zabbix/datasource.js +++ b/src/datasource-zabbix/datasource.js @@ -396,19 +396,20 @@ export class ZabbixAPIDatasource { } return _.map(events, event => { - var title =''; + let tags; if (annotation.showHostname) { - title += event.hosts[0].name + ': '; + tags = _.map(event.hosts, 'name'); } // Show event type (OK or Problem) - title += Number(event.value) ? 'Problem' : 'OK'; + let title = Number(event.value) ? 'Problem' : 'OK'; - var formatted_acknowledges = utils.formatAcknowledges(event.acknowledges); + let formatted_acknowledges = utils.formatAcknowledges(event.acknowledges); return { annotation: annotation, time: event.clock * 1000, title: title, + tags: tags, text: indexedTriggers[event.objectid].description + formatted_acknowledges }; }); From a8079316d7b83ca4be8e730110929346a641c77d Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Wed, 9 Nov 2016 15:00:36 +0300 Subject: [PATCH 41/64] Update datemath mock to lodash 4. --- src/datasource-zabbix/specs/modules/datemath.js | 2 +- src/datasource-zabbix/specs/test-main.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/datasource-zabbix/specs/modules/datemath.js b/src/datasource-zabbix/specs/modules/datemath.js index fd97ab8..efbf0bf 100644 --- a/src/datasource-zabbix/specs/modules/datemath.js +++ b/src/datasource-zabbix/specs/modules/datemath.js @@ -91,7 +91,7 @@ export function parseDateMath(mathString, time, roundUp) { } unit = mathString.charAt(i++); - if (!_.contains(units, unit)) { + if (!_.includes(units, unit)) { return undefined; } else { if (type === 0) { diff --git a/src/datasource-zabbix/specs/test-main.js b/src/datasource-zabbix/specs/test-main.js index 23fa7c1..cdaec55 100644 --- a/src/datasource-zabbix/specs/test-main.js +++ b/src/datasource-zabbix/specs/test-main.js @@ -4,7 +4,7 @@ import prunk from 'prunk'; import {jsdom} from 'jsdom'; import chai from 'chai'; -import sinon from 'sinon'; +// import sinon from 'sinon'; import sinonChai from 'sinon-chai'; import * as dateMath from './modules/datemath'; From f8419f04a979a0dd96deb7f8148d8a84dd799779 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Wed, 9 Nov 2016 16:52:12 +0300 Subject: [PATCH 42/64] Tests: added tests for template variables replacing. --- src/datasource-zabbix/datasource.js | 4 +- .../specs/datasource_specs.js | 75 ++++++++++++++++--- 2 files changed, 66 insertions(+), 13 deletions(-) diff --git a/src/datasource-zabbix/datasource.js b/src/datasource-zabbix/datasource.js index f2176c2..b860833 100644 --- a/src/datasource-zabbix/datasource.js +++ b/src/datasource-zabbix/datasource.js @@ -449,7 +449,7 @@ function formatMetric(metricObj) { * template variables, for example * /CPU $cpu_item.*time/ where $cpu_item is system,user,iowait */ -function zabbixTemplateFormat(value) { +export function zabbixTemplateFormat(value) { if (typeof value === 'string') { return utils.escapeRegex(value); } @@ -468,7 +468,7 @@ function zabbixTemplateFormat(value) { */ function replaceTemplateVars(templateSrv, target, scopedVars) { var replacedTarget = templateSrv.replace(target, scopedVars, zabbixTemplateFormat); - if (target !== replacedTarget && !utils.regexPattern.test(replacedTarget)) { + if (target !== replacedTarget && !utils.isRegex(replacedTarget)) { replacedTarget = '/^' + replacedTarget + '$/'; } return replacedTarget; diff --git a/src/datasource-zabbix/specs/datasource_specs.js b/src/datasource-zabbix/specs/datasource_specs.js index 4b292d8..fc44a5c 100644 --- a/src/datasource-zabbix/specs/datasource_specs.js +++ b/src/datasource-zabbix/specs/datasource_specs.js @@ -1,13 +1,14 @@ import {Datasource} from "../module"; +import {zabbixTemplateFormat} from "../datasource"; import Q from "q"; import sinon from 'sinon'; import _ from 'lodash'; -describe('ZabbixDatasource', function() { +describe('ZabbixDatasource', () => { var ctx = {}; var defined = sinon.match.defined; - beforeEach(function() { + beforeEach(() => { ctx.instanceSettings = { jsonData: { username: 'zabbix', @@ -19,19 +20,19 @@ describe('ZabbixDatasource', function() { ctx.$q = Q; ctx.templateSrv = {}; ctx.alertSrv = {}; - ctx.zabbixAPIService = function() {}; - ctx.ZabbixCachingProxy = function() {}; - ctx.QueryProcessor = function() {}; + ctx.zabbixAPIService = () => {}; + ctx.ZabbixCachingProxy = () => {}; + ctx.QueryProcessor = () => {}; ctx.ds = new Datasource(ctx.instanceSettings, ctx.$q, ctx.templateSrv, ctx.alertSrv, ctx.zabbixAPIService, ctx.ZabbixCachingProxy, ctx.QueryProcessor); - - ctx.ds.replaceTemplateVars = function(str) { - return str; - }; }); - describe('When querying data', function() { + describe('When querying data', () => { + beforeEach(() => { + ctx.ds.replaceTemplateVars = (str) => { return str; }; + }); + ctx.options = { targets: [ { @@ -83,10 +84,62 @@ describe('ZabbixDatasource', function() { expect(ctx.ds.queryNumericData) .to.have.been.calledWith(defined, defined, defined, false); }); - done(); }); }); + describe('When replacing template variables', () => { + + function testReplacingVariable(target, varValue, expectedResult, done) { + ctx.ds.templateSrv.replace = () => { + return zabbixTemplateFormat(varValue); + }; + + let result = ctx.ds.replaceTemplateVars(target); + expect(result).to.equal(expectedResult); + done(); + } + + /* + * Alphanumerics, spaces, dots, dashes and underscores + * are allowed in Zabbix host name. + * 'AaBbCc0123 .-_' + */ + it('should return properly escaped regex', (done) => { + let target = '$host'; + let template_var_value = 'AaBbCc0123 .-_'; + let expected_result = '/^AaBbCc0123 \\.-_$/'; + + testReplacingVariable(target, template_var_value, expected_result, done); + }); + + /* + * Single-value variable + * $host = backend01 + * $host => /^backend01|backend01$/ + */ + it('should return proper regex for single value', (done) => { + let target = '$host'; + let template_var_value = 'backend01'; + let expected_result = '/^backend01$/'; + + testReplacingVariable(target, template_var_value, expected_result, done); + }); + + /* + * Multi-value variable + * $host = [backend01, backend02] + * $host => /^(backend01|backend01)$/ + */ + it('should return proper regex for multi-value', (done) => { + let target = '$host'; + let template_var_value = ['backend01', 'backend02']; + let expected_result = '/^(backend01|backend02)$/'; + + testReplacingVariable(target, template_var_value, expected_result, done); + }); + + }); + }); From cb5461a472cc1fa0b48caf4bce7a1b6c91fcb2c6 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Wed, 9 Nov 2016 20:14:04 +0300 Subject: [PATCH 43/64] Tests: added tests for template variable query, issue #303. --- .../specs/datasource_specs.js | 112 ++++++++++++++++-- 1 file changed, 102 insertions(+), 10 deletions(-) diff --git a/src/datasource-zabbix/specs/datasource_specs.js b/src/datasource-zabbix/specs/datasource_specs.js index fc44a5c..923d99a 100644 --- a/src/datasource-zabbix/specs/datasource_specs.js +++ b/src/datasource-zabbix/specs/datasource_specs.js @@ -5,8 +5,8 @@ import sinon from 'sinon'; import _ from 'lodash'; describe('ZabbixDatasource', () => { - var ctx = {}; - var defined = sinon.match.defined; + let ctx = {}; + let defined = sinon.match.defined; beforeEach(() => { ctx.instanceSettings = { @@ -30,7 +30,7 @@ describe('ZabbixDatasource', () => { describe('When querying data', () => { beforeEach(() => { - ctx.ds.replaceTemplateVars = (str) => { return str; }; + ctx.ds.replaceTemplateVars = (str) => str; }); ctx.options = { @@ -45,19 +45,19 @@ describe('ZabbixDatasource', () => { range: {from: 'now-7d', to: 'now'} }; - it('should return an empty array when no targets are set', function(done) { - var options = { + it('should return an empty array when no targets are set', (done) => { + let options = { targets: [], range: {from: 'now-6h', to: 'now'} }; - ctx.ds.query(options).then(function(result) { + ctx.ds.query(options).then(result => { expect(result.data).to.have.length(0); done(); }); }); - it('should use trends if it enabled and time more than trendsFrom', function(done) { - var ranges = ['now-7d', 'now-168h', 'now-1M', 'now-1y']; + it('should use trends if it enabled and time more than trendsFrom', (done) => { + let ranges = ['now-7d', 'now-168h', 'now-1M', 'now-1y']; _.forEach(ranges, range => { ctx.options.range.from = range; @@ -72,8 +72,8 @@ describe('ZabbixDatasource', () => { done(); }); - it('shouldnt use trends if it enabled and time less than trendsFrom', function(done) { - var ranges = ['now-6d', 'now-167h', 'now-1h', 'now-30m', 'now-30s']; + it('shouldnt use trends if it enabled and time less than trendsFrom', (done) => { + let ranges = ['now-6d', 'now-167h', 'now-1h', 'now-30m', 'now-30s']; _.forEach(ranges, range => { ctx.options.range.from = range; @@ -139,7 +139,99 @@ describe('ZabbixDatasource', () => { testReplacingVariable(target, template_var_value, expected_result, done); }); + }); + describe('When invoking metricFindQuery()', () => { + beforeEach(() => { + ctx.ds.replaceTemplateVars = (str) => str; + ctx.ds.zabbixCache = { + getGroups: () => Q.when([]) + }; + ctx.ds.queryProcessor = { + getGroups: () => Q.when([]), + getHosts: () => Q.when([]), + getApps: () => Q.when([]), + getItems: () => Q.when([]) + }; + }); + + it('should return groups', (done) => { + const tests = [ + {query: '*', expect: '/.*/'}, + {query: '', expect: ''}, + {query: 'Backend', expect: 'Backend'}, + {query: 'Back*', expect: 'Back*'} + ]; + + let getGroups = sinon.spy(ctx.ds.zabbixCache, 'getGroups'); + for (const test of tests) { + ctx.ds.metricFindQuery(test.query); + expect(getGroups).to.have.been.calledWith(test.expect); + getGroups.reset(); + } + done(); + }); + + it('should return hosts', (done) => { + const tests = [ + {query: '*.*', expect: '/.*/'}, + {query: '.', expect: ''}, + {query: 'Backend.*', expect: 'Backend'}, + {query: 'Back*.', expect: 'Back*'} + ]; + + let getHosts = sinon.spy(ctx.ds.queryProcessor, 'getHosts'); + for (const test of tests) { + ctx.ds.metricFindQuery(test.query); + expect(getHosts).to.have.been.calledWith(test.expect); + getHosts.reset(); + } + done(); + }); + + it('should return applications', (done) => { + const tests = [ + {query: '*.*.*', expect: ['/.*/', '/.*/']}, + {query: '.*.', expect: ['', '/.*/']}, + {query: 'Backend.backend01.*', expect: ['Backend', 'backend01']}, + {query: 'Back*.*.', expect: ['Back*', '/.*/']} + ]; + + let getApps = sinon.spy(ctx.ds.queryProcessor, 'getApps'); + for (const test of tests) { + ctx.ds.metricFindQuery(test.query); + expect(getApps).to.have.been.calledWith(test.expect[0], test.expect[1]); + getApps.reset(); + } + done(); + }); + + it('should return items', (done) => { + const tests = [ + {query: '*.*.*.*', expect: ['/.*/', '/.*/', '']}, + {query: '.*.*.*', expect: ['', '/.*/', '']}, + {query: 'Backend.backend01.*.*', expect: ['Backend', 'backend01', '']}, + {query: 'Back*.*.cpu.*', expect: ['Back*', '/.*/', 'cpu']} + ]; + + let getItems = sinon.spy(ctx.ds.queryProcessor, 'getItems'); + for (const test of tests) { + ctx.ds.metricFindQuery(test.query); + expect(getItems) + .to.have.been.calledWith(test.expect[0], test.expect[1], test.expect[2]); + getItems.reset(); + } + done(); + }); + + it('should invoke method with proper arguments', (done) => { + let query = '*.*'; + + let getHosts = sinon.spy(ctx.ds.queryProcessor, 'getHosts'); + ctx.ds.metricFindQuery(query); + expect(getHosts).to.have.been.calledWith('/.*/'); + done(); + }); }); }); From cb7962929cef55fabc483595f69038691222f4a8 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Wed, 9 Nov 2016 20:40:16 +0300 Subject: [PATCH 44/64] Bump version to 3.1.2 --- src/plugin.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugin.json b/src/plugin.json index d0232e7..95bf0d7 100644 --- a/src/plugin.json +++ b/src/plugin.json @@ -31,8 +31,8 @@ {"name": "Metric Editor", "path": "img/screenshot-metric_editor.png"}, {"name": "Triggers", "path": "img/screenshot-triggers.png"} ], - "version": "3.1.1", - "updated": "2016-09-27" + "version": "3.1.2", + "updated": "2016-11-09" }, "includes": [ From afe2cfc7cf9b75557e75b31f56ac9600b7a3314e Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Wed, 9 Nov 2016 21:23:03 +0300 Subject: [PATCH 45/64] Fixed zabbixTemplateFormat() error. --- src/datasource-zabbix/datasource.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/datasource-zabbix/datasource.js b/src/datasource-zabbix/datasource.js index b860833..583d4f3 100644 --- a/src/datasource-zabbix/datasource.js +++ b/src/datasource-zabbix/datasource.js @@ -9,7 +9,7 @@ import './zabbixAPI.service.js'; import './zabbixCache.service.js'; import './queryProcessor.service.js'; -export class ZabbixAPIDatasource { +class ZabbixAPIDatasource { /** @ngInject */ constructor(instanceSettings, $q, templateSrv, alertSrv, zabbixAPIService, ZabbixCachingProxy, QueryProcessor) { @@ -449,7 +449,7 @@ function formatMetric(metricObj) { * template variables, for example * /CPU $cpu_item.*time/ where $cpu_item is system,user,iowait */ -export function zabbixTemplateFormat(value) { +function zabbixTemplateFormat(value) { if (typeof value === 'string') { return utils.escapeRegex(value); } @@ -498,6 +498,8 @@ function sequence(funcsArray) { }; } +export {ZabbixAPIDatasource, zabbixTemplateFormat}; + // Fix for backward compatibility with lodash 2.4 if (!_.includes) {_.includes = _.contains;} if (!_.keyBy) {_.keyBy = _.indexBy;} From 145bc80e787b37fa8a3fd80b17bde4496bfa32c0 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Fri, 11 Nov 2016 18:40:28 +0300 Subject: [PATCH 46/64] Grunt: remove tests from watch task. --- Gruntfile.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index 025733b..fd3051f 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -33,7 +33,7 @@ module.exports = function(grunt) { watch: { rebuild_all: { files: ['src/**/*', 'plugin.json'], - tasks: ['default'], + tasks: ['watchTask'], options: {spawn: false} }, }, @@ -135,4 +135,14 @@ module.exports = function(grunt) { 'babel', 'mochaTest' ]); + + grunt.registerTask('watchTask', [ + 'clean', + 'copy:src_to_dist', + 'copy:pluginDef', + 'jshint', + 'jscs', + 'sass', + 'babel:dist' + ]); }; From 062d975319fe4634e05f4b83e554b081438630a2 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Fri, 11 Nov 2016 19:38:23 +0300 Subject: [PATCH 47/64] Refactor: improved working with promises in testDatasource() method. --- src/datasource-zabbix/datasource.js | 49 ++++++++++++++--------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/src/datasource-zabbix/datasource.js b/src/datasource-zabbix/datasource.js index 583d4f3..79edb6a 100644 --- a/src/datasource-zabbix/datasource.js +++ b/src/datasource-zabbix/datasource.js @@ -8,6 +8,7 @@ import DataProcessor from './DataProcessor'; import './zabbixAPI.service.js'; import './zabbixCache.service.js'; import './queryProcessor.service.js'; +import {ZabbixAPIError} from './zabbixAPICore.service.js'; class ZabbixAPIDatasource { @@ -257,38 +258,34 @@ class ZabbixAPIDatasource { * @return {object} Connection status and Zabbix API version */ testDatasource() { + let zabbixVersion; return this.zabbixAPI.getVersion() - .then(version => { - return this.zabbixAPI.login() - .then(auth => { - if (auth) { - return { - status: "success", - title: "Success", - message: "Zabbix API version: " + version - }; - } else { - return { - status: "error", - title: "Invalid user name or password", - message: "Zabbix API version: " + version - }; - } - }, error => { - return { - status: "error", - title: error.message, - message: error.data - }; - }); - }, error => { - console.log(error); + .then(version => { + zabbixVersion = version; + return this.zabbixAPI.login(); + }) + .then(() => { + return { + status: "success", + title: "Success", + message: "Zabbix API version: " + zabbixVersion + }; + }) + .catch(error => { + if (error instanceof ZabbixAPIError) { + return { + status: "error", + title: error.message, + message: error.data + }; + } else { return { status: "error", title: "Connection failed", message: "Could not connect to given url" }; - }); + } + }); } //////////////// From 6579c6acbc494b52ac69c1dbb9384f169e9c17ed Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Fri, 11 Nov 2016 21:53:10 +0300 Subject: [PATCH 48/64] Refactor: zabbixAPI.service --- src/datasource-zabbix/zabbixAPI.service.js | 170 ++++++++++----------- 1 file changed, 77 insertions(+), 93 deletions(-) diff --git a/src/datasource-zabbix/zabbixAPI.service.js b/src/datasource-zabbix/zabbixAPI.service.js index b8f9edb..e81f2aa 100644 --- a/src/datasource-zabbix/zabbixAPI.service.js +++ b/src/datasource-zabbix/zabbixAPI.service.js @@ -4,7 +4,7 @@ import * as utils from './utils'; import './zabbixAPICore.service'; /** @ngInject */ -function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) { +function ZabbixAPIServiceFactory($q, alertSrv, zabbixAPICoreService) { /** * Zabbix API Wrapper. @@ -25,6 +25,8 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) { }; this.loginPromise = null; + this.loginErrorCount = 0; + this.maxLoginAttempts = 3; this.$q = $q; this.alertSrv = alertSrv; @@ -39,27 +41,24 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) { ////////////////////////// request(method, params) { - var self = this; - - return this.zabbixAPICore - .request(this.url, method, params, this.requestOptions, this.auth) - .then((result) => { - return result; - }, (error) => { - // Handle API errors - if (isNotAuthorized(error.data)) { - return self.loginOnce().then( - function() { - return self.request(method, params); - }, - // Handle user.login method errors - function(error) { - self.alertAPIError(error.data); - }); + return this.zabbixAPICore.request(this.url, method, params, this.requestOptions, this.auth) + .catch(error => { + if (isNotAuthorized(error.data)) { + // Handle auth errors + this.loginErrorCount++; + if (this.loginErrorCount > this.maxLoginAttempts) { + this.loginErrorCount = 0; + return null; } else { - this.alertSrv.set("Connection Error", error.data, 'error', 5000); + return this.loginOnce() + .then(() => this.request(method, params)); } - }); + } else { + // Handle API errors + let message = error.data ? error.data : error.statusText; + this.alertAPIError(message); + } + }); } alertAPIError(message, timeout = 5000) { @@ -78,25 +77,16 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) { * @return login promise */ loginOnce() { - var self = this; - var deferred = this.$q.defer(); - if (!self.loginPromise) { - self.loginPromise = deferred.promise; - self.login().then( - function(auth) { - self.loginPromise = null; - self.auth = auth; - deferred.resolve(auth); - }, - function(error) { - self.loginPromise = null; - deferred.reject(error); - } + if (!this.loginPromise) { + this.loginPromise = Promise.resolve( + this.login().then(auth => { + this.auth = auth; + this.loginPromise = null; + return auth; + }) ); - } else { - return self.loginPromise; } - return deferred.promise; + return this.loginPromise; } /** @@ -197,13 +187,16 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) { } return this.request('item.get', params) - .then(items => { - return _.forEach(items, item => { - item.item = item.name; - item.name = utils.expandItemName(item.item, item.key_); - return item; - }); + .then(expandItems); + + function expandItems(items) { + items.forEach(item => { + item.item = item.name; + item.name = utils.expandItemName(item.item, item.key_); + return item; }); + return items; + } } getLastValue(itemid) { @@ -211,48 +204,42 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) { output: ['lastvalue'], itemids: itemid }; - return this.request('item.get', params).then(function(items) { - if (items.length) { - return items[0].lastvalue; - } else { - return null; - } - }); + return this.request('item.get', params) + .then(items => items.length ? items[0].lastvalue : null); } /** * Perform history query from Zabbix API * * @param {Array} items Array of Zabbix item objects - * @param {Number} time_from Time in seconds - * @param {Number} time_till Time in seconds + * @param {Number} timeFrom Time in seconds + * @param {Number} timeTill Time in seconds * @return {Array} Array of Zabbix history objects */ - getHistory(items, time_from, time_till) { - var self = this; + getHistory(items, timeFrom, timeTill) { - // Group items by value type - var grouped_items = _.groupBy(items, 'value_type'); - - // Perform request for each value type - return this.$q.all(_.map(grouped_items, function (items, value_type) { - var itemids = _.map(items, 'itemid'); - var params = { + // Group items by value type and perform request for each value type + let grouped_items = _.groupBy(items, 'value_type'); + let promises = _.map(grouped_items, (items, value_type) => { + let itemids = _.map(items, 'itemid'); + let params = { output: 'extend', history: value_type, itemids: itemids, sortfield: 'clock', sortorder: 'ASC', - time_from: time_from + time_from: timeFrom }; // Relative queries (e.g. last hour) don't include an end time - if (time_till) { - params.time_till = time_till; + if (timeTill) { + params.time_till = timeTill; } - return self.request('history.get', params); - })).then(_.flatten); + return this.request('history.get', params); + }); + + return Promise.all(promises).then(_.flatten); } /** @@ -264,31 +251,30 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) { * @param {Number} time_till Time in seconds * @return {Array} Array of Zabbix trend objects */ - getTrend_ZBXNEXT1193(items, time_from, time_till) { - var self = this; + getTrend_ZBXNEXT1193(items, timeFrom, timeTill) { - // Group items by value type - var grouped_items = _.groupBy(items, 'value_type'); - - // Perform request for each value type - return this.$q.all(_.map(grouped_items, function (items, value_type) { - var itemids = _.map(items, 'itemid'); - var params = { + // Group items by value type and perform request for each value type + let grouped_items = _.groupBy(items, 'value_type'); + let promises = _.map(grouped_items, (items, value_type) => { + let itemids = _.map(items, 'itemid'); + let params = { output: 'extend', trend: value_type, itemids: itemids, sortfield: 'clock', sortorder: 'ASC', - time_from: time_from + time_from: timeFrom }; // Relative queries (e.g. last hour) don't include an end time - if (time_till) { - params.time_till = time_till; + if (timeTill) { + params.time_till = timeTill; } - return self.request('trend.get', params); - })).then(_.flatten); + return this.request('trend.get', params); + }); + + return Promise.all(promises).then(_.flatten); } getTrend_30(items, time_from, time_till, value_type) { @@ -312,7 +298,7 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) { return self.request('trend.get', params); } - getITService(/* optional */ serviceids) { + getITService(serviceids) { var params = { output: 'extend', serviceids: serviceids @@ -320,12 +306,12 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) { return this.request('service.get', params); } - getSLA(serviceids, from, to) { + getSLA(serviceids, timeFrom, timeTo) { var params = { serviceids: serviceids, intervals: [{ - from: from, - to: to + from: timeFrom, + to: timeTo }] }; return this.request('service.getsla', params); @@ -364,11 +350,11 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) { return this.request('trigger.get', params); } - getEvents(objectids, from, to, showEvents) { + getEvents(objectids, timeFrom, timeTo, showEvents) { var params = { output: 'extend', - time_from: from, - time_till: to, + time_from: timeFrom, + time_till: timeTo, objectids: objectids, select_acknowledges: 'extend', selectHosts: 'extend', @@ -389,11 +375,9 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) { }; return this.request('event.get', params) - .then(function (events) { - return _.filter(events, function(event) { - return event.acknowledges.length; - }); - }); + .then(events => { + return _.filter(events, (event) => event.acknowledges.length); + }); } } @@ -411,4 +395,4 @@ function isNotAuthorized(message) { angular .module('grafana.services') - .factory('zabbixAPIService', ZabbixAPIService); + .factory('zabbixAPIService', ZabbixAPIServiceFactory); From 4a73957c162e0cf9b4485903ac337258c9ea9027 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Fri, 11 Nov 2016 21:56:21 +0300 Subject: [PATCH 49/64] Refactor: improved working with promises in zabbixAPICore.service. --- .../zabbixAPICore.service.js | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/src/datasource-zabbix/zabbixAPICore.service.js b/src/datasource-zabbix/zabbixAPICore.service.js index 2a7473c..70017d2 100644 --- a/src/datasource-zabbix/zabbixAPICore.service.js +++ b/src/datasource-zabbix/zabbixAPICore.service.js @@ -17,8 +17,7 @@ class ZabbixAPICoreService { * @return {object} response.result */ request(api_url, method, params, options, auth) { - var deferred = this.$q.defer(); - var requestData = { + let requestData = { jsonrpc: '2.0', method: method, params: params, @@ -27,20 +26,19 @@ class ZabbixAPICoreService { if (auth === "") { // Reject immediately if not authenticated - deferred.reject({data: "Not authorised."}); - return deferred.promise; + return Promise.reject(new ZabbixAPIError({data: "Not authorised."})); } else if (auth) { // Set auth parameter only if it needed requestData.auth = auth; } - var requestOptions = { + let requestOptions = { method: 'POST', + url: api_url, + data: requestData, headers: { 'Content-Type': 'application/json' - }, - url: api_url, - data: requestData + } }; // Set request options for basic auth @@ -51,24 +49,23 @@ class ZabbixAPICoreService { requestOptions.headers.Authorization = options.basicAuth; } - this.backendSrv.datasourceRequest(requestOptions) - .then((response) => { - // General connection issues - if (!response.data) { - deferred.reject(response); - } + return this.datasourceRequest(requestOptions); + } + + datasourceRequest(requestOptions) { + return this.backendSrv.datasourceRequest(requestOptions) + .then(response => { + if (!response.data) { + return Promise.reject(new ZabbixAPIError({data: "General Error, no data"})); + } else if (response.data.error) { // Handle Zabbix API errors - else if (response.data.error) { - deferred.reject(response.data.error); - } + return Promise.reject(new ZabbixAPIError(response.data.error)); + } - deferred.resolve(response.data.result); - }, (error) => { - deferred.reject(error.err); - }); - - return deferred.promise; + // Success + return response.data.result; + }); } /** @@ -76,7 +73,7 @@ class ZabbixAPICoreService { * @return {string} auth token */ login(api_url, username, password, options) { - var params = { + let params = { user: username, password: password }; @@ -93,15 +90,18 @@ class ZabbixAPICoreService { } // Define zabbix API exception type -function ZabbixException(error) { - this.code = error.code; - this.errorType = error.message; - this.message = error.data; -} +export class ZabbixAPIError { + constructor(error) { + this.code = error.code; + this.name = error.data; + this.message = error.data; + this.data = error.data; + } -ZabbixException.prototype.toString = function() { - return this.errorType + ": " + this.message; -}; + toString() { + return this.name + ": " + this.message; + } +} angular .module('grafana.services') From 2e57b9b166a452552c666ba1d7439a6f390ef69f Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sat, 12 Nov 2016 18:04:29 +0300 Subject: [PATCH 50/64] Refactor: queryProcessor.service. --- src/datasource-zabbix/datasource.js | 19 +- src/datasource-zabbix/query.controller.js | 92 +++---- .../queryProcessor.service.js | 260 +++++++----------- src/panel-triggers/editor.js | 62 ++--- 4 files changed, 166 insertions(+), 267 deletions(-) diff --git a/src/datasource-zabbix/datasource.js b/src/datasource-zabbix/datasource.js index 79edb6a..fc61f52 100644 --- a/src/datasource-zabbix/datasource.js +++ b/src/datasource-zabbix/datasource.js @@ -146,12 +146,10 @@ class ZabbixAPIDatasource { } queryNumericData(target, timeFrom, timeTo, useTrends) { - // Build query in asynchronous manner - return this.queryProcessor.build(target.group.filter, - target.host.filter, - target.application.filter, - target.item.filter, - 'num') + let options = { + itemtype: 'num' + }; + return this.queryProcessor.build(target, options) .then(items => { // Add hostname for items from multiple hosts var addHostName = utils.isRegex(target.host.filter); @@ -227,11 +225,10 @@ class ZabbixAPIDatasource { } queryTextData(target, timeFrom, timeTo) { - return this.queryProcessor.build(target.group.filter, - target.host.filter, - target.application.filter, - target.item.filter, - 'text') + let options = { + itemtype: 'text' + }; + return this.queryProcessor.build(target, options) .then(items => { if (items.length) { return this.zabbixAPI.getHistory(items, timeFrom, timeTo) diff --git a/src/datasource-zabbix/query.controller.js b/src/datasource-zabbix/query.controller.js index 5536d48..d8e6623 100644 --- a/src/datasource-zabbix/query.controller.js +++ b/src/datasource-zabbix/query.controller.js @@ -20,6 +20,7 @@ export class ZabbixQueryController extends QueryCtrl { this.zabbix = this.datasource.zabbixAPI; this.cache = this.datasource.zabbixCache; + this.queryProcessor = this.datasource.queryProcessor; this.$q = $q; // Use custom format for template variables @@ -115,76 +116,47 @@ export class ZabbixQueryController extends QueryCtrl { } suggestGroups() { - var self = this; - return this.cache.getGroups().then(groups => { - self.metric.groupList = groups; + return this.queryProcessor.getAllGroups() + .then(groups => { + this.metric.groupList = groups; return groups; }); } suggestHosts() { - var self = this; - var groupFilter = this.replaceTemplateVars(this.target.group.filter); - return this.datasource.queryProcessor - .filterGroups(self.metric.groupList, groupFilter) - .then(groups => { - var groupids = _.map(groups, 'groupid'); - return self.zabbix - .getHosts(groupids) - .then(hosts => { - self.metric.hostList = hosts; - return hosts; - }); - }); + let groupFilter = this.replaceTemplateVars(this.target.group.filter); + return this.queryProcessor.getAllHosts(groupFilter) + .then(hosts => { + this.metric.hostList = hosts; + return hosts; + }); } suggestApps() { - var self = this; - var hostFilter = this.replaceTemplateVars(this.target.host.filter); - return this.datasource.queryProcessor - .filterHosts(self.metric.hostList, hostFilter) - .then(hosts => { - var hostids = _.map(hosts, 'hostid'); - return self.zabbix - .getApps(hostids) - .then(apps => { - return self.metric.appList = apps; - }); - }); + let groupFilter = this.replaceTemplateVars(this.target.group.filter); + let hostFilter = this.replaceTemplateVars(this.target.host.filter); + return this.queryProcessor.getAllApps(groupFilter, hostFilter) + .then(apps => { + this.metric.appList = apps; + return apps; + }); } - suggestItems(itemtype='num') { - var self = this; - var appFilter = this.replaceTemplateVars(this.target.application.filter); - if (appFilter) { - // Filter by applications - return this.datasource.queryProcessor - .filterApps(self.metric.appList, appFilter) - .then(apps => { - var appids = _.map(apps, 'applicationid'); - return self.zabbix - .getItems(undefined, appids, itemtype) - .then(items => { - if (!self.target.options.showDisabledItems) { - items = _.filter(items, {'status': '0'}); - } - self.metric.itemList = items; - return items; - }); - }); - } else { - // Return all items belonged to selected hosts - var hostids = _.map(self.metric.hostList, 'hostid'); - return self.zabbix - .getItems(hostids, undefined, itemtype) - .then(items => { - if (!self.target.options.showDisabledItems) { - items = _.filter(items, {'status': '0'}); - } - self.metric.itemList = items; - return items; - }); - } + suggestItems(itemtype = 'num') { + let groupFilter = this.replaceTemplateVars(this.target.group.filter); + let hostFilter = this.replaceTemplateVars(this.target.host.filter); + let appFilter = this.replaceTemplateVars(this.target.application.filter); + let options = { + itemtype: itemtype, + showDisabledItems: this.target.options.showDisabledItems + }; + + return this.queryProcessor + .getAllItems(groupFilter, hostFilter, appFilter, options) + .then(items => { + this.metric.itemList = items; + return items; + }); } isRegex(str) { diff --git a/src/datasource-zabbix/queryProcessor.service.js b/src/datasource-zabbix/queryProcessor.service.js index d151ff0..90bb6a5 100644 --- a/src/datasource-zabbix/queryProcessor.service.js +++ b/src/datasource-zabbix/queryProcessor.service.js @@ -2,197 +2,137 @@ import angular from 'angular'; import _ from 'lodash'; import * as utils from './utils'; -/** @ngInject */ -angular.module('grafana.services').factory('QueryProcessor', function($q) { +function QueryProcessorFactory() { class QueryProcessor { constructor(zabbixCacheInstance) { this.cache = zabbixCacheInstance; - this.$q = $q; + } + + initializeCache() { + if (this.cache._initialized) { + return Promise.resolve(); + } else { + return this.cache.refresh(); + } } /** - * Build query in asynchronous manner + * Build query - convert target filters to array of Zabbix items */ - build(groupFilter, hostFilter, appFilter, itemFilter, itemtype) { - var self = this; - if (this.cache._initialized) { - return this.$q.when(self.buildFromCache(groupFilter, hostFilter, appFilter, itemFilter, itemtype)); - } else { - return this.cache.refresh().then(function() { - return self.buildFromCache(groupFilter, hostFilter, appFilter, itemFilter, itemtype); - }); + build(target, options) { + function getFiltersFromTarget(target) { + let parts = ['group', 'host', 'application', 'item']; + return _.map(parts, p => target[p].filter); } + + return this.initializeCache() + .then(() => { + return this.getItems(...getFiltersFromTarget(target), options); + }); } /** * Build trigger query in asynchronous manner */ buildTriggerQuery(groupFilter, hostFilter, appFilter) { - var self = this; - if (this.cache._initialized) { - return this.$q.when(self.buildTriggerQueryFromCache(groupFilter, hostFilter, appFilter)); - } else { - return this.cache.refresh().then(function() { - return self.buildTriggerQueryFromCache(groupFilter, hostFilter, appFilter); - }); - } + return this.initializeCache() + .then(() => { + return this.buildTriggerQueryFromCache(groupFilter, hostFilter, appFilter); + }); } - filterGroups(groups, groupFilter) { - return this.$q.when( - findByFilter(groups, groupFilter) - ); - } - - /** - * Get list of host belonging to given groups. - * @return list of hosts - */ - filterHosts(hosts, hostFilter) { - return this.$q.when( - findByFilter(hosts, hostFilter) - ); - } - - filterApps(apps, appFilter) { - return this.$q.when( - findByFilter(apps, appFilter) - ); - } - - /** - * Build query - convert target filters to array of Zabbix items - */ - buildFromCache(groupFilter, hostFilter, appFilter, itemFilter, itemtype, showDisabledItems) { - return this.getItems(groupFilter, hostFilter, appFilter, itemtype, showDisabledItems) - .then(items => { - return getByFilter(items, itemFilter); - }); - } - - getGroups() { + getAllGroups() { return this.cache.getGroups(); } + getGroups(groupFilter) { + return this.getAllGroups() + .then(groups => findByFilter(groups, groupFilter)); + } + /** * Get list of host belonging to given groups. - * @return list of hosts */ - getHosts(groupFilter) { - var self = this; - return this.cache - .getGroups() - .then(groups => { - return findByFilter(groups, groupFilter); - }) - .then(groups => { - var groupids = _.map(groups, 'groupid'); - return self.cache.getHosts(groupids); - }); + getAllHosts(groupFilter) { + return this.getGroups(groupFilter) + .then(groups => { + let groupids = _.map(groups, 'groupid'); + return this.cache.getHosts(groupids); + }); + } + + getHosts(groupFilter, hostFilter) { + return this.getAllHosts(groupFilter) + .then(hosts => findByFilter(hosts, hostFilter)); } /** * Get list of applications belonging to given groups and hosts. - * @return list of applications belonging to given hosts */ - getApps(groupFilter, hostFilter) { - var self = this; - return this.getHosts(groupFilter) - .then(hosts => { - return findByFilter(hosts, hostFilter); - }) - .then(hosts => { - var hostids = _.map(hosts, 'hostid'); - return self.cache.getApps(hostids); - }); + getAllApps(groupFilter, hostFilter) { + return this.getHosts(groupFilter, hostFilter) + .then(hosts => { + let hostids = _.map(hosts, 'hostid'); + return this.cache.getApps(hostids); + }); } - getItems(groupFilter, hostFilter, appFilter, itemtype, showDisabledItems) { - var self = this; - return this.getHosts(groupFilter) - .then(hosts => { - return findByFilter(hosts, hostFilter); - }) - .then(hosts => { - var hostids = _.map(hosts, 'hostid'); - if (appFilter) { - return self.cache - .getApps(hostids) - .then(apps => { - // Use getByFilter for proper item filtering - return getByFilter(apps, appFilter); - }); - } else { - return { - appFilterEmpty: true, - hostids: hostids - }; - } - }) - .then(apps => { - if (apps.appFilterEmpty) { - return self.cache - .getItems(apps.hostids, undefined, itemtype) - .then(items => { - if (showDisabledItems) { - items = _.filter(items, {'status': '0'}); - } - return items; - }); - } else { - var appids = _.map(apps, 'applicationid'); - return self.cache - .getItems(undefined, appids, itemtype) - .then(items => { - if (showDisabledItems) { - items = _.filter(items, {'status': '0'}); - } - return items; - }); - } - }); + getApps(groupFilter, hostFilter, appFilter) { + return this.getHosts(groupFilter, hostFilter) + .then(hosts => { + let hostids = _.map(hosts, 'hostid'); + if (appFilter) { + return this.cache.getApps(hostids) + .then(apps => filterByQuery(apps, appFilter)); + } else { + return { + appFilterEmpty: true, + hostids: hostids + }; + } + }); + } + + getAllItems(groupFilter, hostFilter, appFilter, options = {}) { + return this.getApps(groupFilter, hostFilter, appFilter) + .then(apps => { + if (apps.appFilterEmpty) { + return this.cache.getItems(apps.hostids, undefined, options.itemtype); + } else { + let appids = _.map(apps, 'applicationid'); + return this.cache.getItems(undefined, appids, options.itemtype); + } + }) + .then(items => { + if (!options.showDisabledItems) { + items = _.filter(items, {'status': '0'}); + } + return items; + }); + } + + getItems(groupFilter, hostFilter, appFilter, itemFilter, options = {}) { + return this.getAllItems(groupFilter, hostFilter, appFilter, options) + .then(items => filterByQuery(items, itemFilter)); } /** * Build query - convert target filters to array of Zabbix items */ buildTriggerQueryFromCache(groupFilter, hostFilter, appFilter) { - var promises = [ - this.cache.getGroups().then(function(groups) { - return _.filter(groups, function(group) { - if (utils.isRegex(groupFilter)) { - return utils.buildRegex(groupFilter).test(group.name); - } else { - return group.name === groupFilter; - } - }); - }), - this.getHosts(groupFilter).then(function(hosts) { - return _.filter(hosts, function(host) { - if (utils.isRegex(hostFilter)) { - return utils.buildRegex(hostFilter).test(host.name); - } else { - return host.name === hostFilter; - } - }); - }), - this.getApps(groupFilter, hostFilter).then(function(apps) { - return _.filter(apps, function(app) { - if (utils.isRegex(appFilter)) { - return utils.buildRegex(appFilter).test(app.name); - } else { - return app.name === appFilter; - } - }); - }) + let promises = [ + this.getGroups(groupFilter), + this.getHosts(groupFilter, hostFilter), + this.getApps(groupFilter, hostFilter, appFilter) ]; - return this.$q.all(promises).then(function(results) { - var filteredGroups = results[0]; - var filteredHosts = results[1]; - var filteredApps = results[2]; - var query = {}; + return Promise.all(promises) + .then(results => { + let filteredGroups = results[0]; + let filteredHosts = results[1]; + let filteredApps = results[2]; + let query = {}; if (appFilter) { query.applicationids = _.flatten(_.map(filteredApps, 'applicationid')); @@ -278,7 +218,11 @@ angular.module('grafana.services').factory('QueryProcessor', function($q) { } return QueryProcessor; -}); +} + +angular + .module('grafana.services') + .factory('QueryProcessor', QueryProcessorFactory); /** * Find group, host, app or item by given name. @@ -312,7 +256,7 @@ function filterByName(list, name) { } } -function findByRegex(list, regex) { +function filterByRegex(list, regex) { var filterPattern = utils.buildRegex(regex); return _.filter(list, function (zbx_obj) { return filterPattern.test(zbx_obj.name); @@ -321,15 +265,15 @@ function findByRegex(list, regex) { function findByFilter(list, filter) { if (utils.isRegex(filter)) { - return findByRegex(list, filter); + return filterByRegex(list, filter); } else { return findByName(list, filter); } } -function getByFilter(list, filter) { +function filterByQuery(list, filter) { if (utils.isRegex(filter)) { - return findByRegex(list, filter); + return filterByRegex(list, filter); } else { return filterByName(list, filter); } diff --git a/src/panel-triggers/editor.js b/src/panel-triggers/editor.js index 0c48fec..6c6377a 100644 --- a/src/panel-triggers/editor.js +++ b/src/panel-triggers/editor.js @@ -62,8 +62,6 @@ class TriggerPanelEditorCtrl { }; _.defaults(this, scopeDefaults); - var self = this; - // Get zabbix data sources var datasources = _.filter(this.datasourceSrv.getMetricSources(), datasource => { return datasource.meta.id === 'alexanderzobnin-zabbix-datasource'; @@ -75,10 +73,12 @@ class TriggerPanelEditorCtrl { this.panel.datasource = this.datasources[0]; } // Load datasource - this.datasourceSrv.get(this.panel.datasource).then(function (datasource) { - self.datasource = datasource; - self.initFilters(); - self.panelCtrl.refresh(); + this.datasourceSrv.get(this.panel.datasource) + .then(datasource => { + this.datasource = datasource; + this.queryProcessor = datasource.queryProcessor; + this.initFilters(); + this.panelCtrl.refresh(); }); } @@ -91,44 +91,30 @@ class TriggerPanelEditorCtrl { } suggestGroups() { - var self = this; - return this.datasource.zabbixCache - .getGroups() - .then(groups => { - self.metric.groupList = groups; - return groups; - }); + return this.queryProcessor.getAllGroups() + .then(groups => { + this.metric.groupList = groups; + return groups; + }); } suggestHosts() { - var self = this; - var groupFilter = this.datasource.replaceTemplateVars(this.panel.triggers.group.filter); - return this.datasource.queryProcessor - .filterGroups(self.metric.groupList, groupFilter) - .then(groups => { - var groupids = _.map(groups, 'groupid'); - return self.datasource.zabbixAPI - .getHosts(groupids) - .then(hosts => { - self.metric.hostList = hosts; - return hosts; - }); - }); + let groupFilter = this.datasource.replaceTemplateVars(this.panel.triggers.group.filter); + return this.queryProcessor.getAllHosts(groupFilter) + .then(hosts => { + this.metric.hostList = hosts; + return hosts; + }); } suggestApps() { - var self = this; - var hostFilter = this.datasource.replaceTemplateVars(this.panel.triggers.host.filter); - return this.datasource.queryProcessor - .filterHosts(self.metric.hostList, hostFilter) - .then(hosts => { - var hostids = _.map(hosts, 'hostid'); - return self.datasource.zabbixAPI - .getApps(hostids) - .then(apps => { - return self.metric.appList = apps; - }); - }); + let groupFilter = this.datasource.replaceTemplateVars(this.panel.triggers.group.filter); + let hostFilter = this.datasource.replaceTemplateVars(this.panel.triggers.host.filter); + return this.queryProcessor.getAllApps(groupFilter, hostFilter) + .then(apps => { + this.metric.appList = apps; + return apps; + }); } onVariableChange() { From 88ddcd0c42f7f45be5b74f69daf1dbb798a55fa0 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sat, 12 Nov 2016 21:26:34 +0300 Subject: [PATCH 51/64] Refactor: move zabbix api response handle to separate module. --- src/datasource-zabbix/datasource.js | 16 +-- .../queryProcessor.service.js | 98 ---------------- src/datasource-zabbix/responseHandler.js | 106 ++++++++++++++++++ 3 files changed, 114 insertions(+), 106 deletions(-) create mode 100644 src/datasource-zabbix/responseHandler.js diff --git a/src/datasource-zabbix/datasource.js b/src/datasource-zabbix/datasource.js index fc61f52..71c2e6c 100644 --- a/src/datasource-zabbix/datasource.js +++ b/src/datasource-zabbix/datasource.js @@ -5,6 +5,7 @@ import * as utils from './utils'; import * as migrations from './migrations'; import * as metricFunctions from './metricFunctions'; import DataProcessor from './DataProcessor'; +import responseHandler from './responseHandler'; import './zabbixAPI.service.js'; import './zabbixCache.service.js'; import './queryProcessor.service.js'; @@ -120,11 +121,10 @@ class ZabbixAPIDatasource { } return this.zabbixAPI - .getSLA(target.itservice.serviceid, timeFrom, timeTo) - .then(slaObject => { - return this.queryProcessor - .handleSLAResponse(target.itservice, target.slaProperty, slaObject); - }); + .getSLA(target.itservice.serviceid, timeFrom, timeTo) + .then(slaObject => { + return responseHandler.handleSLAResponse(target.itservice, target.slaProperty, slaObject); + }); } }); @@ -168,7 +168,7 @@ class ZabbixAPIDatasource { getHistory = this.zabbixAPI .getTrend(items, timeFrom, timeTo) .then(history => { - return this.queryProcessor.handleTrends(history, items, addHostName, valueType); + return responseHandler.handleTrends(history, items, addHostName, valueType); }); } @@ -177,7 +177,7 @@ class ZabbixAPIDatasource { getHistory = this.zabbixCache .getHistory(items, timeFrom, timeTo) .then(history => { - return this.queryProcessor.handleHistory(history, items, addHostName); + return responseHandler.handleHistory(history, items, addHostName); }); } @@ -233,7 +233,7 @@ class ZabbixAPIDatasource { if (items.length) { return this.zabbixAPI.getHistory(items, timeFrom, timeTo) .then(history => { - return this.queryProcessor.convertHistory(history, items, false, (point) => { + return responseHandler.convertHistory(history, items, false, (point) => { let value = point.value; // Regex-based extractor diff --git a/src/datasource-zabbix/queryProcessor.service.js b/src/datasource-zabbix/queryProcessor.service.js index 90bb6a5..0951cad 100644 --- a/src/datasource-zabbix/queryProcessor.service.js +++ b/src/datasource-zabbix/queryProcessor.service.js @@ -147,74 +147,6 @@ function QueryProcessorFactory() { return query; }); } - - /** - * Convert Zabbix API history.get response to Grafana format - * - * @return {Array} Array of timeseries in Grafana format - * { - * target: "Metric name", - * datapoints: [[, ], ...] - * } - */ - convertHistory(history, items, addHostName, convertPointCallback) { - /** - * Response should be in the format: - * data: [ - * { - * target: "Metric name", - * datapoints: [[, ], ...] - * }, ... - * ] - */ - - // Group history by itemid - var grouped_history = _.groupBy(history, 'itemid'); - var hosts = _.uniq(_.flatten(_.map(items, 'hosts')),'hostid'); //uniq is needed to deduplicate - - return _.map(grouped_history, function(hist, itemid) { - var item = _.find(items, {'itemid': itemid}); - var alias = item.name; - if (_.keys(hosts).length > 1 && addHostName) { //only when actual multi hosts selected - var host = _.find(hosts, {'hostid': item.hostid}); - alias = host.name + ": " + alias; - } - return { - target: alias, - datapoints: _.map(hist, convertPointCallback) - }; - }); - } - - handleHistory(history, items, addHostName) { - return this.convertHistory(history, items, addHostName, convertHistoryPoint); - } - - handleTrends(history, items, addHostName, valueType) { - var convertPointCallback = _.partial(convertTrendPoint, valueType); - return this.convertHistory(history, items, addHostName, convertPointCallback); - } - - handleSLAResponse(itservice, slaProperty, slaObject) { - var targetSLA = slaObject[itservice.serviceid].sla[0]; - if (slaProperty.property === 'status') { - var targetStatus = parseInt(slaObject[itservice.serviceid].status); - return { - target: itservice.name + ' ' + slaProperty.name, - datapoints: [ - [targetStatus, targetSLA.to * 1000] - ] - }; - } else { - return { - target: itservice.name + ' ' + slaProperty.name, - datapoints: [ - [targetSLA[slaProperty.property], targetSLA.from * 1000], - [targetSLA[slaProperty.property], targetSLA.to * 1000] - ] - }; - } - } } return QueryProcessor; @@ -278,33 +210,3 @@ function filterByQuery(list, filter) { return filterByName(list, filter); } } - -function convertHistoryPoint(point) { - // Value must be a number for properly work - return [ - Number(point.value), - point.clock * 1000 - ]; -} - -function convertTrendPoint(valueType, point) { - var value; - switch (valueType) { - case "min": - value = point.value_min; - break; - case "max": - value = point.value_max; - break; - case "avg": - value = point.value_avg; - break; - default: - value = point.value_avg; - } - - return [ - Number(value), - point.clock * 1000 - ]; -} diff --git a/src/datasource-zabbix/responseHandler.js b/src/datasource-zabbix/responseHandler.js new file mode 100644 index 0000000..0f6a8e2 --- /dev/null +++ b/src/datasource-zabbix/responseHandler.js @@ -0,0 +1,106 @@ +import _ from 'lodash'; + +/** + * Convert Zabbix API history.get response to Grafana format + * + * @return {Array} Array of timeseries in Grafana format + * { + * target: "Metric name", + * datapoints: [[, ], ...] + * } + */ +function convertHistory(history, items, addHostName, convertPointCallback) { + /** + * Response should be in the format: + * data: [ + * { + * target: "Metric name", + * datapoints: [[, ], ...] + * }, ... + * ] + */ + + // Group history by itemid + var grouped_history = _.groupBy(history, 'itemid'); + var hosts = _.uniq(_.flatten(_.map(items, 'hosts')),'hostid'); //uniq is needed to deduplicate + + return _.map(grouped_history, function(hist, itemid) { + var item = _.find(items, {'itemid': itemid}); + var alias = item.name; + if (_.keys(hosts).length > 1 && addHostName) { //only when actual multi hosts selected + var host = _.find(hosts, {'hostid': item.hostid}); + alias = host.name + ": " + alias; + } + return { + target: alias, + datapoints: _.map(hist, convertPointCallback) + }; + }); +} + +function handleHistory(history, items, addHostName) { + return convertHistory(history, items, addHostName, convertHistoryPoint); +} + +function handleTrends(history, items, addHostName, valueType) { + var convertPointCallback = _.partial(convertTrendPoint, valueType); + return convertHistory(history, items, addHostName, convertPointCallback); +} + +function handleSLAResponse(itservice, slaProperty, slaObject) { + var targetSLA = slaObject[itservice.serviceid].sla[0]; + if (slaProperty.property === 'status') { + var targetStatus = parseInt(slaObject[itservice.serviceid].status); + return { + target: itservice.name + ' ' + slaProperty.name, + datapoints: [ + [targetStatus, targetSLA.to * 1000] + ] + }; + } else { + return { + target: itservice.name + ' ' + slaProperty.name, + datapoints: [ + [targetSLA[slaProperty.property], targetSLA.from * 1000], + [targetSLA[slaProperty.property], targetSLA.to * 1000] + ] + }; + } +} + +function convertHistoryPoint(point) { + // Value must be a number for properly work + return [ + Number(point.value), + point.clock * 1000 + ]; +} + +function convertTrendPoint(valueType, point) { + var value; + switch (valueType) { + case "min": + value = point.value_min; + break; + case "max": + value = point.value_max; + break; + case "avg": + value = point.value_avg; + break; + default: + value = point.value_avg; + } + + return [ + Number(value), + point.clock * 1000 + ]; +} + +export default { + handleHistory: handleHistory, + convertHistory: convertHistory, + handleTrends: handleTrends, + handleSLAResponse: handleSLAResponse +}; From 032927cf8f54a4458d8de94faf1d3fdbf5b8f7c8 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sun, 13 Nov 2016 13:16:07 +0300 Subject: [PATCH 52/64] Refactor: queryProcessor renamend to queryBuilder. --- src/datasource-zabbix/datasource.js | 18 +++++++++--------- src/datasource-zabbix/query.controller.js | 10 +++++----- ...eryProcessor.service.js => queryBuilder.js} | 8 ++++---- .../specs/datasource_specs.js | 14 +++++++------- src/panel-triggers/editor.js | 8 ++++---- src/panel-triggers/module.js | 4 ++-- 6 files changed, 31 insertions(+), 31 deletions(-) rename src/datasource-zabbix/{queryProcessor.service.js => queryBuilder.js} (97%) diff --git a/src/datasource-zabbix/datasource.js b/src/datasource-zabbix/datasource.js index 71c2e6c..bd3c2b2 100644 --- a/src/datasource-zabbix/datasource.js +++ b/src/datasource-zabbix/datasource.js @@ -8,13 +8,13 @@ import DataProcessor from './DataProcessor'; import responseHandler from './responseHandler'; import './zabbixAPI.service.js'; import './zabbixCache.service.js'; -import './queryProcessor.service.js'; +import './queryBuilder.js'; import {ZabbixAPIError} from './zabbixAPICore.service.js'; class ZabbixAPIDatasource { /** @ngInject */ - constructor(instanceSettings, $q, templateSrv, alertSrv, zabbixAPIService, ZabbixCachingProxy, QueryProcessor) { + constructor(instanceSettings, $q, templateSrv, alertSrv, zabbixAPIService, ZabbixCachingProxy, QueryBuilder) { // General data source settings this.name = instanceSettings.name; @@ -42,7 +42,7 @@ class ZabbixAPIDatasource { this.zabbixCache = new ZabbixCachingProxy(this.zabbixAPI, this.cacheTTL); // Initialize query builder - this.queryProcessor = new QueryProcessor(this.zabbixCache); + this.queryBuilder = new QueryBuilder(this.zabbixCache); // Dependencies this.q = $q; @@ -149,7 +149,7 @@ class ZabbixAPIDatasource { let options = { itemtype: 'num' }; - return this.queryProcessor.build(target, options) + return this.queryBuilder.build(target, options) .then(items => { // Add hostname for items from multiple hosts var addHostName = utils.isRegex(target.host.filter); @@ -228,7 +228,7 @@ class ZabbixAPIDatasource { let options = { itemtype: 'text' }; - return this.queryProcessor.build(target, options) + return this.queryBuilder.build(target, options) .then(items => { if (items.length) { return this.zabbixAPI.getHistory(items, timeFrom, timeTo) @@ -318,13 +318,13 @@ class ZabbixAPIDatasource { if (template.app === '/.*/') { template.app = ''; } - result = this.queryProcessor.getItems(template.group, template.host, template.app); + result = this.queryBuilder.getItems(template.group, template.host, template.app); } else if (parts.length === 3) { // Get applications - result = this.queryProcessor.getApps(template.group, template.host); + result = this.queryBuilder.getApps(template.group, template.host); } else if (parts.length === 2) { // Get hosts - result = this.queryProcessor.getHosts(template.group); + result = this.queryBuilder.getHosts(template.group); } else if (parts.length === 1) { // Get groups result = this.zabbixCache.getGroups(template.group); @@ -350,7 +350,7 @@ class ZabbixAPIDatasource { // Show all triggers var showTriggers = [0, 1]; - var buildQuery = this.queryProcessor + var buildQuery = this.queryBuilder .buildTriggerQuery(this.replaceTemplateVars(annotation.group, {}), this.replaceTemplateVars(annotation.host, {}), this.replaceTemplateVars(annotation.application, {})); diff --git a/src/datasource-zabbix/query.controller.js b/src/datasource-zabbix/query.controller.js index d8e6623..19acdca 100644 --- a/src/datasource-zabbix/query.controller.js +++ b/src/datasource-zabbix/query.controller.js @@ -20,7 +20,7 @@ export class ZabbixQueryController extends QueryCtrl { this.zabbix = this.datasource.zabbixAPI; this.cache = this.datasource.zabbixCache; - this.queryProcessor = this.datasource.queryProcessor; + this.queryBuilder = this.datasource.queryBuilder; this.$q = $q; // Use custom format for template variables @@ -116,7 +116,7 @@ export class ZabbixQueryController extends QueryCtrl { } suggestGroups() { - return this.queryProcessor.getAllGroups() + return this.queryBuilder.getAllGroups() .then(groups => { this.metric.groupList = groups; return groups; @@ -125,7 +125,7 @@ export class ZabbixQueryController extends QueryCtrl { suggestHosts() { let groupFilter = this.replaceTemplateVars(this.target.group.filter); - return this.queryProcessor.getAllHosts(groupFilter) + return this.queryBuilder.getAllHosts(groupFilter) .then(hosts => { this.metric.hostList = hosts; return hosts; @@ -135,7 +135,7 @@ export class ZabbixQueryController extends QueryCtrl { suggestApps() { let groupFilter = this.replaceTemplateVars(this.target.group.filter); let hostFilter = this.replaceTemplateVars(this.target.host.filter); - return this.queryProcessor.getAllApps(groupFilter, hostFilter) + return this.queryBuilder.getAllApps(groupFilter, hostFilter) .then(apps => { this.metric.appList = apps; return apps; @@ -151,7 +151,7 @@ export class ZabbixQueryController extends QueryCtrl { showDisabledItems: this.target.options.showDisabledItems }; - return this.queryProcessor + return this.queryBuilder .getAllItems(groupFilter, hostFilter, appFilter, options) .then(items => { this.metric.itemList = items; diff --git a/src/datasource-zabbix/queryProcessor.service.js b/src/datasource-zabbix/queryBuilder.js similarity index 97% rename from src/datasource-zabbix/queryProcessor.service.js rename to src/datasource-zabbix/queryBuilder.js index 0951cad..8188e59 100644 --- a/src/datasource-zabbix/queryProcessor.service.js +++ b/src/datasource-zabbix/queryBuilder.js @@ -2,9 +2,9 @@ import angular from 'angular'; import _ from 'lodash'; import * as utils from './utils'; -function QueryProcessorFactory() { +function QueryBuilderFactory() { - class QueryProcessor { + class QueryBuilder { constructor(zabbixCacheInstance) { this.cache = zabbixCacheInstance; } @@ -149,12 +149,12 @@ function QueryProcessorFactory() { } } - return QueryProcessor; + return QueryBuilder; } angular .module('grafana.services') - .factory('QueryProcessor', QueryProcessorFactory); + .factory('QueryBuilder', QueryBuilderFactory); /** * Find group, host, app or item by given name. diff --git a/src/datasource-zabbix/specs/datasource_specs.js b/src/datasource-zabbix/specs/datasource_specs.js index 923d99a..7919856 100644 --- a/src/datasource-zabbix/specs/datasource_specs.js +++ b/src/datasource-zabbix/specs/datasource_specs.js @@ -22,10 +22,10 @@ describe('ZabbixDatasource', () => { ctx.alertSrv = {}; ctx.zabbixAPIService = () => {}; ctx.ZabbixCachingProxy = () => {}; - ctx.QueryProcessor = () => {}; + ctx.queryBuilder = () => {}; ctx.ds = new Datasource(ctx.instanceSettings, ctx.$q, ctx.templateSrv, ctx.alertSrv, - ctx.zabbixAPIService, ctx.ZabbixCachingProxy, ctx.QueryProcessor); + ctx.zabbixAPIService, ctx.ZabbixCachingProxy, ctx.queryBuilder); }); describe('When querying data', () => { @@ -147,7 +147,7 @@ describe('ZabbixDatasource', () => { ctx.ds.zabbixCache = { getGroups: () => Q.when([]) }; - ctx.ds.queryProcessor = { + ctx.ds.queryBuilder = { getGroups: () => Q.when([]), getHosts: () => Q.when([]), getApps: () => Q.when([]), @@ -180,7 +180,7 @@ describe('ZabbixDatasource', () => { {query: 'Back*.', expect: 'Back*'} ]; - let getHosts = sinon.spy(ctx.ds.queryProcessor, 'getHosts'); + let getHosts = sinon.spy(ctx.ds.queryBuilder, 'getHosts'); for (const test of tests) { ctx.ds.metricFindQuery(test.query); expect(getHosts).to.have.been.calledWith(test.expect); @@ -197,7 +197,7 @@ describe('ZabbixDatasource', () => { {query: 'Back*.*.', expect: ['Back*', '/.*/']} ]; - let getApps = sinon.spy(ctx.ds.queryProcessor, 'getApps'); + let getApps = sinon.spy(ctx.ds.queryBuilder, 'getApps'); for (const test of tests) { ctx.ds.metricFindQuery(test.query); expect(getApps).to.have.been.calledWith(test.expect[0], test.expect[1]); @@ -214,7 +214,7 @@ describe('ZabbixDatasource', () => { {query: 'Back*.*.cpu.*', expect: ['Back*', '/.*/', 'cpu']} ]; - let getItems = sinon.spy(ctx.ds.queryProcessor, 'getItems'); + let getItems = sinon.spy(ctx.ds.queryBuilder, 'getItems'); for (const test of tests) { ctx.ds.metricFindQuery(test.query); expect(getItems) @@ -227,7 +227,7 @@ describe('ZabbixDatasource', () => { it('should invoke method with proper arguments', (done) => { let query = '*.*'; - let getHosts = sinon.spy(ctx.ds.queryProcessor, 'getHosts'); + let getHosts = sinon.spy(ctx.ds.queryBuilder, 'getHosts'); ctx.ds.metricFindQuery(query); expect(getHosts).to.have.been.calledWith('/.*/'); done(); diff --git a/src/panel-triggers/editor.js b/src/panel-triggers/editor.js index 6c6377a..e9a3088 100644 --- a/src/panel-triggers/editor.js +++ b/src/panel-triggers/editor.js @@ -76,7 +76,7 @@ class TriggerPanelEditorCtrl { this.datasourceSrv.get(this.panel.datasource) .then(datasource => { this.datasource = datasource; - this.queryProcessor = datasource.queryProcessor; + this.queryBuilder = datasource.queryBuilder; this.initFilters(); this.panelCtrl.refresh(); }); @@ -91,7 +91,7 @@ class TriggerPanelEditorCtrl { } suggestGroups() { - return this.queryProcessor.getAllGroups() + return this.queryBuilder.getAllGroups() .then(groups => { this.metric.groupList = groups; return groups; @@ -100,7 +100,7 @@ class TriggerPanelEditorCtrl { suggestHosts() { let groupFilter = this.datasource.replaceTemplateVars(this.panel.triggers.group.filter); - return this.queryProcessor.getAllHosts(groupFilter) + return this.queryBuilder.getAllHosts(groupFilter) .then(hosts => { this.metric.hostList = hosts; return hosts; @@ -110,7 +110,7 @@ class TriggerPanelEditorCtrl { suggestApps() { let groupFilter = this.datasource.replaceTemplateVars(this.panel.triggers.group.filter); let hostFilter = this.datasource.replaceTemplateVars(this.panel.triggers.host.filter); - return this.queryProcessor.getAllApps(groupFilter, hostFilter) + return this.queryBuilder.getAllApps(groupFilter, hostFilter) .then(apps => { this.metric.appList = apps; return apps; diff --git a/src/panel-triggers/module.js b/src/panel-triggers/module.js index ce412f5..c583b7c 100644 --- a/src/panel-triggers/module.js +++ b/src/panel-triggers/module.js @@ -108,7 +108,7 @@ class TriggerPanelCtrl extends MetricsPanelCtrl { // Load datasource return this.datasourceSrv.get(this.panel.datasource).then(datasource => { var zabbix = datasource.zabbixAPI; - var queryProcessor = datasource.queryProcessor; + var queryBuilder = datasource.queryBuilder; var showEvents = self.panel.showEvents.value; var triggerFilter = self.panel.triggers; @@ -117,7 +117,7 @@ class TriggerPanelCtrl extends MetricsPanelCtrl { var hostFilter = datasource.replaceTemplateVars(triggerFilter.host.filter); var appFilter = datasource.replaceTemplateVars(triggerFilter.application.filter); - var buildQuery = queryProcessor.buildTriggerQuery(groupFilter, hostFilter, appFilter); + var buildQuery = queryBuilder.buildTriggerQuery(groupFilter, hostFilter, appFilter); return buildQuery.then(query => { return zabbix.getTriggers(query.groupids, query.hostids, From 775a422c84c70ae939db03ba3c537305a6e2affd Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sun, 13 Nov 2016 15:00:58 +0300 Subject: [PATCH 53/64] Refactor: zabbixCache. --- src/datasource-zabbix/zabbixCache.service.js | 180 +++++++++---------- 1 file changed, 86 insertions(+), 94 deletions(-) diff --git a/src/datasource-zabbix/zabbixCache.service.js b/src/datasource-zabbix/zabbixCache.service.js index e2a9a00..55131c3 100644 --- a/src/datasource-zabbix/zabbixCache.service.js +++ b/src/datasource-zabbix/zabbixCache.service.js @@ -5,7 +5,7 @@ import _ from 'lodash'; // Each datasource instance must initialize its own cache. /** @ngInject */ -angular.module('grafana.services').factory('ZabbixCachingProxy', function($q, $interval) { +function ZabbixCachingProxyFactory($q, $interval) { class ZabbixCachingProxy { constructor(zabbixAPI, ttl) { @@ -37,79 +37,76 @@ angular.module('grafana.services').factory('ZabbixCachingProxy', function($q, $i $interval(_.bind(this.refresh, this), this.ttl); // Don't run duplicated history requests - this.getHistory = callHistoryOnce(_.bind(this.zabbixAPI.getHistory, this.zabbixAPI), - this.historyPromises); + this.getHistory = callAPIRequestOnce(_.bind(this.zabbixAPI.getHistory, this.zabbixAPI), + this.historyPromises, getHistoryRequestHash); // Don't run duplicated requests this.groupPromises = {}; this.getGroupsOnce = callAPIRequestOnce(_.bind(this.zabbixAPI.getGroups, this.zabbixAPI), - this.groupPromises); + this.groupPromises, getAPIRequestHash); this.hostPromises = {}; this.getHostsOnce = callAPIRequestOnce(_.bind(this.zabbixAPI.getHosts, this.zabbixAPI), - this.hostPromises); + this.hostPromises, getAPIRequestHash); this.appPromises = {}; this.getAppsOnce = callAPIRequestOnce(_.bind(this.zabbixAPI.getApps, this.zabbixAPI), - this.appPromises); + this.appPromises, getAPIRequestHash); this.itemPromises = {}; this.getItemsOnce = callAPIRequestOnce(_.bind(this.zabbixAPI.getItems, this.zabbixAPI), - this.itemPromises); + this.itemPromises, getAPIRequestHash); } _refresh() { - var self = this; - var promises = [ + let promises = [ this.zabbixAPI.getGroups() ]; - return this.$q.all(promises).then(function(results) { + return Promise.all(promises) + .then(results => { if (results.length) { - self._groups = results[0]; + this._groups = results[0]; } - self._initialized = true; + this._initialized = true; }); } getGroups() { - var self = this; if (this._groups) { - return this.$q.when(self._groups); + return Promise.resolve(this._groups); } else { return this.getGroupsOnce() - .then(groups => { - self._groups = groups; - return self._groups; - }); + .then(groups => { + this._groups = groups; + return groups; + }); } } getHosts(groupids) { - //var self = this; return this.getHostsOnce(groupids) - .then(hosts => { - // iss #196 - disable caching due performance issues - //self._hosts = _.union(self._hosts, hosts); - return hosts; - }); + .then(hosts => { + // iss #196 - disable caching due performance issues + //this._hosts = _.union(this._hosts, hosts); + return hosts; + }); } getApps(hostids) { return this.getAppsOnce(hostids) - .then(apps => { - return apps; - }); + .then(apps => { + return apps; + }); } getItems(hostids, appids, itemtype) { - //var self = this; return this.getItemsOnce(hostids, appids, itemtype) - .then(items => { - // iss #196 - disable caching due performance issues - //self._items = _.union(self._items, items); - return items; - }); + .then(items => { + // iss #196 - disable caching due performance issues + //this._items = _.union(this._items, items); + return items; + }); } getHistoryFromCache(items, time_from, time_till) { @@ -156,61 +153,51 @@ angular.module('grafana.services').factory('ZabbixCachingProxy', function($q, $i } } - function callAPIRequestOnce(func, promiseKeeper) { - return function() { - var hash = getAPIRequestHash(arguments); - var deferred = $q.defer(); - if (!promiseKeeper[hash]) { - promiseKeeper[hash] = deferred.promise; - func.apply(this, arguments).then(function(result) { - deferred.resolve(result); - promiseKeeper[hash] = null; - }); - } else { - return promiseKeeper[hash]; - } - return deferred.promise; - }; - } - - function callHistoryOnce(func, promiseKeeper) { - return function() { - var itemids = _.map(arguments[0], 'itemid'); - var stamp = itemids.join() + arguments[1] + arguments[2]; - var hash = stamp.getHash(); - - var deferred = $q.defer(); - if (!promiseKeeper[hash]) { - promiseKeeper[hash] = deferred.promise; - func.apply(this, arguments).then(function(result) { - deferred.resolve(result); - promiseKeeper[hash] = null; - }); - } else { - return promiseKeeper[hash]; - } - return deferred.promise; - }; - } - - function callOnce(func, promiseKeeper) { - return function() { - var deferred = $q.defer(); - if (!promiseKeeper) { - promiseKeeper = deferred.promise; - func.apply(this, arguments).then(function(result) { - deferred.resolve(result); - promiseKeeper = null; - }); - } else { - return promiseKeeper; - } - return deferred.promise; - }; - } - return ZabbixCachingProxy; -}); +} + +angular + .module('grafana.services') + .factory('ZabbixCachingProxy', ZabbixCachingProxyFactory); + +/** + * Wrap function to prevent multiple calls + * when waiting for result. + */ +function callOnce(func, promiseKeeper) { + return function() { + if (!promiseKeeper) { + promiseKeeper = Promise.resolve( + func.apply(this, arguments) + .then(result => { + promiseKeeper = null; + return result; + }) + ); + } + return promiseKeeper; + }; +} + +/** + * Wrap zabbix API request to prevent multiple calls + * with same params when waiting for result. + */ +function callAPIRequestOnce(func, promiseKeeper, argsHashFunc) { + return function() { + var hash = argsHashFunc(arguments); + if (!promiseKeeper[hash]) { + promiseKeeper[hash] = Promise.resolve( + func.apply(this, arguments) + .then(result => { + promiseKeeper[hash] = null; + return result; + }) + ); + } + return promiseKeeper[hash]; + }; +} function getAPIRequestHash(args) { var requestStamp = _.map(args, arg => { @@ -223,15 +210,20 @@ function getAPIRequestHash(args) { return requestStamp.getHash(); } +function getHistoryRequestHash(args) { + let itemids = _.map(args[0], 'itemid'); + let stamp = itemids.join() + args[1] + args[2]; + return stamp.getHash(); +} + String.prototype.getHash = function() { var hash = 0, i, chr, len; - if (this.length === 0) { - return hash; - } - for (i = 0, len = this.length; i < len; i++) { - chr = this.charCodeAt(i); - hash = ((hash << 5) - hash) + chr; - hash |= 0; // Convert to 32bit integer + if (this.length !== 0) { + for (i = 0, len = this.length; i < len; i++) { + chr = this.charCodeAt(i); + hash = ((hash << 5) - hash) + chr; + hash |= 0; // Convert to 32bit integer + } } return hash; }; From ce8d9f4be8687550363c2956236e463baaf20c45 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sun, 13 Nov 2016 15:55:23 +0300 Subject: [PATCH 54/64] Refactor: replace angular $q by native Promise. --- src/datasource-zabbix/datasource.js | 15 +++++++-------- src/datasource-zabbix/query.controller.js | 17 ++++++++--------- .../specs/datasource_specs.js | 3 +-- src/datasource-zabbix/zabbixAPI.service.js | 3 +-- src/datasource-zabbix/zabbixAPICore.service.js | 3 +-- src/datasource-zabbix/zabbixCache.service.js | 18 +++++++----------- src/panel-triggers/editor.js | 13 ++++++------- src/panel-triggers/module.js | 2 +- 8 files changed, 32 insertions(+), 42 deletions(-) diff --git a/src/datasource-zabbix/datasource.js b/src/datasource-zabbix/datasource.js index bd3c2b2..0e4a419 100644 --- a/src/datasource-zabbix/datasource.js +++ b/src/datasource-zabbix/datasource.js @@ -14,7 +14,7 @@ import {ZabbixAPIError} from './zabbixAPICore.service.js'; class ZabbixAPIDatasource { /** @ngInject */ - constructor(instanceSettings, $q, templateSrv, alertSrv, zabbixAPIService, ZabbixCachingProxy, QueryBuilder) { + constructor(instanceSettings, templateSrv, alertSrv, zabbixAPIService, ZabbixCachingProxy, QueryBuilder) { // General data source settings this.name = instanceSettings.name; @@ -45,7 +45,6 @@ class ZabbixAPIDatasource { this.queryBuilder = new QueryBuilder(this.zabbixCache); // Dependencies - this.q = $q; this.templateSrv = templateSrv; this.alertSrv = alertSrv; @@ -129,7 +128,7 @@ class ZabbixAPIDatasource { }); // Data for panel (all targets) - return this.q.all(_.flatten(promises)) + return Promise.all(_.flatten(promises)) .then(_.flatten) .then(timeseries_data => { @@ -245,7 +244,7 @@ class ZabbixAPIDatasource { }); }); } else { - return this.q.when([]); + return Promise.resolve([]); } }); } @@ -318,18 +317,18 @@ class ZabbixAPIDatasource { if (template.app === '/.*/') { template.app = ''; } - result = this.queryBuilder.getItems(template.group, template.host, template.app); + result = this.queryBuilder.getItems(template.group, template.host, template.app, template.item); } else if (parts.length === 3) { // Get applications - result = this.queryBuilder.getApps(template.group, template.host); + result = this.queryBuilder.getApps(template.group, template.host, template.app); } else if (parts.length === 2) { // Get hosts - result = this.queryBuilder.getHosts(template.group); + result = this.queryBuilder.getHosts(template.group, template.host); } else if (parts.length === 1) { // Get groups result = this.zabbixCache.getGroups(template.group); } else { - result = this.q.when([]); + result = Promise.resolve([]); } return result.then(metrics => { diff --git a/src/datasource-zabbix/query.controller.js b/src/datasource-zabbix/query.controller.js index 19acdca..3e5bdd4 100644 --- a/src/datasource-zabbix/query.controller.js +++ b/src/datasource-zabbix/query.controller.js @@ -13,7 +13,7 @@ import './css/query-editor.css!'; export class ZabbixQueryController extends QueryCtrl { // ZabbixQueryCtrl constructor - constructor($scope, $injector, $rootScope, $sce, $q, templateSrv) { + constructor($scope, $injector, $rootScope, $sce, templateSrv) { // Call superclass constructor super($scope, $injector); @@ -21,7 +21,6 @@ export class ZabbixQueryController extends QueryCtrl { this.zabbix = this.datasource.zabbixAPI; this.cache = this.datasource.zabbixCache; this.queryBuilder = this.datasource.queryBuilder; - this.$q = $q; // Use custom format for template variables this.replaceTemplateVars = this.datasource.replaceTemplateVars; @@ -107,12 +106,13 @@ export class ZabbixQueryController extends QueryCtrl { } initFilters() { - var self = this; - var itemtype = self.editorModes[self.target.mode].value; - return this.$q.when(this.suggestGroups()) - .then(() => {return self.suggestHosts();}) - .then(() => {return self.suggestApps();}) - .then(() => {return self.suggestItems(itemtype);}); + let itemtype = this.editorModes[this.target.mode].value; + return Promise.all([ + this.suggestGroups(), + this.suggestHosts(), + this.suggestApps(), + this.suggestItems(itemtype) + ]); } suggestGroups() { @@ -303,7 +303,6 @@ export class ZabbixQueryController extends QueryCtrl { this.panelCtrl.refresh(); } } - } // Set templateUrl as static property diff --git a/src/datasource-zabbix/specs/datasource_specs.js b/src/datasource-zabbix/specs/datasource_specs.js index 7919856..f57b910 100644 --- a/src/datasource-zabbix/specs/datasource_specs.js +++ b/src/datasource-zabbix/specs/datasource_specs.js @@ -17,14 +17,13 @@ describe('ZabbixDatasource', () => { trendsFrom: '7d' } }; - ctx.$q = Q; ctx.templateSrv = {}; ctx.alertSrv = {}; ctx.zabbixAPIService = () => {}; ctx.ZabbixCachingProxy = () => {}; ctx.queryBuilder = () => {}; - ctx.ds = new Datasource(ctx.instanceSettings, ctx.$q, ctx.templateSrv, ctx.alertSrv, + ctx.ds = new Datasource(ctx.instanceSettings, ctx.templateSrv, ctx.alertSrv, ctx.zabbixAPIService, ctx.ZabbixCachingProxy, ctx.queryBuilder); }); diff --git a/src/datasource-zabbix/zabbixAPI.service.js b/src/datasource-zabbix/zabbixAPI.service.js index e81f2aa..735888b 100644 --- a/src/datasource-zabbix/zabbixAPI.service.js +++ b/src/datasource-zabbix/zabbixAPI.service.js @@ -4,7 +4,7 @@ import * as utils from './utils'; import './zabbixAPICore.service'; /** @ngInject */ -function ZabbixAPIServiceFactory($q, alertSrv, zabbixAPICoreService) { +function ZabbixAPIServiceFactory(alertSrv, zabbixAPICoreService) { /** * Zabbix API Wrapper. @@ -28,7 +28,6 @@ function ZabbixAPIServiceFactory($q, alertSrv, zabbixAPICoreService) { this.loginErrorCount = 0; this.maxLoginAttempts = 3; - this.$q = $q; this.alertSrv = alertSrv; this.zabbixAPICore = zabbixAPICoreService; diff --git a/src/datasource-zabbix/zabbixAPICore.service.js b/src/datasource-zabbix/zabbixAPICore.service.js index 70017d2..4ab33c1 100644 --- a/src/datasource-zabbix/zabbixAPICore.service.js +++ b/src/datasource-zabbix/zabbixAPICore.service.js @@ -7,8 +7,7 @@ import angular from 'angular'; class ZabbixAPICoreService { /** @ngInject */ - constructor($q, backendSrv) { - this.$q = $q; + constructor(backendSrv) { this.backendSrv = backendSrv; } diff --git a/src/datasource-zabbix/zabbixCache.service.js b/src/datasource-zabbix/zabbixCache.service.js index 55131c3..410284e 100644 --- a/src/datasource-zabbix/zabbixCache.service.js +++ b/src/datasource-zabbix/zabbixCache.service.js @@ -5,15 +5,13 @@ import _ from 'lodash'; // Each datasource instance must initialize its own cache. /** @ngInject */ -function ZabbixCachingProxyFactory($q, $interval) { +function ZabbixCachingProxyFactory($interval) { class ZabbixCachingProxy { constructor(zabbixAPI, ttl) { this.zabbixAPI = zabbixAPI; this.ttl = ttl; - this.$q = $q; - // Internal objects for data storing this._groups = undefined; this._hosts = undefined; @@ -110,34 +108,32 @@ function ZabbixCachingProxyFactory($q, $interval) { } getHistoryFromCache(items, time_from, time_till) { - var deferred = this.$q.defer(); var historyStorage = this.storage.history; var full_history; - var expired = _.filter(_.keyBy(items, 'itemid'), function(item, itemid) { + var expired = _.filter(_.keyBy(items, 'itemid'), (item, itemid) => { return !historyStorage[itemid]; }); if (expired.length) { - this.zabbixAPI.getHistory(expired, time_from, time_till).then(function(history) { + return this.zabbixAPI.getHistory(expired, time_from, time_till).then(function(history) { var grouped_history = _.groupBy(history, 'itemid'); - _.forEach(expired, function(item) { + _.forEach(expired, item => { var itemid = item.itemid; historyStorage[itemid] = item; historyStorage[itemid].time_from = time_from; historyStorage[itemid].time_till = time_till; historyStorage[itemid].history = grouped_history[itemid]; }); - full_history = _.map(items, function(item) { + full_history = _.map(items, item => { return historyStorage[item.itemid].history; }); - deferred.resolve(_.flatten(full_history, true)); + return _.flatten(full_history, true); }); } else { full_history = _.map(items, function(item) { return historyStorage[item.itemid].history; }); - deferred.resolve(_.flatten(full_history, true)); + return Promise.resolve(_.flatten(full_history, true)); } - return deferred.promise; } getHistoryFromAPI(items, time_from, time_till) { diff --git a/src/panel-triggers/editor.js b/src/panel-triggers/editor.js index e9a3088..faebb37 100644 --- a/src/panel-triggers/editor.js +++ b/src/panel-triggers/editor.js @@ -19,12 +19,11 @@ import '../datasource-zabbix/css/query-editor.css!'; class TriggerPanelEditorCtrl { /** @ngInject */ - constructor($scope, $rootScope, $q, uiSegmentSrv, datasourceSrv, templateSrv, popoverSrv) { + constructor($scope, $rootScope, uiSegmentSrv, datasourceSrv, templateSrv, popoverSrv) { $scope.editor = this; this.panelCtrl = $scope.ctrl; this.panel = this.panelCtrl.panel; - this.$q = $q; this.datasourceSrv = datasourceSrv; this.templateSrv = templateSrv; this.popoverSrv = popoverSrv; @@ -83,11 +82,11 @@ class TriggerPanelEditorCtrl { } initFilters() { - var self = this; - return this.$q - .when(this.suggestGroups()) - .then(() => {return self.suggestHosts();}) - .then(() => {return self.suggestApps();}); + return Promise.all([ + this.suggestGroups(), + this.suggestHosts(), + this.suggestApps() + ]); } suggestGroups() { diff --git a/src/panel-triggers/module.js b/src/panel-triggers/module.js index c583b7c..33d7bd8 100644 --- a/src/panel-triggers/module.js +++ b/src/panel-triggers/module.js @@ -61,7 +61,7 @@ var defaultTimeFormat = "DD MMM YYYY HH:mm:ss"; class TriggerPanelCtrl extends MetricsPanelCtrl { /** @ngInject */ - constructor($scope, $injector, $q, $element, datasourceSrv, templateSrv, contextSrv) { + constructor($scope, $injector, $element, datasourceSrv, templateSrv, contextSrv) { super($scope, $injector); this.datasourceSrv = datasourceSrv; this.templateSrv = templateSrv; From 1259501190ef1b25377492540954cf5778024678 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sun, 13 Nov 2016 17:28:42 +0300 Subject: [PATCH 55/64] Added link to Templating Guide. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4b76dba..1c42da0 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ Or see more installation options in [docs](http://docs.grafana-zabbix.org/instal - [About](http://docs.grafana-zabbix.org) - [Installation](http://docs.grafana-zabbix.org/installation) - [Getting Started](http://docs.grafana-zabbix.org/guides/gettingstarted) + - [Templating](http://docs.grafana-zabbix.org/guides/templating) ## Dockerized Grafana with Zabbix datasource From d11f1b76166417bdad3fe57d3299e9b9cd06dfca Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sun, 13 Nov 2016 17:15:50 +0300 Subject: [PATCH 56/64] Refactor: move zabbix requests to single place (zabbix.js module). --- src/datasource-zabbix/datasource.js | 147 ++++++------- .../{queryBuilder.js => zabbix.js} | 76 +++---- src/panel-triggers/module.js | 202 +++++++++--------- 3 files changed, 202 insertions(+), 223 deletions(-) rename src/datasource-zabbix/{queryBuilder.js => zabbix.js} (72%) diff --git a/src/datasource-zabbix/datasource.js b/src/datasource-zabbix/datasource.js index 0e4a419..f055406 100644 --- a/src/datasource-zabbix/datasource.js +++ b/src/datasource-zabbix/datasource.js @@ -6,15 +6,15 @@ import * as migrations from './migrations'; import * as metricFunctions from './metricFunctions'; import DataProcessor from './DataProcessor'; import responseHandler from './responseHandler'; -import './zabbixAPI.service.js'; -import './zabbixCache.service.js'; -import './queryBuilder.js'; +import './zabbix.js'; import {ZabbixAPIError} from './zabbixAPICore.service.js'; class ZabbixAPIDatasource { /** @ngInject */ - constructor(instanceSettings, templateSrv, alertSrv, zabbixAPIService, ZabbixCachingProxy, QueryBuilder) { + constructor(instanceSettings, templateSrv, alertSrv, Zabbix) { + this.templateSrv = templateSrv; + this.alertSrv = alertSrv; // General data source settings this.name = instanceSettings.name; @@ -34,19 +34,7 @@ class ZabbixAPIDatasource { var ttl = instanceSettings.jsonData.cacheTTL || '1h'; this.cacheTTL = utils.parseInterval(ttl); - // Initialize Zabbix API - var ZabbixAPI = zabbixAPIService; - this.zabbixAPI = new ZabbixAPI(this.url, this.username, this.password, this.basicAuth, this.withCredentials); - - // Initialize cache service - this.zabbixCache = new ZabbixCachingProxy(this.zabbixAPI, this.cacheTTL); - - // Initialize query builder - this.queryBuilder = new QueryBuilder(this.zabbixCache); - - // Dependencies - this.templateSrv = templateSrv; - this.alertSrv = alertSrv; + this.zabbix = new Zabbix(this.url, this.username, this.password, this.basicAuth, this.withCredentials, this.cacheTTL); // Use custom format for template variables this.replaceTemplateVars = _.partial(replaceTemplateVars, this.templateSrv); @@ -119,7 +107,7 @@ class ZabbixAPIDatasource { return []; } - return this.zabbixAPI + return this.zabbix .getSLA(target.itservice.serviceid, timeFrom, timeTo) .then(slaObject => { return responseHandler.handleSLAResponse(target.itservice, target.slaProperty, slaObject); @@ -148,7 +136,7 @@ class ZabbixAPIDatasource { let options = { itemtype: 'num' }; - return this.queryBuilder.build(target, options) + return this.zabbix.getItemsFromTarget(target, options) .then(items => { // Add hostname for items from multiple hosts var addHostName = utils.isRegex(target.host.filter); @@ -164,7 +152,7 @@ class ZabbixAPIDatasource { }); var valueType = trendValueFunc ? trendValueFunc.params[0] : "avg"; - getHistory = this.zabbixAPI + getHistory = this.zabbix .getTrend(items, timeFrom, timeTo) .then(history => { return responseHandler.handleTrends(history, items, addHostName, valueType); @@ -173,7 +161,7 @@ class ZabbixAPIDatasource { // Use history else { - getHistory = this.zabbixCache + getHistory = this.zabbix .getHistory(items, timeFrom, timeTo) .then(history => { return responseHandler.handleHistory(history, items, addHostName); @@ -227,10 +215,10 @@ class ZabbixAPIDatasource { let options = { itemtype: 'text' }; - return this.queryBuilder.build(target, options) + return this.zabbix.getItemsFromTarget(target, options) .then(items => { if (items.length) { - return this.zabbixAPI.getHistory(items, timeFrom, timeTo) + return this.zabbix.getHistory(items, timeFrom, timeTo) .then(history => { return responseHandler.convertHistory(history, items, false, (point) => { let value = point.value; @@ -255,10 +243,10 @@ class ZabbixAPIDatasource { */ testDatasource() { let zabbixVersion; - return this.zabbixAPI.getVersion() + return this.zabbix.getVersion() .then(version => { zabbixVersion = version; - return this.zabbixAPI.login(); + return this.zabbix.login(); }) .then(() => { return { @@ -317,16 +305,16 @@ class ZabbixAPIDatasource { if (template.app === '/.*/') { template.app = ''; } - result = this.queryBuilder.getItems(template.group, template.host, template.app, template.item); + result = this.zabbix.getItems(template.group, template.host, template.app, template.item); } else if (parts.length === 3) { // Get applications - result = this.queryBuilder.getApps(template.group, template.host, template.app); + result = this.zabbix.getApps(template.group, template.host, template.app); } else if (parts.length === 2) { // Get hosts - result = this.queryBuilder.getHosts(template.group, template.host); + result = this.zabbix.getHosts(template.group, template.host); } else if (parts.length === 1) { // Get groups - result = this.zabbixCache.getGroups(template.group); + result = this.zabbix.getGroups(template.group); } else { result = Promise.resolve([]); } @@ -349,64 +337,61 @@ class ZabbixAPIDatasource { // Show all triggers var showTriggers = [0, 1]; - var buildQuery = this.queryBuilder - .buildTriggerQuery(this.replaceTemplateVars(annotation.group, {}), - this.replaceTemplateVars(annotation.host, {}), - this.replaceTemplateVars(annotation.application, {})); + var getTriggers = this.zabbix + .getTriggers(this.replaceTemplateVars(annotation.group, {}), + this.replaceTemplateVars(annotation.host, {}), + this.replaceTemplateVars(annotation.application, {}), + showTriggers); - return buildQuery.then(query => { - return this.zabbixAPI - .getTriggers(query.groupids, query.hostids, query.applicationids, showTriggers) - .then(triggers => { + return getTriggers.then(triggers => { - // Filter triggers by description - if (utils.isRegex(annotation.trigger)) { - triggers = _.filter(triggers, trigger => { - return utils.buildRegex(annotation.trigger).test(trigger.description); - }); - } else if (annotation.trigger) { - triggers = _.filter(triggers, trigger => { - return trigger.description === annotation.trigger; + // Filter triggers by description + if (utils.isRegex(annotation.trigger)) { + triggers = _.filter(triggers, trigger => { + return utils.buildRegex(annotation.trigger).test(trigger.description); + }); + } else if (annotation.trigger) { + triggers = _.filter(triggers, trigger => { + return trigger.description === annotation.trigger; + }); + } + + // Remove events below the chose severity + triggers = _.filter(triggers, trigger => { + return Number(trigger.priority) >= Number(annotation.minseverity); + }); + + var objectids = _.map(triggers, 'triggerid'); + return this.zabbix + .getEvents(objectids, timeFrom, timeTo, showOkEvents) + .then(events => { + var indexedTriggers = _.keyBy(triggers, 'triggerid'); + + // Hide acknowledged events if option enabled + if (annotation.hideAcknowledged) { + events = _.filter(events, event => { + return !event.acknowledges.length; }); } - // Remove events below the chose severity - triggers = _.filter(triggers, trigger => { - return Number(trigger.priority) >= Number(annotation.minseverity); + return _.map(events, event => { + let tags; + if (annotation.showHostname) { + tags = _.map(event.hosts, 'name'); + } + + // Show event type (OK or Problem) + let title = Number(event.value) ? 'Problem' : 'OK'; + + let formatted_acknowledges = utils.formatAcknowledges(event.acknowledges); + return { + annotation: annotation, + time: event.clock * 1000, + title: title, + tags: tags, + text: indexedTriggers[event.objectid].description + formatted_acknowledges + }; }); - - var objectids = _.map(triggers, 'triggerid'); - return this.zabbixAPI - .getEvents(objectids, timeFrom, timeTo, showOkEvents) - .then(events => { - var indexedTriggers = _.keyBy(triggers, 'triggerid'); - - // Hide acknowledged events if option enabled - if (annotation.hideAcknowledged) { - events = _.filter(events, event => { - return !event.acknowledges.length; - }); - } - - return _.map(events, event => { - let tags; - if (annotation.showHostname) { - tags = _.map(event.hosts, 'name'); - } - - // Show event type (OK or Problem) - let title = Number(event.value) ? 'Problem' : 'OK'; - - let formatted_acknowledges = utils.formatAcknowledges(event.acknowledges); - return { - annotation: annotation, - time: event.clock * 1000, - title: title, - tags: tags, - text: indexedTriggers[event.objectid].description + formatted_acknowledges - }; - }); - }); }); }); } diff --git a/src/datasource-zabbix/queryBuilder.js b/src/datasource-zabbix/zabbix.js similarity index 72% rename from src/datasource-zabbix/queryBuilder.js rename to src/datasource-zabbix/zabbix.js index 8188e59..7500b91 100644 --- a/src/datasource-zabbix/queryBuilder.js +++ b/src/datasource-zabbix/zabbix.js @@ -1,45 +1,40 @@ import angular from 'angular'; import _ from 'lodash'; import * as utils from './utils'; +import './zabbixAPI.service.js'; +import './zabbixCache.service.js'; -function QueryBuilderFactory() { +// Use factory() instead service() for multiple data sources support. +// Each Zabbix data source instance should initialize its own API instance. - class QueryBuilder { - constructor(zabbixCacheInstance) { - this.cache = zabbixCacheInstance; +/** @ngInject */ +function ZabbixFactory(zabbixAPIService, ZabbixCachingProxy) { + + class Zabbix { + constructor(url, username, password, basicAuth, withCredentials, cacheTTL) { + + // Initialize Zabbix API + var ZabbixAPI = zabbixAPIService; + this.zabbixAPI = new ZabbixAPI(url, username, password, basicAuth, withCredentials); + + // Initialize cache + this.cache = new ZabbixCachingProxy(this.zabbixAPI, cacheTTL); + + // Proxy methods + this.getHistory = this.cache.getHistory.bind(this.cache); + + this.getTrend = this.zabbixAPI.getTrend.bind(this.zabbixAPI); + this.getEvents = this.zabbixAPI.getEvents.bind(this.zabbixAPI); + this.getAcknowledges = this.zabbixAPI.getAcknowledges.bind(this.zabbixAPI); + this.getSLA = this.zabbixAPI.getSLA.bind(this.zabbixAPI); + this.getVersion = this.zabbixAPI.getVersion.bind(this.zabbixAPI); + this.login = this.zabbixAPI.login.bind(this.zabbixAPI); } - initializeCache() { - if (this.cache._initialized) { - return Promise.resolve(); - } else { - return this.cache.refresh(); - } - } - - /** - * Build query - convert target filters to array of Zabbix items - */ - build(target, options) { - function getFiltersFromTarget(target) { - let parts = ['group', 'host', 'application', 'item']; - return _.map(parts, p => target[p].filter); - } - - return this.initializeCache() - .then(() => { - return this.getItems(...getFiltersFromTarget(target), options); - }); - } - - /** - * Build trigger query in asynchronous manner - */ - buildTriggerQuery(groupFilter, hostFilter, appFilter) { - return this.initializeCache() - .then(() => { - return this.buildTriggerQueryFromCache(groupFilter, hostFilter, appFilter); - }); + getItemsFromTarget(target, options) { + let parts = ['group', 'host', 'application', 'item']; + let filters = _.map(parts, p => target[p].filter); + return this.getItems(...filters, options); } getAllGroups() { @@ -120,7 +115,7 @@ function QueryBuilderFactory() { /** * Build query - convert target filters to array of Zabbix items */ - buildTriggerQueryFromCache(groupFilter, hostFilter, appFilter) { + getTriggers(groupFilter, hostFilter, appFilter, showTriggers) { let promises = [ this.getGroups(groupFilter), this.getHosts(groupFilter, hostFilter), @@ -145,16 +140,21 @@ function QueryBuilderFactory() { } return query; + }).then(query => { + return this.zabbixAPI + .getTriggers(query.groupids, query.hostids, query.applicationids, showTriggers); }); } } - return QueryBuilder; + return Zabbix; } angular .module('grafana.services') - .factory('QueryBuilder', QueryBuilderFactory); + .factory('Zabbix', ZabbixFactory); + +/////////////////////////////////////////////////////////////////////////////// /** * Find group, host, app or item by given name. diff --git a/src/panel-triggers/module.js b/src/panel-triggers/module.js index 33d7bd8..7ccbfd5 100644 --- a/src/panel-triggers/module.js +++ b/src/panel-triggers/module.js @@ -106,9 +106,9 @@ class TriggerPanelCtrl extends MetricsPanelCtrl { var self = this; // Load datasource - return this.datasourceSrv.get(this.panel.datasource).then(datasource => { - var zabbix = datasource.zabbixAPI; - var queryBuilder = datasource.queryBuilder; + return this.datasourceSrv.get(this.panel.datasource) + .then(datasource => { + var zabbix = datasource.zabbix; var showEvents = self.panel.showEvents.value; var triggerFilter = self.panel.triggers; @@ -117,118 +117,112 @@ class TriggerPanelCtrl extends MetricsPanelCtrl { var hostFilter = datasource.replaceTemplateVars(triggerFilter.host.filter); var appFilter = datasource.replaceTemplateVars(triggerFilter.application.filter); - var buildQuery = queryBuilder.buildTriggerQuery(groupFilter, hostFilter, appFilter); - return buildQuery.then(query => { - return zabbix.getTriggers(query.groupids, - query.hostids, - query.applicationids, - showEvents) - .then(triggers => { - return _.map(triggers, trigger => { - let triggerObj = trigger; + var getTriggers = zabbix.getTriggers(groupFilter, hostFilter, appFilter, showEvents); + return getTriggers.then(triggers => { + return _.map(triggers, trigger => { + let triggerObj = trigger; - // Format last change and age - trigger.lastchangeUnix = Number(trigger.lastchange); - let timestamp = moment.unix(trigger.lastchangeUnix); - if (self.panel.customLastChangeFormat) { - // User defined format - triggerObj.lastchange = timestamp.format(self.panel.lastChangeFormat); - } else { - triggerObj.lastchange = timestamp.format(self.defaultTimeFormat); - } - triggerObj.age = timestamp.fromNow(true); + // Format last change and age + trigger.lastchangeUnix = Number(trigger.lastchange); + let timestamp = moment.unix(trigger.lastchangeUnix); + if (self.panel.customLastChangeFormat) { + // User defined format + triggerObj.lastchange = timestamp.format(self.panel.lastChangeFormat); + } else { + triggerObj.lastchange = timestamp.format(self.defaultTimeFormat); + } + triggerObj.age = timestamp.fromNow(true); - // Set host that the trigger belongs - if (trigger.hosts.length) { - triggerObj.host = trigger.hosts[0].name; - triggerObj.hostTechName = trigger.hosts[0].host; - } + // Set host that the trigger belongs + if (trigger.hosts.length) { + triggerObj.host = trigger.hosts[0].name; + triggerObj.hostTechName = trigger.hosts[0].host; + } - // Set color - if (trigger.value === '1') { - // Problem state - triggerObj.color = self.panel.triggerSeverity[trigger.priority].color; - } else { - // OK state - triggerObj.color = self.panel.okEventColor; - } + // Set color + if (trigger.value === '1') { + // Problem state + triggerObj.color = self.panel.triggerSeverity[trigger.priority].color; + } else { + // OK state + triggerObj.color = self.panel.okEventColor; + } - triggerObj.severity = self.panel.triggerSeverity[trigger.priority].severity; - return triggerObj; - }); - }) - .then(triggerList => { + triggerObj.severity = self.panel.triggerSeverity[trigger.priority].severity; + return triggerObj; + }); + }) + .then(triggerList => { - // Request acknowledges for trigger - var eventids = _.map(triggerList, trigger => { - return trigger.lastEvent.eventid; + // Request acknowledges for trigger + var eventids = _.map(triggerList, trigger => { + return trigger.lastEvent.eventid; + }); + + return zabbix.getAcknowledges(eventids) + .then(events => { + + // Map events to triggers + _.each(triggerList, trigger => { + var event = _.find(events, event => { + return event.eventid === trigger.lastEvent.eventid; }); - return zabbix.getAcknowledges(eventids) - .then(events => { - - // Map events to triggers - _.each(triggerList, trigger => { - var event = _.find(events, event => { - return event.eventid === trigger.lastEvent.eventid; - }); - - if (event) { - trigger.acknowledges = _.map(event.acknowledges, ack => { - let timestamp = moment.unix(ack.clock); - if (self.panel.customLastChangeFormat) { - ack.time = timestamp.format(self.panel.lastChangeFormat); - } else { - ack.time = timestamp.format(self.defaultTimeFormat); - } - ack.user = ack.alias + ' (' + ack.name + ' ' + ack.surname + ')'; - return ack; - }); - - // Mark acknowledged triggers with different color - if (self.panel.markAckEvents && trigger.acknowledges.length) { - trigger.color = self.panel.ackEventColor; - } - } - }); - - // Filter triggers by description - var triggerFilter = self.panel.triggers.trigger.filter; - if (triggerFilter) { - triggerList = filterTriggers(triggerList, triggerFilter); - } - - // Filter acknowledged triggers - if (self.panel.showTriggers === 'unacknowledged') { - triggerList = _.filter(triggerList, trigger => { - return !trigger.acknowledges; - }); - } else if (self.panel.showTriggers === 'acknowledged') { - triggerList = _.filter(triggerList, 'acknowledges'); + if (event) { + trigger.acknowledges = _.map(event.acknowledges, ack => { + let timestamp = moment.unix(ack.clock); + if (self.panel.customLastChangeFormat) { + ack.time = timestamp.format(self.panel.lastChangeFormat); } else { - triggerList = triggerList; + ack.time = timestamp.format(self.defaultTimeFormat); } - - // Filter triggers by severity - triggerList = _.filter(triggerList, trigger => { - return self.panel.triggerSeverity[trigger.priority].show; - }); - - // Sort triggers - if (self.panel.sortTriggersBy.value === 'priority') { - triggerList = _.sortBy(triggerList, 'priority').reverse(); - } else { - triggerList = _.sortBy(triggerList, 'lastchangeUnix').reverse(); - } - - // Limit triggers number - self.triggerList = triggerList.slice(0, self.panel.limit); - - // Notify panel that request is finished - self.setTimeQueryEnd(); - self.loading = false; + ack.user = ack.alias + ' (' + ack.name + ' ' + ack.surname + ')'; + return ack; }); + + // Mark acknowledged triggers with different color + if (self.panel.markAckEvents && trigger.acknowledges.length) { + trigger.color = self.panel.ackEventColor; + } + } }); + + // Filter triggers by description + var triggerFilter = self.panel.triggers.trigger.filter; + if (triggerFilter) { + triggerList = filterTriggers(triggerList, triggerFilter); + } + + // Filter acknowledged triggers + if (self.panel.showTriggers === 'unacknowledged') { + triggerList = _.filter(triggerList, trigger => { + return !trigger.acknowledges; + }); + } else if (self.panel.showTriggers === 'acknowledged') { + triggerList = _.filter(triggerList, 'acknowledges'); + } else { + triggerList = triggerList; + } + + // Filter triggers by severity + triggerList = _.filter(triggerList, trigger => { + return self.panel.triggerSeverity[trigger.priority].show; + }); + + // Sort triggers + if (self.panel.sortTriggersBy.value === 'priority') { + triggerList = _.sortBy(triggerList, 'priority').reverse(); + } else { + triggerList = _.sortBy(triggerList, 'lastchangeUnix').reverse(); + } + + // Limit triggers number + self.triggerList = triggerList.slice(0, self.panel.limit); + + // Notify panel that request is finished + self.setTimeQueryEnd(); + self.loading = false; + }); }); }); } From 0bf7f4b870e235939b4ae152b3c8f1c8eaea96db Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sun, 13 Nov 2016 18:40:01 +0300 Subject: [PATCH 57/64] Refactor: fix query.controller.js --- src/datasource-zabbix/query.controller.js | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/datasource-zabbix/query.controller.js b/src/datasource-zabbix/query.controller.js index 3e5bdd4..48465a5 100644 --- a/src/datasource-zabbix/query.controller.js +++ b/src/datasource-zabbix/query.controller.js @@ -14,13 +14,8 @@ export class ZabbixQueryController extends QueryCtrl { // ZabbixQueryCtrl constructor constructor($scope, $injector, $rootScope, $sce, templateSrv) { - - // Call superclass constructor super($scope, $injector); - - this.zabbix = this.datasource.zabbixAPI; - this.cache = this.datasource.zabbixCache; - this.queryBuilder = this.datasource.queryBuilder; + this.zabbix = this.datasource.zabbix; // Use custom format for template variables this.replaceTemplateVars = this.datasource.replaceTemplateVars; @@ -116,7 +111,7 @@ export class ZabbixQueryController extends QueryCtrl { } suggestGroups() { - return this.queryBuilder.getAllGroups() + return this.zabbix.getAllGroups() .then(groups => { this.metric.groupList = groups; return groups; @@ -125,7 +120,7 @@ export class ZabbixQueryController extends QueryCtrl { suggestHosts() { let groupFilter = this.replaceTemplateVars(this.target.group.filter); - return this.queryBuilder.getAllHosts(groupFilter) + return this.zabbix.getAllHosts(groupFilter) .then(hosts => { this.metric.hostList = hosts; return hosts; @@ -135,7 +130,7 @@ export class ZabbixQueryController extends QueryCtrl { suggestApps() { let groupFilter = this.replaceTemplateVars(this.target.group.filter); let hostFilter = this.replaceTemplateVars(this.target.host.filter); - return this.queryBuilder.getAllApps(groupFilter, hostFilter) + return this.zabbix.getAllApps(groupFilter, hostFilter) .then(apps => { this.metric.appList = apps; return apps; @@ -151,7 +146,7 @@ export class ZabbixQueryController extends QueryCtrl { showDisabledItems: this.target.options.showDisabledItems }; - return this.queryBuilder + return this.zabbix .getAllItems(groupFilter, hostFilter, appFilter, options) .then(items => { this.metric.itemList = items; From 834b953e4100969f42a8114624ce2b2269ae080e Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sun, 13 Nov 2016 18:40:50 +0300 Subject: [PATCH 58/64] Add host name to metric if multiple hosts returned. --- src/datasource-zabbix/responseHandler.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/datasource-zabbix/responseHandler.js b/src/datasource-zabbix/responseHandler.js index 0f6a8e2..7ebe7a9 100644 --- a/src/datasource-zabbix/responseHandler.js +++ b/src/datasource-zabbix/responseHandler.js @@ -22,7 +22,7 @@ function convertHistory(history, items, addHostName, convertPointCallback) { // Group history by itemid var grouped_history = _.groupBy(history, 'itemid'); - var hosts = _.uniq(_.flatten(_.map(items, 'hosts')),'hostid'); //uniq is needed to deduplicate + var hosts = _.uniqBy(_.flatten(_.map(items, 'hosts')), 'hostid'); //uniqBy is needed to deduplicate return _.map(grouped_history, function(hist, itemid) { var item = _.find(items, {'itemid': itemid}); @@ -38,11 +38,11 @@ function convertHistory(history, items, addHostName, convertPointCallback) { }); } -function handleHistory(history, items, addHostName) { +function handleHistory(history, items, addHostName = true) { return convertHistory(history, items, addHostName, convertHistoryPoint); } -function handleTrends(history, items, addHostName, valueType) { +function handleTrends(history, items, valueType, addHostName = true) { var convertPointCallback = _.partial(convertTrendPoint, valueType); return convertHistory(history, items, addHostName, convertPointCallback); } From 6e0f59cd696d642f72a18dcede845e2d5c74984c Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sun, 13 Nov 2016 18:41:10 +0300 Subject: [PATCH 59/64] Refactor: datasource.js --- src/datasource-zabbix/datasource.js | 202 ++++++++++++++-------------- 1 file changed, 100 insertions(+), 102 deletions(-) diff --git a/src/datasource-zabbix/datasource.js b/src/datasource-zabbix/datasource.js index f055406..22331a3 100644 --- a/src/datasource-zabbix/datasource.js +++ b/src/datasource-zabbix/datasource.js @@ -58,12 +58,12 @@ class ZabbixAPIDatasource { // Create request for each target var promises = _.map(options.targets, target => { - // Prevent changes of original object target = _.cloneDeep(target); + this.replaceTargetVariables(target, options); + // Metrics or Text query mode if (target.mode !== 1) { - // Migrate old targets target = migrations.migrate(target); @@ -72,30 +72,9 @@ class ZabbixAPIDatasource { return []; } - // Replace templated variables - target.group.filter = this.replaceTemplateVars(target.group.filter, options.scopedVars); - target.host.filter = this.replaceTemplateVars(target.host.filter, options.scopedVars); - target.application.filter = this.replaceTemplateVars(target.application.filter, options.scopedVars); - target.item.filter = this.replaceTemplateVars(target.item.filter, options.scopedVars); - target.textFilter = this.replaceTemplateVars(target.textFilter, options.scopedVars); - - _.forEach(target.functions, func => { - func.params = _.map(func.params, param => { - if (typeof param === 'number') { - return +this.templateSrv.replace(param.toString(), options.scopedVars); - } else { - return this.templateSrv.replace(param, options.scopedVars); - } - }); - }); - - // Query numeric data if (!target.mode || target.mode === 0) { return this.queryNumericData(target, timeFrom, timeTo, useTrends); - } - - // Query text data - else if (target.mode === 2) { + } else if (target.mode === 2) { return this.queryTextData(target, timeFrom, timeTo); } } @@ -107,8 +86,7 @@ class ZabbixAPIDatasource { return []; } - return this.zabbix - .getSLA(target.itservice.serviceid, timeFrom, timeTo) + return this.zabbix.getSLA(target.itservice.serviceid, timeFrom, timeTo) .then(slaObject => { return responseHandler.handleSLAResponse(target.itservice, target.slaProperty, slaObject); }); @@ -119,15 +97,9 @@ class ZabbixAPIDatasource { return Promise.all(_.flatten(promises)) .then(_.flatten) .then(timeseries_data => { - - // Series downsampling - var data = _.map(timeseries_data, timeseries => { - if (timeseries.datapoints.length > options.maxDataPoints) { - timeseries.datapoints = DataProcessor - .groupBy(options.interval, DataProcessor.AVERAGE, timeseries.datapoints); - } - return timeseries; - }); + return downsampleSeries(timeseries_data, options); + }) + .then(data => { return { data: data }; }); } @@ -137,78 +109,75 @@ class ZabbixAPIDatasource { itemtype: 'num' }; return this.zabbix.getItemsFromTarget(target, options) - .then(items => { - // Add hostname for items from multiple hosts - var addHostName = utils.isRegex(target.host.filter); - var getHistory; + .then(items => { + let getHistoryPromise; - // Use trends - if (useTrends) { - - // Find trendValue() function and get specified trend value - var trendFunctions = _.map(metricFunctions.getCategories()['Trends'], 'name'); - var trendValueFunc = _.find(target.functions, func => { - return _.includes(trendFunctions, func.def.name); + if (useTrends) { + let valueType = this.getTrendValueType(target); + getHistoryPromise = this.zabbix.getTrend(items, timeFrom, timeTo) + .then(history => { + return responseHandler.handleTrends(history, items, valueType); }); - var valueType = trendValueFunc ? trendValueFunc.params[0] : "avg"; - - getHistory = this.zabbix - .getTrend(items, timeFrom, timeTo) - .then(history => { - return responseHandler.handleTrends(history, items, addHostName, valueType); - }); - } - + } else { // Use history - else { - getHistory = this.zabbix - .getHistory(items, timeFrom, timeTo) - .then(history => { - return responseHandler.handleHistory(history, items, addHostName); - }); - } - - return getHistory.then(timeseries_data => { - let transformFunctions = bindFunctionDefs(target.functions, 'Transform'); - let aggregationFunctions = bindFunctionDefs(target.functions, 'Aggregate'); - let filterFunctions = bindFunctionDefs(target.functions, 'Filter'); - let aliasFunctions = bindFunctionDefs(target.functions, 'Alias'); - - // Apply transformation functions - timeseries_data = _.map(timeseries_data, timeseries => { - timeseries.datapoints = sequence(transformFunctions)(timeseries.datapoints); - return timeseries; + getHistoryPromise = this.zabbix.getHistory(items, timeFrom, timeTo) + .then(history => { + return responseHandler.handleHistory(history, items); }); + } - // Apply filter functions - if (filterFunctions.length) { - timeseries_data = sequence(filterFunctions)(timeseries_data); - } - - // Apply aggregations - if (aggregationFunctions.length) { - let dp = _.map(timeseries_data, 'datapoints'); - dp = sequence(aggregationFunctions)(dp); - - let aggFuncNames = _.map(metricFunctions.getCategories()['Aggregate'], 'name'); - let lastAgg = _.findLast(target.functions, func => { - return _.includes(aggFuncNames, func.def.name); - }); - - timeseries_data = [ - { - target: lastAgg.text, - datapoints: dp - } - ]; - } - - // Apply alias functions - _.each(timeseries_data, sequence(aliasFunctions)); - - return timeseries_data; - }); + return getHistoryPromise.then(timeseries_data => { + return this.applyDataProcessingFunctions(timeseries_data, target); }); + }); + } + + getTrendValueType(target) { + // Find trendValue() function and get specified trend value + var trendFunctions = _.map(metricFunctions.getCategories()['Trends'], 'name'); + var 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'); + + // Apply transformation functions + timeseries_data = _.map(timeseries_data, timeseries => { + timeseries.datapoints = sequence(transformFunctions)(timeseries.datapoints); + return timeseries; + }); + + // Apply filter functions + if (filterFunctions.length) { + timeseries_data = sequence(filterFunctions)(timeseries_data); + } + + // Apply aggregations + if (aggregationFunctions.length) { + let dp = _.map(timeseries_data, 'datapoints'); + dp = sequence(aggregationFunctions)(dp); + + let aggFuncNames = _.map(metricFunctions.getCategories()['Aggregate'], 'name'); + let lastAgg = _.findLast(target.functions, func => { + return _.includes(aggFuncNames, func.def.name); + }); + + timeseries_data = [{ + target: lastAgg.text, + datapoints: dp + }]; + } + + // Apply alias functions + _.each(timeseries_data, sequence(aliasFunctions)); + + return timeseries_data; } queryTextData(target, timeFrom, timeTo) { @@ -320,7 +289,7 @@ class ZabbixAPIDatasource { } return result.then(metrics => { - return _.map(metrics, formatMetric); + return metrics.map(formatMetric); }); } @@ -396,6 +365,25 @@ class ZabbixAPIDatasource { }); } + // Replace template variables + replaceTargetVariables(target, options) { + let parts = ['group', 'host', 'application', 'item']; + parts.forEach(p => { + target[p].filter = this.replaceTemplateVars(target[p].filter, options.scopedVars); + }); + target.textFilter = this.replaceTemplateVars(target.textFilter, options.scopedVars); + + target.functions.forEach(func => { + func.params = func.params.map(param => { + if (typeof param === 'number') { + return +this.templateSrv.replace(param.toString(), options.scopedVars); + } else { + return this.templateSrv.replace(param, options.scopedVars); + } + }); + }); + } + } function bindFunctionDefs(functionDefs, category) { @@ -410,6 +398,16 @@ function bindFunctionDefs(functionDefs, category) { }); } +function downsampleSeries(timeseries_data, options) { + return _.map(timeseries_data, timeseries => { + if (timeseries.datapoints.length > options.maxDataPoints) { + timeseries.datapoints = DataProcessor + .groupBy(options.interval, DataProcessor.AVERAGE, timeseries.datapoints); + } + return timeseries; + }); +} + function formatMetric(metricObj) { return { text: metricObj.name, From 4dc4e3b8ea894684bbc21f4a6d6e1952b5fe1b72 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sun, 13 Nov 2016 19:02:37 +0300 Subject: [PATCH 60/64] Refactor: fix tests. --- src/datasource-zabbix/datasource.js | 2 +- .../specs/datasource_specs.js | 22 +++++++------------ 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/datasource-zabbix/datasource.js b/src/datasource-zabbix/datasource.js index 22331a3..9b2f991 100644 --- a/src/datasource-zabbix/datasource.js +++ b/src/datasource-zabbix/datasource.js @@ -373,7 +373,7 @@ class ZabbixAPIDatasource { }); target.textFilter = this.replaceTemplateVars(target.textFilter, options.scopedVars); - target.functions.forEach(func => { + _.forEach(target.functions, func => { func.params = func.params.map(param => { if (typeof param === 'number') { return +this.templateSrv.replace(param.toString(), options.scopedVars); diff --git a/src/datasource-zabbix/specs/datasource_specs.js b/src/datasource-zabbix/specs/datasource_specs.js index f57b910..6b4dc45 100644 --- a/src/datasource-zabbix/specs/datasource_specs.js +++ b/src/datasource-zabbix/specs/datasource_specs.js @@ -19,12 +19,9 @@ describe('ZabbixDatasource', () => { }; ctx.templateSrv = {}; ctx.alertSrv = {}; - ctx.zabbixAPIService = () => {}; - ctx.ZabbixCachingProxy = () => {}; - ctx.queryBuilder = () => {}; + ctx.zabbix = () => {}; - ctx.ds = new Datasource(ctx.instanceSettings, ctx.templateSrv, ctx.alertSrv, - ctx.zabbixAPIService, ctx.ZabbixCachingProxy, ctx.queryBuilder); + ctx.ds = new Datasource(ctx.instanceSettings, ctx.templateSrv, ctx.alertSrv, ctx.zabbix); }); describe('When querying data', () => { @@ -143,10 +140,7 @@ describe('ZabbixDatasource', () => { describe('When invoking metricFindQuery()', () => { beforeEach(() => { ctx.ds.replaceTemplateVars = (str) => str; - ctx.ds.zabbixCache = { - getGroups: () => Q.when([]) - }; - ctx.ds.queryBuilder = { + ctx.ds.zabbix = { getGroups: () => Q.when([]), getHosts: () => Q.when([]), getApps: () => Q.when([]), @@ -162,7 +156,7 @@ describe('ZabbixDatasource', () => { {query: 'Back*', expect: 'Back*'} ]; - let getGroups = sinon.spy(ctx.ds.zabbixCache, 'getGroups'); + let getGroups = sinon.spy(ctx.ds.zabbix, 'getGroups'); for (const test of tests) { ctx.ds.metricFindQuery(test.query); expect(getGroups).to.have.been.calledWith(test.expect); @@ -179,7 +173,7 @@ describe('ZabbixDatasource', () => { {query: 'Back*.', expect: 'Back*'} ]; - let getHosts = sinon.spy(ctx.ds.queryBuilder, 'getHosts'); + let getHosts = sinon.spy(ctx.ds.zabbix, 'getHosts'); for (const test of tests) { ctx.ds.metricFindQuery(test.query); expect(getHosts).to.have.been.calledWith(test.expect); @@ -196,7 +190,7 @@ describe('ZabbixDatasource', () => { {query: 'Back*.*.', expect: ['Back*', '/.*/']} ]; - let getApps = sinon.spy(ctx.ds.queryBuilder, 'getApps'); + let getApps = sinon.spy(ctx.ds.zabbix, 'getApps'); for (const test of tests) { ctx.ds.metricFindQuery(test.query); expect(getApps).to.have.been.calledWith(test.expect[0], test.expect[1]); @@ -213,7 +207,7 @@ describe('ZabbixDatasource', () => { {query: 'Back*.*.cpu.*', expect: ['Back*', '/.*/', 'cpu']} ]; - let getItems = sinon.spy(ctx.ds.queryBuilder, 'getItems'); + let getItems = sinon.spy(ctx.ds.zabbix, 'getItems'); for (const test of tests) { ctx.ds.metricFindQuery(test.query); expect(getItems) @@ -226,7 +220,7 @@ describe('ZabbixDatasource', () => { it('should invoke method with proper arguments', (done) => { let query = '*.*'; - let getHosts = sinon.spy(ctx.ds.queryBuilder, 'getHosts'); + let getHosts = sinon.spy(ctx.ds.zabbix, 'getHosts'); ctx.ds.metricFindQuery(query); expect(getHosts).to.have.been.calledWith('/.*/'); done(); From fd772fab153f1134ef3728d6581dfab6f3c1fae2 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Mon, 14 Nov 2016 17:27:13 +0300 Subject: [PATCH 61/64] Refactor: dataProcessor.js --- src/datasource-zabbix/DataProcessor.js | 281 ------------------------ src/datasource-zabbix/dataProcessor.js | 291 +++++++++++++++++++++++++ src/datasource-zabbix/datasource.js | 8 +- 3 files changed, 295 insertions(+), 285 deletions(-) delete mode 100644 src/datasource-zabbix/DataProcessor.js create mode 100644 src/datasource-zabbix/dataProcessor.js diff --git a/src/datasource-zabbix/DataProcessor.js b/src/datasource-zabbix/DataProcessor.js deleted file mode 100644 index cb6c4c2..0000000 --- a/src/datasource-zabbix/DataProcessor.js +++ /dev/null @@ -1,281 +0,0 @@ -import _ from 'lodash'; -import * as utils from './utils'; - -export default class DataProcessor { - - /** - * Downsample datapoints series - */ - static downsampleSeries(datapoints, time_to, ms_interval, func) { - var downsampledSeries = []; - var 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 = []; - - for (var 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 { - value_avg = points_num ? points_sum / points_num : 0; - - if (func === "max") { - downsampledSeries.push([_.max(frame), timeWindow.to]); - } - else if (func === "min") { - downsampledSeries.push([_.min(frame), timeWindow.to]); - } - - // avg by default - else { - downsampledSeries.push([value_avg, timeWindow.to]); - } - - // Shift time window - timeWindow.to = timeWindow.from; - timeWindow.from -= ms_interval; - - points_sum = 0; - points_num = 0; - frame = []; - - // Process point again - i++; - } - } - return downsampledSeries.reverse(); - } - - /** - * Group points by given time interval - * datapoints: [[, ], ...] - */ - static groupBy(interval, groupByCallback, datapoints) { - var ms_interval = utils.parseInterval(interval); - - // Calculate frame timestamps - var frames = _.groupBy(datapoints, function(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) { - return point[0]; - }); - return groupByCallback(points); - }); - - // Convert points to Grafana format - return sortByTime(_.map(grouped, function(value, timestamp) { - return [Number(value), Number(timestamp)]; - })); - } - - static sumSeries(timeseries) { - - // Calculate new points for interpolation - var new_timestamps = _.uniq(_.map(_.flatten(timeseries, true), function(point) { - return point[1]; - })); - new_timestamps = _.sortBy(new_timestamps); - - var interpolated_timeseries = _.map(timeseries, function(series) { - var timestamps = _.map(series, function(point) { - return point[1]; - }); - var new_points = _.map(_.difference(new_timestamps, timestamps), function(timestamp) { - return [null, timestamp]; - }); - var 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--) { - sum = 0; - for (var j = interpolated_timeseries.length - 1; j >= 0; j--) { - sum += interpolated_timeseries[j][i][0]; - } - new_timeseries.push([sum, new_timestamps[i]]); - } - - return sortByTime(new_timeseries); - } - - static limit(order, n, orderByFunc, timeseries) { - let orderByCallback = DataProcessor.aggregationFunctions[orderByFunc]; - let sortByIteratee = (ts) => { - let values = _.map(ts.datapoints, (point) => { - return point[0]; - }); - return orderByCallback(values); - }; - let sortedTimeseries = _.sortBy(timeseries, sortByIteratee); - if (order === 'bottom') { - return sortedTimeseries.slice(0, n); - } else { - return sortedTimeseries.slice(-n); - } - } - - static AVERAGE(values) { - var sum = 0; - _.each(values, function(value) { - sum += value; - }); - return sum / values.length; - } - - static MIN(values) { - return _.min(values); - } - - static MAX(values) { - return _.max(values); - } - - static MEDIAN(values) { - var sorted = _.sortBy(values); - return sorted[Math.floor(sorted.length / 2)]; - } - - static setAlias(alias, timeseries) { - timeseries.target = alias; - return timeseries; - } - - static scale(factor, datapoints) { - return _.map(datapoints, point => { - return [ - point[0] * factor, - point[1] - ]; - }); - } - - static delta(datapoints) { - let newSeries = []; - let deltaValue; - for (var i = 1; i < datapoints.length; i++) { - deltaValue = datapoints[i][0] - datapoints[i - 1][0]; - newSeries.push([deltaValue, datapoints[i][1]]); - } - return newSeries; - } - - static groupByWrapper(interval, groupFunc, datapoints) { - var groupByCallback = DataProcessor.aggregationFunctions[groupFunc]; - return DataProcessor.groupBy(interval, groupByCallback, datapoints); - } - - static aggregateByWrapper(interval, aggregateFunc, datapoints) { - // Flatten all points in frame and then just use groupBy() - var flattenedPoints = _.flatten(datapoints, true); - var groupByCallback = DataProcessor.aggregationFunctions[aggregateFunc]; - return DataProcessor.groupBy(interval, groupByCallback, flattenedPoints); - } - - static aggregateWrapper(groupByCallback, interval, datapoints) { - var flattenedPoints = _.flatten(datapoints, true); - return DataProcessor.groupBy(interval, groupByCallback, flattenedPoints); - } - - static get aggregationFunctions() { - return { - avg: this.AVERAGE, - min: this.MIN, - max: this.MAX, - median: this.MEDIAN - }; - } - - static get metricFunctions() { - return { - groupBy: this.groupByWrapper, - scale: this.scale, - delta: this.delta, - aggregateBy: this.aggregateByWrapper, - average: _.partial(this.aggregateWrapper, this.AVERAGE), - min: _.partial(this.aggregateWrapper, this.MIN), - max: _.partial(this.aggregateWrapper, this.MAX), - median: _.partial(this.aggregateWrapper, this.MEDIAN), - sumSeries: this.sumSeries, - top: _.partial(this.limit, 'top'), - bottom: _.partial(this.limit, 'bottom'), - setAlias: this.setAlias, - }; - } -} - -function sortByTime(series) { - return _.sortBy(series, function(point) { - return point[1]; - }); -} - -/** - * Interpolate series with gaps - */ -function interpolateSeries(series) { - var left, right; - - // Interpolate series - for (var i = series.length - 1; i >= 0; i--) { - if (!series[i][0]) { - left = findNearestLeft(series, series[i]); - right = findNearestRight(series, series[i]); - if (!left) { - left = right; - } - if (!right) { - right = left; - } - series[i][0] = linearInterpolation(series[i][1], left, right); - } - } - return series; -} - -function linearInterpolation(timestamp, left, right) { - if (left[1] === right[1]) { - return (left[0] + right[0]) / 2; - } else { - return (left[0] + (right[0] - left[0]) / (right[1] - left[1]) * (timestamp - left[1])); - } -} - -function findNearestRight(series, point) { - var point_index = _.indexOf(series, point); - var nearestRight; - for (var i = point_index; i < series.length; i++) { - if (series[i][0] !== null) { - return series[i]; - } - } - return nearestRight; -} - -function findNearestLeft(series, point) { - var point_index = _.indexOf(series, point); - var nearestLeft; - for (var i = point_index; i > 0; i--) { - if (series[i][0] !== null) { - return series[i]; - } - } - return nearestLeft; -} diff --git a/src/datasource-zabbix/dataProcessor.js b/src/datasource-zabbix/dataProcessor.js new file mode 100644 index 0000000..dd299b3 --- /dev/null +++ b/src/datasource-zabbix/dataProcessor.js @@ -0,0 +1,291 @@ +import _ from 'lodash'; +import * as utils from './utils'; + +/** + * Downsample datapoints series + */ +function downsampleSeries(datapoints, time_to, ms_interval, func) { + var downsampledSeries = []; + var 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 = []; + + for (var 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 { + value_avg = points_num ? points_sum / points_num : 0; + + if (func === "max") { + downsampledSeries.push([_.max(frame), timeWindow.to]); + } + else if (func === "min") { + downsampledSeries.push([_.min(frame), timeWindow.to]); + } + + // avg by default + else { + downsampledSeries.push([value_avg, timeWindow.to]); + } + + // Shift time window + timeWindow.to = timeWindow.from; + timeWindow.from -= ms_interval; + + points_sum = 0; + points_num = 0; + frame = []; + + // Process point again + i++; + } + } + return downsampledSeries.reverse(); +} + +/** + * Group points by given time interval + * datapoints: [[, ], ...] + */ +function groupBy(interval, groupByCallback, datapoints) { + var ms_interval = utils.parseInterval(interval); + + // Calculate frame timestamps + var frames = _.groupBy(datapoints, function(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) { + return point[0]; + }); + return groupByCallback(points); + }); + + // Convert points to Grafana format + return sortByTime(_.map(grouped, function(value, timestamp) { + return [Number(value), Number(timestamp)]; + })); +} + +function sumSeries(timeseries) { + + // Calculate new points for interpolation + var new_timestamps = _.uniq(_.map(_.flatten(timeseries, true), function(point) { + return point[1]; + })); + new_timestamps = _.sortBy(new_timestamps); + + var interpolated_timeseries = _.map(timeseries, function(series) { + var timestamps = _.map(series, function(point) { + return point[1]; + }); + var new_points = _.map(_.difference(new_timestamps, timestamps), function(timestamp) { + return [null, timestamp]; + }); + var 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--) { + sum = 0; + for (var j = interpolated_timeseries.length - 1; j >= 0; j--) { + sum += interpolated_timeseries[j][i][0]; + } + new_timeseries.push([sum, new_timestamps[i]]); + } + + return sortByTime(new_timeseries); +} + +function limit(order, n, orderByFunc, timeseries) { + let orderByCallback = aggregationFunctions[orderByFunc]; + let sortByIteratee = (ts) => { + let values = _.map(ts.datapoints, (point) => { + return point[0]; + }); + return orderByCallback(values); + }; + let sortedTimeseries = _.sortBy(timeseries, sortByIteratee); + if (order === 'bottom') { + return sortedTimeseries.slice(0, n); + } else { + return sortedTimeseries.slice(-n); + } +} + +function AVERAGE(values) { + var sum = 0; + _.each(values, function(value) { + sum += value; + }); + return sum / values.length; +} + +function MIN(values) { + return _.min(values); +} + +function MAX(values) { + return _.max(values); +} + +function MEDIAN(values) { + var sorted = _.sortBy(values); + return sorted[Math.floor(sorted.length / 2)]; +} + +function setAlias(alias, timeseries) { + timeseries.target = alias; + return timeseries; +} + +function scale(factor, datapoints) { + return _.map(datapoints, point => { + return [ + point[0] * factor, + point[1] + ]; + }); +} + +function delta(datapoints) { + let newSeries = []; + let deltaValue; + for (var i = 1; i < datapoints.length; i++) { + deltaValue = datapoints[i][0] - datapoints[i - 1][0]; + newSeries.push([deltaValue, datapoints[i][1]]); + } + return newSeries; +} + +function groupByWrapper(interval, groupFunc, datapoints) { + var groupByCallback = aggregationFunctions[groupFunc]; + return groupBy(interval, groupByCallback, datapoints); +} + +function aggregateByWrapper(interval, aggregateFunc, datapoints) { + // Flatten all points in frame and then just use groupBy() + var flattenedPoints = _.flatten(datapoints, true); + var groupByCallback = aggregationFunctions[aggregateFunc]; + return groupBy(interval, groupByCallback, flattenedPoints); +} + +function aggregateWrapper(groupByCallback, interval, datapoints) { + var flattenedPoints = _.flatten(datapoints, true); + return groupBy(interval, groupByCallback, flattenedPoints); +} + +function sortByTime(series) { + return _.sortBy(series, function(point) { + return point[1]; + }); +} + +/** + * Interpolate series with gaps + */ +function interpolateSeries(series) { + var left, right; + + // Interpolate series + for (var i = series.length - 1; i >= 0; i--) { + if (!series[i][0]) { + left = findNearestLeft(series, series[i]); + right = findNearestRight(series, series[i]); + if (!left) { + left = right; + } + if (!right) { + right = left; + } + series[i][0] = linearInterpolation(series[i][1], left, right); + } + } + return series; +} + +function linearInterpolation(timestamp, left, right) { + if (left[1] === right[1]) { + return (left[0] + right[0]) / 2; + } else { + return (left[0] + (right[0] - left[0]) / (right[1] - left[1]) * (timestamp - left[1])); + } +} + +function findNearestRight(series, point) { + var point_index = _.indexOf(series, point); + var nearestRight; + for (var i = point_index; i < series.length; i++) { + if (series[i][0] !== null) { + return series[i]; + } + } + return nearestRight; +} + +function findNearestLeft(series, point) { + var point_index = _.indexOf(series, point); + var nearestLeft; + for (var i = point_index; i > 0; i--) { + if (series[i][0] !== null) { + return series[i]; + } + } + return nearestLeft; +} + +let metricFunctions = { + groupBy: groupByWrapper, + scale: scale, + delta: delta, + aggregateBy: aggregateByWrapper, + average: _.partial(aggregateWrapper, AVERAGE), + min: _.partial(aggregateWrapper, MIN), + max: _.partial(aggregateWrapper, MAX), + median: _.partial(aggregateWrapper, MEDIAN), + sumSeries: sumSeries, + top: _.partial(limit, 'top'), + bottom: _.partial(limit, 'bottom'), + setAlias: setAlias +}; + +let aggregationFunctions = { + avg: AVERAGE, + min: MIN, + max: MAX, + median: MEDIAN +}; + +export default { + downsampleSeries: downsampleSeries, + groupBy: groupBy, + AVERAGE: AVERAGE, + MIN: MIN, + MAX: MAX, + MEDIAN: MEDIAN, + + get aggregationFunctions() { + return aggregationFunctions; + }, + + get metricFunctions() { + return metricFunctions; + } +}; diff --git a/src/datasource-zabbix/datasource.js b/src/datasource-zabbix/datasource.js index 9b2f991..f4867e1 100644 --- a/src/datasource-zabbix/datasource.js +++ b/src/datasource-zabbix/datasource.js @@ -4,7 +4,7 @@ import * as dateMath from 'app/core/utils/datemath'; import * as utils from './utils'; import * as migrations from './migrations'; import * as metricFunctions from './metricFunctions'; -import DataProcessor from './DataProcessor'; +import dataProcessor from './dataProcessor'; import responseHandler from './responseHandler'; import './zabbix.js'; import {ZabbixAPIError} from './zabbixAPICore.service.js'; @@ -394,15 +394,15 @@ function bindFunctionDefs(functionDefs, category) { return _.map(aggFuncDefs, function(func) { var funcInstance = metricFunctions.createFuncInstance(func.def, func.params); - return funcInstance.bindFunction(DataProcessor.metricFunctions); + return funcInstance.bindFunction(dataProcessor.metricFunctions); }); } function downsampleSeries(timeseries_data, options) { return _.map(timeseries_data, timeseries => { if (timeseries.datapoints.length > options.maxDataPoints) { - timeseries.datapoints = DataProcessor - .groupBy(options.interval, DataProcessor.AVERAGE, timeseries.datapoints); + timeseries.datapoints = dataProcessor + .groupBy(options.interval, dataProcessor.AVERAGE, timeseries.datapoints); } return timeseries; }); From d15e9c8acd1929bcfe88a68342786e22edc69856 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Mon, 14 Nov 2016 18:01:54 +0300 Subject: [PATCH 62/64] Refactor: zabbixCache renamed to zabbixCachingProxy. --- src/datasource-zabbix/zabbix.js | 20 +++++++++---------- ...rvice.js => zabbixCachingProxy.service.js} | 0 2 files changed, 10 insertions(+), 10 deletions(-) rename src/datasource-zabbix/{zabbixCache.service.js => zabbixCachingProxy.service.js} (100%) diff --git a/src/datasource-zabbix/zabbix.js b/src/datasource-zabbix/zabbix.js index 7500b91..335c362 100644 --- a/src/datasource-zabbix/zabbix.js +++ b/src/datasource-zabbix/zabbix.js @@ -2,7 +2,7 @@ import angular from 'angular'; import _ from 'lodash'; import * as utils from './utils'; import './zabbixAPI.service.js'; -import './zabbixCache.service.js'; +import './zabbixCachingProxy.service.js'; // Use factory() instead service() for multiple data sources support. // Each Zabbix data source instance should initialize its own API instance. @@ -17,11 +17,11 @@ function ZabbixFactory(zabbixAPIService, ZabbixCachingProxy) { var ZabbixAPI = zabbixAPIService; this.zabbixAPI = new ZabbixAPI(url, username, password, basicAuth, withCredentials); - // Initialize cache - this.cache = new ZabbixCachingProxy(this.zabbixAPI, cacheTTL); + // Initialize caching proxy for requests + this.cachingProxy = new ZabbixCachingProxy(this.zabbixAPI, cacheTTL); // Proxy methods - this.getHistory = this.cache.getHistory.bind(this.cache); + this.getHistory = this.cachingProxy.getHistory.bind(this.cachingProxy); this.getTrend = this.zabbixAPI.getTrend.bind(this.zabbixAPI); this.getEvents = this.zabbixAPI.getEvents.bind(this.zabbixAPI); @@ -38,7 +38,7 @@ function ZabbixFactory(zabbixAPIService, ZabbixCachingProxy) { } getAllGroups() { - return this.cache.getGroups(); + return this.cachingProxy.getGroups(); } getGroups(groupFilter) { @@ -53,7 +53,7 @@ function ZabbixFactory(zabbixAPIService, ZabbixCachingProxy) { return this.getGroups(groupFilter) .then(groups => { let groupids = _.map(groups, 'groupid'); - return this.cache.getHosts(groupids); + return this.cachingProxy.getHosts(groupids); }); } @@ -69,7 +69,7 @@ function ZabbixFactory(zabbixAPIService, ZabbixCachingProxy) { return this.getHosts(groupFilter, hostFilter) .then(hosts => { let hostids = _.map(hosts, 'hostid'); - return this.cache.getApps(hostids); + return this.cachingProxy.getApps(hostids); }); } @@ -78,7 +78,7 @@ function ZabbixFactory(zabbixAPIService, ZabbixCachingProxy) { .then(hosts => { let hostids = _.map(hosts, 'hostid'); if (appFilter) { - return this.cache.getApps(hostids) + return this.cachingProxy.getApps(hostids) .then(apps => filterByQuery(apps, appFilter)); } else { return { @@ -93,10 +93,10 @@ function ZabbixFactory(zabbixAPIService, ZabbixCachingProxy) { return this.getApps(groupFilter, hostFilter, appFilter) .then(apps => { if (apps.appFilterEmpty) { - return this.cache.getItems(apps.hostids, undefined, options.itemtype); + return this.cachingProxy.getItems(apps.hostids, undefined, options.itemtype); } else { let appids = _.map(apps, 'applicationid'); - return this.cache.getItems(undefined, appids, options.itemtype); + return this.cachingProxy.getItems(undefined, appids, options.itemtype); } }) .then(items => { diff --git a/src/datasource-zabbix/zabbixCache.service.js b/src/datasource-zabbix/zabbixCachingProxy.service.js similarity index 100% rename from src/datasource-zabbix/zabbixCache.service.js rename to src/datasource-zabbix/zabbixCachingProxy.service.js From f56c4c66d641212979c4fa45d5799d85bf634de2 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Mon, 14 Nov 2016 22:28:37 +0300 Subject: [PATCH 63/64] ZabbixAPI: improved requests caching. Cache groups, hosts, apps and items. --- src/datasource-zabbix/zabbix.js | 6 +- .../zabbixCachingProxy.service.js | 87 ++++++++++++------- 2 files changed, 59 insertions(+), 34 deletions(-) diff --git a/src/datasource-zabbix/zabbix.js b/src/datasource-zabbix/zabbix.js index 335c362..fd995b0 100644 --- a/src/datasource-zabbix/zabbix.js +++ b/src/datasource-zabbix/zabbix.js @@ -18,7 +18,11 @@ function ZabbixFactory(zabbixAPIService, ZabbixCachingProxy) { this.zabbixAPI = new ZabbixAPI(url, username, password, basicAuth, withCredentials); // Initialize caching proxy for requests - this.cachingProxy = new ZabbixCachingProxy(this.zabbixAPI, cacheTTL); + let cacheOptions = { + enabled: true, + ttl: cacheTTL + }; + this.cachingProxy = new ZabbixCachingProxy(this.zabbixAPI, cacheOptions); // Proxy methods this.getHistory = this.cachingProxy.getHistory.bind(this.cachingProxy); diff --git a/src/datasource-zabbix/zabbixCachingProxy.service.js b/src/datasource-zabbix/zabbixCachingProxy.service.js index 410284e..ffca3b2 100644 --- a/src/datasource-zabbix/zabbixCachingProxy.service.js +++ b/src/datasource-zabbix/zabbixCachingProxy.service.js @@ -5,12 +5,13 @@ import _ from 'lodash'; // Each datasource instance must initialize its own cache. /** @ngInject */ -function ZabbixCachingProxyFactory($interval) { +function ZabbixCachingProxyFactory() { class ZabbixCachingProxy { - constructor(zabbixAPI, ttl) { + constructor(zabbixAPI, cacheOptions) { this.zabbixAPI = zabbixAPI; - this.ttl = ttl; + this.cacheEnabled = cacheOptions.enabled; + this.ttl = cacheOptions.ttl || 600000; // 10 minutes by default // Internal objects for data storing this._groups = undefined; @@ -22,6 +23,13 @@ function ZabbixCachingProxyFactory($interval) { trends: {} }; + this.cache = { + groups: {}, + hosts: {}, + applications: {}, + items: {} + }; + // Check is a service initialized or not this._initialized = undefined; @@ -32,7 +40,7 @@ function ZabbixCachingProxyFactory($interval) { this.refresh = callOnce(this._refresh, this.refreshPromise); // Update cache periodically - $interval(_.bind(this.refresh, this), this.ttl); + // $interval(_.bind(this.refresh, this), this.ttl); // Don't run duplicated history requests this.getHistory = callAPIRequestOnce(_.bind(this.zabbixAPI.getHistory, this.zabbixAPI), @@ -41,19 +49,19 @@ function ZabbixCachingProxyFactory($interval) { // Don't run duplicated requests this.groupPromises = {}; this.getGroupsOnce = callAPIRequestOnce(_.bind(this.zabbixAPI.getGroups, this.zabbixAPI), - this.groupPromises, getAPIRequestHash); + this.groupPromises, getRequestHash); this.hostPromises = {}; this.getHostsOnce = callAPIRequestOnce(_.bind(this.zabbixAPI.getHosts, this.zabbixAPI), - this.hostPromises, getAPIRequestHash); + this.hostPromises, getRequestHash); this.appPromises = {}; this.getAppsOnce = callAPIRequestOnce(_.bind(this.zabbixAPI.getApps, this.zabbixAPI), - this.appPromises, getAPIRequestHash); + this.appPromises, getRequestHash); this.itemPromises = {}; this.getItemsOnce = callAPIRequestOnce(_.bind(this.zabbixAPI.getItems, this.zabbixAPI), - this.itemPromises, getAPIRequestHash); + this.itemPromises, getRequestHash); } _refresh() { @@ -70,41 +78,50 @@ function ZabbixCachingProxyFactory($interval) { }); } - getGroups() { - if (this._groups) { - return Promise.resolve(this._groups); + isExpired(cacheObject) { + if (cacheObject) { + let object_age = Date.now() - cacheObject.timestamp; + return !(cacheObject.timestamp && object_age < this.ttl); } else { - return this.getGroupsOnce() - .then(groups => { - this._groups = groups; - return groups; + return true; + } + } + + /** + * Check that result is present in cache and up to date + * or send request to API. + */ + proxyRequest(request, params, cacheObject) { + let hash = getRequestHash(params); + if (this.cacheEnabled && !this.isExpired(cacheObject[hash])) { + return Promise.resolve(cacheObject[hash].value); + } else { + return request(...params) + .then(result => { + cacheObject[hash] = { + value: result, + timestamp: Date.now() + }; + return result; }); } } + getGroups() { + return this.proxyRequest(this.getGroupsOnce, [], this.cache.groups); + } + getHosts(groupids) { - return this.getHostsOnce(groupids) - .then(hosts => { - // iss #196 - disable caching due performance issues - //this._hosts = _.union(this._hosts, hosts); - return hosts; - }); + return this.proxyRequest(this.getHostsOnce, [groupids], this.cache.hosts); } getApps(hostids) { - return this.getAppsOnce(hostids) - .then(apps => { - return apps; - }); + return this.proxyRequest(this.getAppsOnce, [hostids], this.cache.applications); } getItems(hostids, appids, itemtype) { - return this.getItemsOnce(hostids, appids, itemtype) - .then(items => { - // iss #196 - disable caching due performance issues - //this._items = _.union(this._items, items); - return items; - }); + let params = [hostids, appids, itemtype]; + return this.proxyRequest(this.getItemsOnce, params, this.cache.items); } getHistoryFromCache(items, time_from, time_till) { @@ -195,12 +212,16 @@ function callAPIRequestOnce(func, promiseKeeper, argsHashFunc) { }; } -function getAPIRequestHash(args) { +function getRequestHash(args) { var requestStamp = _.map(args, arg => { if (arg === undefined) { return 'undefined'; } else { - return arg.toString(); + if (_.isArray(arg)) { + return arg.sort().toString(); + } else { + return arg.toString(); + } } }).join(); return requestStamp.getHash(); From 9392d45ad92fc91aacf431213123c8cf1c77cb9d Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Mon, 14 Nov 2016 22:36:21 +0300 Subject: [PATCH 64/64] Refactor: removed unused code from zabbixCachingProxy. --- src/datasource-zabbix/utils.js | 19 ++++++ .../zabbixCachingProxy.service.js | 66 ++----------------- 2 files changed, 23 insertions(+), 62 deletions(-) diff --git a/src/datasource-zabbix/utils.js b/src/datasource-zabbix/utils.js index 01e0932..c410d57 100644 --- a/src/datasource-zabbix/utils.js +++ b/src/datasource-zabbix/utils.js @@ -93,6 +93,25 @@ export function convertToZabbixAPIUrl(url) { } } +/** + * Wrap function to prevent multiple calls + * when waiting for result. + */ +export function callOnce(func, promiseKeeper) { + return function() { + if (!promiseKeeper) { + promiseKeeper = Promise.resolve( + func.apply(this, arguments) + .then(result => { + promiseKeeper = null; + return result; + }) + ); + } + return promiseKeeper; + }; +} + // Fix for backward compatibility with lodash 2.4 if (!_.includes) { _.includes = _.contains; diff --git a/src/datasource-zabbix/zabbixCachingProxy.service.js b/src/datasource-zabbix/zabbixCachingProxy.service.js index ffca3b2..b1cd7ce 100644 --- a/src/datasource-zabbix/zabbixCachingProxy.service.js +++ b/src/datasource-zabbix/zabbixCachingProxy.service.js @@ -14,34 +14,17 @@ function ZabbixCachingProxyFactory() { this.ttl = cacheOptions.ttl || 600000; // 10 minutes by default // Internal objects for data storing - this._groups = undefined; - this._hosts = undefined; - this._applications = undefined; - this._items = undefined; - this.storage = { - history: {}, - trends: {} - }; - this.cache = { groups: {}, hosts: {}, applications: {}, - items: {} + items: {}, + history: {}, + trends: {} }; - // Check is a service initialized or not - this._initialized = undefined; - - this.refreshPromise = false; this.historyPromises = {}; - // Wrap _refresh() method to call it once. - this.refresh = callOnce(this._refresh, this.refreshPromise); - - // Update cache periodically - // $interval(_.bind(this.refresh, this), this.ttl); - // Don't run duplicated history requests this.getHistory = callAPIRequestOnce(_.bind(this.zabbixAPI.getHistory, this.zabbixAPI), this.historyPromises, getHistoryRequestHash); @@ -64,20 +47,6 @@ function ZabbixCachingProxyFactory() { this.itemPromises, getRequestHash); } - _refresh() { - let promises = [ - this.zabbixAPI.getGroups() - ]; - - return Promise.all(promises) - .then(results => { - if (results.length) { - this._groups = results[0]; - } - this._initialized = true; - }); - } - isExpired(cacheObject) { if (cacheObject) { let object_age = Date.now() - cacheObject.timestamp; @@ -125,7 +94,7 @@ function ZabbixCachingProxyFactory() { } getHistoryFromCache(items, time_from, time_till) { - var historyStorage = this.storage.history; + var historyStorage = this.cache.history; var full_history; var expired = _.filter(_.keyBy(items, 'itemid'), (item, itemid) => { return !historyStorage[itemid]; @@ -156,14 +125,6 @@ function ZabbixCachingProxyFactory() { getHistoryFromAPI(items, time_from, time_till) { return this.zabbixAPI.getHistory(items, time_from, time_till); } - - getHost(hostid) { - return _.find(this._hosts, {'hostid': hostid}); - } - - getItem(itemid) { - return _.find(this._items, {'itemid': itemid}); - } } return ZabbixCachingProxy; @@ -173,25 +134,6 @@ angular .module('grafana.services') .factory('ZabbixCachingProxy', ZabbixCachingProxyFactory); -/** - * Wrap function to prevent multiple calls - * when waiting for result. - */ -function callOnce(func, promiseKeeper) { - return function() { - if (!promiseKeeper) { - promiseKeeper = Promise.resolve( - func.apply(this, arguments) - .then(result => { - promiseKeeper = null; - return result; - }) - ); - } - return promiseKeeper; - }; -} - /** * Wrap zabbix API request to prevent multiple calls * with same params when waiting for result.