소스 검색

[editor] Add missing status texts to the new result table component

Johan Ahlen 4 년 전
부모
커밋
b4e172dd4e

+ 0 - 94
desktop/core/src/desktop/js/apps/editor/components/result/ResultGrid.vue

@@ -1,94 +0,0 @@
-<!--
-  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="result-grid" :class="{ 'grayed-out': grayedOut }">
-    <HueTable
-      :columns="tableColumns"
-      :rows="tableRows"
-      :sticky-header="true"
-      :sticky-first-column="true"
-      @scroll-to-end="onScrollToEnd"
-    />
-  </div>
-</template>
-
-<script lang="ts">
-  import { ResultMeta } from 'apps/editor/execution/api';
-  import { ResultRow } from 'apps/editor/execution/executionResult';
-  import { Column, Row } from 'components/HueTable';
-  import HueTable from 'components/HueTable.vue';
-  import Vue from 'vue';
-  import Component from 'vue-class-component';
-  import { Prop, Watch } from 'vue-property-decorator';
-
-  @Component({
-    components: { HueTable }
-  })
-  export default class ResultGrid extends Vue {
-    @Prop()
-    rows!: ResultRow[];
-    @Prop()
-    meta!: ResultMeta[];
-    @Prop()
-    hasMore!: boolean;
-
-    grayedOut = false;
-
-    get tableColumns(): Column<ResultRow>[] {
-      return this.meta.map(({ name }, index) => ({
-        label: name,
-        key: index,
-        htmlValue: true
-      }));
-    }
-
-    get tableRows(): Row[] {
-      return this.rows;
-    }
-
-    @Watch('rows')
-    @Watch('hasMore')
-    resultChanged(): void {
-      this.grayedOut = false;
-    }
-
-    onScrollToEnd(): void {
-      if (this.hasMore) {
-        this.grayedOut = true;
-        this.$emit('fetch-more');
-      }
-    }
-  }
-</script>
-
-<style lang="scss" scoped>
-  .result-grid {
-    position: relative;
-    height: 100%;
-    width: 100%;
-
-    &.grayed-out {
-      opacity: 0.5;
-
-      /deep/ .hue-table-container {
-        overflow: hidden !important;
-      }
-    }
-  }
-</style>

+ 107 - 23
desktop/core/src/desktop/js/apps/editor/components/result/ExecutionResults.vue → desktop/core/src/desktop/js/apps/editor/components/result/ResultTable.vue

@@ -17,15 +17,40 @@
 -->
 
 <template>
-  <ResultGrid :rows="rows" :meta="meta" :has-more="hasMore" @fetch-more="fetchMore" />
+  <div class="result-grid" :class="{ 'grayed-out': grayedOut }">
+    <HueTable
+      v-if="rows.length"
+      :columns="tableColumns"
+      :rows="rows"
+      :sticky-header="true"
+      :sticky-first-column="true"
+      @scroll-to-end="onScrollToEnd"
+    />
+    <div v-if="isExecuting">
+      <h1 class="empty"><i class="fa fa-spinner fa-spin" /> {{ I18n('Executing...') }}</h1>
+    </div>
+    <div v-else-if="hasEmptySuccessResult">
+      <h1 class="empty">{{ I18n('Success.') }}</h1>
+    </div>
+    <div v-else-if="isExpired">
+      <h1 class="empty">{{ I18n('Results have expired, rerun the query if needed.') }}</h1>
+    </div>
+    <div v-else-if="hasEmptyResult">
+      <h1 class="empty">{{ I18n('Empty result.') }}</h1>
+    </div>
+    <div v-else-if="isStreaming">
+      <h1 class="empty">{{ I18n('Waiting for streaming data...') }}</h1>
+    </div>
+    <div v-else-if="!rows.length && !executable.result">
+      <h1 class="empty">{{ I18n('Select and execute a query to see the result.') }}</h1>
+    </div>
+  </div>
 </template>
 
 <script lang="ts">
   import Vue from 'vue';
   import Component from 'vue-class-component';
   import { Prop, Watch } from 'vue-property-decorator';
-
-  import ResultGrid from './ResultGrid.vue';
   import { ResultMeta } from 'apps/editor/execution/api';
   import Executable, {
     EXECUTABLE_UPDATED_EVENT,
@@ -36,33 +61,34 @@
     ResultRow,
     ResultType
   } from 'apps/editor/execution/executionResult';
