Selaa lähdekoodia

AMBARI-16679. Redirect URLs from background ops / host component logs to LogSearch UI do not work (alexantonenko)

Alex Antonenko 9 vuotta sitten
vanhempi
commit
8fbfd89f65

+ 29 - 1
ambari-web/app/controllers/global/update_controller.js

@@ -643,6 +643,34 @@ App.UpdateController = Em.Controller.extend({
 
 
   updateWizardWatcher: function(callback) {
   updateWizardWatcher: function(callback) {
     App.router.get('wizardWatcherController').getUser().complete(callback);
     App.router.get('wizardWatcherController').getUser().complete(callback);
-  }
+  },
+
+  /**
+   * Request to fetch logging info for specified host.
+   *
+   * @method updateLogging
+   * @param {string} hostName
+   * @param {string[]|boolean} fields additional fields to request e.g. ['field1', 'field2']
+   * @param {function} callback execute on when request succeed, json response will be passed to first argument
+   * @returns {$.Deferred.promise()}
+   */
+  updateLogging: function(hostName, fields, callback) {
+    var flds = !!fields ? "," + fields.join(',') : "" ;
+
+    return App.ajax.send({
+      name: 'host.logging',
+      sender: this,
+      data: {
+        hostName: hostName,
+        callback: callback,
+        fields: flds
+      },
+      success: 'updateLoggingSuccess'
+    });
+  },
 
 
+  updateLoggingSuccess: function(data, opt, params) {
+    var clbk = params.callback || function() {};
+    clbk(data);
+  }
 });
 });

+ 23 - 17
ambari-web/app/templates/common/host_progress_popup.hbs

@@ -161,22 +161,26 @@
       </div>
       </div>
     </div>
     </div>
     <div id="host-log">
     <div id="host-log">
