From 7842f468d4ba89f7a444989e3bbb91bf790f3986 Mon Sep 17 00:00:00 2001 From: Tom Zhang Date: Thu, 14 Jul 2016 11:10:48 +0930 Subject: [PATCH 01/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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 74503655bd55956af37e52227b80f1e8ced3fa7e Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Thu, 15 Sep 2016 22:14:26 +0300 Subject: [PATCH 09/11] 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 10/11] 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 11/11] 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" },