| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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. |
| */ |
| |
| var App = require('app'); |
| var stringUtils = require('utils/string_utils'); |
| |
| App.WidgetMixin = Ember.Mixin.create({ |
| |
| /** |
| * type of metric query from which the widget is comprised |
| */ |
| |
| metricType: 'POINT_IN_TIME', |
| /** |
| * @type {RegExp} |
| * @const |
| */ |
| EXPRESSION_REGEX: /\$\{([\w\s\.\,\+\-\*\/\(\)\:\=\[\]]*)\}/g, |
| |
| /** |
| * @type {RegExp} |
| * @const |
| */ |
| MATH_EXPRESSION_REGEX: /^[\d\s\+\-\*\/\(\)\.]+$/, |
| |
| /** |
| * @type {RegExp} |
| * @const |
| */ |
| VALUE_NAME_REGEX: /[^(\s+\-\*\/\\+\s+)+](\w+\s+\w+)?[\w\.\,\-\:\=\[\]]*/g, |
| |
| /** |
| * @type {string} |
| * @const |
| */ |
| CLONE_SUFFIX: '(Copy)', |
| |
| /** |
| * @type {number|null} |
| */ |
| timeoutId: null, |
| |
| /** |
| * common metrics container |
| * @type {Array} |
| */ |
| metrics: [], |
| |
| /** |
| * |
| */ |
| aggregatorFunc: ['._sum', '._avg', '._min', '._max', '._rate'], |
| |
| /** |
| * @type {boolean} |
| */ |
| isLoaded: false, |
| |
| /** |
| * @type {App.Widget} |
| * @default null |
| */ |
| content: null, |
| |
| /** |
| * color of content calculated by thresholds |
| * @type {string} |
| */ |
| contentColor: Em.computed.ifThenElse('value', 'green', 'grey'), |
| |
| beforeRender: function () { |
| this.loadMetrics(); |
| }, |
| |
| |
| /** |
| * load metrics |
| */ |
| loadMetrics: function () { |
| var requestData = this.getRequestData(this.get('content.metrics')), |
| request, |
| requestCounter = 0, |
| self = this; |
| |
| this.set('metrics', []); |
| |
| for (var i in requestData) { |
| request = requestData[i]; |
| requestCounter++; |
| if (this.get('content.widgetType') === 'HEATMAP') { |
| if (request.service_name === 'STACK') { |
| this.getHostsMetrics(request).complete(function () { |
| requestCounter--; |
| if (requestCounter === 0) self.onMetricsLoaded(); |
| }); |
| } else { |
| this.getHostComponentsMetrics(request).complete(function () { |
| requestCounter--; |
| if (requestCounter === 0) self.onMetricsLoaded(); |
| }); |
| } |
| } else if (request.host_component_criteria) { |
| App.WidgetLoadAggregator.add({ |
| data: request, |
| context: this, |
| startCallName: 'getHostComponentMetrics', |
| successCallback: this.getHostComponentMetricsSuccessCallback, |
| errorCallback: this.getMetricsErrorCallback, |
| completeCallback: function (xhr) { |
| requestCounter--; |
| if (requestCounter === 0) this.onMetricsLoaded(); |
| if (this.get('graphView')) { |
| var graph = this.get('childViews') && this.get('childViews').findProperty('runningRequests'); |
| if (graph) { |
| var requestsArrayName = graph.get('isPopup') ? 'runningPopupRequests' : 'runningRequests'; |
| graph.set(requestsArrayName, graph.get(requestsArrayName).reject(function (item) { |
| return item === xhr; |
| })); |
| } |
| } |
| } |
| }); |
| } else { |
| App.WidgetLoadAggregator.add({ |
| data: request, |
| context: this, |
| startCallName: 'getServiceComponentMetrics', |
| successCallback: this.getMetricsSuccessCallback, |
| errorCallback: this.getMetricsErrorCallback, |
| completeCallback: function (xhr) { |
| requestCounter--; |
| if (requestCounter === 0) this.onMetricsLoaded(); |
| if (this.get('graphView')) { |
| var graph = this.get('childViews') && this.get('childViews').findProperty('runningRequests'); |
| if (graph) { |
| var requestsArrayName = graph.get('isPopup') ? 'runningPopupRequests' : 'runningRequests'; |
| graph.set(requestsArrayName, graph.get(requestsArrayName).reject(function (item) { |
| return item === xhr; |
| })); |
| } |
| } |
| } |
| }); |
| } |
| } |
| }.observes('customTimeRange', 'content.properties.time_range'), |
| |
| /** |
| * get data formatted for request |
| * @param {Array} metrics |
| */ |
| getRequestData: function (metrics) { |
| var requestsData = {}; |
| if (metrics) { |
| metrics.forEach(function (metric) { |
| var key; |
| if (metric.host_component_criteria) { |
| key = metric.service_name + '_' + metric.component_name + '_' + metric.host_component_criteria; |
| } else { |
| key = metric.service_name + '_' + metric.component_name; |
| } |
| var requestMetric = $.extend({}, metric); |
| |
| if (requestsData[key]) { |
| requestsData[key]["metric_paths"].push({ |
| metric_path: requestMetric["metric_path"], |
| metric_type: this.get('metricType'), |
| id: requestMetric["metric_path"] + "_" + this.get('metricType'), |
| context: this |
| }); |
| } else { |
| requestMetric["metric_paths"] = [{ |
| metric_path: requestMetric["metric_path"], |
| metric_type: this.get('metricType'), |
| id: requestMetric["metric_path"] + "_" + this.get('metricType'), |
| context: this}]; |
| delete requestMetric["metric_path"]; |
| requestMetric.tag = this.get('content.tag'); |
| requestsData[key] = requestMetric; |
| } |
| }, this); |
| } |
| return requestsData; |
| }, |
| |
| /** |
| * Tweak host component criteria from the actual host component criteria |
| * @param {object} request |
| */ |
| computeHostComponentCriteria: function (request) { |
| return request.host_component_criteria.replace('host_components/', '&').trim(); |
| }, |
| |
| /** |
| * make GET call to server in order to fetch service-component metrics |
| * @param {object} request |
| * @returns {$.ajax} |
| */ |
| getServiceComponentMetrics: function (request) { |
| var xhr = App.ajax.send({ |
| name: 'widgets.serviceComponent.metrics.get', |
| sender: this, |
| data: { |
| serviceName: request.service_name, |
| componentName: request.component_name, |
| metricPaths: this.prepareMetricPaths(request.metric_paths) |
| } |
| }); |
| if (this.get('graphView')) { |
| var graph = this.get('childViews') && this.get('childViews').findProperty('runningRequests'); |
| if (graph) { |
| var requestsArrayName = graph.get('isPopup') ? 'runningPopupRequests' : 'runningRequests'; |
| graph.get(requestsArrayName).push(xhr); |
| } |
| } |
| return xhr; |
| }, |
| |
| /** |
| * aggregate all metric names in the query. Add time range and step to temporal queries |
| * @param {Array} metricPaths |
| * @returns {string} |
| */ |
| prepareMetricPaths: function(metricPaths) { |
| var temporalMetrics = metricPaths.filterProperty('metric_type', 'TEMPORAL'); |
| var pointInTimeMetrics = metricPaths.filterProperty('metric_type', 'POINT_IN_TIME'); |
| var result = temporalMetrics.length ? temporalMetrics[0].context.addTimeProperties(temporalMetrics.mapProperty('metric_path')) : []; |
| |
| if (pointInTimeMetrics.length) { |
| result = result.concat(pointInTimeMetrics.mapProperty('metric_path')); |
| } |
| |
| return result.join(','); |
| }, |
| |
| |
| /** |
| * make GET call to server in order to fetch specific host-component metrics |
| * @param {object} request |
| * @returns {$.ajax} |
| */ |
| getHostComponentMetrics: function (request) { |
| var metricPaths = this.prepareMetricPaths(request.metric_paths); |
| var data = { |
| componentName: request.component_name, |
| metricPaths: this.prepareMetricPaths(request.metric_paths), |
| hostComponentCriteria: this.computeHostComponentCriteria(request) |
| }; |
| |
| if (request.tag) { |
| data.selectedHostsParam = '&HostRoles/host_name.in(' + App.HDFSService.find('HDFS').get('masterComponentGroups').findProperty('name', request.tag).hosts.join(',') + ')'; |
| } |
| |
| if (metricPaths.length) { |
| var xhr = App.ajax.send({ |
| name: 'widgets.hostComponent.metrics.get', |
| sender: this, |
| data: data |
| }), |
| graph = this.get('graphView') && this.get('childViews') && this.get('childViews').findProperty('runningRequests'); |
| if (graph) { |
| var requestsArrayName = graph.get('isPopup') ? 'runningPopupRequests' : 'runningRequests'; |
| graph.get(requestsArrayName).push(xhr); |
| } |
| return xhr; |
| } |
| return jQuery.Deferred().reject().promise(); |
| }, |
| |
| getHostComponentMetricsSuccessCallback: function (data) { |
| if (data.items[0]) { |
| this.getMetricsSuccessCallback(data.items[0]); |
| } |
| }, |
| |
| /** |
| * callback on getting aggregated metrics and host component metrics |
| * @param data |
| */ |
| getMetricsSuccessCallback: function (data) { |
| var atLeastOneMetricPresent = false; |
| |
| if (this.get('content.metrics')) { |
| this.get('content.metrics').forEach(function (_metric) { |
| var metricPath = _metric.metric_path; |
| |
| var metric_data = Em.get(data, metricPath.replace(/\//g, '.')); |
| if (Em.isNone(metric_data)) { |
| metric_data = this.parseMetricsWithAggregatorFunc(data, metricPath); |
| } |
| if (!Em.isNone(metric_data)) { |
| atLeastOneMetricPresent = true; |
| _metric.data = metric_data; |
| this.get('metrics').pushObject(_metric); |
| } |
| }, this); |
| |
| if (!atLeastOneMetricPresent) { |
| this.disableGraph(); |
| } |
| } |
| }, |
| |
| parseMetricsWithAggregatorFunc: function(data, metric_path) { |
| let isAggregatorFunc = false; |
| let metric = null; |
| this.aggregatorFunc.forEach(function (_item) { |
| if (metric_path.endsWith(_item) && !isAggregatorFunc) { |
| isAggregatorFunc = true; |
| var metricBeanProperty = metric_path.split("/").pop(); |
| var metricBean; |
| metric_path = metric_path.substring(0, metric_path.indexOf(metricBeanProperty)); |
| |
| if (metric_path.endsWith("/")) { |
| metric_path = metric_path.slice(0, -1); |
| } |
| metricBean = Em.get(data, metric_path.replace(/\//g, '.')); |
| if (!Em.isNone(metricBean)) { |
| metric = metricBean[metricBeanProperty]; |
| } |
| } |
| }, this); |
| return metric; |
| }, |
| |
| /** |
| * if no metrics were received from server then disable graph |
| */ |
| disableGraph: function() { |
| if (this.get('graphView')) { |
| var graph = this.get('childViews') && this.get('childViews').findProperty('_showMessage'); |
| if (graph) { |
| graph.set('hasData', false); |
| this.set('isExportButtonHidden', true); |
| graph._showMessage('info', this.t('graphs.noData.title'), this.t('graphs.noDataAtTime.message')); |
| } |
| } |
| this.set('metrics', this.get('metrics').reject(function (item) { |
| return this.get('content.metrics').someProperty('name', item.name); |
| }, this)); |
| }, |
| |
| /** |
| * error callback on getting aggregated metrics and host component metrics |
| * @param {object} xhr |
| * @param {string} textStatus |
| * @param {string} errorThrown |
| */ |
| getMetricsErrorCallback: function (xhr, textStatus, errorThrown) { |
| if (this.get('graphView') && !xhr.isForcedAbort) { |
| var graph = this.get('childViews') && this.get('childViews').findProperty('_showMessage'); |
| if (graph) { |
| if (xhr.readyState == 4 && xhr.status) { |
| textStatus = xhr.status + " " + textStatus; |
| } |
| graph.set('hasData', false); |
| this.set('isExportButtonHidden', true); |
| graph._showMessage('warn', this.t('graphs.error.title'), this.t('graphs.error.message').format(textStatus, errorThrown)); |
| this.set('metrics', this.get('metrics').reject(function (item) { |
| return this.get('content.metrics').someProperty('name', item.name); |
| }, this)); |
| } |
| } |
| }, |
| |
| /** |
| * make GET call to get metrics value for all host components |
| * @param {object} request |
| * @return {$.ajax} |
| */ |
| getHostComponentsMetrics: function (request) { |
| request.metric_paths = request.metric_paths.map((_metric) => { |
| return "host_components/" + _metric.metric_path; |
| }); |
| return App.ajax.send({ |
| name: 'widgets.serviceComponent.metrics.get', |
| sender: this, |
| data: { |
| serviceName: request.service_name, |
| componentName: request.component_name, |
| metricPaths: request.metric_paths.join(',') |
| }, |
| success: 'getHostComponentsMetricsSuccessCallback' |
| }); |
| }, |
| |
| |
| getHostComponentsMetricsSuccessCallback: function (data) { |
| var metrics = this.get('content.metrics'); |
| data.host_components.forEach(function (item) { |
| metrics.forEach(function (_metric) { |
| const metric = $.extend({}, _metric, true); |
| metric.hostName = item.HostRoles.host_name; |
| if (!Em.isNone(Em.get(item, _metric.metric_path.replace(/\//g, '.')))) { |
| metric.data = Em.get(item, _metric.metric_path.replace(/\//g, '.')); |
| } |
| this.get('metrics').pushObject(metric); |
| }, this); |
| }, this); |
| }, |
| |
| getHostsMetrics: function (request) { |
| var metricPaths = request.metric_paths.mapProperty('metric_path'); |
| return App.ajax.send({ |
| name: 'widgets.hosts.metrics.get', |
| sender: this, |
| data: { |
| metricPaths: metricPaths.join(',') |
| }, |
| success: 'getHostsMetricsSuccessCallback' |
| }); |
| }, |
| |
| getHostsMetricsSuccessCallback: function (data) { |
| var metrics = this.get('content.metrics'); |
| data.items.forEach(function (item) { |
| metrics.forEach(function (_metric) { |
| const metric = $.extend({}, _metric, true); |
| metric.hostName = item.Hosts.host_name; |
| if (!Em.isNone(Em.get(item, _metric.metric_path.replace(/\//g, '.')))) { |
| metric.data = Em.get(item, _metric.metric_path.replace(/\//g, '.')); |
| } |
| this.get('metrics').pushObject(metric); |
| }, this); |
| }, this); |
| }, |
| |
| /** |
| * callback on metrics loaded |
| */ |
| onMetricsLoaded: function () { |
| var self = this; |
| if (!this.get('isLoaded')) this.set('isLoaded', true); |
| this.drawWidget(); |
| clearTimeout(this.get('timeoutId')); |
| this.set('timeoutId', setTimeout(function () { |
| self.loadMetrics(); |
| }, App.contentUpdateInterval)); |
| }, |
| |
| |
| /** |
| * draw widget |
| */ |
| drawWidget: function () { |
| if (this.get('isLoaded')) { |
| this.calculateValues(); |
| this.set('value', (this.get('content.values')[0]) ? this.get('content.values')[0].computedValue : ''); |
| } |
| }, |
| |
| /** |
| * initialize tooltips |
| */ |
| initTooltip: function () { |
| var self = this; |
| |
| if (this.get('isLoaded')) { |
| Em.run.next(function(){ |
| if (self.get('state') === 'inDOM') { |
| App.tooltip(self.$(".corner-icon > .glyphicon-copy"), {title: Em.I18n.t('common.clone')}); |
| App.tooltip(self.$(".corner-icon > .glyphicon-edit"), {title: Em.I18n.t('common.edit')}); |
| App.tooltip(self.$(".corner-icon > .glyphicon-save"), {title: Em.I18n.t('common.export')}); |
| } |
| }); |
| } |
| }.observes('isLoaded'), |
| |
| willDestroyElement: function() { |
| this.$(".corner-icon > .glyphicon-copy").tooltip('destroy'); |
| this.$(".corner-icon > .glyphicon-edit").tooltip('destroy'); |
| this.$(".corner-icon > .glyphicon-save").tooltip('destroy'); |
| }, |
| |
| /** |
| * calculate series datasets for graph widgets |
| */ |
| calculateValues: function () { |
| this.get('content.values').forEach(function (value) { |
| var computeExpression = this.computeExpression(this.extractExpressions(value), this.get('metrics')); |
| value.computedValue = value.value.replace(this.get('EXPRESSION_REGEX'), function (match) { |
| var float = parseFloat(computeExpression[match]); |
| if (isNaN(float)) { |
| return computeExpression[match] || "<span class=\"grey\">n/a</span>"; |
| } else { |
| return String((float % 1 !== 0) ? float.toFixed(2) : float); |
| } |
| }); |
| }, this); |
| }, |
| |
| |
| /** |
| * extract expressions |
| * Example: |
| * input: "${a/b} equal ${b+a}" |
| * expressions: ['a/b', 'b+a'] |
| * |
| * @param {object} input |
| * @returns {Array} |
| */ |
| extractExpressions: function (input) { |
| var pattern = this.get('EXPRESSION_REGEX'), |
| expressions = [], |
| match; |
| |
| if (Em.isNone(input)) return expressions; |
| |
| while (match = pattern.exec(input.value)) { |
| expressions.push(match[1]); |
| } |
| return expressions; |
| }, |
| |
| |
| /** |
| * compute expression |
| * @param expressions |
| * @param metrics |
| * @returns {object} |
| */ |
| computeExpression: function (expressions, metrics) { |
| var result = {}; |
| |
| expressions.forEach(function (_expression) { |
| var validExpression = true; |
| var value = ""; |
| |
| //replace values with metrics data |
| var beforeCompute = _expression.replace(this.get('VALUE_NAME_REGEX'), function (match) { |
| if (window.isNaN(match)) { |
| if (metrics.someProperty('name', match)) { |
| return metrics.findProperty('name', match).data; |
| } else { |
| validExpression = false; |
| } |
| } else { |
| return match; |
| } |
| }); |
| |
| //check for correct math expression |
| if (!(validExpression && this.get('MATH_EXPRESSION_REGEX').test(beforeCompute))) { |
| validExpression = false; |
| } |
| |
| result['${' + _expression + '}'] = (validExpression) ? Number(window.eval(beforeCompute)).toString() : value; |
| }, this); |
| return result; |
| }, |
| |
| /* |
| * make call when clicking on "remove icon" on widget |
| */ |
| hideWidget: function (event) { |
| this.get('controller').hideWidget( |
| { |
| context: Em.Object.create({ |
| id: event.contexts[0], |
| nsLayout: event.contexts[1] |
| }) |
| } |
| ); |
| }, |
| |
| /* |
| * make call when clicking on "clone icon" on widget |
| */ |
| cloneWidget: function () { |
| var self = this; |
| return App.showConfirmationPopup( |
| function () { |
| self.postWidgetDefinition(true); |
| }, |
| Em.I18n.t('widget.clone.body').format(stringUtils.htmlEntities(self.get('content.widgetName'))), |
| null, |
| null, |
| Em.I18n.t('common.clone') |
| ); |
| }, |
| |
| /** |
| * collect all needed data to create new widget |
| * @returns {{WidgetInfo: {widget_name: *, widget_type: *, description: *, scope: *, metrics: *, values: *, properties: *}}} |
| */ |
| collectWidgetData: function () { |
| var widgetData = { |
| WidgetInfo: { |
| widget_name: this.get('content.widgetName'), |
| widget_type: this.get('content.widgetType'), |
| description: this.get('content.widgetDescription'), |
| scope: this.get('content.scope'), |
| "metrics": this.get('content.metrics').map(function (metric) { |
| return { |
| "name": metric.name, |
| "service_name": metric.service_name, |
| "component_name": metric.component_name, |
| "host_component_criteria": metric.host_component_criteria, |
| "metric_path": metric.metric_path |
| } |
| }), |
| values: this.get('content.values'), |
| properties: this.get('content.properties') |
| } |
| }; |
| |
| this.get('content.metrics').forEach(function (metric) { |
| if (metric.tag) widgetData.WidgetInfo.tag = metric.tag; |
| }); |
| |
| return widgetData; |
| }, |
| |
| /** |
| * post widget definition to server |
| * @param {boolean} isClone |
| * * @param {boolean} isEditClonedWidget |
| * @returns {$.ajax} |
| */ |
| postWidgetDefinition: function (isClone, isEditClonedWidget) { |
| var data = this.collectWidgetData(); |
| if (isClone) { |
| data.WidgetInfo.widget_name += this.get('CLONE_SUFFIX'); |
| data.WidgetInfo.scope = 'USER'; |
| } |
| var successCallback = isEditClonedWidget ? 'editNewClonedWidget' : 'postWidgetDefinitionSuccessCallback'; |
| return App.ajax.send({ |
| name: 'widgets.wizard.add', |
| sender: this, |
| data: { |
| data: data |
| }, |
| success: successCallback |
| }); |
| }, |
| |
| postWidgetDefinitionSuccessCallback: function (data) { |
| var widgets = this.get('content.layout.widgets').toArray(); |
| widgets.pushObject(Em.Object.create({ |
| id: data.resources[0].WidgetInfo.id |
| })); |
| var mainServiceInfoMetricsController = App.router.get('mainServiceInfoMetricsController'); |
| mainServiceInfoMetricsController.saveWidgetLayout(widgets).done(function(){ |
| mainServiceInfoMetricsController.updateActiveLayout(); |
| }); |
| }, |
| |
| /* |
| * enter edit wizard of the newly cloned widget |
| */ |
| editNewClonedWidget: function (data) { |
| var controller = this.get('controller'); |
| var widgets = this.get('content.layout.widgets').toArray(); |
| var id = data.resources[0].WidgetInfo.id; |
| widgets.pushObject(Em.Object.create({ |
| id: id |
| })); |
| var mainServiceInfoMetricsController = App.router.get('mainServiceInfoMetricsController'); |
| mainServiceInfoMetricsController.saveWidgetLayout(widgets).done(function() { |
| mainServiceInfoMetricsController.getActiveWidgetLayout().done(function() { |
| controller.editWidget(App.Widget.find(id)); |
| }); |
| }); |
| }, |
| |
| /* |
| * make call when clicking on "edit icon" on widget |
| */ |
| editWidget: function () { |
| var self = this; |
| var isShared = this.get('content.scope') === 'CLUSTER'; |
| if (!isShared) { |
| self.get('controller').editWidget(self.get('content')); |
| } else { |
| return App.ModalPopup.show({ |
| header: Em.I18n.t('common.warning'), |
| bodyClass: Em.View.extend({ |
| template: Ember.Handlebars.compile('{{t widget.edit.body}}') |
| }), |
| primary: App.isAuthorized('CLUSTER.MANAGE_WIDGETS') ? Em.I18n.t('widget.edit.button.primary') : null, |
| secondaryClass: App.isAuthorized('CLUSTER.MANAGE_WIDGETS') ? 'btn-default' : 'btn-success', |
| secondary: Em.I18n.t('widget.edit.button.secondary'), |
| third: Em.I18n.t('common.cancel'), |
| onPrimary: function () { |
| this.hide(); |
| self.get('controller').editWidget(self.get('content')); |
| }, |
| onSecondary: function () { |
| this.hide(); |
| self.postWidgetDefinition(true, true); |
| }, |
| onThird: function () { |
| this.hide(); |
| } |
| }); |
| } |
| } |
| |
| }); |
| App.WidgetPreviewMixin = Ember.Mixin.create({ |
| beforeRender: Em.K, |
| isLoaded: true, |
| isPreview: true, |
| metrics: [], |
| content: Em.Object.create({ |
| id: 1, |
| values: [] |
| }), |
| loadMetrics: function () { |
| this.get('content').setProperties({ |
| 'values': this.get('controller.widgetValues'), |
| 'properties': this.get('controller.widgetProperties'), |
| 'widgetName': this.get('controller.widgetName'), |
| 'metrics': this.get('controller.widgetMetrics') |
| }); |
| this._super(); |
| }.observes('controller.widgetProperties', 'controller.widgetValues', 'controller.widgetMetrics', 'controller.widgetName'), |
| onMetricsLoaded: function () { |
| this.drawWidget(); |
| } |
| }); |
| |
| |
| /** |
| * aggregate requests to load metrics by component name |
| * requests can be added via add method |
| * input example: |
| * { |
| * data: request, |
| * context: this, |
| * startCallName: this.getServiceComponentMetrics, |
| * successCallback: this.getMetricsSuccessCallback, |
| * completeCallback: function () { |
| * requestCounter--; |
| * if (requestCounter === 0) this.onMetricsLoaded(); |
| * } |
| * } |
| * @type {Em.Object} |
| */ |
| App.WidgetLoadAggregator = Em.Object.create({ |
| /** |
| * @type {Array} |
| */ |
| requests: [], |
| |
| /** |
| * @type {number|null} |
| */ |
| timeoutId: null, |
| |
| /** |
| * @type {number} |
| * @const |
| */ |
| BULK_INTERVAL: 1000, |
| |
| arrayUtils: require('utils/array_utils'), |
| |
| /** |
| * add request |
| * every {{BULK_INTERVAL}} requests get collected, aggregated and sent to server |
| * |
| * @param {object} request |
| */ |
| add: function (request) { |
| var self = this; |
| |
| this.get('requests').push(request); |
| if (Em.isNone(this.get('timeoutId'))) { |
| this.set('timeoutId', window.setTimeout(function () { |
| //clear requests that are belongs to destroyed views |
| self.set('requests', self.get('requests').filterProperty('context.state', 'inDOM')); |
| |
| self.runRequests(self.get('requests')); |
| self.get('requests').clear(); |
| clearTimeout(self.get('timeoutId')); |
| self.set('timeoutId', null); |
| }, this.get('BULK_INTERVAL'))); |
| } |
| }, |
| |
| /** |
| * return requests which grouped into bulks |
| * @param {Array} requests |
| * @returns {object} bulks |
| */ |
| groupRequests: function (requests) { |
| var bulks = {}; |
| |
| requests.forEach(function (request) { |
| //poll metrics for graph widgets separately |
| var graphSuffix = request.context.get('content.widgetType') === "GRAPH" ? "_graph" : ''; |
| var tagSuffix = request.context.get('content.tag') ? '_' + request.context.get('content.tag') : ''; |
| var id = request.startCallName + "_" + request.data.component_name + graphSuffix + tagSuffix; |
| |
| if (Em.isNone(bulks[id])) { |
| bulks[id] = { |
| data: request.data, |
| context: request.context, |
| startCallName: request.startCallName |
| }; |
| bulks[id].subRequests = [{ |
| context: request.context, |
| successCallback: request.successCallback, |
| errorCallback: request.errorCallback, |
| completeCallback: request.completeCallback |
| }]; |
| } else { |
| bulks[id].data.metric_paths.pushObjects(request.data.metric_paths); |
| bulks[id].subRequests.push({ |
| context: request.context, |
| successCallback: request.successCallback, |
| errorCallback: request.errorCallback, |
| completeCallback: request.completeCallback |
| }); |
| } |
| }, this); |
| return bulks; |
| }, |
| |
| /** |
| * run aggregated requests |
| * @param {Array} requests |
| */ |
| runRequests: function (requests) { |
| var bulks = this.groupRequests(requests); |
| var self = this; |
| for (var id in bulks) { |
| (function (_request) { |
| _request.data.metric_paths = self.arrayUtils.uniqObjectsbyId(_request.data.metric_paths, "id"); |
| _request.context[_request.startCallName].call(_request.context, _request.data).done(function (response) { |
| _request.subRequests.forEach(function (subRequest) { |
| subRequest.successCallback.call(subRequest.context, response); |
| }, this); |
| }).fail(function (xhr, textStatus, errorThrown) { |
| _request.subRequests.forEach(function (subRequest) { |
| if (subRequest.errorCallback) { |
| subRequest.errorCallback.call(subRequest.context, xhr, textStatus, errorThrown); |
| } |
| }, this); |
| }).always(function (xhr) { |
| _request.subRequests.forEach(function (subRequest) { |
| subRequest.completeCallback.call(subRequest.context, xhr); |
| }, this); |
| }); |
| })(bulks[id]); |
| } |
| } |
| }); |