Repository: ambari Updated Branches: refs/heads/trunk 391cc7a6f -> bfb586d85
AMBARI-10223 Draw Number widget from the relevant retrieved widget data from the API. (atkach) Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/bfb586d8 Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/bfb586d8 Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/bfb586d8 Branch: refs/heads/trunk Commit: bfb586d8562c7ffe54e9f313f0eac22389e075f5 Parents: 391cc7a Author: Andrii Tkach <atk...@hortonworks.com> Authored: Fri Mar 27 17:19:26 2015 +0200 Committer: Andrii Tkach <atk...@hortonworks.com> Committed: Fri Mar 27 18:11:33 2015 +0200 ---------------------------------------------------------------------- .../HBASE/Append_num_ops_&_Delete_num_ops.json | 7 +- .../data/widget_layouts/HBASE/stack_layout.json | 116 --------- ambari-web/app/assets/test/tests.js | 1 + ambari-web/app/mixins.js | 1 + ambari-web/app/mixins/common/widget_mixin.js | 179 ++++++++++++++ ambari-web/app/models/widget.js | 2 + ambari-web/app/styles/widget_layout.less | 39 +++ .../templates/common/widget/template_widget.hbs | 22 ++ .../app/templates/main/service/info/summary.hbs | 6 +- ambari-web/app/views.js | 1 + .../views/common/widget/graph_widget_view.js | 153 +----------- .../views/common/widget/template_widget_view.js | 96 ++++++++ .../test/mixins/common/widget_mixin_test.js | 238 +++++++++++++++++++ 13 files changed, 594 insertions(+), 267 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/bfb586d8/ambari-web/app/assets/data/metrics/HBASE/Append_num_ops_&_Delete_num_ops.json ---------------------------------------------------------------------- diff --git a/ambari-web/app/assets/data/metrics/HBASE/Append_num_ops_&_Delete_num_ops.json b/ambari-web/app/assets/data/metrics/HBASE/Append_num_ops_&_Delete_num_ops.json index 97d1bee..2db12a0 100644 --- a/ambari-web/app/assets/data/metrics/HBASE/Append_num_ops_&_Delete_num_ops.json +++ b/ambari-web/app/assets/data/metrics/HBASE/Append_num_ops_&_Delete_num_ops.json @@ -1,4 +1,3 @@ - { "href" : "http://c6401.ambari.apache.org:8080/api/v1/clusters/c1/services/HBASE/components/HBASE_REGIONSERVER?fields=metrics/hbase/regionserver/Server/Append_num_ops[1426795139,1426798739,15]", "ServiceComponentInfo" : { @@ -8,8 +7,14 @@ }, "metrics" : { "hbase" : { + "ipc" : { + "IPC" : { + "numOpenConnections" : 11.5 + } + }, "regionserver" : { "Server" : { + "percentFilesLocal" : 99, "Append_num_ops" : [ [ 2.0, http://git-wip-us.apache.org/repos/asf/ambari/blob/bfb586d8/ambari-web/app/assets/data/widget_layouts/HBASE/stack_layout.json ---------------------------------------------------------------------- diff --git a/ambari-web/app/assets/data/widget_layouts/HBASE/stack_layout.json b/ambari-web/app/assets/data/widget_layouts/HBASE/stack_layout.json index f921ad7..4c8d3ee 100644 --- a/ambari-web/app/assets/data/widget_layouts/HBASE/stack_layout.json +++ b/ambari-web/app/assets/data/widget_layouts/HBASE/stack_layout.json @@ -19,21 +19,6 @@ "widget_type": "GRAPH", "metrics":[ { - "name": "regionserver.Server.Get_num_ops", - "widget_id": "metrics/hbase/regionserver/Server/Get_num_ops", - "category": "", - "service_name": "HBASE", - "component_name": "HBASE_REGIONSERVER", - "host_component_criteria" : "isActive=true" - }, - { - "name": "regionserver.Server.Scan_num_ops", - "widget_id": "metrics/hbase/regionserver/Server/Scan_num_ops", - "category": "", - "service_name": "HBASE", - "component_name": "HBASE_REGIONSERVER" - }, - { "name": "regionserver.Server.Append_num_ops", "widget_id": "metrics/hbase/regionserver/Server/Append_num_ops", "category": "", @@ -46,20 +31,6 @@ "category": "", "service_name": "HBASE", "component_name": "HBASE_REGIONSERVER" - }, - { - "name": "regionserver.Server.Increment_num_ops", - "widget_id": "metrics/hbase/regionserver/Server/Increment_num_ops", - "category": "", - "service_name": "HBASE", - "component_name": "HBASE_REGIONSERVER" - }, - { - "name": "regionserver.Server.Mutate_num_ops", - "widget_id": "metrics/hbase/regionserver/Server/Mutate_num_ops", - "category": "", - "service_name": "HBASE", - "component_name": "HBASE_REGIONSERVER" } ], "values": [ @@ -79,67 +50,6 @@ } }, { - "widget_name": "OPEN_CONNECTIONS", - "display_name": "Open Connections", - "description": "This widget shows number of current open connections", - "widget_type": "GRAPH", - "metrics":[ - { - "name": "ipc.IPC.numOpenConnections", - "widget_id": "metrics/hbase/ipc/IPC/numOpenConnections", - "category": "", - "service_name": "HBASE", - "component_name": "HBASE_REGIONSERVER" - } - ], - "values": [ - { - "name": "Open Connections", - "value": "${ipc.IPC.numOpenConnections}" - } - ], - "properties": { - "display_unit": "Connections", - "graph_type": "STACK", - "time_range": "1 hour" - } - }, - { - "widget_name": "ACTIVE_HANDLER", - "display_name": "Active Handlers vs Calls in General Queue", - "widget_type": "GRAPH", - "metrics":[ - { - "name": "ipc.IPC.numOpenConnections", - "widget_id": "metrics/hbase/ipc/IPC/numOpenConnections", - "category": "", - "service_name": "HBASE", - "component_name": "HBASE_REGIONSERVER" - }, - { - "name": "ipc.IPC.numCallsInGeneralQueue", - "widget_id": "metrics/hbase/ipc/IPC/numOpenConnections", - "category": "", - "service_name": "HBASE", - "component_name": "HBASE_REGIONSERVER" - } - ], - "values": [ - { - "name": "Active Handlers", - "value": "${ipc.IPC.numActiveHandler}" - }, - { - "name": "Calls in General Queue", - "value": "${ipc.IPC.numCallsInGeneralQueue}" - } - ], - "properties": { - "graph_type": "LINE", - "time_range": "1 hour" - } - }, - { "widget_name": "FILES_LOCAL", "display_name": "Files Local", "description": "This widget shows percentage of files local.", @@ -162,32 +72,6 @@ "properties": { "display_unit": "%" } - }, - { - "widget_name": "UPDATED_BLOCKED_TIME", - "display_name": "Updated Blocked Time", - "description": "", - "widget_type": "GRAPH", - "metrics":[ - { - "name": "regionserver.Server.updatesBlockedTime", - "widget_id": "metrics/hbase/regionserver/Server/updatesBlockedTime", - "category": "", - "service_name": "HBASE", - "component_name": "HBASE_REGIONSERVER" - } - ], - "values": [ - { - "name": "Updated Blocked Time", - "value": "${regionserver.Server.updatesBlockedTime}" - } - ], - "properties": { - "display_unit": "seconds", - "graph_type": "LINE", - "time_range": "1 day" - } } ] } http://git-wip-us.apache.org/repos/asf/ambari/blob/bfb586d8/ambari-web/app/assets/test/tests.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/assets/test/tests.js b/ambari-web/app/assets/test/tests.js index c668a9f..01dbdb3 100644 --- a/ambari-web/app/assets/test/tests.js +++ b/ambari-web/app/assets/test/tests.js @@ -141,6 +141,7 @@ var files = ['test/init_model_test', 'test/mixins/common/reload_popup_test', 'test/mixins/common/serverValidator_test', 'test/mixins/common/table_server_view_mixin_test', + 'test/mixins/common/widget_mixin_test', 'test/mixins/main/host/details/host_components/decommissionable_test', 'test/mixins/routers/redirections_test', 'test/mixins/wizard/addSeccurityConfigs_test', http://git-wip-us.apache.org/repos/asf/ambari/blob/bfb586d8/ambari-web/app/mixins.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/mixins.js b/ambari-web/app/mixins.js index 52091c1..045333f 100644 --- a/ambari-web/app/mixins.js +++ b/ambari-web/app/mixins.js @@ -38,3 +38,4 @@ require('mixins/wizard/selectHost'); require('mixins/wizard/addSecurityConfigs'); require('mixins/wizard/wizard_menu_view'); require('mixins/common/configs/enhanced_configs'); +require('mixins/common/widget_mixin'); http://git-wip-us.apache.org/repos/asf/ambari/blob/bfb586d8/ambari-web/app/mixins/common/widget_mixin.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/mixins/common/widget_mixin.js b/ambari-web/app/mixins/common/widget_mixin.js new file mode 100644 index 0000000..1f90500 --- /dev/null +++ b/ambari-web/app/mixins/common/widget_mixin.js @@ -0,0 +1,179 @@ +/** + * 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'); + +App.WidgetMixin = Ember.Mixin.create({ + + /** + * @type {RegExp} + * @const + */ + EXPRESSION_REGEX: /\$\{([\w\.\+\-\*\/\(\)]*)\}/g, + + /** + * @type {RegExp} + * @const + */ + MATH_EXPRESSION_REGEX: /^[\d\+\-\*\/\(\)\.]+$/, + + /** + * @type {RegExp} + * @const + */ + VALUE_NAME_REGEX: /[\w\.]+/g, + + /** + * common metrics container + * @type {Array} + */ + metrics: [], + + /** + * @type {boolean} + */ + isLoaded: false, + + /** + * @type {App.Widget} + * @default null + */ + content: null, + + /** + * load metrics + */ + beforeRender: function () { + var requestData = this.getRequestData(this.get('content.metrics')), + request, + requestCounter = 0, + self = this; + + for (var i in requestData) { + request = requestData[i]; + requestCounter++; + if (request.host_component_criteria) { + this.getHostComponentMetrics(request).complete(function () { + requestCounter--; + if (requestCounter === 0) self.set('isLoaded', true); + }); + } else { + this.getServiceComponentMetrics(request).complete(function () { + requestCounter--; + if (requestCounter === 0) self.set('isLoaded', true); + }); + } + } + }, + + /** + * 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; + + while (match = pattern.exec(input.value)) { + expressions.push(match[1]); + } + return expressions; + }, + + /** + * get data formatted for request + * @param {Array} metrics + */ + getRequestData: function (metrics) { + var requestsData = {}; + + metrics.forEach(function (metric) { + var key = metric.service_name + '_' + metric.component_name + '_' + metric.host_component_criteria; + var requestMetric = $.extend({}, metric); + + if (requestsData[key]) { + requestsData[key]["widget_ids"].push(requestMetric["widget_id"]); + } else { + requestMetric["widget_ids"] = [requestMetric["widget_id"]]; + delete requestMetric["widget_id"]; + requestsData[key] = requestMetric; + } + }, this); + return requestsData; + }, + + /** + * make GET call to server in order to fetch service-component metrics + * @param {object} request + * @returns {$.ajax} + */ + getServiceComponentMetrics: function (request) { + return App.ajax.send({ + name: 'widgets.serviceComponent.metrics.get', + sender: this, + data: { + serviceName: request.service_name, + componentName: request.component_name, + widgetIds: request.widget_ids.join(',') + }, + success: 'getServiceComponentMetricsSuccessCallback' + }); + }, + + getServiceComponentMetricsSuccessCallback: function (data, opt, params) { + var metrics = []; + var metricsData = data.metrics[params.serviceName.toLowerCase()]; + + this.get('content.metrics').forEach(function (_metric) { + if (Em.get(metricsData, _metric.name)) { + _metric.data = Em.get(metricsData, _metric.name); + this.get('metrics').pushObject(_metric); + } + }, this); + }, + + /** + * make GET call to server in order to fetch host-component metrics + * @param {object} request + * @returns {$.ajax} + */ + getHostComponentMetrics: function (request) { + return App.ajax.send({ + name: 'widgets.hostComponent.metrics.get', + sender: this, + data: { + serviceName: request.service_name, + componentName: request.component_name, + widgetIds: request.widget_ids.join(','), + hostComponentCriteria: 'host_components/HostRoles/' + request.host_component_criteria + }, + success: 'getHostComponentMetricsSuccessCallback' + }); + }, + + getHostComponentMetricsSuccessCallback: function () { + //TODO push data to metrics after response structure approved + } + +}); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/bfb586d8/ambari-web/app/models/widget.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/models/widget.js b/ambari-web/app/models/widget.js index 3d0ddc7..bdbb527 100644 --- a/ambari-web/app/models/widget.js +++ b/ambari-web/app/models/widget.js @@ -57,6 +57,8 @@ App.Widget = DS.Model.extend({ switch (this.get('widgetType')) { case 'GRAPH': return App.GraphWidgetView; + case 'NUMBER': + return App.TemplateWidgetView; default: return Em.View; } http://git-wip-us.apache.org/repos/asf/ambari/blob/bfb586d8/ambari-web/app/styles/widget_layout.less ---------------------------------------------------------------------- diff --git a/ambari-web/app/styles/widget_layout.less b/ambari-web/app/styles/widget_layout.less new file mode 100644 index 0000000..265e666 --- /dev/null +++ b/ambari-web/app/styles/widget_layout.less @@ -0,0 +1,39 @@ +/** + * 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. + */ + +#widget_layout { + .widget { + .template-widget { + height: 150px; + width: 90%; + .title { + padding: 5px 0 0 5px; + height: 25px; + font-weight: bold; + text-align: left; + } + .content { + text-align: center; + color: #5ab400; + padding-top: 35px; + font-weight: bold; + font-size: 35px; + } + } + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/bfb586d8/ambari-web/app/templates/common/widget/template_widget.hbs ---------------------------------------------------------------------- diff --git a/ambari-web/app/templates/common/widget/template_widget.hbs b/ambari-web/app/templates/common/widget/template_widget.hbs new file mode 100644 index 0000000..fda71ea --- /dev/null +++ b/ambari-web/app/templates/common/widget/template_widget.hbs @@ -0,0 +1,22 @@ +{{! +* 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. +}} + +<div class="template-widget thumbnail"> + <div class="caption title">{{view.title}}</div> + <div class="content"> {{view.value}}</div> +</div> http://git-wip-us.apache.org/repos/asf/ambari/blob/bfb586d8/ambari-web/app/templates/main/service/info/summary.hbs ---------------------------------------------------------------------- diff --git a/ambari-web/app/templates/main/service/info/summary.hbs b/ambari-web/app/templates/main/service/info/summary.hbs index b6a5e1d..e87ca6b 100644 --- a/ambari-web/app/templates/main/service/info/summary.hbs +++ b/ambari-web/app/templates/main/service/info/summary.hbs @@ -99,10 +99,12 @@ <div class=""> <table class="graphs"> {{#if App.supports.customizedWidgets}} - <tr> + <tr id="widget_layout"> {{#each widget in controller.widgets}} <td> - {{view widget.viewClass contentBinding="widget"}} + <div class="widget"> + {{view widget.viewClass contentBinding="widget"}} + </div> </td> {{/each}} </tr> http://git-wip-us.apache.org/repos/asf/ambari/blob/bfb586d8/ambari-web/app/views.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/views.js b/ambari-web/app/views.js index a7719fc..cb121ec 100644 --- a/ambari-web/app/views.js +++ b/ambari-web/app/views.js @@ -63,6 +63,7 @@ require('views/common/configs/widgets/time_interval_spinner_view'); require('views/common/configs/widgets/toggle_config_widget_view'); require('views/common/configs/widgets/overrides/slider_config_widget_override_view'); require('views/common/configs/service_config_layout_tab_view'); +require('views/common/widget/template_widget_view'); require('views/common/filter_combobox'); require('views/common/filter_combo_cleanable'); require('views/common/table_view'); http://git-wip-us.apache.org/repos/asf/ambari/blob/bfb586d8/ambari-web/app/views/common/widget/graph_widget_view.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/views/common/widget/graph_widget_view.js b/ambari-web/app/views/common/widget/graph_widget_view.js index a6cd420..c245622 100644 --- a/ambari-web/app/views/common/widget/graph_widget_view.js +++ b/ambari-web/app/views/common/widget/graph_widget_view.js @@ -18,7 +18,7 @@ var App = require('app'); -App.GraphWidgetView = App.ChartLinearTimeView.extend({ +App.GraphWidgetView = App.ChartLinearTimeView.extend(App.WidgetMixin, { /** * @type {string} @@ -42,22 +42,10 @@ App.GraphWidgetView = App.ChartLinearTimeView.extend({ }.property('properties.graph_type'), /** - * @type {RegExp} - * @const - */ - EXPRESSION_REGEX: /\$\{([\w\.\+\-\*\/\(\)]*)\}/g, - - /** - * @type {RegExp} - * @const - */ - MATH_EXPRESSION_REGEX: /^[\d\+\-\*\/\(\)\.]+$/, - - /** - * @type {RegExp} - * @const + * common metrics container + * @type {Array} */ - VALUE_NAME_REGEX: /[\w\.]+/g, + metrics: [], /** * value in ms @@ -71,49 +59,6 @@ App.GraphWidgetView = App.ChartLinearTimeView.extend({ */ timeStep: 15, - /** - * common metrics container - * @type {Array} - */ - metrics: [], - - /** - * @type {boolean} - */ - isLoaded: false, - - /** - * @type {App.Widget} - * @default null - */ - content: null, - - /** - * load metrics - */ - beforeRender: function () { - var requestData = this.getRequestData(this.get('content.metrics')), - request, - requestCounter = 0, - self = this; - - for (var i in requestData) { - request = requestData[i]; - requestCounter++; - if (request.host_component_criteria) { - this.getHostComponentMetrics(request).complete(function () { - requestCounter--; - if (requestCounter === 0) self.set('isLoaded', true); - }); - } else { - this.getServiceComponentMetrics(request).complete(function () { - requestCounter--; - if (requestCounter === 0) self.set('isLoaded', true); - }); - } - } - }, - didInsertElement: Em.K, transformToSeries: function (seriesData) { @@ -131,7 +76,6 @@ App.GraphWidgetView = App.ChartLinearTimeView.extend({ */ calculateSeries: function () { var metrics = this.get('metrics'); - var widgetType = this.get('content.widgetType'); var seriesData = []; this.get('content.values').forEach(function (value) { @@ -183,56 +127,6 @@ App.GraphWidgetView = App.ChartLinearTimeView.extend({ }, /** - * TODO should be used for simple type of widgets - * @param expressions - * @param metrics - * @returns {object} - *//* - computeExpressions: 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 (metrics.someProperty('name', match)) { - return metrics.findProperty('name', match).data; - } else { - validExpression = false; - console.warn('Metrics not found to compute expression'); - } - }); - - if (validExpression) { - //check for correct math expression - validExpression = this.get('MATH_EXPRESSION_REGEX').test(beforeCompute); - !validExpression && console.warn('Value is not correct mathematical expression'); - } - - result['${' + _expression + '}'] = (validExpression) ? Number(window.eval(beforeCompute)).toString() : value; - }, this); - return result; - },*/ - - /** - * extract expressions - * @param {object} value - * @returns {Array} - */ - extractExpressions: function (value) { - var pattern = this.get('EXPRESSION_REGEX'), - expressions = [], - match; - - while (match = pattern.exec(value.value)) { - expressions.push(match[1]); - } - return expressions; - }, - - /** * make GET call to server in order to fetch service-component metrics * @param {object} request * @returns {$.ajax} @@ -250,18 +144,6 @@ App.GraphWidgetView = App.ChartLinearTimeView.extend({ }); }, - getServiceComponentMetricsSuccessCallback: function (data, opt, params) { - var metrics = []; - var metricsData = data.metrics[params.serviceName.toLowerCase()]; - - this.get('content.metrics').forEach(function (_metric) { - if (Em.get(metricsData, _metric.name)) { - _metric.data = Em.get(metricsData, _metric.name); - this.get('metrics').pushObject(_metric); - } - }, this); - }, - /** * make GET call to server in order to fetch host-component metrics * @param {object} request @@ -281,31 +163,6 @@ App.GraphWidgetView = App.ChartLinearTimeView.extend({ }); }, - getHostComponentMetricsSuccessCallback: function () { - //TODO push data to metrics after response structure approved - }, - - /** - * get data formatted for request - * @param {Array} metrics - */ - getRequestData: function (metrics) { - var requestsData = {}; - - metrics.forEach(function (metric) { - var key = metric.service_name + '_' + metric.component_name + '_' + metric.host_component_criteria; - - if (requestsData[key]) { - requestsData[key]["widget_ids"].push(metric["widget_id"]); - } else { - metric["widget_ids"] = [metric["widget_id"]]; - delete metric["widget_id"]; - requestsData[key] = metric; - } - }, this); - return requestsData; - }, - /** * add time properties * @param {Array} widgetIds @@ -323,4 +180,4 @@ App.GraphWidgetView = App.ChartLinearTimeView.extend({ return result; } -}); \ No newline at end of file +}); http://git-wip-us.apache.org/repos/asf/ambari/blob/bfb586d8/ambari-web/app/views/common/widget/template_widget_view.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/views/common/widget/template_widget_view.js b/ambari-web/app/views/common/widget/template_widget_view.js new file mode 100644 index 0000000..580b92a --- /dev/null +++ b/ambari-web/app/views/common/widget/template_widget_view.js @@ -0,0 +1,96 @@ +/** + * 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'); + +App.TemplateWidgetView = Em.View.extend(App.WidgetMixin, { + templateName: require('templates/common/widget/template_widget'), + + /** + * @type {string} + */ + title: '', + + /** + * @type {string} + */ + value: '', + + /** + * common metrics container + * @type {Array} + */ + metrics: [], + + drawWidget: function () { + if (this.get('isLoaded')) { + this.calculateValues(); + this.set('value', this.get('content.values')[0].computedValue); + this.set('title', this.get('content.values')[0].name); + } + }.observes('isLoaded'), + + /** + * calculate series datasets for graph widgets + */ + calculateValues: function () { + var metrics = this.get('metrics'); + var displayUnit = this.get('content.properties.display_unit'); + + this.get('content.values').forEach(function (value) { + var computeExpression = this.computeExpression(this.extractExpressions(value), metrics); + value.computedValue = value.value.replace(this.get('EXPRESSION_REGEX'), function (match) { + return (computeExpression[match]) ? computeExpression[match] + displayUnit : Em.I18n.t('common.na'); + }); + }, this); + }, + + /** + * 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 (metrics.someProperty('name', match)) { + return metrics.findProperty('name', match).data; + } else { + validExpression = false; + console.warn('Metrics not found to compute expression'); + } + }); + + if (validExpression) { + //check for correct math expression + validExpression = this.get('MATH_EXPRESSION_REGEX').test(beforeCompute); + !validExpression && console.warn('Value is not correct mathematical expression'); + } + + result['${' + _expression + '}'] = (validExpression) ? Number(window.eval(beforeCompute)).toString() : value; + }, this); + return result; + } +}); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/bfb586d8/ambari-web/test/mixins/common/widget_mixin_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/mixins/common/widget_mixin_test.js b/ambari-web/test/mixins/common/widget_mixin_test.js new file mode 100644 index 0000000..6b78769 --- /dev/null +++ b/ambari-web/test/mixins/common/widget_mixin_test.js @@ -0,0 +1,238 @@ +/** + * 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'); + +describe('App.WidgetMixin', function() { + var mixinClass = Em.Object.extend(App.WidgetMixin, {metrics: [], content: {}}); + + describe('#beforeRender()', function () { + var mixinObject = mixinClass.create(); + beforeEach(function () { + this.mock = sinon.stub(mixinObject, 'getRequestData'); + sinon.stub(mixinObject, 'getHostComponentMetrics').returns({complete: function(callback){ + callback(); + }}); + sinon.stub(mixinObject, 'getServiceComponentMetrics').returns({complete: function(callback){ + callback(); + }}); + }); + afterEach(function () { + this.mock.restore(); + mixinObject.getHostComponentMetrics.restore(); + mixinObject.getServiceComponentMetrics.restore(); + }); + it('has host_component_criteria', function () { + this.mock.returns({'key1': {host_component_criteria: 'criteria'}}); + mixinObject.set('isLoaded', false); + mixinObject.beforeRender(); + + expect(mixinObject.getHostComponentMetrics.calledWith({host_component_criteria: 'criteria'})).to.be.true; + expect(mixinObject.get('isLoaded')).to.be.true; + }); + it('host_component_criteria is absent', function () { + this.mock.returns({'key1': {}}); + mixinObject.set('isLoaded', false); + mixinObject.beforeRender(); + + expect(mixinObject.getServiceComponentMetrics.calledWith({})).to.be.true; + expect(mixinObject.get('isLoaded')).to.be.true; + }); + }); + + describe("#extractExpressions()", function() { + var mixinObject = mixinClass.create(); + var testCases = [ + { + data: '', + result: [] + }, + { + data: 'text', + result: [] + }, + { + data: 'text${a}', + result: ['a'] + }, + { + data: 'text${a} - ${a.b}', + result: ['a', 'a.b'] + }, + { + data: '${o.a-(b+4)/cc*tt}', + result: ['o.a-(b+4)/cc*tt'] + } + ]; + testCases.forEach(function (test) { + it('input: ' + test.data, function () { + var input = {value: test.data}; + expect(mixinObject.extractExpressions(input)).to.eql(test.result); + }); + }); + }); + + describe("#getRequestData()", function() { + var mixinObject = mixinClass.create(); + it("", function() { + var data = [ + { + "name": "regionserver.Server.percentFilesLocal", + "widget_id": "metrics/hbase/regionserver/percentFilesLocal", + "service_name": "HBASE", + "component_name": "HBASE_REGIONSERVER" + }, + { + "name": "regionserver.Server.percentFilesLocal2", + "widget_id": "w2", + "service_name": "HBASE", + "component_name": "HBASE_REGIONSERVER" + }, + { + "name": "regionserver.Server.percentFilesLocal", + "widget_id": "metrics/hbase/regionserver/percentFilesLocal", + "service_name": "HBASE", + "component_name": "HBASE_REGIONSERVER", + "host_component_criteria": 'c1' + }, + { + "name": "regionserver.Server.percentFilesLocal", + "widget_id": "metrics/hbase/regionserver/percentFilesLocal", + "service_name": "HDFS", + "component_name": "DATANODE", + "host_component_criteria": 'c1' + } + ]; + + expect(mixinObject.getRequestData(data)).to.eql({ + "HBASE_HBASE_REGIONSERVER_undefined": { + "name": "regionserver.Server.percentFilesLocal", + "service_name": "HBASE", + "component_name": "HBASE_REGIONSERVER", + "widget_ids": [ + "metrics/hbase/regionserver/percentFilesLocal", + "w2" + ] + }, + "HBASE_HBASE_REGIONSERVER_c1": { + "name": "regionserver.Server.percentFilesLocal", + "service_name": "HBASE", + "component_name": "HBASE_REGIONSERVER", + "host_component_criteria": "c1", + "widget_ids": [ + "metrics/hbase/regionserver/percentFilesLocal" + ] + }, + "HDFS_DATANODE_c1": { + "name": "regionserver.Server.percentFilesLocal", + "service_name": "HDFS", + "component_name": "DATANODE", + "host_component_criteria": "c1", + "widget_ids": [ + "metrics/hbase/regionserver/percentFilesLocal" + ] + } + }); + }); + }); + + describe("#getServiceComponentMetrics()", function () { + var mixinObject = mixinClass.create(); + before(function () { + sinon.stub(App.ajax, 'send'); + }); + after(function () { + App.ajax.send.restore(); + }); + it("", function () { + var request = { + service_name: 'S1', + component_name: 'C1', + widget_ids: ['w1', 'w2'] + }; + mixinObject.getServiceComponentMetrics(request); + expect(App.ajax.send.getCall(0).args[0]).to.eql({ + name: 'widgets.serviceComponent.metrics.get', + sender: mixinObject, + data: { + serviceName: 'S1', + componentName: 'C1', + widgetIds: 'w1,w2' + }, + success: 'getServiceComponentMetricsSuccessCallback' + }) + }); + }); + + describe("#getServiceComponentMetricsSuccessCallback()", function() { + var mixinObject = mixinClass.create(); + + it("", function() { + var data = { + metrics: { + "hbase" : { + "ipc" : { + "IPC" : { + "numOpenConnections" : 11.5 + } + } + } + } + }; + mixinObject.set('content.metrics', [ + { + name: 'ipc.IPC.numOpenConnections' + } + ]); + mixinObject.getServiceComponentMetricsSuccessCallback(data, {}, {serviceName: 'hbase'}); + expect(mixinObject.get('metrics').findProperty('name', 'ipc.IPC.numOpenConnections').data).to.equal(11.5); + }); + }); + + describe("#getHostComponentMetrics()", function () { + var mixinObject = mixinClass.create(); + before(function () { + sinon.stub(App.ajax, 'send'); + }); + after(function () { + App.ajax.send.restore(); + }); + it("", function () { + var request = { + service_name: 'S1', + component_name: 'C1', + widget_ids: ['w1', 'w2'], + host_component_criteria: 'c1' + }; + mixinObject.getHostComponentMetrics(request); + expect(App.ajax.send.getCall(0).args[0]).to.eql({ + name: 'widgets.hostComponent.metrics.get', + sender: mixinObject, + data: { + serviceName: 'S1', + componentName: 'C1', + widgetIds: 'w1,w2', + hostComponentCriteria: 'host_components/HostRoles/c1' + }, + success: 'getHostComponentMetricsSuccessCallback' + }) + }); + }); + +}); +