-      {{#if view.isTasksEmptyList}}
-        <div class="log-list-wrap">{{t hostPopup.noTasksToShow}}</div>
-      {{else}}
-        {{#each taskInfo in view.tasks}}
-          <div {{action toggleTaskLog taskInfo}} {{bindAttr class="taskInfo.isVisible::hidden :log-list-wrap"}}>
-            <div class="task-list-line-cursor">
-              <div class="operation-name-icon-wrap">
-                {{view statusIcon servicesInfoBinding="taskInfo"}}
-                <a href="#">
-                  {{taskInfo.commandDetail}}
-                </a>
+      {{#if view.hostInfoLoaded}}
+        {{#if view.isTasksEmptyList}}
+          <div class="log-list-wrap">{{t hostPopup.noTasksToShow}}</div>
+        {{else}}
+          {{#each taskInfo in view.tasks}}
+            <div {{action toggleTaskLog taskInfo}} {{bindAttr class="taskInfo.isVisible::hidden :log-list-wrap"}}>
+              <div class="task-list-line-cursor">
+                <div class="operation-name-icon-wrap">
+                  {{view statusIcon servicesInfoBinding="taskInfo"}}
+                  <a href="#">
+                    {{taskInfo.commandDetail}}
+                  </a>
+                </div>
+                <div class="show-details"><i class="icon-caret-right"></i></div>
               </div>
               </div>
-              <div class="show-details"><i class="icon-caret-right"></i></div>
             </div>
             </div>
-          </div>
-        {{/each}}
+          {{/each}}
+        {{/if}}
+      {{else}}
+        {{view App.SpinnerView}}
       {{/if}}
       {{/if}}
     </div>
     </div>
   </div>
   </div>
@@ -256,9 +260,11 @@
                 <p {{bindAttr class="view.isClipBoardActive:hidden"}}>
                 <p {{bindAttr class="view.isClipBoardActive:hidden"}}>
                   <span class="text-bold">{{t common.file}}: &nbsp; </span>
                   <span class="text-bold">{{t common.file}}: &nbsp; </span>
                   <span class="text-bold muted">{{hostLog.fileName}}</span>
                   <span class="text-bold muted">{{hostLog.fileName}}</span>
-                  <a class="pull-right" {{bindAttr href="hostLog.url"}} target="_blank">
-                    <i class="icon-external-link"></i>
-                    {{t popup.logTail.openInLogSearch}}</a>
+                  {{#view App.LogSearchUILinkView linkQueryParamsBinding="hostLog.linkTail" tagName="span"}}
+                    <a {{bindAttr href="view.formatedLink" class=":pull-right view.isLodaded::disabled"}} target="_blank">
+                      <i class="icon-external-link"></i>
+                      {{t popup.logTail.openInLogSearch}}</a>
+                  {{/view}}
                 </p>
                 </p>
                 <div {{bindAttr class="view.isClipBoardActive:hidden"}}>
                 <div {{bindAttr class="view.isClipBoardActive:hidden"}}>
                   {{view view.logTailView contentBinding="hostLog"}}
                   {{view view.logTailView contentBinding="hostLog"}}

+ 6 - 4
ambari-web/app/templates/main/host/logs.hbs

@@ -39,10 +39,12 @@
             {{#each file in row.fileNamesObject}}
             {{#each file in row.fileNamesObject}}
               <p>
               <p>
                 <a {{action openLogFile row file.filePath target="view.parentView"}} href="#" rel="log-file-name-tooltip" {{bindAttr data-original-title="file.filePath"}}>{{file.fileName}}</a>
                 <a {{action openLogFile row file.filePath target="view.parentView"}} href="#" rel="log-file-name-tooltip" {{bindAttr data-original-title="file.filePath"}}>{{file.fileName}}</a>
-                <a {{bindAttr href="file.url"}} target="_blank" rel="log-file-name-tooltip" {{translateAttr title="popup.logTail.openInLogSearch"}} class="pull-right external-link">
-                  <i class="icon-external-link"></i>
-                  {{t popup.logTail.openInLogSearch}}
-                </a>
+                {{#view App.LogSearchUILinkView linkQueryParamsBinding="file.linkTail" tagName="span"}}
+                  <a {{bindAttr href="view.formatedLink"}} target="_blank" rel="log-file-name-tooltip" {{translateAttr title="popup.logTail.openInLogSearch"}} class="pull-right external-link">
+                    <i class="icon-external-link"></i>
+                    {{t popup.logTail.openInLogSearch}}
+                  </a>
+                {{/view}}
               </p>
               </p>
               {{/each}}
               {{/each}}
           </td>
           </td>

+ 1 - 0
ambari-web/app/views.js

@@ -20,6 +20,7 @@
 // load all views here
 // load all views here
 
 
 require('views/application');
 require('views/application');
+require('views/common/log_search_ui_link_view');
 require('views/common/log_file_search_view');
 require('views/common/log_file_search_view');
 require('views/common/log_tail_view');
 require('views/common/log_tail_view');
 require('views/common/global/spinner');
 require('views/common/global/spinner');

+ 41 - 17
ambari-web/app/views/common/host_progress_popup_body_view.js

@@ -94,6 +94,14 @@ App.HostProgressPopupBodyView = App.TableView.extend({
 
 
   isClipBoardActive: false,
   isClipBoardActive: false,
 
 
+  /**
+   * Determines that host information become loaded and mapped with <code>App.hostsMapper</code>.
+   * This property play in only when Log Search service installed.
+   *
+   * @type {boolean}
+   */
+  hostInfoLoaded: true,
+
   /**
   /**
    * Alias for <code>controller.hosts</code>
    * Alias for <code>controller.hosts</code>
    *
    *
@@ -835,10 +843,41 @@ App.HostProgressPopupBodyView = App.TableView.extend({
     this.set('currentHost.tasks', tasksInfo);
     this.set('currentHost.tasks', tasksInfo);
     this.set("parentView.isHostListHidden", true);
     this.set("parentView.isHostListHidden", true);
     this.set("parentView.isTaskListHidden", false);
     this.set("parentView.isTaskListHidden", false);
+    this.preloadHostModel(Em.getWithDefault(event.context || {}, 'name', false));
     $(".modal").scrollTop(0);
     $(".modal").scrollTop(0);
     $(".modal-body").scrollTop(0);
     $(".modal-body").scrollTop(0);
   },
   },
 
 
+  /**
+   * Will add <code>App.Host</code> model with relevant host info by specified host name only when it missed
+   * and Log Search service installed.
+   * This update needed to get actual status of logs for components located on this host and get appropriate model
+   * for navigation to hosts/:host_id/logs route.
+   *
+   * @method preloadHostModel
+   * @param {string} hostName host name to get info
+   */
+  preloadHostModel: function(hostName) {
+    var self = this,
+        fields;
+    if (!hostName) {
+      self.set('hostInfoLoaded', true);
+      return;
+    }
+    if (this.get('isLogSearchInstalled') && App.get('supports.logSearch') && !App.Host.find().someProperty('hostName', hostName)) {
+      this.set('hostInfoLoaded', false);
+      fields = ['stack_versions/repository_versions/RepositoryVersions/repository_version',
+                'host_components/HostRoles/host_name'];
+      App.router.get('updateController').updateLogging(hostName, fields, function(data) {
+        App.hostsMapper.map({ items: [data] });
+      }).always(function() {
+        self.set('hostInfoLoaded', true);
+      });
+    } else {
+      self.set('hostInfoLoaded', true);
+    }
+  },
+
   /**
   /**
    * @method stopRebalanceHDFS
    * @method stopRebalanceHDFS
    * @returns {App.ModalPopup}
    * @returns {App.ModalPopup}
@@ -956,6 +995,7 @@ App.HostProgressPopupBodyView = App.TableView.extend({
         componentName,
         componentName,
         hostName,
         hostName,
         logFile,
         logFile,
+        linkTailTpl = '?host_name={0}&file_name={1}&component_name={2}',
         self = this;
         self = this;
 
 
     if (this.get('openedTask.id')) {
     if (this.get('openedTask.id')) {
@@ -979,7 +1019,7 @@ App.HostProgressPopupBodyView = App.TableView.extend({
                 tabClassName: tabClassName,
                 tabClassName: tabClassName,
                 tabClassNameSelector: '.' + tabClassName,
                 tabClassNameSelector: '.' + tabClassName,
                 displayedFileName: fileUtils.fileNameFromPath(logFileName),
                 displayedFileName: fileUtils.fileNameFromPath(logFileName),
-                url: self.get('logSearchUrlTemplate').format(hostName, logFileName, logComponentName),
+                linkTail: linkTailTpl.format(hostName, logFileName, logComponentName),
                 isActive: false
                 isActive: false
               });
               });
             }));
             }));
@@ -990,22 +1030,6 @@ App.HostProgressPopupBodyView = App.TableView.extend({
     return [];
     return [];
   }.property('openedTaskId', 'isLevelLoaded'),
   }.property('openedTaskId', 'isLevelLoaded'),
 
 
-
-  /**
-   * Log Search UI link template used for 'Open In Log Search' links.
-   *
-   * @property {string}
-   */
-  logSearchUrlTemplate: function() {
-    var quickLink = App.QuickLinks.find().findProperty('site', 'logsearch-env'),
-        logSearchServerHost = App.HostComponent.find().findProperty('componentName', 'LOGSEARCH_SERVER').get('host.hostName');
-
-    if (quickLink) {
-      return quickLink.get('template').fmt('http', logSearchServerHost, quickLink.get('default_http_port')) + '?host_name={0}&file_name={1}&component_name={2}';
-    }
-    return '#';
-  }.property('hostComponentLogsExists'),
-
   /**
   /**
    * Determines if there are component logs for selected component within 'TASK_DETAILS' level.
    * Determines if there are component logs for selected component within 'TASK_DETAILS' level.
    *
    *

+ 40 - 0
ambari-web/app/views/common/log_search_ui_link_view.js

@@ -0,0 +1,40 @@
+/**
+ * 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');
+
+require('views/common/quick_view_link_view');
+
+/**
+ * This view helps to get and format correct link to LogSearch UI with query params.
+ * Used mostly for navigation to concrete log file.
+ *
+ * @augments App.QuickViewLinks
+ */
+App.LogSearchUILinkView = App.QuickViewLinks.extend({
+  content: function() {
+    return App.Service.find().findProperty('serviceName', 'LOGSEARCH');
+  }.property(),
+
+  formatedLink: function() {
+    if (this.get('quickLinks.length')) {
+      return Em.get(this.get('quickLinks').findProperty('label', 'Log Search UI'), 'url') + this.get('linkQueryParams');
+    }
+    return false;
+  }.property('quickLinks.length', 'linkQueryParams')
+});

+ 1 - 1
ambari-web/app/views/common/modal_popups/log_tail_popup.js

@@ -39,7 +39,7 @@ App.showLogTailPopup = function(content) {
 
 
       logSearchUrl: function() {
       logSearchUrl: function() {
         var quickLink = App.QuickLinks.find().findProperty('site', 'logsearch-env'),
         var quickLink = App.QuickLinks.find().findProperty('site', 'logsearch-env'),
-            logSearchServerHost = App.HostComponent.find().findProperty('componentName', 'LOGSEARCH_SERVER').get('host.hostName');
+            logSearchServerHost = App.HostComponent.find().findProperty('componentName', 'LOGSEARCH_SERVER').get('hostName');
 
 
         if (quickLink) {
         if (quickLink) {
           return quickLink.get('template').fmt('http', logSearchServerHost, quickLink.get('default_http_port')) + '?host_name=' + this.get('content.hostName') + '&file_name=' + this.get('content.filePath') + '&component_name=' + this.get('content.logComponentName');
           return quickLink.get('template').fmt('http', logSearchServerHost, quickLink.get('default_http_port')) + '?host_name=' + this.get('content.hostName') + '&file_name=' + this.get('content.filePath') + '&component_name=' + this.get('content.logComponentName');

+ 4 - 12
ambari-web/app/views/main/host/logs_view.js

@@ -37,18 +37,10 @@ App.MainHostLogsView = App.TableView.extend({
     return App.HostComponentLog.find().filterProperty('hostName', this.get('host.hostName'));
     return App.HostComponentLog.find().filterProperty('hostName', this.get('host.hostName'));
   }.property('App.HostComponentLog.length'),
   }.property('App.HostComponentLog.length'),
 
 
-  logSearchUrlTemplate: function() {
-    var quickLink = App.QuickLinks.find().findProperty('site', 'logsearch-env'),
-        logSearchServerHost = App.HostComponent.find().findProperty('componentName', 'LOGSEARCH_SERVER').get('host.hostName');
-
-    if (quickLink) {
-      return quickLink.get('template').fmt('http', logSearchServerHost, quickLink.get('default_http_port')) + '?host_name=' + this.get('host.hostName') + '&file_name={0}&component_name={1}';
-    }
-    return '#';
-  }.property(),
-
   content: function() {
   content: function() {
-    var self = this;
+    var self = this,
+        linkTailTpl = '?host_name={0}&file_name={1}&component_name={2}';
+
     return this.get('hostLogs').map(function(i) {
     return this.get('hostLogs').map(function(i) {
       return Em.Object.create({
       return Em.Object.create({
         serviceName: i.get('hostComponent.service.serviceName'),
         serviceName: i.get('hostComponent.service.serviceName'),
@@ -61,7 +53,7 @@ App.MainHostLogsView = App.TableView.extend({
           return {
           return {
             fileName: fileUtils.fileNameFromPath(filePath),
             fileName: fileUtils.fileNameFromPath(filePath),
             filePath: filePath,
             filePath: filePath,
-            url: self.get('logSearchUrlTemplate').format(filePath, i.get('name'))
+            linkTail: linkTailTpl.format(i.get('hostName'), filePath, i.get('name'))
           };
           };
         }),
         }),
         fileNamesFilterValue: i.get('logFileNames').join(',')
         fileNamesFilterValue: i.get('logFileNames').join(',')

+ 116 - 1
ambari-web/test/views/common/host_progress_popup_body_view_test.js

@@ -59,7 +59,7 @@ describe('App.HostProgressPopupBodyView', function () {
     var cases;
     var cases;
 
 
     beforeEach(function() {
     beforeEach(function() {
-      sinon.stub(App.StackServiceComponent, 'find').returns([{componentName: 'DATANODE'}])
+      sinon.stub(App.StackServiceComponent, 'find').returns([{componentName: 'DATANODE'}]);
       sinon.stub(App.StackService, 'find').returns([{serviceName: 'HDFS'}])
       sinon.stub(App.StackService, 'find').returns([{serviceName: 'HDFS'}])
     });
     });
 
 
@@ -124,4 +124,119 @@ describe('App.HostProgressPopupBodyView', function () {
     });
     });
 
 
   });
   });
+
+  describe('#preloadHostModel', function() {
+    describe('When Log Search installed', function() {
+      var cases;
+      beforeEach(function() {
+        this.HostModelStub = sinon.stub(App.Host, 'find');
+        this.isLogSearchInstalled = sinon.stub(view, 'get').withArgs('isLogSearchInstalled');
+        this.logSearchSupported = sinon.stub(App, 'get').withArgs('supports.logSearch');
+        this.updateCtrlStub = sinon.stub(App.router.get('updateController'), 'updateLogging');
+      });
+
+      afterEach(function () {
+        App.Host.find.restore();
+        view.get.restore();
+        App.get.restore();
+        App.router.get('updateController').updateLogging.restore();
+      });
+
+      cases = [
+        {
+          hostName: 'host1',
+          logSearchSupported: true,
+          isLogSearchInstalled: true,
+          requestFailed: false,
+          hosts: [
+            {
+              hostName: 'host2'
+            }
+          ],
+          e: {
+            updateLoggingCalled: true
+          },
+          m: 'Host absent, log search installed and supported'
+        },
+        {
+          hostName: 'host1',
+          logSearchSupported: true,
+          isLogSearchInstalled: true,
+          requestFailed: false,
+          hosts: [
+            {
+              hostName: 'host1'
+            }
+          ],
+          e: {
+            updateLoggingCalled: false
+          },
+          m: 'Host present, log search installed and supported'
+        },
+        {
+          hostName: 'host1',
+          logSearchSupported: false,
+          isLogSearchInstalled: true,
+          requestFailed: false,
+          hosts: [
+            {
+              hostName: 'host1'
+            }
+          ],
+          e: {
+            updateLoggingCalled: false
+          },
+          m: 'Host present, log search installed and support is off'
+        },
+        {
+          hostName: 'host1',
+          logSearchSupported: true,
+          isLogSearchInstalled: true,
+          requestFailed: true,
+          hosts: [
+            {
+              hostName: 'host2'
+            }
+          ],
+          e: {
+            updateLoggingCalled: true
+          },
+          m: 'Host is absent, log search installed and supported, update request was failed'
+        },
+        {
+          hostName: 'host1',
+          logSearchSupported: true,
+          isLogSearchInstalled: false,
+          requestFailed: true,
+          hosts: [
+            {
+              hostName: 'host2'
+            }
+          ],
+          e: {
+            updateLoggingCalled: false
+          },
+          m: 'Host is absent, log search not installed and supported'
+        }
+      ];
+
+      cases.forEach(function(test) {
+        it(test.m, function() {
+          assert.equal(Em.get(view, 'hostInfoLoaded'), true, 'hostInfoLoaded should be true on init');
+          this.HostModelStub.returns(test.hosts);
+          this.isLogSearchInstalled.returns(test.isLogSearchInstalled);
+          this.logSearchSupported.returns(test.logSearchSupported);
+          if (test.requestFailed) {
+            this.updateCtrlStub.returns($.Deferred().reject().promise());
+          } else {
+            this.updateCtrlStub.returns($.Deferred().resolve().promise());
+          }
+          Em.set(view, 'hostInfoLoaded', false);
+          view.preloadHostModel(test.hostName);
+          assert.equal(App.router.get('updateController').updateLogging.called, test.e.updateLoggingCalled, 'updateLogging call validation');
+          assert.equal(Em.get(view, 'hostInfoLoaded'), true, 'in result hostInfoLoaded should be always true');
+        });
+      }, this);
+    });
+  });
 });
 });