| /** |
| * 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 string_utils = require('utils/string_utils'); |
| var dateUtils = require('utils/date/date'); |
| |
| /** |
| * @class |
| * |
| * This is a view which GETs data from a URL and shows it as a time based line |
| * graph. Time is shown on the X axis with data series shown on Y axis. It |
| * optionally also has the ability to auto refresh itself over a given time |
| * interval. |
| * |
| * This is an abstract class which is meant to be extended. |
| * |
| * Extending classes should override the following: |
| * <ul> |
| * <li>url - from where the data can be retrieved |
| * <li>title - Title to be displayed when showing the chart |
| * <li>id - which uniquely identifies this chart in any page |
| * <li>seriesTemplate - template used by getData method to process server data |
| * </ul> |
| * |
| * Extending classes could optionally override the following: |
| * <ul> |
| * <li>#getData(jsonData) - function to map server data into series format |
| * ready for export to graph and JSON formats |
| * <li>#colorForSeries(series) - function to get custom colors per series |
| * </ul> |
| * |
| * @extends Ember.Object |
| * @extends Ember.View |
| */ |
| App.ChartLinearTimeView = Ember.View.extend(App.ExportMetricsMixin, { |
| templateName: require('templates/main/charts/linear_time'), |
| |
| /** |
| * The URL from which data can be retrieved. |
| * |
| * This property must be provided for the graph to show properly. |
| * |
| * @type String |
| * @default null |
| */ |
| url: null, |
| |
| /** |
| * A unique ID for this chart. |
| * |
| * @type String |
| * @default null |
| */ |
| id: null, |
| |
| /** |
| * Title to be shown under the chart. |
| * |
| * @type String |
| * @default null |
| */ |
| title: null, |
| |
| /** |
| * @private |
| * |
| * @type Rickshaw.Graph |
| * @default null |
| */ |
| _graph: null, |
| |
| /** |
| * Array of classnames for each series (in widget) |
| * @type Rickshaw.Graph |
| */ |
| _popupGraph: null, |
| |
| /** |
| * Array of classnames for each series |
| * @type Array |
| */ |
| _seriesProperties: null, |
| |
| /** |
| * Array of classnames for each series (in widget) |
| * @type Array |
| */ |
| _seriesPropertiesWidget: null, |
| |
| |
| /** |
| * Renderer type |
| * See <code>Rickshaw.Graph.Renderer</code> for more info |
| * @type String |
| */ |
| renderer: 'area', |
| |
| /** |
| * Suffix used in DOM-elements selectors |
| * @type String |
| */ |
| popupSuffix: '-popup', |
| |
| /** |
| * Is popup for current graph open |
| * @type Boolean |
| */ |
| isPopup: false, |
| |
| /** |
| * Is graph ready |
| * @type Boolean |
| */ |
| isReady: false, |
| |
| /** |
| * Is popup-graph ready |
| * @type Boolean |
| */ |
| isPopupReady: false, |
| |
| /** |
| * Is data for graph available |
| * @type Boolean |
| */ |
| hasData: true, |
| |
| /** |
| * chart height |
| * @type {number} |
| * @default 150 |
| */ |
| height: 150, |
| |
| /** |
| * @type {string} |
| * @default null |
| */ |
| displayUnit: null, |
| |
| /** |
| * Object containing information to get metrics data from API response |
| * Supported properties: |
| * <ul> |
| * <li>path - exact path to metrics data in response JSON (required) |
| * <li>displayName(name, hostName) - returns display name for metrics |
| * depending on property name in response JSON and host name for Flume Agents |
| * <li>factor - number that metrics values should be multiplied by |
| * <li>flumePropertyName - property name to access certain Flume metrics |
| * </ul> |
| * @type {Object} |
| */ |
| seriesTemplate: null, |
| |
| /** |
| * Incomplete metrics requests |
| * @type {array} |
| * @default [] |
| */ |
| runningRequests: [], |
| |
| /** |
| * Incomplete metrics requests for detailed view |
| * @type {array} |
| * @default [] |
| */ |
| runningPopupRequests: [], |
| |
| _containerSelector: Em.computed.format('#{0}-container', 'id'), |
| |
| _popupSelector: Em.computed.concat('', '_containerSelector', 'popupSuffix'), |
| |
| /** |
| * @type {boolean} |
| */ |
| isRequestRunning: function() { |
| var requestsArrayName = this.get('isPopup') ? 'runningPopupRequests' : 'runningRequests'; |
| return this.get(requestsArrayName).mapProperty('ajaxIndex').contains(this.get('ajaxIndex')); |
| }.property('runningPopupRequests', 'runningRequests'), |
| |
| didInsertElement: function () { |
| var self = this; |
| this.setYAxisFormatter(); |
| this.loadData(); |
| this.registerGraph(); |
| this.$().parent().on('mouseleave', function () { |
| self.set('isExportMenuHidden', true); |
| }); |
| App.tooltip(this.$("[rel='ZoomInTooltip']"), { |
| placement: 'left', |
| template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner graph-tooltip"></div></div>' |
| }); |
| }, |
| |
| setCurrentTimeIndex: function () { |
| this.set('currentTimeIndex', this.get('parentView.currentTimeRangeIndex')); |
| }.observes('parentView.currentTimeRangeIndex'), |
| |
| /** |
| * Maps server data into series format ready for export to graph and JSON formats |
| * @param jsonData |
| * @returns {Array} |
| */ |
| getData: function (jsonData) { |
| var dataArray = [], |
| template = this.get('seriesTemplate'), |
| data = Em.get(jsonData, template.path); |
| if (data) { |
| for (var name in data) { |
| var currentData = data[name]; |
| if (currentData) { |
| if (Array.isArray(currentData)) { |
| currentData = currentData.slice(0); |
| } |
| var factor = template.factor, |
| displayName = template.displayName ? template.displayName(name) : name; |
| if (!Em.isNone(factor)) { |
| var dataLength = currentData.length; |
| for (var i = dataLength; i--;) { |
| currentData[i] = [currentData[i][0] * factor, currentData[i][1]]; |
| } |
| } |
| dataArray.push({ |
| name: displayName, |
| data: currentData |
| }); |
| } |
| } |
| } |
| return dataArray; |
| }, |
| |
| /** |
| * Maps server data for certain Flume metrics into series format ready for export |
| * to graph and JSON formats |
| * @param jsonData |
| * @returns {Array} |
| */ |
| getFlumeData: function (jsonData) { |
| var dataArray = []; |
| if (jsonData && jsonData.host_components) { |
| jsonData.host_components.forEach(function (hc) { |
| var hostName = hc.HostRoles.host_name, |
| host = App.Host.find(hostName), |
| template = this.get('template'), |
| data = Em.get(hc, template.path); |
| if (host && host.get('publicHostName')) { |
| hostName = host.get('publicHostName'); |
| } |
| if (data) { |
| for (var cname in data) { |
| var seriesName = template.displayName ? template.displayName(cname, hostName) : cname, |
| seriesData = template.flumePropertyName ? data[cname][template.flumePropertyName] : data[cname]; |
| if (seriesData) { |
| var factor = template.factor; |
| if (!Em.isNone(factor)) { |
| var dataLength = seriesData.length; |
| for (var i = dataLength; i--;) { |
| seriesData[i][0] *= factor; |
| } |
| } |
| dataArray.push({ |
| name: seriesName, |
| data: seriesData |
| }); |
| } |
| } |
| } |
| }, this); |
| } |
| return dataArray; |
| }, |
| |
| /** |
| * Function to map data into graph series |
| * @param jsonData |
| * @returns {Array} |
| */ |
| transformToSeries: function (jsonData) { |
| var seriesArray = [], |
| seriesData = this.getData(jsonData), |
| dataLength = seriesData.length; |
| for (var i = 0; i < dataLength; i++) { |
| seriesArray.push(this.transformData(seriesData[i].data, seriesData[i].name)); |
| } |
| return seriesArray; |
| }, |
| |
| setExportTooltip: function () { |
| if (this.get('isReady')) { |
| Em.run.next(this, function () { |
| var icon = this.$('.corner-icon'); |
| if (icon) { |
| icon.on('mouseover', function () { |
| $(this).closest("[rel='ZoomInTooltip']").trigger('mouseleave'); |
| }); |
| App.tooltip(icon.children('.glyphicon-save'), { |
| title: Em.I18n.t('common.export') |
| }); |
| } |
| }); |
| } |
| }.observes('isReady'), |
| |
| willDestroyElement: function () { |
| this.$("[rel='ZoomInTooltip']").tooltip('destroy'); |
| $(this.get('_containerSelector') + ' li.line').off(); |
| $(this.get('_popupSelector') + ' li.line').off(); |
| App.ajax.abortRequests(this.get('runningRequests')); |
| }, |
| |
| registerGraph: function () { |
| var graph = { |
| name: this.get('title'), |
| id: this.get('elementId'), |
| popupId: this.get('id') |
| }; |
| App.router.get('updateController.graphs').push(graph); |
| }, |
| |
| loadData: function () { |
| var self = this, |
| isPopup = this.get('isPopup'); |
| if (this.get('loadGroup') && !isPopup) { |
| return App.ChartLinearTimeView.LoadAggregator.add(this, this.get('loadGroup')); |
| } else { |
| var requestsArrayName = isPopup ? 'runningPopupRequests' : 'runningRequests', |
| request = App.ajax.send({ |
| name: this.get('ajaxIndex'), |
| sender: this, |
| data: this.getDataForAjaxRequest(), |
| success: 'loadDataSuccessCallback', |
| error: 'loadDataErrorCallback', |
| callback: function () { |
| self.set(requestsArrayName, self.get(requestsArrayName).reject(function (item) { |
| return item === request; |
| })); |
| } |
| }); |
| if (request) request.ajaxIndex = this.get('ajaxIndex'); |
| this.get(requestsArrayName).push(request); |
| return request; |
| } |
| }, |
| |
| getDataForAjaxRequest: function () { |
| var fromSeconds, |
| toSeconds, |
| hostName = (this.get('content')) ? this.get('content.hostName') : "", |
| YARNService = App.YARNService.find().objectAt(0), |
| resourceManager = YARNService ? YARNService.get('resourceManager.hostName') : ""; |
| if (this.get('currentTimeIndex') === 8 && !Em.isNone(this.get('customStartTime')) && !Em.isNone(this.get('customEndTime'))) { |
| // Custom start and end time is specified by user |
| toSeconds = this.get('customEndTime') / 1000; |
| fromSeconds = this.get('customStartTime') / 1000; |
| } else { |
| // Preset time range is specified by user |
| var timeUnit = this.get('timeUnitSeconds'); |
| toSeconds = Math.round(App.dateTime() / 1000); |
| fromSeconds = toSeconds - timeUnit; |
| } |
| return { |
| toSeconds: toSeconds, |
| fromSeconds: fromSeconds, |
| stepSeconds: 15, |
| hostName: hostName, |
| resourceManager: resourceManager |
| }; |
| }, |
| |
| loadDataSuccessCallback: function (response) { |
| this._refreshGraph(response); |
| }, |
| |
| loadDataErrorCallback: function (xhr, textStatus, errorThrown) { |
| if (!xhr.isForcedAbort) { |
| this.set('isReady', true); |
| if (xhr.readyState == 4 && xhr.status) { |
| textStatus = xhr.status + " " + textStatus; |
| } |
| this._showMessage('warn', this.t('graphs.error.title'), this.t('graphs.error.message').format(textStatus, errorThrown)); |
| this.setProperties({ |
| hasData: false, |
| isExportButtonHidden: true |
| }); |
| } |
| }, |
| |
| /** |
| * Shows a yellow warning message in place of the chart. |
| * |
| * @param type Can be any of 'warn', 'error', 'info', 'success' |
| * @param title Bolded title for the message |
| * @param message String representing the message |
| * @param tooltip Tooltip content |
| * @type: Function |
| */ |
| _showMessage: function (type, title, message, tooltip) { |
| var popupSuffix = this.get('isPopup') ? this.get('popupSuffix') : ''; |
| var chartOverlay = '#' + this.get('id'); |
| var chartOverlayId = chartOverlay + '-chart' + popupSuffix; |
| var chartOverlayY = chartOverlay + '-yaxis' + popupSuffix; |
| var chartOverlayX = chartOverlay + '-xaxis' + popupSuffix; |
| var chartOverlayLegend = chartOverlay + '-legend' + popupSuffix; |
| var chartOverlayTimeline = chartOverlay + '-timeline' + popupSuffix; |
| var tooltipTitle = tooltip ? tooltip : Em.I18n.t('graphs.tooltip.title'); |
| var chartContent = ''; |
| |
| var typeClass; |
| switch (type) { |
| case 'error': |
| typeClass = 'alert-danger'; |
| break; |
| case 'success': |
| typeClass = 'alert-success'; |
| break; |
| case 'info': |
| typeClass = 'alert-info'; |
| break; |
| default: |
| typeClass = 'alert-warning'; |
| break; |
| } |
| $(chartOverlayId + ', ' + chartOverlayY + ', ' + chartOverlayX + ', ' + chartOverlayLegend + ', ' + chartOverlayTimeline).html(''); |
| chartContent += '<div class=\"alert ' + typeClass + '\">'; |
| if (title) { |
| chartContent += '<strong>' + title + '</strong> '; |
| } |
| chartContent += message + '</div>'; |
| $(chartOverlayId).append(chartContent); |
| $(chartOverlayId).parent().attr('data-original-title', tooltipTitle); |
| }, |
| |
| /** |
| * Transforms the JSON data retrieved from the server into the series |
| * format that Rickshaw.Graph understands. |
| * |
| * The series object is generally in the following format: [ { name : |
| * "Series 1", data : [ { x : 0, y : 0 }, { x : 1, y : 1 } ] } ] |
| * |
| * Extending classes should override this method. |
| * |
| * @param seriesData |
| * Data retrieved from the server |
| * @param displayName |
| * Graph title |
| * @type: Function |
| * |
| */ |
| transformData: function (seriesData, displayName) { |
| if (!Em.isNone(seriesData)) { |
| // Is it a string? |
| if ("string" == typeof seriesData) { |
| seriesData = JSON.parse(seriesData); |
| } |
| // Is it a number? |
| if ("number" == typeof seriesData) { |
| // Same number applies to all time. |
| var number = seriesData; |
| seriesData = []; |
| seriesData.push([number, App.dateTime() - (60 * 60)]); |
| seriesData.push([number, App.dateTime()]); |
| } |
| // We have valid data |
| var series = {}; |
| series.name = displayName; |
| series.data = []; |
| var timeDiff = App.dateTimeWithTimeZone(seriesData[0][1] * 1000) / 1000 - seriesData[0][1]; |
| for (var index = 0; index < seriesData.length; index++) { |
| series.data.push({ |
| x: seriesData[index][1] + timeDiff, |
| y: seriesData[index][0] |
| }); |
| } |
| return series; |
| } |
| return null; |
| }, |
| |
| /** |
| * Provides the formatter to use in displaying Y axis. |
| * |
| * By default, uses the App.ChartLinearTimeView.DefaultFormatter which shows 10K, |
| * 300M etc. |
| * |
| * @type Function |
| */ |
| yAxisFormatter: function (y) { |
| return App.ChartLinearTimeView.DefaultFormatter(y); |
| }, |
| |
| /** |
| * Sets the formatter to use in displaying Y axis depending on graph unit. |
| * |
| * @type Function |
| */ |
| setYAxisFormatter: function () { |
| var method, |
| formatterMap = { |
| '%': 'PercentageFormatter', |
| '/s': 'CreateRateFormatter', |
| 'B': 'BytesFormatter', |
| 'ms': 'TimeElapsedFormatter' |
| }, |
| methodName = formatterMap[this.get('displayUnit')]; |
| if (methodName) { |
| method = (methodName == 'CreateRateFormatter') ? |
| App.ChartLinearTimeView.CreateRateFormatter('', App.ChartLinearTimeView.DefaultFormatter) : |
| App.ChartLinearTimeView[methodName]; |
| this.set('yAxisFormatter', method); |
| } |
| }, |
| |
| /** |
| * Provides the color (in any HTML color format) to use for a particular |
| * series. |
| * May be redefined in child views |
| * |
| * @param series |
| * Series for which color is being requested |
| * @return color String. Returning null allows this chart to pick a color |
| * from palette. |
| * @default null |
| * @type Function |
| */ |
| colorForSeries: function (series) { |
| return null; |
| }, |
| |
| /** |
| * Check whether seriesData is correct data for chart drawing |
| * @param {Array} seriesData |
| * @return {Boolean} |
| */ |
| checkSeries: function (seriesData) { |
| if (!seriesData || !seriesData.length) { |
| return false; |
| } |
| var result = true; |
| seriesData.forEach(function (item) { |
| if (Em.isNone(Em.get(item, 'data.0.x'))) { |
| result = false; |
| } |
| }); |
| return result; |
| }, |
| |
| /** |
| * @private |
| * |
| * Refreshes the graph with the latest JSON data. |
| * |
| * @type Function |
| */ |
| _refreshGraph: function (jsonData, graphView) { |
| if (this.get('isDestroyed')) { |
| return; |
| } |
| console.time('_refreshGraph'); |
| var seriesData = this.transformToSeries(jsonData); |
| |
| //if graph opened as modal popup |
| var popup_path = $(this.get('_popupSelector')); |
| var graph_container = $(this.get('_containerSelector')); |
| var seconds = this.get('parentView.graphSeconds'); |
| var container; |
| if (!Em.isNone(seconds)) { |
| this.set('timeUnitSeconds', seconds); |
| this.set('parentView.graphSeconds', null); |
| } |
| if (popup_path.length) { |
| popup_path.children().each(function () { |
| $(this).children().remove(); |
| }); |
| this.set('isPopup', true); |
| } |
| else { |
| graph_container.children().each(function () { |
| if (!($(this).is('.export-graph-list-container, .corner-icon'))) { |
| $(this).children().remove(); |
| } |
| }); |
| } |
| var hasData = this.checkSeries(seriesData); |
| var view = graphView || this; |
| view.set('isExportButtonHidden', !hasData); |
| if (hasData) { |
| // Check container exists (may be not, if we go to another page and wait while graphs loading) |
| if (graph_container.length) { |
| container = $(this.get('_containerSelector')); |
| this.draw(seriesData); |
| this.set('hasData', true); |
| //move yAxis value lower to make them fully visible |
| container.find('.y_axis text').attr('y', 8); |
| container.attr('data-original-title', Em.I18n.t('graphs.tooltip.title')); |
| } |
| } |
| else { |
| this.set('isPopupReady', true); |
| this.set('isReady', true); |
| //if Axis X time interval is default(60 minutes) |
| if (this.get('timeUnitSeconds') === 3600) { |
| this._showMessage('info', null, this.t('graphs.noData.message'), this.t('graphs.noData.tooltip.title')); |
| this.set('hasData', false); |
| } |
| else { |
| this._showMessage('info', this.t('graphs.noData.title'), this.t('graphs.noDataAtTime.message')); |
| } |
| } |
| graph_container = null; |
| container = null; |
| popup_path = null; |
| console.timeEnd('_refreshGraph'); |
| }, |
| |
| /** |
| * Returns a custom time unit, that depends on X axis interval length, for the graph's X axis. |
| * This is needed as Rickshaw's default time X axis uses UTC time, which can be confusing |
| * for users expecting locale specific time. |
| * |
| * If <code>null</code> is returned, Rickshaw's default time unit is used. |
| * |
| * @type Function |
| * @return Rickshaw.Fixtures.Time |
| */ |
| localeTimeUnit: function (timeUnitSeconds) { |
| var timeUnit = new Rickshaw.Fixtures.Time(), |
| unitName; |
| if (timeUnitSeconds < 172800) { |
| timeUnit = { |
| name: timeUnitSeconds / 240 + ' minute', |
| seconds: timeUnitSeconds / 4, |
| formatter: function (d) { |
| // format locale specific time |
| var minutes = dateUtils.dateFormatZeroFirst(d.getMinutes()); |
| var hours = dateUtils.dateFormatZeroFirst(d.getHours()); |
| return hours + ":" + minutes; |
| } |
| }; |
| } else if (timeUnitSeconds < 1209600) { |
| timeUnit = timeUnit.unit('day'); |
| } else if (timeUnitSeconds < 5184000) { |
| timeUnit = timeUnit.unit('week'); |
| } else if (timeUnitSeconds < 62208000) { |
| timeUnit = timeUnit.unit('month'); |
| } else { |
| timeUnit = timeUnit.unit('year'); |
| } |
| return timeUnit; |
| }, |
| |
| /** |
| * calculate statistic data for popup legend and set proper colors for series |
| * @param {Array} data |
| */ |
| dataPreProcess: function (data) { |
| var self = this; |
| var palette = new Rickshaw.Color.Palette({scheme: 'munin'}); |
| // Format series for display |
| var series_min_length = 100000000; |
| data.forEach(function (series) { |
| var displayUnit = self.get('displayUnit'); |
| var seriesColor = self.colorForSeries(series); |
| if (Em.isNone(seriesColor)) { |
| seriesColor = palette.color(); |
| } |
| series.color = seriesColor; |
| series.stroke = 'rgba(0,0,0,0.3)'; |
| // calculate statistic data for popup legend |
| var avg = 0; |
| var min = Number.MAX_VALUE; |
| var max = Number.MIN_VALUE; |
| var numberOfNotNullValues = 0; |
| series.isZero = true; |
| for (var i = 0; i < series.data.length; i++) { |
| avg += series.data[i]['y']; |
| if (series.data[i]['y'] !== null) { |
| numberOfNotNullValues++; |
| } |
| if (!Em.isNone(series.data[i]['y'])) { |
| if (series.data[i]['y'] < min) { |
| min = series.data[i]['y']; |
| } |
| } |
| if (series.data[i]['y'] > max) { |
| max = series.data[i]['y']; |
| } |
| if (series.data[i]['y'] > 1) { |
| series.isZero = false; |
| } |
| } |
| if (self.get('isPopup')) { |
| series.name = string_utils.pad(series.name.length > 36 ? series.name.substr(0, 36) + '...' : series.name, 40, ' ', 2) + '| ' + |
| string_utils.pad('min', 5, ' ', 3) + |
| string_utils.pad(self.get('yAxisFormatter')(min), 12, ' ', 3) + |
| string_utils.pad('avg', 5, ' ', 3) + |
| string_utils.pad(self.get('yAxisFormatter')(avg / numberOfNotNullValues), 12, ' ', 3) + |
| string_utils.pad('max', 12, ' ', 3) + |
| string_utils.pad(self.get('yAxisFormatter')(max), 5, ' ', 3); |
| } |
| if (series.isZero) { |
| series.stroke = series.color; |
| } |
| if (series.data.length < series_min_length) { |
| series_min_length = series.data.length; |
| } |
| }); |
| |
| // All series should have equal length |
| data.forEach(function (series) { |
| if (series.data.length > series_min_length) { |
| series.data.length = series_min_length; |
| } |
| }); |
| }, |
| |
| draw: function (seriesData) { |
| var self = this; |
| var isPopup = this.get('isPopup'); |
| var p = isPopup ? this.get('popupSuffix') : ''; |
| |
| this.dataPreProcess(seriesData); |
| |
| var chartElement = document.querySelector("#" + this.get('id') + "-chart" + p); |
| var overlayElement = document.querySelector(+this.get('id') + "-container" + p); |
| var xaxisElement = document.querySelector("#" + this.get('id') + "-xaxis" + p); |
| var yaxisElement = document.querySelector("#" + this.get('id') + "-yaxis" + p); |
| var legendElement = document.querySelector("#" + this.get('id') + "-legend" + p); |
| |
| var graphSize = this._calculateGraphSize(); |
| |
| var _graph = new Rickshaw.GraphReopened({ |
| height: graphSize.height, |
| width: graphSize.width, |
| element: chartElement, |
| series: seriesData, |
| interpolation: 'step-after', |
| stroke: true, |
| renderer: this.get('renderer'), |
| strokeWidth: 'area' === this.get('renderer') ? 1 : 2, |
| max: seriesData.everyProperty('isZero') ? 1 : null |
| }); |
| |
| if ('area' === this.get('renderer')) { |
| _graph.renderer.unstack = false; |
| } |
| |
| new Rickshaw.Graph.Axis.Time({ |
| graph: _graph, |
| timeUnit: this.localeTimeUnit(this.get('timeUnitSeconds')) |
| }); |
| |
| new Rickshaw.Graph.Axis.Y({ |
| tickFormat: this.yAxisFormatter, |
| pixelsPerTick: isPopup ? 75 : 40, |
| element: yaxisElement, |
| orientation: isPopup ? 'left' : 'right', |
| graph: _graph |
| }); |
| |
| var legend = new Rickshaw.Graph.Legend({ |
| graph: _graph, |
| element: legendElement, |
| description: self.get('description') |
| }); |
| |
| new Rickshaw.Graph.Behavior.Series.Toggle({ |
| graph: _graph, |
| legend: legend |
| }); |
| |
| new Rickshaw.Graph.Behavior.Series.Order({ |
| graph: _graph, |
| legend: legend |
| }); |
| |
| if (!isPopup) { |
| $(overlayElement).on('mousemove', function () { |
| $(xaxisElement).removeClass('hide'); |
| $(legendElement).removeClass('hide'); |
| $(chartElement).children("div").removeClass('hide'); |
| }); |
| $(overlayElement).on('mouseout', function () { |
| $(legendElement).addClass('hide'); |
| }); |
| _graph.onUpdate(function () { |
| $(legendElement).addClass('hide'); |
| }); |
| } |
| |
| this.$().on('remove', function () { |
| $(overlayElement).off(); |
| }); |
| |
| //show the graph when it's loaded |
| _graph.onUpdate(function () { |
| self.set('isReady', true); |
| }); |
| _graph.render(); |
| |
| if (isPopup) { |
| new Rickshaw.Graph.HoverDetail({ |
| graph: _graph, |
| yFormatter: function (y) { |
| return self.yAxisFormatter(y); |
| }, |
| xFormatter: function (x) { |
| return (new Date(x * 1000)).toLocaleTimeString(); |
| }, |
| formatter: function (series, x, y, formattedX, formattedY) { |
| return formattedY + '<br />' + formattedX; |
| } |
| }); |
| } |
| |
| _graph = this.updateSeriesInGraph(_graph); |
| if (isPopup) { |
| //show the graph when it's loaded |
| _graph.onUpdate(function () { |
| self.set('isPopupReady', true); |
| }); |
| _graph.update(); |
| |
| var popupSelector = this.get('_popupSelector'); |
| $(popupSelector + ' li.line').click(function () { |
| var series = []; |
| $(popupSelector + ' a.action').each(function (index, v) { |
| series[index] = v.parentNode.classList; |
| }); |
| self.set('_seriesProperties', series); |
| }); |
| |
| this.set('_popupGraph', _graph); |
| } |
| else { |
| _graph.update(); |
| var containerSelector = this.get('_containerSelector'); |
| $(containerSelector + ' li.line').click(function () { |
| var series = []; |
| $(containerSelector + ' a.action').each(function (index, v) { |
| series[index] = v.parentNode.classList; |
| }); |
| self.set('_seriesPropertiesWidget', series); |
| }); |
| |
| this.set('_graph', _graph); |
| } |
| }, |
| |
| /** |
| * Calculate graph size |
| * @returns {{width: number, height: number}} |
| * @private |
| */ |
| _calculateGraphSize: function () { |
| var isPopup = this.get('isPopup'); |
| var height = this.get('height'); |
| var width = 400; |
| var diff = 32; |
| |
| if (this.get('inWidget')) { |
| height = 105; // for widgets view |
| diff = 22; |
| } |
| if (isPopup) { |
| height = 180; |
| width = 670; |
| } |
| else { |
| // If not in popup, the width could vary. |
| // We determine width based on div's size. |
| var thisElement = this.get('element'); |
| if (!Em.isNone(thisElement)) { |
| var calculatedWidth = $(thisElement).width(); |
| if (calculatedWidth > diff) { |
| width = calculatedWidth - diff; |
| } |
| } |
| } |
| return { |
| width: width, |
| height: height |
| } |
| }, |
| |
| /** |
| * |
| * @param {Rickshaw.Graph} graph |
| * @returns {Rickshaw.Graph} |
| */ |
| updateSeriesInGraph: function (graph) { |
| var id = this.get('id'); |
| var isPopup = this.get('isPopup'); |
| var popupSuffix = this.get('popupSuffix'); |
| var _series = isPopup ? this.get('_seriesProperties') : this.get('_seriesPropertiesWidget'); |
| graph.series.forEach(function (series, index) { |
| if (_series && !Em.isNone(_series[index])) { |
| if (_series[_series.length - index - 1].length > 1) { |
| var s = '#' + id + '-container' + (isPopup ? popupSuffix : '') + ' a.action:eq(' + (_series.length - index - 1) + ')'; |
| $(s).parent('li').addClass('disabled'); |
| series.disable(); |
| } |
| } |
| }); |
| return graph; |
| }, |
| |
| showGraphInPopup: function () { |
| if (!this.get('hasData') || this.get('isPreview')) { |
| return; |
| } |
| |
| this.set('isPopup', true); |
| if (this.get('inWidget') && !this.get('parentView.isClusterMetricsWidget')) { |
| this.setProperties({ |
| currentTimeIndex: this.get('parentView.timeIndex'), |
| customStartTime: this.get('parentView.startTime'), |
| customEndTime: this.get('parentView.endTime'), |
| customDurationFormatted: this.get('parentView.durationFormatted') |
| }); |
| } |
| var self = this; |
| |
| App.ModalPopup.show({ |
| bodyClass: Em.View.extend(App.ExportMetricsMixin, App.TimeRangeMixin, { |
| |
| containerId: null, |
| containerClass: null, |
| yAxisId: null, |
| yAxisClass: null, |
| xAxisId: null, |
| xAxisClass: null, |
| legendId: null, |
| legendClass: null, |
| chartId: null, |
| chartClass: null, |
| titleId: null, |
| titleClass: null, |
| |
| timeRangeClassName: 'graph-details-time-range', |
| |
| isReady: Em.computed.alias('parentView.graph.isPopupReady'), |
| |
| currentTimeRangeIndex: self.get('currentTimeIndex'), |
| customStartTime: self.get('currentTimeIndex') === 8 ? self.get('customStartTime') : null, |
| customEndTime: self.get('currentTimeIndex') === 8 ? self.get('customEndTime') : null, |
| customDurationFormatted: self.get('currentTimeIndex') === 8 ? self.get('customDurationFormatted') : null, |
| |
| didInsertElement: function () { |
| var popupBody = this; |
| this._super(); |
| App.tooltip(this.$('.corner-icon > .glyphicon-save'), { |
| title: Em.I18n.t('common.export') |
| }); |
| this.$().closest('.modal').on('click', function (event) { |
| if (!($(event.target).is('.corner-icon, .corner-icon span, .glyphicon-save, .export-graph-list-container, .export-graph-list-container *'))) { |
| popupBody.set('isExportMenuHidden', true); |
| } |
| }); |
| $('#modal').addClass('modal-graph-line'); |
| var popupSuffix = this.get('parentView.graph.popupSuffix'); |
| var id = this.get('parentView.graph.id'); |
| var idTemplate = id + '-{element}' + popupSuffix; |
| |
| this.set('containerId', idTemplate.replace('{element}', 'container')); |
| this.set('containerClass', 'chart-container' + popupSuffix); |
| this.set('yAxisId', idTemplate.replace('{element}', 'yaxis')); |
| this.set('yAxisClass', this.get('yAxisId').replace(popupSuffix, '')); |
| this.set('xAxisId', idTemplate.replace('{element}', 'xaxis')); |
| this.set('xAxisClass', this.get('xAxisId').replace(popupSuffix, '')); |
| this.set('legendId', idTemplate.replace('{element}', 'legend')); |
| this.set('legendClass', this.get('legendId').replace(popupSuffix, '')); |
| this.set('chartId', idTemplate.replace('{element}', 'chart')); |
| this.set('chartClass', this.get('chartId').replace(popupSuffix, '')); |
| this.set('titleId', idTemplate.replace('{element}', 'title')); |
| this.set('titleClass', this.get('titleId').replace(popupSuffix, '')); |
| }, |
| |
| templateName: require('templates/common/chart/linear_time'), |
| /** |
| * check is time paging feature is enable for graph |
| */ |
| isTimePagingEnable: function () { |
| return !self.get('isTimePagingDisable'); |
| }.property(), |
| |
| rightArrowVisible: function () { |
| // Time range is neither custom nor the least possible preset |
| return (this.get('isReady') && this.get('parentView.currentTimeIndex') != 0 && |
| this.get('parentView.currentTimeIndex') != 8); |
| }.property('isReady', 'parentView.currentTimeIndex'), |
| |
| leftArrowVisible: function () { |
| // Time range is neither custom nor the largest possible preset |
| return (this.get('isReady') && this.get('parentView.currentTimeIndex') < 7); |
| }.property('isReady', 'parentView.currentTimeIndex'), |
| |
| exportGraphData: function (event) { |
| this.set('isExportMenuHidden', true); |
| var ajaxIndex = this.get('parentView.graph.ajaxIndex'), |
| targetView = ajaxIndex ? this.get('parentView.graph') : self.get('parentView'); |
| targetView.exportGraphData({ |
| context: event.context |
| }); |
| }, |
| |
| setTimeRange: function (event) { |
| var index = event.context.index, |
| callback = this.get('parentView').reloadGraphByTime.bind(this.get('parentView'), index); |
| this._super(event, callback, self); |
| |
| // Preset time range is specified by user |
| if (index !== 8) { |
| callback(); |
| } |
| } |
| }), |
| header: this.get('title'), |
| /** |
| * App.ChartLinearTimeView |
| */ |
| graph: self, |
| |
| secondary: null, |
| |
| onPrimary: function () { |
| var targetView = Em.isNone(self.get('parentView.currentTimeRangeIndex')) ? self.get('parentView.parentView') : self.get('parentView'); |
| self.setProperties({ |
| currentTimeIndex: targetView.get('currentTimeRangeIndex'), |
| customStartTime: targetView.get('customStartTime'), |
| customEndTime: targetView.get('customEndTime'), |
| customDurationFormatted: targetView.get('customDurationFormatted'), |
| isPopup: false |
| }); |
| App.ajax.abortRequests(this.get('graph.runningPopupRequests')); |
| this._super(); |
| }, |
| |
| onClose: function () { |
| this.onPrimary(); |
| }, |
| /** |
| * move graph back by time |
| * @param event |
| */ |
| switchTimeBack: function (event) { |
| var index = this.get('currentTimeIndex'); |
| // 7 - number of last preset time state |
| if (index < 7) { |
| this.reloadGraphByTime(++index); |
| } |
| }, |
| /** |
| * move graph forward by time |
| * @param event |
| */ |
| switchTimeForward: function (event) { |
| var index = this.get('currentTimeIndex'); |
| if (index) { |
| this.reloadGraphByTime(--index); |
| } |
| }, |
| /** |
| * reload graph depending on the time |
| * @param index |
| */ |
| reloadGraphByTime: function (index) { |
| this.set('childViews.firstObject.currentTimeRangeIndex', index); |
| this.set('currentTimeIndex', index); |
| self.setProperties({ |
| currentTimeIndex: index, |
| isPopupReady: false |
| }); |
| App.ajax.abortRequests(this.get('graph.runningPopupRequests')); |
| }, |
| currentTimeIndex: self.get('currentTimeIndex'), |
| |
| currentTimeState: function () { |
| return self.get('timeStates').objectAt(this.get('currentTimeIndex')); |
| }.property('currentTimeIndex') |
| |
| }); |
| Em.run.next(function () { |
| self.loadData(); |
| self.set('isPopupReady', false); |
| }); |
| }, |
| reloadGraphByTime: function () { |
| Em.run.once(this, function () { |
| this.loadData(); |
| }); |
| }.observes('timeUnitSeconds', 'customStartTime', 'customEndTime'), |
| |
| timeStates: [ |
| {name: Em.I18n.t('graphs.timeRange.hour'), seconds: 3600}, |
| {name: Em.I18n.t('graphs.timeRange.twoHours'), seconds: 7200}, |
| {name: Em.I18n.t('graphs.timeRange.fourHours'), seconds: 14400}, |
| {name: Em.I18n.t('graphs.timeRange.twelveHours'), seconds: 43200}, |
| {name: Em.I18n.t('graphs.timeRange.day'), seconds: 86400}, |
| {name: Em.I18n.t('graphs.timeRange.week'), seconds: 604800}, |
| {name: Em.I18n.t('graphs.timeRange.month'), seconds: 2592000}, |
| {name: Em.I18n.t('graphs.timeRange.year'), seconds: 31104000}, |
| {name: Em.I18n.t('common.custom'), seconds: 0} |
| ], |
| // should be set by time range control dropdown list when create current graph |
| currentTimeIndex: 0, |
| customStartTime: null, |
| customEndTime: null, |
| customDurationFormatted: null, |
| setCurrentTimeIndexFromParent: function () { |
| // 8 index corresponds to custom start and end time selection |
| var targetView = !Em.isNone(this.get('parentView.currentTimeRangeIndex')) ? this.get('parentView') : this.get('parentView.parentView'), |
| index = targetView.get('currentTimeRangeIndex'), |
| customStartTime = (index === 8) ? targetView.get('customStartTime') : null, |
| customEndTime = (index === 8) ? targetView.get('customEndTime'): null, |
| customDurationFormatted = (index === 8) ? targetView.get('customDurationFormatted'): null; |
| this.setProperties({ |
| currentTimeIndex: index, |
| customStartTime: customStartTime, |
| customEndTime: customEndTime, |
| customDurationFormatted: customDurationFormatted |
| }); |
| if (index !== 8 || targetView.get('customStartTime') && targetView.get('customEndTime')) { |
| App.ajax.abortRequests(this.get('runningRequests')); |
| if (this.get('inWidget') && !this.get('parentView.isClusterMetricsWidget')) { |
| this.set('parentView.isLoaded', false); |
| } else { |
| this.set('isReady', false); |
| } |
| } |
| }.observes('parentView.parentView.currentTimeRangeIndex', 'parentView.currentTimeRangeIndex', 'parentView.parentView.customStartTime', 'parentView.customStartTime', 'parentView.parentView.customEndTime', 'parentView.customEndTime'), |
| timeUnitSeconds: 3600, |
| timeUnitSecondsSetter: function () { |
| var index = this.get('currentTimeIndex'), |
| startTime = this.get('customStartTime'), |
| endTime = this.get('customEndTime'), |
| durationFormatted = this.get('customDurationFormatted'), |
| seconds; |
| if (index !== 8) { |
| // Preset time range is specified by user |
| seconds = this.get('timeStates').objectAt(this.get('currentTimeIndex')).seconds; |
| } else if (!Em.isNone(startTime) && !Em.isNone(endTime)) { |
| // Custom start and end time is specified by user |
| seconds = (endTime - startTime) / 1000; |
| } |
| if (!Em.isNone(seconds)) { |
| this.set('timeUnitSeconds', seconds); |
| if (this.get('inWidget') && !this.get('parentView.isClusterMetricsWidget')) { |
| this.get('parentView').setProperties({ |
| graphSeconds: seconds, |
| timeIndex: index, |
| startTime: startTime, |
| endTime: endTime, |
| durationFormatted: durationFormatted |
| }); |
| } |
| } |
| }.observes('currentTimeIndex', 'customStartTime', 'customEndTime') |
| |
| }); |
| |
| /** |
| * A formatter which will turn a number into computer storage sizes of the |
| * format '23 GB' etc. |
| * |
| * @type {Function} |
| * @return {string} |
| */ |
| App.ChartLinearTimeView.BytesFormatter = function (y) { |
| if (0 == y) { |
| return '0 B'; |
| } |
| var value = Rickshaw.Fixtures.Number.formatBase1024KMGTP(y); |
| if (!y) { |
| return '0 B'; |
| } |
| if ("number" == typeof value) { |
| value = String(value); |
| } |
| if ("string" == typeof value) { |
| value = value.replace(/\.\d(\d+)/, function ($0, $1) { // Remove only 1-digit after decimal part |
| return $0.replace($1, ''); |
| }); |
| // Either it ends with digit or ends with character |
| value = value.replace(/(\d$)/, '$1 '); // Ends with digit like '120' |
| value = value.replace(/([a-zA-Z]$)/, ' $1'); // Ends with character like |
| // '120M' |
| value = value + 'B'; // Append B to make B, MB, GB etc. |
| } |
| return value; |
| }; |
| |
| /** |
| * A formatter which will turn a number into percentage display like '42%' |
| * |
| * @type {Function} |
| * @param {number} percentage |
| * @return {string} |
| */ |
| App.ChartLinearTimeView.PercentageFormatter = function (percentage) { |
| return percentage ? |
| percentage.toFixed(3).replace(/0+$/, '').replace(/\.$/, '') + '%' : |
| '0 %'; |
| }; |
| |
| /** |
| * A formatter which will turn a number into percentage display like '42%' |
| * |
| * @type {Function} |
| * @param {number} value |
| * @param {string} displayUnit |
| * @return {string} |
| */ |
| App.ChartLinearTimeView.DisplayUnitFormatter = function (value, displayUnit) { |
| return value ? |
| value.toFixed(3).replace(/0+$/, '').replace(/\.$/, '') + " " + displayUnit : |
| '0 ' + displayUnit; |
| }; |
| |
| /** |
| * A formatter which will turn elapsed time into display time like '50 ms', |
| * '5s', '10 m', '3 hr' etc. Time is expected to be provided in milliseconds. |
| * |
| * @type {Function} |
| * @return {string} |
| */ |
| App.ChartLinearTimeView.TimeElapsedFormatter = function (millis) { |
| if (!millis) { |
| return '0 ms'; |
| } |
| if ('number' == Em.typeOf(millis)) { |
| var seconds = millis > 1000 ? Math.round(millis / 1000) : 0; |
| var minutes = seconds > 60 ? Math.round(seconds / 60) : 0; |
| var hours = minutes > 60 ? Math.round(minutes / 60) : 0; |
| var days = hours > 24 ? Math.round(hours / 24) : 0; |
| if (days > 0) { |
| return days + ' d'; |
| } |
| if (hours > 0) { |
| return hours + ' hr'; |
| } |
| if (minutes > 0) { |
| return minutes + ' m'; |
| } |
| if (seconds > 0) { |
| return seconds + ' s'; |
| } |
| return millis.toFixed(3).replace(/0+$/, '').replace(/\.$/, '') + ' ms'; |
| } |
| return millis; |
| }; |
| |
| /** |
| * The default formatter which uses Rickshaw.Fixtures.Number.formatKMBT |
| * which shows 10K, 300M etc. |
| * |
| * @type {Function} |
| * @return {string|number} |
| */ |
| App.ChartLinearTimeView.DefaultFormatter = function (y) { |
| if (!y) { |
| return 0; |
| } |
| var value = Rickshaw.Fixtures.Number.formatKMBT(y); |
| if ('' == value) return '0'; |
| value = String(value); |
| var c = value[value.length - 1]; |
| return isNaN(parseInt(c)) ? |
| parseFloat(value.substr(0, value.length - 1)).toFixed(3).replace(/0+$/, '').replace(/\.$/, '') + c : |
| parseFloat(value).toFixed(3).replace(/0+$/, '').replace(/\.$/, ''); |
| }; |
| |
| |
| /** |
| * Creates and returns a formatter that can convert a 'value' |
| * to 'value units/s'. |
| * |
| * @param unitsPrefix Prefix which will be used in 'unitsPrefix/s' |
| * @param valueFormatter Value itself will need further processing |
| * via provided formatter. Ex: '10M requests/s'. Generally |
| * should be App.ChartLinearTimeView.DefaultFormatter. |
| * @return {Function} |
| */ |
| App.ChartLinearTimeView.CreateRateFormatter = function (unitsPrefix, valueFormatter) { |
| var suffix = " " + unitsPrefix + "/s"; |
| return function (value) { |
| value = valueFormatter(value) + suffix; |
| return value; |
| }; |
| }; |
| |
| Rickshaw.GraphReopened = function (a) { |
| Rickshaw.Graph.call(this, a); |
| }; |
| |
| //reopened in order to exclude "null" value from validation |
| Rickshaw.GraphReopened.prototype = Object.create(Rickshaw.Graph.prototype, { |
| validateSeries: { |
| value: function (series) { |
| |
| if (!(series instanceof Array) && !(series instanceof Rickshaw.Series)) { |
| var seriesSignature = Object.prototype.toString.apply(series); |
| throw "series is not an array: " + seriesSignature; |
| } |
| |
| var pointsCount; |
| |
| series.forEach(function (s) { |
| |
| if (!(s instanceof Object)) { |
| throw "series element is not an object: " + s; |
| } |
| if (!(s.data)) { |
| throw "series has no data: " + JSON.stringify(s); |
| } |
| if (!(s.data instanceof Array)) { |
| throw "series data is not an array: " + JSON.stringify(s.data); |
| } |
| |
| pointsCount = pointsCount || s.data.length; |
| |
| if (pointsCount && s.data.length != pointsCount) { |
| throw "series cannot have differing numbers of points: " + |
| pointsCount + " vs " + s.data.length + "; see Rickshaw.Series.zeroFill()"; |
| } |
| }) |
| }, |
| enumerable: true, |
| configurable: false, |
| writable: false |
| } |
| }); |
| |
| //show no line if point is "null" |
| Rickshaw.Graph.Renderer.Line.prototype.seriesPathFactory = function () { |
| |
| var graph = this.graph; |
| |
| return d3.svg.line() |
| .x(function (d) { |
| return graph.x(d.x) |
| }) |
| .y(function (d) { |
| return graph.y(d.y) |
| }) |
| .defined(function (d) { |
| return d.y != null; |
| }) |
| .interpolate(this.graph.interpolation).tension(this.tension); |
| }; |
| |
| //show no area if point is null |
| Rickshaw.Graph.Renderer.Stack.prototype.seriesPathFactory = function () { |
| |
| var graph = this.graph; |
| |
| return d3.svg.area() |
| .x(function (d) { |
| return graph.x(d.x) |
| }) |
| .y0(function (d) { |
| return graph.y(d.y0) |
| }) |
| .y1(function (d) { |
| return graph.y(d.y + d.y0) |
| }) |
| .defined(function (d) { |
| return d.y != null; |
| }) |
| .interpolate(this.graph.interpolation).tension(this.tension); |
| }; |
| |
| |
| /** |
| * 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.ChartLinearTimeView.LoadAggregator = Em.Object.create({ |
| /** |
| * @type {Array} |
| */ |
| requests: [], |
| |
| /** |
| * @type {number|null} |
| */ |
| timeoutId: null, |
| |
| /** |
| * time interval within which calls get collected |
| * @type {number} |
| * @const |
| */ |
| BULK_INTERVAL: 1000, |
| |
| /** |
| * add request |
| * every {{BULK_INTERVAL}} requests get collected, aggregated and sent to server |
| * |
| * @param {object} context |
| * @param {object} requestData |
| */ |
| add: function (context, requestData) { |
| var self = this; |
| |
| requestData.context = context; |
| this.get('requests').push(requestData); |
| if (Em.isNone(this.get('timeoutId'))) { |
| this.set('timeoutId', window.setTimeout(function () { |
| 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) { |
| var id = request.name; |
| |
| if (Em.isNone(bulks[id])) { |
| bulks[id] = { |
| name: request.name, |
| fields: request.fields.slice(), |
| context: request.context |
| }; |
| bulks[id].subRequests = [{ |
| context: request.context |
| }]; |
| } else { |
| bulks[id].fields.pushObjects(request.fields); |
| bulks[id].subRequests.push({ |
| context: request.context |
| }); |
| } |
| }, 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) { |
| var fields = self.formatRequestData(_request); |
| var hostName = (_request.context.get('content')) ? _request.context.get('content.hostName') : ""; |
| var xhr = App.ajax.send({ |
| name: _request.name, |
| sender: _request.context, |
| data: { |
| fields: fields, |
| hostName: hostName |
| } |
| }); |
| |
| xhr.done(function (response) { |
| console.time('==== runRequestsDone'); |
| _request.subRequests.forEach(function (subRequest) { |
| subRequest.context._refreshGraph.call(subRequest.context, response); |
| }, this); |
| console.timeEnd('==== runRequestsDone'); |
| }).fail(function (jqXHR, textStatus, errorThrown) { |
| if (!jqXHR.isForcedAbort) { |
| _request.subRequests.forEach(function (subRequest) { |
| subRequest.context.loadDataErrorCallback.call(subRequest.context, jqXHR, textStatus, errorThrown); |
| }, this); |
| } |
| }).always(function () { |
| _request.context.set('runningRequests', Em.tryInvoke(_request.context.get('runningRequests'), 'without', [xhr])); |
| }); |
| Em.tryInvoke(_request.context.get('runningRequests'), 'push', [xhr]); |
| })(bulks[id]); |
| } |
| }, |
| |
| /** |
| * |
| * @param {object} request |
| * @returns {number[]} |
| */ |
| formatRequestData: function (request) { |
| var fromSeconds, toSeconds; |
| if (request.context.get('currentTimeIndex') === 8 && !Em.isNone(request.context.get('customStartTime')) && !Em.isNone(request.context.get('customEndTime'))) { |
| // Custom start and end time is specified by user |
| toSeconds = request.context.get('customEndTime') / 1000; |
| fromSeconds = request.context.get('customStartTime') / 1000; |
| } else { |
| var timeUnit = request.context.get('timeUnitSeconds'); |
| toSeconds = Math.round(App.dateTime() / 1000); |
| fromSeconds = toSeconds - timeUnit; |
| } |
| var fields = request.fields.uniq().map(function (field) { |
| return field + "[" + (fromSeconds) + "," + toSeconds + "," + 15 + "]"; |
| }); |
| |
| return fields.join(","); |
| } |
| }); |