Browse Source

[editor] Add an execution analysis panel with logs and job links

Johan Ahlen 4 years ago
parent
commit
f44af925de

+ 29 - 0
desktop/core/src/desktop/js/apps/editor/components/executionAnalysis/ExecutionAnalysisPanel.scss

@@ -0,0 +1,29 @@
+@import '../../../../components/styles/colors.scss';
+@import '../../../../components/styles/mixins.scss';
+
+.execution-analysis-panel {
+  @include fillAbsolute;
+  @include flexRowLayout;
+
+  .execution-analysis-jobs,
+  .execution-analysis-logs {
+    @include flexRowLayout;
+
+    h4 {
+      @include flexRowLayoutRow(15px);
+    }
+
+    .execution-analysis-jobs-panel,
+    .execution-analysis-logs-panel {
+      @include flexRowLayoutRow(0 1 100%);
+    }
+  }
+
+  .execution-analysis-jobs {
+    @include flexRowLayoutRow(0 1 80px);
+  }
+
+  .execution-analysis-logs {
+    @include flexRowLayoutRow(0 1 100%);
+  }
+}

+ 117 - 0
desktop/core/src/desktop/js/apps/editor/components/executionAnalysis/ExecutionAnalysisPanel.vue

@@ -0,0 +1,117 @@
+<!--
+  Licensed to Cloudera, Inc. under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  Cloudera, Inc. 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.
+-->
+
+<template>
+  <div class="execution-analysis-panel">
+    <div v-if="!analysisAvailable">
+      <h1 class="empty">{{ I18n('Select and execute a query to see the analysis.') }}</h1>
+    </div>
+
+    <div v-if="analysisAvailable && jobsAvailable" class="execution-analysis-jobs">
+      <h4>{{ I18n('Jobs') }}</h4>
+      <div class="execution-analysis-jobs-panel">
+        <hue-link v-for="job of jobsWithUrls" :key="job.url" :url="job.url" target="_blank">
+          {{ job.name }}
+        </hue-link>
+      </div>
+    </div>
+
+    <div v-if="analysisAvailable" class="execution-analysis-logs">
+      <h4>{{ I18n('Logs') }}</h4>
+      <LogsPanel class="execution-analysis-logs-panel" :logs="logs" />
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+  import { ExecutionJob } from 'apps/editor/execution/api';
+  import HueLink from 'components/HueLink.vue';
+  import Vue from 'vue';
+  import Component from 'vue-class-component';
+  import { Prop } from 'vue-property-decorator';
+
+  import './ExecutionAnalysisPanel.scss';
+  import Executable, {
+    EXECUTABLE_UPDATED_EVENT,
+    ExecutionStatus
+  } from 'apps/editor/execution/executable';
+  import ExecutionLogs, {
+    ExecutionError,
+    LOGS_UPDATED_EVENT
+  } from 'apps/editor/execution/executionLogs';
+  import LogsPanel from 'components/LogsPanel.vue';
+  import SubscriptionTracker from 'components/utils/SubscriptionTracker';
+  import I18n from 'utils/i18n';
+
+  @Component({
+    components: { HueLink, LogsPanel },
+    methods: { I18n }
+  })
+  export default class ExecutionAnalysisPanel extends Vue {
+    @Prop()
+    executable?: Executable;
+
+    logs = '';
+    jobs: ExecutionJob[] = [];
+    errors: ExecutionError[] = [];
+
+    subTracker = new SubscriptionTracker();
+
+    mounted(): void {
+      this.subTracker.subscribe(EXECUTABLE_UPDATED_EVENT, (executable: Executable) => {
+        if (executable.logs) {
+          this.updateFromExecutionLogs(executable.logs);
+        }
+      });
+
+      this.subTracker.subscribe(LOGS_UPDATED_EVENT, this.updateFromExecutionLogs.bind(this));
+    }
+
+    updateFromExecutionLogs(executionLogs: ExecutionLogs): void {
+      if (this.executable === executionLogs.executable) {
+        this.logs = executionLogs.fullLog;
+        this.jobs = executionLogs.jobs;
+        this.errors = executionLogs.errors;
+      }
+    }
+
+    destroyed(): void {
+      this.subTracker.dispose();
+    }
+
+    get analysisAvailable(): boolean {
+      return !!this.executable && this.executable.status !== ExecutionStatus.ready;
+    }
+
+    get jobsWithUrls(): ExecutionJob[] {
+      return (this.jobs && this.jobs.filter(job => job.url)) || [];
+    }
+
+    get jobsAvailable(): boolean {
+      return !!this.jobsWithUrls.length;
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+  .execution-analysis {
+    position: relative;
+    height: 100%;
+    width: 100%;
+  }
+</style>

+ 62 - 0
desktop/core/src/desktop/js/apps/editor/components/executionAnalysis/ExecutionAnalysisPanelKoBridge.vue

@@ -0,0 +1,62 @@
+<!--
+  Licensed to Cloudera, Inc. under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  Cloudera, Inc. 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.
+-->
+
+<template>
+  <ExecutionAnalysisPanel :executable="executable" />
+</template>
+
+<script lang="ts">
+  import Vue from 'vue';
+  import Component from 'vue-class-component';
+  import { Prop } from 'vue-property-decorator';
+  import { wrap } from 'vue/webComponentWrapper';
+
+  import ExecutionAnalysisPanel from './ExecutionAnalysisPanel.vue';
+  import SqlExecutable from 'apps/editor/execution/sqlExecutable';
+  import SubscriptionTracker from 'components/utils/SubscriptionTracker';
+
+  @Component({
+    components: { ExecutionAnalysisPanel }
+  })
+  export default class ExecutionAnalysisPanelKoBridge extends Vue {
+    @Prop()
+    executableObservable?: KnockoutObservable<SqlExecutable | undefined>;
+
+    initialized = false;
+    executable: SqlExecutable | null = null;
+
+    subTracker = new SubscriptionTracker();
+
+    updated(): void {
+      if (!this.initialized && this.executableObservable) {
+        this.executable = this.executableObservable() || null;
+        this.subTracker.subscribe(this.executableObservable, executable => {
+          this.executable = executable || null;
+        });
+        this.initialized = true;
+      }
+    }
+
+    destroyed(): void {
+      this.subTracker.dispose();
+    }
+  }
+
+  export const COMPONENT_NAME = 'execution-analysis-panel-ko-bridge';
+  wrap(COMPONENT_NAME, ExecutionAnalysisPanelKoBridge);
+</script>

+ 4 - 0
desktop/core/src/desktop/js/apps/editor/execution/api.ts

@@ -83,7 +83,11 @@ export interface ExecuteLogsApiResponse {
 }
 }
 
 
 export interface ExecutionJob {
 export interface ExecutionJob {
+  finished: boolean;
+  name: string;
   percentJob?: number;
   percentJob?: number;
+  started: boolean;
+  url: string;
 }
 }
 
 
 export interface ExecutionHandle {
 export interface ExecutionHandle {

+ 1 - 0
desktop/core/src/desktop/js/apps/editor/snippet.js

@@ -28,6 +28,7 @@ import 'apps/editor/components/ko.queryHistory';
 import './components/ExecutableActionsKoBridge.vue';
 import './components/ExecutableActionsKoBridge.vue';
 import './components/EditorResizerKoBridge.vue';
 import './components/EditorResizerKoBridge.vue';
 import './components/aceEditor/AceEditorKoBridge.vue';
 import './components/aceEditor/AceEditorKoBridge.vue';
+import './components/executionAnalysis/ExecutionAnalysisPanelKoBridge.vue';
 import './components/result/ResultTableKoBridge.vue';
 import './components/result/ResultTableKoBridge.vue';
 
 
 import AceAutocompleteWrapper from 'apps/notebook/aceAutocompleteWrapper';
 import AceAutocompleteWrapper from 'apps/notebook/aceAutocompleteWrapper';

+ 23 - 0
desktop/core/src/desktop/js/components/styles/mixins.scss

@@ -150,6 +150,29 @@ $hue-panel-border-radius: 3px;
   @include animation('fade-in-frames 2s');
   @include animation('fade-in-frames 2s');
 }
 }
 
 
+@mixin fillAbsolute {
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  right: 0;
+}
+
+// Flexbox mixins
+
+@mixin flexRowLayout {
+  @include display-flex();
+  @include flex-direction(column);
+
+  overflow: hidden;
+}
+
+@mixin flexRowLayoutRow($flex, $position: relative) {
+  @include flex($flex);
+
+  position: $position;
+}
+
 // Flexbox mixins
 // Flexbox mixins
 
 
 @mixin display-flex() {
 @mixin display-flex() {

+ 7 - 7
desktop/libs/notebook/src/notebook/templates/editor2.mako

@@ -988,9 +988,9 @@
         </li>
         </li>
         <!-- /ko -->
         <!-- /ko -->
 
 
-        <!-- ko if: HAS_WORKLOAD_ANALYTICS && dialect() === 'impala' -->
-        <li data-bind="visible: showExecutionAnalysis, click: function(){ currentQueryTab('executionAnalysis'); }, css: {'active': currentQueryTab() == 'executionAnalysis'}"><a class="inactive-action" href="#executionAnalysis" data-toggle="tab" data-bind="click: function(){ $('a[href=\'#executionAnalysis\']').tab('show'); }, event: {'shown': fetchExecutionAnalysis }"><span>${_('Execution Analysis')} </span><span></span></a></li>
-        <!-- /ko -->
+        <li data-bind="click: function(){ currentQueryTab('executionAnalysis'); }, css: {'active': currentQueryTab() == 'executionAnalysis'}">
+          <a class="inactive-action" href="#executionAnalysis" data-toggle="tab">${_('Execution Analysis')}</a>
+        </li>
 
 
         <li class="editor-bottom-tab-actions">
         <li class="editor-bottom-tab-actions">
           <button data-bind="toggle: $root.bottomExpanded">
           <button data-bind="toggle: $root.bottomExpanded">
@@ -1056,13 +1056,13 @@
         </div>
         </div>
         <!-- /ko -->
         <!-- /ko -->
 
 
-        <!-- ko if: HAS_WORKLOAD_ANALYTICS && dialect() === 'impala' -->
         <div class="tab-pane" id="executionAnalysis" data-bind="css: {'active': currentQueryTab() == 'executionAnalysis'}">
         <div class="tab-pane" id="executionAnalysis" data-bind="css: {'active': currentQueryTab() == 'executionAnalysis'}">
-          <div class="editor-bottom-tab-panel">
-            <!-- ko component: { name: 'hue-execution-analysis' } --><!-- /ko -->
+          <div class="execution-analysis-tab-panel">
+            <execution-analysis-panel-ko-bridge class="execution-analysis-bridge" data-bind="vueKoProps: {
+                executableObservable: activeExecutable
+              }"></execution-analysis-panel-ko-bridge>
           </div>
           </div>
         </div>
         </div>
-        <!-- /ko -->
 
 
         <!-- ko foreach: pinnedContextTabs -->
         <!-- ko foreach: pinnedContextTabs -->
         <div class="tab-pane" data-bind="attr: { 'id': tabId }, css: {'active': $parent.currentQueryTab() === tabId }">
         <div class="tab-pane" data-bind="attr: { 'id': tabId }, css: {'active': $parent.currentQueryTab() === tabId }">