This is an automated email from the ASF dual-hosted git repository.

ababiichuk pushed a commit to branch branch-2.7
in repository https://gitbox.apache.org/repos/asf/ambari.git


The following commit(s) were added to refs/heads/branch-2.7 by this push:
     new 22c18e5  AMBARI-24362 Fixes for modal with config validations and 
dependent properties. (ababiichuk)
22c18e5 is described below

commit 22c18e59a5dd067075d1e407a533297cbd97aefe
Author: ababiichuk <ababiic...@hortonworks.com>
AuthorDate: Thu Jul 26 19:42:48 2018 +0300

    AMBARI-24362 Fixes for modal with config validations and dependent 
properties. (ababiichuk)
---
 ambari-web/app/controllers/main/host/details.js    | 184 ++++++++++++---------
 ambari-web/app/controllers/main/service/item.js    |   4 +-
 .../common/configs/config_recommendations.js       |  12 +-
 ambari-web/app/styles/application.less             |  33 +++-
 ambari-web/app/styles/bootstrap_overrides.less     |   2 +-
 ambari-web/app/styles/common.less                  |  13 ++
 ambari-web/app/styles/modal_popups.less            |  13 +-
 ambari-web/app/styles/top-nav.less                 |   4 +-
 .../templates/common/configs/services_config.hbs   |   4 +-
 .../modal_popups/config_recommendation_popup.hbs   |  40 ++---
 .../common/modal_popups/dependent_configs_list.hbs |   6 +-
 .../modal_popups/dependent_configs_table.hbs       |  24 +--
 ambari-web/app/views/common/modal_popup.js         |  13 +-
 .../config_validation/config_validation_popup.js   |   4 +-
 .../modal_popups/dependent_configs_list_popup.js   |  45 +++--
 .../common/modal_popups/log_file_search_popup.js   |   2 +-
 .../common/configs/config_recommendations_test.js  |  22 ++-
 17 files changed, 269 insertions(+), 156 deletions(-)