+  import { Column } from 'components/HueTable';
+  import HueTable from 'components/HueTable.vue';
   import SubscriptionTracker from 'components/utils/SubscriptionTracker';
+  import { defer } from 'utils/hueUtils';
+  import I18n from 'utils/i18n';
 
   @Component({
-    components: { ResultGrid }
+    components: { HueTable },
+    methods: { I18n }
   })
-  export default class ExecutionResults extends Vue {
+  export default class ResultTable extends Vue {
     @Prop()
     executable?: Executable;
 
-    subTracker = new SubscriptionTracker();
-
-    status: ExecutionStatus | null = null;
-    type = ResultType.Table;
-
+    grayedOut = false;
     fetchedOnce = false;
     hasResultSet = false;
     streaming = false;
     hasMore = false;
-
-    lastRenderedResult?: ExecutionResult;
-
-    images = [];
-    lastFetchedRows: ResultRow[] = [];
     rows: ResultRow[] = [];
     meta: ResultMeta[] = [];
 
-    fetchResultThrottle = -1;
+    status: ExecutionStatus | null = null;
+    type = ResultType.Table;
+    images = [];
+    lastFetchedRows: ResultRow[] = [];
+    lastRenderedResult?: ExecutionResult;
+    subTracker = new SubscriptionTracker();
 
     mounted(): void {
       this.subTracker.subscribe(EXECUTABLE_UPDATED_EVENT, (executable: Executable) => {
@@ -78,13 +104,42 @@
       });
     }
 
-    fetchMore(): void {
-      window.clearTimeout(this.fetchResultThrottle);
-      this.fetchResultThrottle = window.setTimeout(() => {
-        if (this.hasMore && this.executable && this.executable.result) {
-          this.executable.result.fetchRows({ rows: 100 });
-        }
-      }, 100);
+    get hasEmptyResult(): boolean {
+      return (
+        this.rows.length === 0 &&
+        this.hasResultSet &&
+        this.status === ExecutionStatus.available &&
+        this.fetchedOnce
+      );
+    }
+
+    get hasEmptySuccessResult(): boolean {
+      return (
+        this.rows.length === 0 &&
+        !this.hasResultSet &&
+        this.status === ExecutionStatus.available &&
+        this.fetchedOnce
+      );
+    }
+
+    get isExecuting(): boolean {
+      return this.status === ExecutionStatus.running;
+    }
+
+    get isExpired(): boolean {
+      return this.status === ExecutionStatus.expired && this.rows.length > 0;
+    }
+
+    get isStreaming(): boolean {
+      return this.streaming && this.rows.length === 0 && this.status !== ExecutionStatus.running;
+    }
+
+    get tableColumns(): Column<ResultRow>[] {
+      return this.meta.map(({ name }, index) => ({
+        label: name,
+        key: index,
+        htmlValue: true
+      }));
     }
 
     @Watch('executable')
@@ -98,6 +153,19 @@
       }
     }
 
+    async onScrollToEnd(): void {
+      if (this.hasMore && !this.grayedOut && this.executable && this.executable.result) {
+        this.grayedOut = true;
+        try {
+          await this.executable.result.fetchRows({ rows: 100 });
+        } catch (e) {}
+        defer(() => {
+          // Allow executable events to finish before enabling the result scroll again
+          this.grayedOut = false;
+        });
+      }
+    }
+
     resetResultData(): void {
       this.type = ResultType.Table;
 
@@ -147,3 +215,19 @@
     }
   }
 </script>
+
+<style lang="scss" scoped>
+  .result-grid {
+    position: relative;
+    height: 100%;
+    width: 100%;
+
+    &.grayed-out {
+      opacity: 0.5;
+
+      /deep/ .hue-table-container {
+        overflow: hidden !important;
+      }
+    }
+  }
+</style>

+ 6 - 6
desktop/core/src/desktop/js/apps/editor/components/result/ExecutionResultsKoBridge.vue → desktop/core/src/desktop/js/apps/editor/components/result/ResultTableKoBridge.vue

@@ -17,7 +17,7 @@
 -->
 
 <template>
-  <ExecutionResults :executable="executable" />
+  <ResultTable :executable="executable" />
 </template>
 
 <script lang="ts">
@@ -26,14 +26,14 @@
   import { Prop } from 'vue-property-decorator';
   import { wrap } from 'vue/webComponentWrapper';
 
