This is an automated email from the ASF dual-hosted git repository. akovalenko pushed a commit to branch trunk in repository https://gitbox.apache.org/repos/asf/ambari.git
The following commit(s) were added to refs/heads/trunk by this push: new fbc785a AMBARI-23304. Add namespace-specific layout for HDFS metrics (akovalenko) fbc785a is described below commit fbc785ab3556aa554357787411c61e567147c2af Author: Aleksandr Kovalenko <akovale...@apache.org> AuthorDate: Tue Mar 20 16:24:49 2018 +0200 AMBARI-23304. Add namespace-specific layout for HDFS metrics (akovalenko) --- .../main/admin/federation/step4_controller.js | 117 ++++++++++++++++++++- ambari-web/app/messages.js | 7 +- .../app/mixins/common/widgets/widget_section.js | 114 ++++++++++++++++++-- ambari-web/app/models/host_component.js | 2 +- ambari-web/app/models/service/hdfs.js | 2 +- ambari-web/app/models/widget_layout.js | 6 +- .../app/styles/enhanced_service_dashboard.less | 8 +- .../app/templates/main/service/info/metrics.hbs | 23 ++++ .../app/views/main/service/info/metrics_view.js | 35 +++++- .../views/main/service/info/metrics_view_test.js | 10 +- 10 files changed, 298 insertions(+), 26 deletions(-) diff --git a/ambari-web/app/controllers/main/admin/federation/step4_controller.js b/ambari-web/app/controllers/main/admin/federation/step4_controller.js index fd3a6f3..f725f1a 100644 --- a/ambari-web/app/controllers/main/admin/federation/step4_controller.js +++ b/ambari-web/app/controllers/main/admin/federation/step4_controller.js @@ -22,7 +22,7 @@ App.NameNodeFederationWizardStep4Controller = App.HighAvailabilityProgressPageCo name: "nameNodeFederationWizardStep4Controller", - commands: ['stopAllServices', 'reconfigureHDFS', 'installNameNode', 'installZKFC', 'formatNameNode', 'formatZKFC', 'startZKFC', 'startNameNode', 'bootstrapNameNode', 'startZKFC2', 'startNameNode2', 'startAllServices'], + commands: ['stopAllServices', 'reconfigureHDFS', 'installNameNode', 'installZKFC', 'formatNameNode', 'formatZKFC', 'startZKFC', 'startNameNode', 'bootstrapNameNode', 'createWidgets', 'startZKFC2', 'startNameNode2', 'startAllServices'], tasksMessagesPrefix: 'admin.nameNodeFederation.wizard.step', @@ -108,6 +108,74 @@ App.NameNodeFederationWizardStep4Controller = App.HighAvailabilityProgressPageCo }); }, + createWidgets: function () { + var self = this; + this.getNameNodeWidgets().done(function (data) { + var newWidgetsIds = []; + var oldWidgetIds = []; + var nameservice1 = App.HDFSService.find().objectAt(0).get('masterComponentGroups')[0].name; + var nameservice2 = self.get('content.nameServiceId'); + var widgetsCount = data.items.length; + data.items.forEach(function (widget) { + if (!widget.WidgetInfo.tag) { + var oldId = widget.WidgetInfo.id; + oldWidgetIds.push(oldId); + delete widget.href; + delete widget.WidgetInfo.id; + delete widget.WidgetInfo.cluster_name; + delete widget.WidgetInfo.author; + widget.WidgetInfo.tag = nameservice1; + widget.WidgetInfo.metrics = JSON.parse(widget.WidgetInfo.metrics); + widget.WidgetInfo.values = JSON.parse(widget.WidgetInfo.values); + self.createWidget(widget).done(function (w) { + newWidgetsIds.push(w.resources[0].WidgetInfo.id); + widget.WidgetInfo.tag = nameservice2; + self.createWidget(widget).done(function (w) { + newWidgetsIds.push(w.resources[0].WidgetInfo.id); + self.deleteWidget(oldId).done(function () { + if (!--widgetsCount) { + self.getDefaultHDFStWidgetLayout().done(function (layout) { + layout = layout.items[0].WidgetLayoutInfo; + layout.widgets = layout.widgets.filter(function (w) { + return !oldWidgetIds.contains(w.WidgetInfo.id); + }).map(function (w) { + return w.WidgetInfo.id; + }).concat(newWidgetsIds); + self.updateDefaultHDFStWidgetLayout(layout).done(function () { + self.onTaskCompleted(); + }); + }); + } + }); + }); + }); + } else { + widgetsCount--; + } + }); + }); + }, + + createWidget: function (data) { + return App.ajax.send({ + name: 'widgets.wizard.add', + sender: this, + data: { + data: data + } + }); + }, + + deleteWidget: function (id) { + return App.ajax.send({ + name: 'widget.action.delete', + sender: self, + data: { + id: id + } + }); + }, + startZKFC2: function () { this.updateComponent('ZKFC', this.get('newNameNodeHosts')[1], "HDFS", "Start"); }, @@ -118,6 +186,51 @@ App.NameNodeFederationWizardStep4Controller = App.HighAvailabilityProgressPageCo startAllServices: function () { this.startServices(false); - } + }, + + getNameNodeWidgets: function () { + return App.ajax.send({ + name: 'widgets.get', + sender: this, + data: { + urlParams: 'WidgetInfo/widget_type.in(GRAPH,NUMBER,GAUGE)&WidgetInfo/scope=CLUSTER&WidgetInfo/metrics.matches(.*\"component_name\":\"NAMENODE\".*)&fields=*' + } + }); + }, + getDefaultHDFStWidgetLayout: function () { + return App.ajax.send({ + name: 'widget.layout.get', + sender: this, + data: { + urlParams: 'WidgetLayoutInfo/layout_name=default_hdfs_dashboard' + } + }); + }, + + updateDefaultHDFStWidgetLayout: function (widgetLayoutData) { + var layout = widgetLayoutData; + var data = { + "WidgetLayoutInfo": { + "display_name": layout.display_name, + "layout_name": layout.layout_name, + "id": layout.id, + "scope": "USER", + "section_name": layout.section_name, + "widgets": layout.widgets.map(function (id) { + return { + "id":id + } + }) + } + }; + return App.ajax.send({ + name: 'widget.layout.edit', + sender: this, + data: { + layoutId: layout.id, + data: data + }, + }); + } }); diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js index 17420e0..5341389 100644 --- a/ambari-web/app/messages.js +++ b/ambari-web/app/messages.js @@ -1693,9 +1693,10 @@ Em.I18n.translations = { 'admin.nameNodeFederation.wizard.step4.task6.title': 'Start ZKFC', 'admin.nameNodeFederation.wizard.step4.task7.title': 'Start NameNode', 'admin.nameNodeFederation.wizard.step4.task8.title': 'Bootstrap NameNode', - 'admin.nameNodeFederation.wizard.step4.task9.title': 'Start ZKFC', - 'admin.nameNodeFederation.wizard.step4.task10.title': 'Start NameNode', - 'admin.nameNodeFederation.wizard.step4.task11.title': 'Start All Services', + 'admin.nameNodeFederation.wizard.step4.task9.title': 'Create widgets', + 'admin.nameNodeFederation.wizard.step4.task10.title': 'Start ZKFC', + 'admin.nameNodeFederation.wizard.step4.task11.title': 'Start NameNode', + 'admin.nameNodeFederation.wizard.step4.task12.title': 'Start All Services', 'admin.security.title':'Kerberos security has not been enabled', 'admin.security.enabled': 'Kerberos security is enabled', diff --git a/ambari-web/app/mixins/common/widgets/widget_section.js b/ambari-web/app/mixins/common/widgets/widget_section.js index abd0c97..0ac7ba0 100644 --- a/ambari-web/app/mixins/common/widgets/widget_section.js +++ b/ambari-web/app/mixins/common/widgets/widget_section.js @@ -81,6 +81,10 @@ App.WidgetSectionMixin = Ember.Mixin.create({ return isServiceWithWidgetdescriptor; }.property('content.serviceName'), + isHDFSFederatedSummary: function () { + return this.get('content.serviceName') === 'HDFS' && this.get('sectionNameSuffix') === '_SUMMARY' && App.get('hasNameNodeFederation'); + }.property('content.serviceName', 'sectionNameSuffix', 'App.hasNameNodeFederation'), + /** * @Type {App.WidgetLayout} */ @@ -96,10 +100,18 @@ App.WidgetSectionMixin = Ember.Mixin.create({ return []; }.property('isWidgetsLoaded', 'activeWidgetLayout.widgets'), + activeNSWidgetLayouts: [], + + selectedNSWidgetLayout: {}, + isAmbariMetricsInstalled: function () { return App.Service.find().someProperty('serviceName', 'AMBARI_METRICS'); }.property('App.router.mainServiceController.content.length'), + switchNameServiceLayout: function (data) { + this.set('selectedNSWidgetLayout', data.context); + }, + /** * load widgets defined by user * @returns {$.ajax} @@ -108,6 +120,7 @@ App.WidgetSectionMixin = Ember.Mixin.create({ var sectionName = this.get('sectionName'); var urlParams = 'WidgetLayoutInfo/section_name=' + sectionName; this.set('activeWidgetLayout', {}); + this.set('activeNSWidgetLayouts', []); this.set('isWidgetsLoaded', false); if (this.get('isServiceWithEnhancedWidgets')) { return App.ajax.send({ @@ -133,10 +146,16 @@ App.WidgetSectionMixin = Ember.Mixin.create({ getActiveWidgetLayoutSuccessCallback: function (data) { var self = this; if (data.items[0]) { - self.getWidgetLayoutSuccessCallback(data); + if (this.get('isHDFSFederatedSummary') && data.items.length === 1) { + this.createFederationWidgetLayouts(data.items[0]); + } else { + self.getWidgetLayoutSuccessCallback(data); + } } else { self.getAllActiveWidgetLayouts().done(function (activeWidgetLayoutsData) { self.getDefaultWidgetLayoutByName(self.get('defaultLayoutName')).done(function (defaultWidgetLayoutData) { + defaultWidgetLayoutData = defaultWidgetLayoutData.items[0].WidgetLayoutInfo; + defaultWidgetLayoutData.layout_name = self.get('userLayoutName'); self.createUserWidgetLayout(defaultWidgetLayoutData).done(function (userLayoutIdData) { var activeWidgetLayouts; var widgetLayouts = []; @@ -176,14 +195,19 @@ App.WidgetSectionMixin = Ember.Mixin.create({ */ getWidgetLayoutSuccessCallback: function (data) { if (data) { - App.widgetMapper.map(data.items[0].WidgetLayoutInfo); + data.items.forEach(function (item) { + App.widgetMapper.map(item.WidgetLayoutInfo); + }); App.widgetLayoutMapper.map(data); - this.set('activeWidgetLayout', App.WidgetLayout.find(data.items[0].WidgetLayoutInfo.id)); + this.set('activeWidgetLayout', App.WidgetLayout.find().findProperty('layoutName', this.get('userLayoutName'))); + if (this.get('isHDFSFederatedSummary')) { + this.set('activeNSWidgetLayouts', App.WidgetLayout.find().filterProperty('sectionName', this.get('sectionName')).rejectProperty('layoutName', this.get('userLayoutName'))); + this.set('selectedNSWidgetLayout', this.get('activeNSWidgetLayouts')[0]); + } this.set('isWidgetsLoaded', true); } }, - getDefaultWidgetLayoutByName: function (layoutName) { var urlParams = 'WidgetLayoutInfo/layout_name=' + layoutName; return App.ajax.send({ @@ -195,13 +219,12 @@ App.WidgetSectionMixin = Ember.Mixin.create({ }); }, - createUserWidgetLayout: function (defaultWidgetLayoutData) { - var layout = defaultWidgetLayoutData.items[0].WidgetLayoutInfo; - var layoutName = this.get('userLayoutName'); + createUserWidgetLayout: function (widgetLayoutData) { + var layout = widgetLayoutData; var data = { "WidgetLayoutInfo": { "display_name": layout.display_name, - "layout_name": layoutName, + "layout_name": layout.layout_name, "scope": "USER", "section_name": layout.section_name, "user_name": App.router.get('loginName'), @@ -221,6 +244,33 @@ App.WidgetSectionMixin = Ember.Mixin.create({ }); }, + updateUserWidgetLayout: function (widgetLayoutData) { + var layout = widgetLayoutData; + var data = { + "WidgetLayoutInfo": { + "display_name": layout.display_name, + "layout_name": layout.layout_name, + "id": layout.id, + "scope": "USER", + "section_name": layout.section_name, + "user_name": App.router.get('loginName'), + "widgets": layout.widgets.map(function (widget) { + return { + "id": widget.WidgetInfo.id + } + }) + } + }; + return App.ajax.send({ + name: 'widget.layout.edit', + sender: this, + data: { + layoutId: layout.id, + data: data + }, + }); + }, + saveActiveWidgetLayouts: function (activeWidgetLayouts) { return App.ajax.send({ name: 'widget.activelayouts.edit', @@ -270,5 +320,53 @@ App.WidgetSectionMixin = Ember.Mixin.create({ */ clearActiveWidgetLayout: function () { this.set('activeWidgetLayout', {}); + this.set('activeNSWidgetLayouts', []); + }, + + createFederationWidgetLayouts: function (currentLWidgetLayout) { + var self = this; + currentLWidgetLayout = currentLWidgetLayout.WidgetLayoutInfo; + var newLayoutsIds = [currentLWidgetLayout.id]; + var userLayoutName = this.get('userLayoutName'); + var nameServices = App.HDFSService.find().objectAt(0).get('masterComponentGroups'); + var nameServiceToWidgetMap = {all: []}; + var nonNameServiceSpecific = []; + this.getDefaultWidgetLayoutByName(this.get('defaultLayoutName')).done(function (defaultWidgetLayoutData) { + var newLayout = defaultWidgetLayoutData.items[0].WidgetLayoutInfo; + + newLayout.widgets.forEach(function (widget) { + var tag = widget.WidgetInfo.tag; + if (widget.WidgetInfo.scope === 'CLUSTER' && tag) { + if (!nameServiceToWidgetMap[tag]) { + nameServiceToWidgetMap[tag] = []; + } + nameServiceToWidgetMap[tag].push(widget); + nameServiceToWidgetMap.all.push(widget); + } else { + nonNameServiceSpecific.push(widget); + } + }); + + Em.keys(nameServiceToWidgetMap).forEach(function (nameService) { + newLayout.layout_name = userLayoutName + '_nameservice_' + nameService; + newLayout.display_name = nameService === 'all' ? 'All' : nameService; + newLayout.widgets = nameServiceToWidgetMap[nameService]; + self.createUserWidgetLayout(newLayout).done(function (data) { + newLayoutsIds.push(data.resources[0].WidgetLayoutInfo.id); + if (newLayoutsIds.length >= nameServices.length) { + self.saveActiveWidgetLayouts({ + "WidgetLayouts": newLayoutsIds.map(function (layout) { + return {id: layout}; + }) + }).done(function () { + currentLWidgetLayout.widgets = nonNameServiceSpecific; + self.updateUserWidgetLayout(currentLWidgetLayout).done(function () { + self.getActiveWidgetLayout(); + }); + }); + } + }); + }) + }); } }); \ No newline at end of file diff --git a/ambari-web/app/models/host_component.js b/ambari-web/app/models/host_component.js index 9e01c4d..4bfafc0 100644 --- a/ambari-web/app/models/host_component.js +++ b/ambari-web/app/models/host_component.js @@ -545,7 +545,7 @@ App.HostComponentActionMap = { TOGGLE_NN_FEDERATION: { action: 'openNameNodeFederationWizard', label: Em.I18n.t('admin.nameNodeFederation.button.enable'), - cssClass: 'glyphicon glyphicon-arrow-up', + cssClass: 'icon icon-sitemap', //todo: provide disabled flag } }; diff --git a/ambari-web/app/models/service/hdfs.js b/ambari-web/app/models/service/hdfs.js index ba5ee96..64ec0ae 100644 --- a/ambari-web/app/models/service/hdfs.js +++ b/ambari-web/app/models/service/hdfs.js @@ -116,7 +116,7 @@ App.HDFSService = App.Service.extend({ }; if (!existingNameSpace) { result.push(currentNameSpace); - } + } if (!currentNameSpace.hosts.contains(hostName)) { currentNameSpace.hosts.push(hostName); } diff --git a/ambari-web/app/models/widget_layout.js b/ambari-web/app/models/widget_layout.js index 7427375..aaabf69 100644 --- a/ambari-web/app/models/widget_layout.js +++ b/ambari-web/app/models/widget_layout.js @@ -24,7 +24,11 @@ App.WidgetLayout = DS.Model.extend({ sectionName: DS.attr('string'), widgets: DS.hasMany('App.Widget'), scope: DS.attr('string'), - user: DS.attr('string') + user: DS.attr('string'), + + nameServiceId: function () { + return this.get('layoutName').split('_nameservice_')[1] || ''; + }.property('layoutName') }); diff --git a/ambari-web/app/styles/enhanced_service_dashboard.less b/ambari-web/app/styles/enhanced_service_dashboard.less index a930788..d212925 100644 --- a/ambari-web/app/styles/enhanced_service_dashboard.less +++ b/ambari-web/app/styles/enhanced_service_dashboard.less @@ -74,7 +74,7 @@ } } -#widget_layout, +#ns_widget_layout, #widget_layout, #widget-preview { .frame { height: 150px; @@ -208,7 +208,7 @@ padding: 10px 1.3% 10px 1.3%; } - #widget_layout .span2p4 { + #ns_widget_layout .span2p4, #widget_layout .span2p4 { width: 24.5%; *width: 24.5%; } @@ -216,7 +216,7 @@ @media (min-width: 1500px) { - #widget_layout .span2p4 { + #ns_widget_layout .span2p4, #widget_layout .span2p4 { width: 24.6%; *width: 24.6%; } @@ -235,7 +235,7 @@ } } -#widget_layout { +#ns_widget_layout, #widget_layout { .widget { .img-thumbnail { box-sizing: content-box; diff --git a/ambari-web/app/templates/main/service/info/metrics.hbs b/ambari-web/app/templates/main/service/info/metrics.hbs index 6834c06..e6678d7 100644 --- a/ambari-web/app/templates/main/service/info/metrics.hbs +++ b/ambari-web/app/templates/main/service/info/metrics.hbs @@ -96,6 +96,29 @@ {{/each}} </table> </div> + {{#if controller.isHDFSFederatedSummary}} + {{#if controller.selectedNSWidgetLayout}} + <div style="margin-left: 21px; margin-top: 25px; position: relative"> + <button type="button" style="min-width: 200px; text-align: left;" class="btn btn-default dropdown-toggle" data-toggle="dropdown"> + {{controller.selectedNSWidgetLayout.displayName}} <span class="caret pull-right" style="position: relative; top: 6px;"></span> + </button> + <ul class="dropdown-menu"> + {{#each option in controller.activeNSWidgetLayouts}} + <li><a href="#" {{action switchNameServiceLayout option target="controller"}}>{{option.displayName}}</a></li> + {{/each}} + </ul> + </div> + <div class="panel-body service-widgets-box"> + <div id="ns_widget_layout" class="thumbnails"> + {{#each widget in controller.selectedNSWidgetLayout.widgets}} + <div class="widget span2p4" {{bindAttr id="widget.id"}}> + {{view widget.viewClass contentBinding="widget" idBinding="widget.id"}} + </div> + {{/each}} + </div> + </div> + {{/if}} + {{/if}} </div> </div> {{/if}} diff --git a/ambari-web/app/views/main/service/info/metrics_view.js b/ambari-web/app/views/main/service/info/metrics_view.js index 161dce1..2d13747 100644 --- a/ambari-web/app/views/main/service/info/metrics_view.js +++ b/ambari-web/app/views/main/service/info/metrics_view.js @@ -80,12 +80,15 @@ App.MainServiceInfoMetricsView = Em.View.extend(App.Persist, App.TimeRangeMixin, */ persistKey: Em.computed.format('time-range-service-{0}', 'service.serviceName'), + isLoading: false, + didInsertElement: function () { var svcName = this.get('controller.content.serviceName'); this.set('service', this.getServiceModel(svcName)); var isMetricsSupported = svcName !== 'STORM' || App.get('isStormMetricsSupported'); - this.get('controller').getActiveWidgetLayout(); + this.loadActiveWidgetLayout(); + if (App.get('supports.customizedWidgetLayout')) { this.get('controller').loadWidgetLayouts(); } @@ -98,6 +101,14 @@ App.MainServiceInfoMetricsView = Em.View.extend(App.Persist, App.TimeRangeMixin, this.addWidgetTooltip(); }, + loadActiveWidgetLayout: function () { + var isHDFS = this.get('controller.content.serviceName') === 'HDFS'; + if (!this.get('isLoading') && (!isHDFS || (isHDFS && App.router.get('clusterController.isHostComponentMetricsLoaded')))) { + this.set('isLoading', true); + this.get('controller').getActiveWidgetLayout(); + } + }.observes('App.router.clusterController.isHostComponentMetricsLoaded'), + addWidgetTooltip: function() { Em.run.later(this, function () { App.tooltip($("[rel='add-widget-tooltip']")); @@ -116,6 +127,7 @@ App.MainServiceInfoMetricsView = Em.View.extend(App.Persist, App.TimeRangeMixin, $("[rel='add-widget-tooltip']").tooltip('destroy'); $('.img-thumbnail').off(); $('#widget_layout').sortable('destroy'); + $('#ns_widget_layout').sortable('destroy'); $('.widget.span2p4').detach().remove(); this.get('serviceMetricGraphs').clear(); this.set('service', null); @@ -286,5 +298,26 @@ App.MainServiceInfoMetricsView = Em.View.extend(App.Persist, App.TimeRangeMixin, }).disableSelection(); $('html').off('DOMNodeInserted', '#widget_layout'); }); + $('html').on('DOMNodeInserted', '#ns_widget_layout', function () { + $(this).sortable({ + items: "> div", + cursor: "move", + tolerance: "pointer", + scroll: false, + update: function () { + var widgets = misc.sortByOrder($("#ns_widget_layout .widget").map(function () { + return this.id; + }), self.get('controller.widgets')); + self.get('controller').saveWidgetLayout(widgets); + }, + activate: function () { + self.set('isMoving', true); + }, + deactivate: function () { + self.set('isMoving', false); + } + }).disableSelection(); + $('html').off('DOMNodeInserted', '#ns_widget_layout'); + }); } }); diff --git a/ambari-web/test/views/main/service/info/metrics_view_test.js b/ambari-web/test/views/main/service/info/metrics_view_test.js index 916d451..8e0024d 100644 --- a/ambari-web/test/views/main/service/info/metrics_view_test.js +++ b/ambari-web/test/views/main/service/info/metrics_view_test.js @@ -220,7 +220,7 @@ describe('App.MainServiceInfoMetricsView', function() { }); it("sortable() should be called", function() { - expect(mock.sortable.calledOnce).to.be.true; + expect(mock.sortable.called).to.be.true; }); it("off() should be called", function() { @@ -234,7 +234,7 @@ describe('App.MainServiceInfoMetricsView', function() { sinon.stub(view, 'constructGraphObjects', Em.K); this.mock = sinon.stub(App, 'get'); sinon.stub(view, 'getServiceModel'); - sinon.stub(view.get('controller'), 'getActiveWidgetLayout'); + sinon.stub(view, 'loadActiveWidgetLayout'); sinon.stub(view.get('controller'), 'loadWidgetLayouts'); sinon.stub(view, 'makeSortable'); sinon.stub(view, 'addWidgetTooltip'); @@ -245,7 +245,7 @@ describe('App.MainServiceInfoMetricsView', function() { view.constructGraphObjects.restore(); this.mock.restore(); view.getServiceModel.restore(); - view.get('controller').getActiveWidgetLayout.restore(); + view.loadActiveWidgetLayout.restore(); view.get('controller').loadWidgetLayouts.restore(); view.makeSortable.restore(); view.addWidgetTooltip.restore(); @@ -263,9 +263,9 @@ describe('App.MainServiceInfoMetricsView', function() { view.didInsertElement(); expect(view.makeSortable.calledOnce).to.be.true; }); - it("getActiveWidgetLayout should be called", function() { + it("loadActiveWidgetLayout should be called", function() { view.didInsertElement(); - expect(view.get('controller').getActiveWidgetLayout.calledOnce).to.be.true; + expect(view.loadActiveWidgetLayout.called).to.be.true; }); describe("serviceName is null, metrics not supported, widgets not supported", function() { -- To stop receiving notification emails like this one, please contact akovale...@apache.org.