diff --git a/ambari-web/app/controllers/main/host/details.js 
b/ambari-web/app/controllers/main/host/details.js
index d2c9aca..8a445dd 100644
--- a/ambari-web/app/controllers/main/host/details.js
+++ b/ambari-web/app/controllers/main/host/details.js
@@ -540,9 +540,9 @@ App.MainHostDetailsController = 
Em.Controller.extend(App.SupportClientConfigsDow
       header: Em.I18n.t('popup.confirmation.commonHeader'),
       controller: this,
       hasPropertiesToChange: false,
-      classNameBindings: 
['controller.hasPropertiesToChange:common-modal-wrapper', 
'controller.hasPropertiesToChange:modal-full-width'],
+      classNameBindings: 
['controller.hasPropertiesToChange:common-modal-wrapper'],
       modalDialogClasses: function () {
-        return this.get('controller.hasPropertiesToChange') ? ['modal-lg'] : 
[];
+        return this.get('controller.hasPropertiesToChange') ? ['modal-xlg'] : 
[];
       }.property('controller.hasPropertiesToChange'),
       primary: Em.I18n.t('hosts.host.deleteComponent.popup.confirm'),
       bodyClass: this.get('addDeleteComponentPopupBody').extend({
@@ -808,9 +808,9 @@ App.MainHostDetailsController = 
Em.Controller.extend(App.SupportClientConfigsDow
       header: Em.I18n.t('popup.confirmation.commonHeader'),
       controller: self,
       hasPropertiesToChange: false,
-      classNameBindings: ['hasPropertiesToChange:common-modal-wrapper', 
'hasPropertiesToChange:modal-full-width'],
+      classNameBindings: ['hasPropertiesToChange:common-modal-wrapper'],
       modalDialogClasses: function () {
-        return this.get('controller.hasPropertiesToChange') ? ['modal-lg'] : 
[];
+        return this.get('controller.hasPropertiesToChange') ? ['modal-xlg'] : 
[];
       }.property('controller.hasPropertiesToChange'),
       primary: Em.I18n.t('hosts.host.addComponent.popup.confirm'),
       bodyClass: self.get('addDeleteComponentPopupBody').extend({
@@ -990,19 +990,19 @@ App.MainHostDetailsController = 
Em.Controller.extend(App.SupportClientConfigsDow
    * @method updateZkConfigs
    */
   updateZkConfigs: function (configs) {
-    var portValue = configs['zoo.cfg'] && Em.get(configs['zoo.cfg'], 
'clientPort');
-    var zkPort = typeof portValue === 'undefined' ? '2181' : portValue;
-    var infraSolrZnode = configs['infra-solr-env'] ? 
Em.get(configs['infra-solr-env'], 'infra_solr_znode') : '/ambari-solr';
-    var initializer = App.AddZooKeeperComponentsInitializer;
-    var hostComponentsTopology = {
-      masterComponentHosts: []
-    };
-    var propertiesToChange = this.get('allPropertiesToChange');
-    var masterComponents = this.bootstrapHostsMapping('ZOOKEEPER_SERVER');
+    const portValue = configs['zoo.cfg'] && Em.get(configs['zoo.cfg'], 
'clientPort'),
+      zkPort = typeof portValue === 'undefined' ? '2181' : portValue,
+      infraSolrZnode = configs['infra-solr-env'] ? 
Em.get(configs['infra-solr-env'], 'infra_solr_znode') : '/ambari-solr',
+      initializer = App.AddZooKeeperComponentsInitializer,
+      hostComponentsTopology = {
+        masterComponentHosts: []
+      },
+      propertiesToChange = this.get('allPropertiesToChange'),
+      masterComponents = this.bootstrapHostsMapping('ZOOKEEPER_SERVER');
     if (this.get('fromDeleteHost') || this.get('fromDeleteZkServer')) {
       this.set('fromDeleteHost', false);
       this.set('fromDeleteZkServer', false);
-      var removedHost = masterComponents.findProperty('hostName', 
this.get('content.hostName'));
+      let removedHost = masterComponents.findProperty('hostName', 
this.get('content.hostName'));
       if (!Em.isNone(removedHost)) {
         Em.set(removedHost, 'isInstalled', false);
       }
@@ -1014,34 +1014,38 @@ App.MainHostDetailsController = 
Em.Controller.extend(App.SupportClientConfigsDow
         isInstalled: true
       });
     }
-    var dependencies = {
+    const dependencies = {
       zkClientPort: zkPort,
-      infraSolrZnode: infraSolrZnode
+      infraSolrZnode
     };
     hostComponentsTopology.masterComponentHosts = masterComponents;
-    Em.keys(configs).forEach(function(fileName) {
-      var properties = configs[fileName];
-      Em.keys(properties).forEach(function(propertyName) {
-        var currentValue = properties[propertyName],
+    Em.keys(configs).forEach(fileName => {
+      const properties = configs[fileName];
+      Em.keys(properties).forEach(propertyName => {
+        const currentValue = properties[propertyName],
           propertyDef = {
-            fileName: fileName,
+            fileName,
             name: propertyName,
             value: currentValue
           },
           configProperty = initializer.initialValue(propertyDef, 
hostComponentsTopology, dependencies);
         initializer.updateSiteObj(configs[fileName], configProperty);
         if (currentValue !== configs[fileName][propertyName]) {
-          var service = App.config.get('serviceByConfigTypeMap')[fileName];
+          const service = App.config.get('serviceByConfigTypeMap')[fileName],
+            configObject = App.configsCollection.getConfigByName(propertyName, 
fileName),
+            displayName = configObject && configObject.displayName;
           propertiesToChange.pushObject({
             propertyFileName: fileName,
-            propertyName: propertyName,
+            propertyName,
+            propertyTitle: configObject && 
Em.I18n.t('installer.controls.serviceConfigPopover.title').format(displayName, 
displayName === propertyName ? '' : propertyName),
+            propertyDescription: configObject && configObject.description,
             serviceDisplayName: service && service.get('displayName'),
             initialValue: currentValue,
             recommendedValue: propertyDef.value
           });
         }
-      }, this);
-    }, this);
+      });
+    });
   },
 
   /**
@@ -1069,40 +1073,46 @@ App.MainHostDetailsController = 
Em.Controller.extend(App.SupportClientConfigsDow
    * @method onLoadStormConfigs
    */
   onLoadStormConfigs: function (data) {
-    var nimbusHost = this.get('nimbusHost'),
+    const nimbusHost = this.get('nimbusHost'),
       stormNimbusHosts = this.getStormNimbusHosts(),
       configs = {},
       attributes = {},
       propertiesToChange = this.get('allPropertiesToChange');
 
     this.saveLoadedConfigs(data);
-    data.items.forEach(function (item) {
+    data.items.forEach(item => {
       configs[item.type] = item.properties;
       attributes[item.type] = item.properties_attributes || {};
-    }, this);
+    });
 
     this.updateZkConfigs(configs);
 
-    var nimbusSeedsInit = configs['storm-site']['nimbus.seeds'],
+    const propertyName = 'nimbus.seeds',
+      propertyFileName = 'storm-site',
+      nimbusSeedsInit = configs[propertyFileName][propertyName],
       nimbusSeedsRecommended = JSON.stringify(stormNimbusHosts).replace(/"/g, 
"'");
-    configs['storm-site']['nimbus.seeds'] = nimbusSeedsRecommended;
+    configs[propertyFileName][propertyName] = nimbusSeedsRecommended;
     if (this.get('isReconfigureRequired') && nimbusSeedsInit !== 
nimbusSeedsRecommended) {
-      var service = App.config.get('serviceByConfigTypeMap')['storm-site'];
+      const service = 
App.config.get('serviceByConfigTypeMap')[propertyFileName],
+        configObject = App.configsCollection.getConfigByName(propertyName, 
propertyFileName),
+        displayName = configObject && configObject.displayName;
       propertiesToChange.pushObject({
-        propertyFileName: 'storm-site',
-        propertyName: 'nimbus.seeds',
+        propertyFileName,
+        propertyName,
+        propertyTitle: configObject && 
Em.I18n.t('installer.controls.serviceConfigPopover.title').format(displayName, 
displayName === propertyName ? '' : propertyName),
+        propertyDescription: configObject && configObject.description,
         serviceDisplayName: service && service.get('displayName'),
         initialValue: nimbusSeedsInit,
         recommendedValue: nimbusSeedsRecommended
       });
     }
-    var groups = [
+    const groups = [
       {
         properties: {
-          'storm-site': configs['storm-site']
+          [propertyFileName]: configs[propertyFileName]
         },
         properties_attributes: {
-          'storm-site': attributes['storm-site']
+          [propertyFileName]: attributes[propertyFileName]
         }
       }
     ];
@@ -1110,41 +1120,45 @@ App.MainHostDetailsController = 
Em.Controller.extend(App.SupportClientConfigsDow
   },
 
   onLoadAtlasConfigs: function(data) {
-    var atlasServer = this.get('atlasServer'),
+    const atlasServer = this.get('atlasServer'),
       atlasServerHosts = this.getAtlasServerHosts(),
       configs = {},
       attributes = {},
       propertiesToChange = this.get('allPropertiesToChange');
 
     this.saveLoadedConfigs(data);
-    data.items.forEach(function (item) {
+    data.items.forEach(item => {
       configs[item.type] = item.properties;
       attributes[item.type] = item.properties_attributes || {};
-    }, this);
+    });
 
-    var atlasAddresses = 
configs['application-properties']['atlas.rest.address'];
-    var hostMask = 
atlasAddresses.split(',')[0].replace(/([https|http]*\:\/\/)(.*?)(:[0-9]+)/, 
'$1{hostname}$3');
-    var atlasAddressesRecommended = atlasServerHosts.map(function(hostName) {
-      return hostMask.replace('{hostname}', hostName);
-    }).join(',');
-    configs['application-properties']['atlas.rest.address'] = 
atlasAddressesRecommended;
+    const propertyFileName = 'application-properties',
+      propertyName = 'atlas.rest.address',
+      atlasAddresses = configs[propertyFileName][propertyName],
+      hostMask = 
atlasAddresses.split(',')[0].replace(/([https|http]*\:\/\/)(.*?)(:[0-9]+)/, 
'$1{hostname}$3'),
+      atlasAddressesRecommended = atlasServerHosts.map(hostName => 
hostMask.replace('{hostname}', hostName)).join(',');
+    configs[propertyFileName][propertyName] = atlasAddressesRecommended;
     if (this.get('isReconfigureRequired') && atlasAddresses !== 
atlasAddressesRecommended) {
-      var service = 
App.config.get('serviceByConfigTypeMap')['application-properties'];
+      var service = App.config.get('serviceByConfigTypeMap')[propertyFileName],
+        configObject = App.configsCollection.getConfigByName(propertyName, 
propertyFileName),
+        displayName = configObject && configObject.displayName;
       propertiesToChange.pushObject({
-        propertyFileName: 'application-properties',
-        propertyName: 'atlas.rest.address',
+        propertyFileName,
+        propertyName,
+        propertyTitle: configObject && 
Em.I18n.t('installer.controls.serviceConfigPopover.title').format(displayName, 
displayName === propertyName ? '' : propertyName),
+        propertyDescription: configObject && configObject.description,
         serviceDisplayName: service && service.get('displayName'),
         initialValue: atlasAddresses,
         recommendedValue: atlasAddressesRecommended
       });
     }
-    var groups = [
+    const groups = [
       {
         properties: {
-          'application-properties': configs['application-properties']
+          [propertyFileName]: configs[propertyFileName]
         },
         properties_attributes: {
-          'application-properties': attributes['application-properties']
+          [propertyFileName]: attributes[propertyFileName]
         }
       }
     ];
@@ -1202,26 +1216,26 @@ App.MainHostDetailsController = 
Em.Controller.extend(App.SupportClientConfigsDow
    * @method onLoadHiveConfigs
    */
   onLoadHiveConfigs: function (data, opt, params) {
-    var port = "";
-    var configs = {};
-    var attributes = {};
-    var userSetup = {};
-    var localDB = {
-      masterComponentHosts: this.getHiveHosts()
-    };
-    var dependencies = {
-      hiveMetastorePort: ""
-    };
-    var initializer = params.webHCat ? App.AddWebHCatComponentsInitializer : 
App.AddHiveComponentsInitializer;
+    let port = "";
+    const configs = {},
+      attributes = {},
+      userSetup = {},
+      localDB = {
+        masterComponentHosts: this.getHiveHosts()
+      },
+      dependencies = {
+        hiveMetastorePort: ""
+      },
+      initializer = params.webHCat ? App.AddWebHCatComponentsInitializer : 
App.AddHiveComponentsInitializer;
     this.saveLoadedConfigs(data);
     this.set('configs.params', {
       webHCat: params.webHCat
     });
-    data.items.forEach(function (item) {
+    data.items.forEach(item => {
       configs[item.type] = item.properties;
       attributes[item.type] = item.properties_attributes || {};
-    }, this);
-    var propertiesToChange = this.get('allPropertiesToChange');
+    });
+    const propertiesToChange = this.get('allPropertiesToChange');
 
     port = configs['hive-site']['hive.metastore.uris'].match(/:[0-9]{2,4}/);
     port = port ? port[0].slice(1) : "9083";
@@ -1236,30 +1250,34 @@ App.MainHostDetailsController = 
Em.Controller.extend(App.SupportClientConfigsDow
 
     initializer.setup(userSetup);
 
-    ['hive-site', 'webhcat-site', 'hive-env', 
'core-site'].forEach(function(fileName) {
+    ['hive-site', 'webhcat-site', 'hive-env', 'core-site'].forEach(fileName => 
{
       if (configs[fileName]) {
-        Em.keys(configs[fileName]).forEach(function(propertyName) {
-          var currentValue = configs[fileName][propertyName],
+        Em.keys(configs[fileName]).forEach(propertyName => {
+          const currentValue = configs[fileName][propertyName],
             propertyDef = {
-              fileName: fileName,
+              fileName,
               name: propertyName,
               value: currentValue
             },
             configProperty = initializer.initialValue(propertyDef, localDB, 
dependencies);
           initializer.updateSiteObj(configs[fileName], configProperty);
           if (this.get('isReconfigureRequired') && currentValue !== 
configs[fileName][propertyName]) {
-            var service = App.config.get('serviceByConfigTypeMap')[fileName];
+            const service = App.config.get('serviceByConfigTypeMap')[fileName],
+              configObject = 
App.configsCollection.getConfigByName(propertyName, fileName),
+              displayName = configObject && configObject.displayName;
             propertiesToChange.pushObject({
               propertyFileName: fileName,
-              propertyName: propertyName,
+              propertyName,
+              propertyTitle: configObject && 
Em.I18n.t('installer.controls.serviceConfigPopover.title').format(displayName, 
displayName === propertyName ? '' : propertyName),
+              propertyDescription: configObject && configObject.description,
               serviceDisplayName: service && service.get('displayName'),
               initialValue: currentValue,
               recommendedValue: propertyDef.value
             });
           }
-        }, this);
+        });
       }
-    }, this);
+    });
 
     initializer.cleanup();
 
@@ -1416,7 +1434,7 @@ App.MainHostDetailsController = 
Em.Controller.extend(App.SupportClientConfigsDow
    * @method onLoadRangerConfigs
    */
   onLoadRangerConfigs: function (data) {
-    var hdfsProperties = [
+    const hdfsProperties = [
         {
           type: 'core-site',
           name: 'hadoop.security.key.provider.path'
@@ -1490,8 +1508,8 @@ App.MainHostDetailsController = 
Em.Controller.extend(App.SupportClientConfigsDow
 
     this.saveLoadedConfigs(data);
 
-    hdfsProperties.forEach(function (property) {
-      var typeConfigs = data.items.findProperty('type', 
property.type).properties,
+    hdfsProperties.forEach(property => {
+      const typeConfigs = data.items.findProperty('type', 
property.type).properties,
         currentValue = typeConfigs[property.name],
         pattern = new RegExp('^kms:\\/\\/http@(.+):' + rkmsPort + '\\/kms$'),
         patternMatch = currentValue && currentValue.match(pattern),
@@ -1499,10 +1517,16 @@ App.MainHostDetailsController = 
Em.Controller.extend(App.SupportClientConfigsDow
       if (currentHostsList !== rkmsHostsStr) {
         typeConfigs[property.name] = newValue;
         if (this.get('isReconfigureRequired')) {
-          var service = 
App.config.get('serviceByConfigTypeMap')[property.type];
+          const propertyFileName = property.type,
+            propertyName = property.name,
+            service = 
App.config.get('serviceByConfigTypeMap')[propertyFileName],
+            configObject = App.configsCollection.getConfigByName(propertyName, 
propertyFileName),
+            displayName = configObject && configObject.displayName;
           propertiesToChange.pushObject({
-            propertyFileName: property.type,
-            propertyName: property.name,
+            propertyFileName,
+            propertyName,
+            propertyTitle: configObject && 
Em.I18n.t('installer.controls.serviceConfigPopover.title').format(displayName, 
displayName === propertyName ? '' : propertyName),
+            propertyDescription: configObject && configObject.description,
             serviceDisplayName: service && service.get('displayName'),
             initialValue: currentValue,
             recommendedValue: newValue,
@@ -1510,7 +1534,7 @@ App.MainHostDetailsController = 
Em.Controller.extend(App.SupportClientConfigsDow
           });
         }
       }
-    }, this);
+    });
 
     kmsSiteProperties.forEach(function (property) {
       var currentValue = kmsSiteConfigs.properties[property.name];
diff --git a/ambari-web/app/controllers/main/service/item.js 
b/ambari-web/app/controllers/main/service/item.js
index 1bc8410..6560666 100644
--- a/ambari-web/app/controllers/main/service/item.js
+++ b/ambari-web/app/controllers/main/service/item.js
@@ -1521,8 +1521,8 @@ App.MainServiceItemController = 
Em.Controller.extend(App.SupportClientConfigsDow
       primary: popupPrimary,
       primaryClass: 'btn-danger',
       disablePrimary: 
Em.computed.alias('controller.isRecommendationInProgress'),
-      classNameBindings: 
['controller.changedProperties.length:common-modal-wrapper', 
'controller.changedProperties.length:modal-full-width'],
-      modalDialogClasses: 
Em.computed.ifThenElse('controller.changedProperties.length', ['modal-lg'], []),
+      classNameBindings: 
['controller.changedProperties.length:common-modal-wrapper'],
+      modalDialogClasses: 
Em.computed.ifThenElse('controller.changedProperties.length', ['modal-xlg'], 
[]),
       bodyClass: Em.View.extend({
         templateName: 
require('templates/main/service/info/delete_service_warning_popup'),
         warningMessage: new Em.Handlebars.SafeString(warningMessage)
diff --git a/ambari-web/app/mixins/common/configs/config_recommendations.js 
b/ambari-web/app/mixins/common/configs/config_recommendations.js
index bccf3b7..6e51523 100644
--- a/ambari-web/app/mixins/common/configs/config_recommendations.js
+++ b/ambari-web/app/mixins/common/configs/config_recommendations.js
@@ -96,20 +96,24 @@ App.ConfigRecommendations = Em.Mixin.create({
    */
   addRecommendation: function (name, fileName, configGroupName, 
recommendedValue, initialValue, parentPropertyIds, isEditable) {
     Em.assert('name and fileName should be defined', name && fileName);
-    var site = App.config.getConfigTagFromFileName(fileName);
-    var service = App.config.get('serviceByConfigTypeMap')[site];
+    const site = App.config.getConfigTagFromFileName(fileName);
+    const service = App.config.get('serviceByConfigTypeMap')[site];
+    const configObject = App.configsCollection.getConfigByName(name, fileName);
+    const displayName = configObject && configObject.displayName;
 
-    var recommendation = {
+    const recommendation = {
       saveRecommended: true,
       saveRecommendedDefault: true,
       propertyFileName: site,
       propertyName: name,
+      propertyTitle: configObject && 
Em.I18n.t('installer.controls.serviceConfigPopover.title').format(displayName, 
displayName === name ? '' : name),
+      propertyDescription: configObject && configObject.description,
 
       isDeleted: Em.isNone(recommendedValue),
       notDefined: Em.isNone(initialValue),
 
       configGroup: configGroupName || "Default",
-      initialValue: initialValue,
+      initialValue,
       parentConfigs: parentPropertyIds || [],
       serviceName: service.get('serviceName'),
       allowChangeGroup: false,//TODO groupName!= "Default" && 
(service.get('serviceName') != this.get('selectedService.serviceName'))
diff --git a/ambari-web/app/styles/application.less 
b/ambari-web/app/styles/application.less
index 367233b..3f4d2d2 100644
--- a/ambari-web/app/styles/application.less
+++ b/ambari-web/app/styles/application.less
@@ -20,7 +20,7 @@
 
 body {
   overflow-y: scroll;
-  line-height: 1.3em;
+  line-height: @default-line-height;
 }
 
 html, body {
@@ -2171,6 +2171,26 @@ input[type="radio"].align-checkbox, 
input[type="checkbox"].align-checkbox {
 
 .config-validation-warnings {
   table {
+    table-layout: fixed;
+    td, th {
+      &.issue-type-cell {
+        width: 5%;
+      }
+      &.service-name-cell {
+        width: 15%;
+      }
+      &.property-name-cell {
+        width: 15%;
+      }
+      &.property-value-cell {
+        width: 25%;
+        overflow: hidden;
+        overflow-wrap: break-word;
+      }
+      &.property-description-cell {
+        width: 40%;
+      }
+    }
     tbody{
       tr {
         &.warning {
@@ -2221,13 +2241,18 @@ input[type="radio"].align-checkbox, 
input[type="checkbox"].align-checkbox {
 #config-dependencies {
   max-height: 500px;
   overflow-y: visible;
-  td {
-    min-width: 120px;
-    word-break: break-all;
+  td, th {
     &.check-box-col {
       min-width: 5px;
       width: 5px;
+      .checkbox {
+        display: inline-block;
+      }
     }
+  }
+  td {
+    min-width: 120px;
+    word-break: break-all;
     &.config-dependency-name {
       min-width: @config-dependency-t-name-width;
     }
diff --git a/ambari-web/app/styles/bootstrap_overrides.less 
b/ambari-web/app/styles/bootstrap_overrides.less
index 9243ef6..70d9945 100644
--- a/ambari-web/app/styles/bootstrap_overrides.less
+++ b/ambari-web/app/styles/bootstrap_overrides.less
@@ -432,7 +432,7 @@ select.form-control {
 
 .btn-group.open .dropdown-menu input[type="checkbox"] + label,
 .dropdown.open .dropdown-menu input[type="checkbox"] + label {
-  line-height: 1.3em;
+  line-height: @default-line-height;
 }
 
 .navigation-bar-container.collapsed ul.nav.side-nav-menu li ul.sub-menu,
diff --git a/ambari-web/app/styles/common.less 
b/ambari-web/app/styles/common.less
index 15babfa..22ef496 100644
--- a/ambari-web/app/styles/common.less
+++ b/ambari-web/app/styles/common.less
@@ -189,6 +189,7 @@
 @default-font-size: 14px;
 @smaller-font-size: 12px;
 @default-button-height: 34px;
+@default-line-height: 1.3em;
 
 /************************************************************************
 * Modal popup properties
@@ -199,6 +200,10 @@
 @modal-header-height: 57px;
 // modal footer height
 @modal-footer-height: 75px;
+// default modal top margin
+@modal-dialog-margin-top-default: 10px;
+// modal top margin for wide screen
+@modal-dialog-margin-top-wide-screen: 30px;
 
 .ellipsis-overflow {
   overflow: hidden;
@@ -211,3 +216,11 @@
 }
 
 @dashboard-widget-height: 157px;
+
+/************************************************************************
+* Top navbar styles
+***********************************************************************/
+@navbar-header-vertical-padding: 19px;
+@navbar-header-padding-right: 15px;
+@navbar-header-padding-left: 0;
+@navbar-header-font-size: 20px;
diff --git a/ambari-web/app/styles/modal_popups.less 
b/ambari-web/app/styles/modal_popups.less
index 0674541..52c036c 100644
--- a/ambari-web/app/styles/modal_popups.less
+++ b/ambari-web/app/styles/modal_popups.less
@@ -17,6 +17,8 @@
  */
 @import 'common.less';
 
+@modal-top-calculated-without-margin: @navbar-header-vertical-padding + 
(@navbar-header-font-size * unit(@default-line-height));
+
 #modal {
   outline: none;
 }
@@ -25,7 +27,7 @@
   margin-left: 20px;
   margin-right: 20px;
   text-indent: -20px;
-  line-height: 1.3em;
+  line-height: @default-line-height;
 
   a:not(.disabled) {
       cursor: pointer;
@@ -97,7 +99,7 @@
 }
 
 .modal {
-  top: 5%;
+  top: @modal-top-calculated-without-margin - @modal-dialog-margin-top-default;
   .modal-body {
     .top-wrap {
       &.top-wrap-header {
@@ -107,6 +109,13 @@
     }
   }
 }
+
+@media (min-width: 768px) {
+  .modal {
+    top: @modal-top-calculated-without-margin - 
@modal-dialog-margin-top-wide-screen;
+  }
+}
+
 .modal-body {
   max-height: 600px;
   overflow-y: auto;
diff --git a/ambari-web/app/styles/top-nav.less 
b/ambari-web/app/styles/top-nav.less
index 9fcc5f3..f14a014 100644
--- a/ambari-web/app/styles/top-nav.less
+++ b/ambari-web/app/styles/top-nav.less
@@ -26,9 +26,9 @@
     margin-bottom: 10px;
 
     .navbar-header {
-      padding: 19px 15px 19px 0;
+      padding: @navbar-header-vertical-padding @navbar-header-padding-right 
@navbar-header-vertical-padding @navbar-header-padding-left;
       margin-top: -5px;
-      font-size: 20px;
+      font-size: @navbar-header-font-size;
       width: ~"calc(100% - 400px)";
       a {
         color: #313D54;
diff --git a/ambari-web/app/templates/common/configs/services_config.hbs 
b/ambari-web/app/templates/common/configs/services_config.hbs
index adea9df..044f32e7 100644
--- a/ambari-web/app/templates/common/configs/services_config.hbs
+++ b/ambari-web/app/templates/common/configs/services_config.hbs
@@ -72,7 +72,9 @@
       <div id="notifications-dropdown" class="dropdown-menu row">
         <div class="popover-content">
           <div class="popup-arrow-up"></div>
-          {{view App.DependentConfigsListView 
recommendationsBinding="controller.recommendations" 
requiredChangesBinding="controller.requiredChanges"}}
+          {{view App.DependentConfigsListView 
recommendationsBinding="controller.recommendations"
+            requiredChangesBinding="controller.requiredChanges" 
isRecommendationsClickable=true
+            showRecommendationsPopovers=false}}
         </div>
       </div>
     </div>
diff --git 
a/ambari-web/app/templates/common/modal_popups/config_recommendation_popup.hbs 
b/ambari-web/app/templates/common/modal_popups/config_recommendation_popup.hbs
index 9dbd401..0f51473 100644
--- 
a/ambari-web/app/templates/common/modal_popups/config_recommendation_popup.hbs
+++ 
b/ambari-web/app/templates/common/modal_popups/config_recommendation_popup.hbs
@@ -25,29 +25,29 @@
     <table class="table table-hover">
       <thead>
       <tr>
-        <th>{{t common.type}}</th>
-        <th>{{t common.service}}</th>
-        <th>{{t common.property}}</th>
-        <th>{{t common.value}}</th>
-        <th>{{t common.description}}</th>
+        <th class="issue-type-cell">{{t common.type}}</th>
+        <th class="service-name-cell">{{t common.service}}</th>
+        <th class="property-name-cell">{{t common.property}}</th>
+        <th class="property-value-cell">{{t common.value}}</th>
+        <th class="property-description-cell">{{t common.description}}</th>
       </tr>
       </thead>
       <tbody>
       {{#each error in view.configErrors.criticalIssues}}
         <tr {{bindAttr class="error.cssClass"}}>
-          <td>
+          <td class="issue-type-cell">
             {{t common.critical}}
           </td>
-          <td>{{error.serviceName}}</td>
-          <td>
+          <td class="service-name-cell">{{error.serviceName}}</td>
+          <td class="property-name-cell">
             {{#if controller.isInstallWizard}}
               <a href="#" {{action "showConfigProperty" error 
target="controller"}}>{{error.propertyName}}</a>
             {{else}}
               {{error.propertyName}}
             {{/if}}
           </td>
-          <td>{{error.value}}</td>
-          <td>
+          <td class="property-value-cell">{{error.value}}</td>
+          <td class="property-description-cell">
             {{#each message in error.messages}}
               <div class="property-message">{{message}}</div>
             {{/each}}
@@ -71,17 +71,17 @@
     <table class="table table-hover">
       <thead>
       <tr>
-        <th>{{t common.type}}</th>
-        <th>{{t common.service}}</th>
-        <th>{{t common.property}}</th>
-        <th>{{t installer.step7.popup.currentValue}}</th>
-        <th>{{t common.description}}</th>
+        <th class="issue-type-cell">{{t common.type}}</th>
+        <th class="service-name-cell">{{t common.service}}</th>
+        <th class="property-name-cell">{{t common.property}}</th>
+        <th class="property-value-cell">{{t 
installer.step7.popup.currentValue}}</th>
+        <th class="property-description-cell">{{t common.description}}</th>
       </tr>
       </thead>
       <tbody>
       {{#each error in view.configErrors.issues}}
         <tr {{bindAttr class="error.cssClass"}}>
-          <td>
+          <td class="issue-type-cell">
             {{#if error.isError}}
               {{t common.error}}
             {{else}}
@@ -94,16 +94,16 @@
               <td colspan="4">{{error.message}}</td>
             {{/each}}
           {{else}}
-            <td>{{error.serviceName}}</td>
-            <td>
+            <td class="service-name-cell">{{error.serviceName}}</td>
+            <td class="property-name-cell">
               {{#if controller.isInstallWizard}}
                 <a href="#" {{action "showConfigProperty" error 
target="controller"}}>{{error.propertyName}}</a>
               {{else}}
                 {{error.propertyName}}
               {{/if}}
             </td>
-            <td>{{error.value}}</td>
-            <td>
+            <td class="property-value-cell">{{error.value}}</td>
+            <td class="property-description-cell">
               {{#each message in error.messages}}
                 <div class="property-message">{{message}}</div>
               {{/each}}
diff --git 
a/ambari-web/app/templates/common/modal_popups/dependent_configs_list.hbs 
b/ambari-web/app/templates/common/modal_popups/dependent_configs_list.hbs
index d5d54ff..d12af61 100644
--- a/ambari-web/app/templates/common/modal_popups/dependent_configs_list.hbs
+++ b/ambari-web/app/templates/common/modal_popups/dependent_configs_list.hbs
@@ -63,9 +63,11 @@
 {{/if}}
 <span id="config-dependencies" class="limited-height-2">
   {{#if view.recommendations.length}}
-    {{view App.DependentConfigsTableView 
recommendationsBinding="view.recommendations" 
checkboxesFirstBinding="controller.isInstallWizard" isClickable=true}}
+    {{view App.DependentConfigsTableView 
recommendationsBinding="view.recommendations"
+      isClickableBinding="view.isRecommendationsClickable" 
showPopoversBinding="view.showRecommendationsPopovers"}}
   {{/if}}
   {{#if view.requiredChanges.length}}
-    {{view App.DependentConfigsTableView 
recommendationsBinding="view.requiredChanges" isEditable=false 
checkboxesFirstBinding="controller.isInstallWizard" isClickable=true}}
+    {{view App.DependentConfigsTableView 
recommendationsBinding="view.requiredChanges" isEditable=false
+      isClickableBinding="view.isRecommendationsClickable" 
showPopoversBinding="view.showRecommendationsPopovers"}}
   {{/if}}
 </span>
diff --git 
a/ambari-web/app/templates/common/modal_popups/dependent_configs_table.hbs 
b/ambari-web/app/templates/common/modal_popups/dependent_configs_table.hbs
index 637ec26..1f506d0 100644
--- a/ambari-web/app/templates/common/modal_popups/dependent_configs_table.hbs
+++ b/ambari-web/app/templates/common/modal_popups/dependent_configs_table.hbs
@@ -22,10 +22,8 @@
 <table class="table table-hover">
   <thead>
     <tr>
-      {{#if view.checkboxesFirst}}
-        {{#if view.isEditable}}
-          <th class="check-box-col">{{view view.parentView.toggleAll}}<label 
{{bindAttr for="view.parentView.toggleAllId"}}></label></th>
-        {{/if}}
+      {{#if view.isEditable}}
+        <th class="check-box-col">{{view view.parentView.toggleAll}}<label 
{{bindAttr for="view.parentView.toggleAllId"}}></label></th>
       {{/if}}
       <th>{{t common.property}}</th>
       <th>{{t common.service}}</th>
@@ -45,22 +43,15 @@
           </div>
         </div>
       </th>
-      {{#unless view.checkboxesFirst}}
-        {{#if view.isEditable}}
-          <th class="check-box-col">{{view view.parentView.toggleAll}}<label 
{{bindAttr for="view.parentView.toggleAllId"}}></label></th>
-        {{/if}}
-      {{/unless}}
     </tr>
   </thead>
   <tbody>
   {{#each recommendation in view.recommendations}}
     <tr {{bindAttr class="recommendation.saveRecommended:active"}}>
-      {{#if view.checkboxesFirst}}
-        {{#if view.isEditable}}
-          <td class="check-box-col">{{view App.CheckboxView 
checkedBinding="recommendation.saveRecommended"}}</td>
-        {{/if}}
+      {{#if view.isEditable}}
+        <td class="check-box-col">{{view App.CheckboxView 
checkedBinding="recommendation.saveRecommended"}}</td>
       {{/if}}
-      <td class="config-dependency-name">
+      <td class="config-dependency-name" {{bindAttr 
data-original-title="recommendation.propertyTitle" 
data-content="recommendation.propertyDescription"}}>
         {{#if view.isClickable}}
           <a href="#" {{action "showConfigProperty" recommendation 
target="controller"}}>{{recommendation.propertyName}}</a>
         {{else}}
@@ -82,11 +73,6 @@
           {{view App.ConfigDiffView configBinding="recommendation"}}
         </div>
       </td>
-      {{#unless view.checkboxesFirst}}
-        {{#if view.isEditable}}
-          <td class="check-box-col">{{view App.CheckboxView 
checkedBinding="recommendation.saveRecommended"}}</td>
-        {{/if}}
-      {{/unless}}
     </tr>
   {{/each}}
   </tbody>
diff --git a/ambari-web/app/views/common/modal_popup.js 
b/ambari-web/app/views/common/modal_popup.js
index ad27a40..4b9830f 100644
--- a/ambari-web/app/views/common/modal_popup.js
+++ b/ambari-web/app/views/common/modal_popup.js
@@ -162,12 +162,19 @@ App.ModalPopup = Ember.View.extend({
 
   fitHeight: function () {
     if (this.get('state') === 'destroyed') return;
-    var popup = this.$().find('#modal'),
+    const popup = this.$().find('#modal'),
       wrapper = $(popup).find('.modal-dialog'),
       block = $(popup).find('.modal-body'),
       wh = $(window).height(),
-      top = wh * 0.05,
-      newMaxHeight = wh - top * 2 - (wrapper.height() - block.height());
+      ww = $(window).width(),
+      topNavPaddingTop = 19, // from ambari-web/app/styles/common.less
+      topNavFontSize = 20, // from ambari-web/app/styles/common.less
+      topNavLineHeight = 1.3, // from ambari-web/app/styles/common.less
+      modalMarginTopDefault = 10, // from ambari-web/app/styles/common.less
+      modalMarginTopWide = 30, // from ambari-web/app/styles/common.less
+      modalMarginTop = ww < 768 ? modalMarginTopDefault : modalMarginTopWide, 
// from ambari-web/vendor/styles/bootstrap.css
+      top = topNavPaddingTop + topNavFontSize * topNavLineHeight - 
modalMarginTop;
+    let newMaxHeight = wh - top * 2 - (wrapper.height() - block.height());
 
     popup.css({
       'top': top + 'px',
diff --git 
a/ambari-web/app/views/common/modal_popups/config_validation/config_validation_popup.js
 
b/ambari-web/app/views/common/modal_popups/config_validation/config_validation_popup.js
index 665226d..2271be1 100644
--- 
a/ambari-web/app/views/common/modal_popups/config_validation/config_validation_popup.js
+++ 
b/ambari-web/app/views/common/modal_popups/config_validation/config_validation_popup.js
@@ -26,8 +26,8 @@ var App = require('app');
 App.showConfigValidationPopup = function (configErrors, primary, secondary, 
controller) {
   return App.ModalPopup.show({
     header: Em.I18n.t('installer.step7.popup.validation.warning.header'),
-    classNames: ['common-modal-wrapper','modal-full-width'],
-    modalDialogClasses: ['modal-lg'],
+    classNames: ['common-modal-wrapper'],
+    modalDialogClasses: ['modal-xlg'],
     primary: Em.I18n.t('common.proceedAnyway'),
     primaryClass: 'btn-danger',
     marginBottom: 200,
diff --git 
a/ambari-web/app/views/common/modal_popups/dependent_configs_list_popup.js 
b/ambari-web/app/views/common/modal_popups/dependent_configs_list_popup.js
index 7f0c581..451d038 100644
--- a/ambari-web/app/views/common/modal_popups/dependent_configs_list_popup.js
+++ b/ambari-web/app/views/common/modal_popups/dependent_configs_list_popup.js
@@ -21,8 +21,11 @@ var App = require('app');
 App.DependentConfigsTableView = Em.View.extend({
   templateName: 
require('templates/common/modal_popups/dependent_configs_table'),
   recommendations: [],
-  checkboxesFirst: false,
   isClickable: false,
+  showPopovers: true,
+  elementsWithPopover: function () {
+    return this.$('td.config-dependency-name');
+  }.property(),
   hideMessage: function () {
     return this.get('controller.isInstallWizard');
   }.property('controller.isInstallWizard'),
@@ -47,12 +50,28 @@ App.DependentConfigsTableView = Em.View.extend({
       message += Em.I18n.t('popup.dependent.configs.title.required');
     }
     return message;
-  }.property('isEditable')
+  }.property('isEditable'),
+  didInsertElement: function () {
+    if (this.get('showPopovers')) {
+      App.popover(this.get('elementsWithPopover'), {
+        placement: 'auto right',
+        trigger: 'hover',
+        html: true
+      });
+    }
+  },
+  willDestroyElement: function () {
+    if (this.get('showPopovers')) {
+      this.get('elementsWithPopover').popover('destroy');
+    }
+  }
 });
 
 App.DependentConfigsListView = Em.View.extend({
   templateName: 
require('templates/common/modal_popups/dependent_configs_list'),
   isAfterRecommendation: true,
+  isRecommendationsClickable: false,
+  showRecommendationsPopovers: true,
   recommendations: [],
   requiredChanges: [],
   allConfigsWithErrors: [],
@@ -96,32 +115,36 @@ App.DependentConfigsListView = Em.View.extend({
 
 /**
  * Show confirmation popup
- * @param {[Object]} recommendedChanges
+ * @param {[Object]} recommendations
  * @param {[Object]} requiredChanges
  * @param {function} [primary=null]
  * @param {function} [secondary=null]
+ * @param {boolean} [isRecommendationsClickable=false]
+ * @param {boolean} [isRecommendationsClickable=true]
  * we use this parameter to defer saving configs before we make some decisions.
  * @return {App.ModalPopup}
  */
-App.showDependentConfigsPopup = function (recommendedChanges, requiredChanges, 
primary, secondary, controller) {
+App.showDependentConfigsPopup = function (recommendations, requiredChanges, 
primary, secondary, controller, isRecommendationsClickable = false, 
showRecommendationsPopovers = true) {
   return App.ModalPopup.show({
     encodeBody: false,
     header: Em.I18n.t('popup.dependent.configs.header'),
-    classNames: ['common-modal-wrapper','modal-full-width'],
-    modalDialogClasses: ['modal-lg'],
+    classNames: ['common-modal-wrapper'],
+    modalDialogClasses: ['modal-xlg'],
     secondaryClass: 'cancel-button',
     bodyClass: App.DependentConfigsListView.extend({
-      recommendations: recommendedChanges,
-      requiredChanges: requiredChanges,
-      controller: controller
+      recommendations,
+      requiredChanges,
+      controller,
+      isRecommendationsClickable,
+      showRecommendationsPopovers
     }),
     saveChanges: function() {
-      recommendedChanges.forEach(function (c) {
+      recommendations.forEach(function (c) {
         Em.set(c, 'saveRecommendedDefault', Em.get(c, 'saveRecommended'));
       });
     },
     discardChanges: function() {
-      recommendedChanges.forEach(function(c) {
+      recommendations.forEach(function(c) {
         Em.set(c, 'saveRecommended', Em.get(c, 'saveRecommendedDefault'));
       });
     },
diff --git a/ambari-web/app/views/common/modal_popups/log_file_search_popup.js 
b/ambari-web/app/views/common/modal_popups/log_file_search_popup.js
index af40367..3c5ca11 100644
--- a/ambari-web/app/views/common/modal_popups/log_file_search_popup.js
+++ b/ambari-web/app/views/common/modal_popups/log_file_search_popup.js
@@ -20,7 +20,7 @@ var App = require('app');
 
 App.LogFileSearchPopup = function(header) {
   return App.ModalPopup.show({
-    classNames: ['modal-full-width', 'common-modal-wrapper', 
'log-file-search-popup'],
+    classNames: ['common-modal-wrapper', 'log-file-search-popup'],
     modalDialogClasses: ['modal-lg'],
     header: header,
     bodyView: null,
diff --git 
a/ambari-web/test/mixins/common/configs/config_recommendations_test.js 
b/ambari-web/test/mixins/common/configs/config_recommendations_test.js
index a72f6d3..42fc7aa 100644
--- a/ambari-web/test/mixins/common/configs/config_recommendations_test.js
+++ b/ambari-web/test/mixins/common/configs/config_recommendations_test.js
@@ -32,12 +32,17 @@ describe('App.ConfigRecommendations', function() {
       sinon.stub(App.config, 
'get').withArgs('serviceByConfigTypeMap').returns({
         'pFile': Em.Object.create({serviceName: 'sName', displayName: 
'sDisplayName'})
       });
-    sinon.stub(Handlebars, 'SafeString');
+      sinon.stub(App.configsCollection, 'getConfigByName').withArgs('pName', 
'pFile').returns({
+        displayName: 'pDisplayName',
+        description: 'pDescription'
+      });
+      sinon.stub(Handlebars, 'SafeString');
     });
     afterEach(function() {
       instanceObject.formatParentProperties.restore();
       App.config.get.restore();
-    Handlebars.SafeString.restore();
+      App.configsCollection.getConfigByName.restore();
+      Handlebars.SafeString.restore();
     });
 
     it('adds new recommendation', function() {
@@ -47,6 +52,8 @@ describe('App.ConfigRecommendations', function() {
         saveRecommendedDefault: true,
         propertyFileName: 'pFile',
         propertyName: 'pName',
+        propertyTitle: 'pDisplayName<br><small>pName</small>',
+        propertyDescription: 'pDescription',
         isDeleted: false,
         notDefined: false,
         configGroup: 'pGroup',
@@ -123,6 +130,8 @@ describe('App.ConfigRecommendations', function() {
           saveRecommendedDefault: true,
           propertyFileName: 'pFile',
           propertyName: 'pName',
+          propertyTitle: 'pDisplayName<br><small>pName</small>',
+          propertyDescription: 'pDescription',
           isDeleted: false,
           notDefined: false,
           configGroup: 'pGroup',
@@ -144,6 +153,8 @@ describe('App.ConfigRecommendations', function() {
           saveRecommendedDefault: true,
           propertyFileName: 'pFile',
           propertyName: 'pName',
+          propertyTitle: 'pDisplayName<br><small>pName</small>',
+          propertyDescription: 'pDescription',
           isDeleted: false,
           notDefined: false,
           configGroup: 'pGroup',
@@ -165,6 +176,8 @@ describe('App.ConfigRecommendations', function() {
           saveRecommendedDefault: true,
           propertyFileName: 'pFile',
           propertyName: 'pName',
+          propertyTitle: 'pDisplayName<br><small>pName</small>',
+          propertyDescription: 'pDescription',
           isDeleted: true,
           notDefined: true,
           configGroup: 'Default',
@@ -187,12 +200,17 @@ describe('App.ConfigRecommendations', function() {
             'pFile': c.service
           });
           sinon.stub(Handlebars, 'SafeString');
+          sinon.stub(App.configsCollection, 
'getConfigByName').withArgs('pName', 'pFile.xml').returns({
+            displayName: 'pDisplayName',
+            description: 'pDescription'
+          });
           recommendation = instanceObject.addRecommendation(c.name, c.file, 
c.group, c.recommended, c.initial, c.parent, c.isEditable);
         });
 
         afterEach(function() {
           App.config.get.restore();
           Handlebars.SafeString.restore();
+          App.configsCollection.getConfigByName.restore();
         });
 
         it(c.title, function() {

Reply via email to