-  import ExecutionResults from './ExecutionResults.vue';
+  import ResultTable from './ResultTable.vue';
   import SqlExecutable from 'apps/editor/execution/sqlExecutable';
   import SubscriptionTracker from 'components/utils/SubscriptionTracker';
 
   @Component({
-    components: { ExecutionResults }
+    components: { ResultTable }
   })
-  export default class ExecutionResultsKoBridge extends Vue {
+  export default class ResultTableKoBridge extends Vue {
     @Prop()
     executableObservable?: KnockoutObservable<SqlExecutable | undefined>;
 
@@ -57,6 +57,6 @@
     }
   }
 
-  export const COMPONENT_NAME = 'execution-results-ko-bridge';
-  wrap(COMPONENT_NAME, ExecutionResultsKoBridge);
+  export const COMPONENT_NAME = 'result-table-ko-bridge';
+  wrap(COMPONENT_NAME, ResultTableKoBridge);
 </script>

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

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

+ 2 - 2
desktop/core/src/desktop/js/ko/components/ko.editorDroppableMenu.js

@@ -23,7 +23,7 @@ import sqlUtils from 'sql/sqlUtils';
 import DisposableComponent from './DisposableComponent';
 import { DRAGGABLE_TEXT_META_EVENT } from 'ko/bindings/ko.draggableText';
 import { INSERT_AT_CURSOR_EVENT } from 'ko/bindings/ace/ko.aceEditor';
-import { defer, sleep } from 'utils/hueUtils';
+import { sleep } from 'utils/hueUtils';
 
 export const NAME = 'hue-editor-droppable-menu';
 
@@ -83,7 +83,7 @@ class EditorDroppableMenu extends DisposableComponent {
       $tableDropMenu,
       $('.content-panel'),
       async () => {
-        await defer();
+        await sleep(0);
         $(document).on('click.' + NAME, async () => {
           if (this.menu) {
             $tableDropMenu.css('opacity', 0);

+ 4 - 4
desktop/core/src/desktop/js/sql/autocompleteResults.test.js

@@ -26,7 +26,7 @@ import huePubSub from 'utils/huePubSub';
 import I18n from 'utils/i18n';
 import LOTS_OF_PARSE_RESULTS from './test/lotsOfParseResults';
 import * as sqlReferenceRepository from 'sql/reference/sqlReferenceRepository';
-import { defer } from '../utils/hueUtils';
+import { sleep } from '../utils/hueUtils';
 
 describe('AutocompleteResults.js', () => {
   const failResponse = {
@@ -400,7 +400,7 @@ describe('AutocompleteResults.js', () => {
       suggestFunctions: {}
     });
 
-    await defer();
+    await sleep(0);
 
     expect(spy).toHaveBeenCalled();
 
@@ -427,7 +427,7 @@ describe('AutocompleteResults.js', () => {
       }
     });
 
-    await defer();
+    await sleep(0);
 
     expect(spy).toHaveBeenCalled();
 
@@ -465,7 +465,7 @@ describe('AutocompleteResults.js', () => {
       suggestSetOptions: {}
     });
 
-    await defer();
+    await sleep(0);
 
     expect(spy).toHaveBeenCalled();
 

+ 2 - 5
desktop/core/src/desktop/js/utils/hueUtils.ts

@@ -599,11 +599,8 @@ export const onHueLinkClick = (event: Event, url: string, target?: string): void
 export const sleep = async (timeout: number): Promise<void> =>
   new Promise(resolve => setTimeout(resolve, timeout));
 
-export const defer = async (callback: () => void): Promise<void> => {
-  await sleep(0);
-  if (callback) {
-    callback();
-  }
+export const defer = (callback: () => void): void => {
+  sleep(0).finally(callback);
 };
 
 export default {

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

@@ -1023,9 +1023,9 @@
 
         <div class="tab-pane" id="queryResults" data-bind="css: {'active': currentQueryTab() == 'queryResults'}">
           <div class="execution-results-tab-panel">
-            <execution-results-ko-bridge class="table-results-bridge" data-bind="vueKoProps: {
+            <result-table-ko-bridge class="table-results-bridge" data-bind="vueKoProps: {
                 executableObservable: activeExecutable
-              }"></execution-results-ko-bridge>
+              }"></result-table-ko-bridge>
           </div>
         </div>