Browse Source

Merge pull request #2837 from cloudera/bjorn--Left-Assists-should-have-an-icon-to-indicate-Iceberg-Tables

[frontend] Add iceberg icon to tables in left assist popover
Bjorn Alm 3 years ago
parent
commit
2a9fb19104

+ 8 - 1
desktop/core/src/desktop/js/catalog/DataCatalogEntry.ts

@@ -180,7 +180,7 @@ export interface NavigatorMeta extends TimestampedData {
 }
 }
 
 
 export interface TableAnalysis extends TimestampedData {
 export interface TableAnalysis extends TimestampedData {
-  cols: { comment?: string | null; type: string; name: string };
+  cols: { comment?: string | null; type: string; name: string }[];
   comment?: string | null;
   comment?: string | null;
   details: {
   details: {
     properties: { [propertyKey: string]: string };
     properties: { [propertyKey: string]: string };
@@ -1414,6 +1414,13 @@ export default class DataCatalogEntry {
     return false;
     return false;
   }
   }
 
 
+  /**
+   * Returns true if the entry is an Iceberg table
+   */
+  isIcebergTable(): boolean {
+    return this.analysis?.details?.stats?.table_type === 'ICEBERG';
+  }
+
   /**
   /**
    * Returns true if the entry is a view. It will be accurate once the source meta has been loaded.
    * Returns true if the entry is a view. It will be accurate once the source meta has been loaded.
    */
    */

+ 90 - 1
desktop/core/src/desktop/js/catalog/dataCatalogEntry.test.ts

@@ -16,7 +16,7 @@
 
 
 import { CancellablePromise } from 'api/cancellablePromise';
 import { CancellablePromise } from 'api/cancellablePromise';
 import dataCatalog from 'catalog/dataCatalog';
 import dataCatalog from 'catalog/dataCatalog';
-import DataCatalogEntry, { Sample } from 'catalog/DataCatalogEntry';
+import DataCatalogEntry, { Sample, TableAnalysis } from 'catalog/DataCatalogEntry';
 import { Compute, Connector, Namespace } from 'config/types';
 import { Compute, Connector, Namespace } from 'config/types';
 import * as CatalogApi from './api';
 import * as CatalogApi from './api';
 
 
@@ -60,6 +60,95 @@ describe('dataCatalogEntry.ts', () => {
 
 
   afterAll(clearStorage);
   afterAll(clearStorage);
 
 
+  describe('getAnalysis', () => {
+    const emptyAnalysisApiSpy = () => {
+      jest.spyOn(CatalogApi, 'fetchDescribe').mockReturnValue(
+        CancellablePromise.resolve<TableAnalysis>({
+          message: '',
+          name: 'iceberg-table-test',
+          partition_keys: [],
+          cols: [{ name: 'i', type: 'int', comment: '' }],
+          path_location: 'test',
+          hdfs_link: '/test',
+          is_view: false,
+          properties: [],
+          details: {
+            stats: {
+              table_type: 'ICEBERG'
+            },
+            properties: {}
+          },
+          stats: [],
+          primary_keys: []
+        })
+      );
+    };
+
+    it('should return true for isIcebergTable after the analysis has has been loaded', async () => {
+      emptyAnalysisApiSpy();
+      const entry = await getEntry('someDb.someIcebergTable');
+
+      expect(entry.isIcebergTable()).toBeFalsy();
+
+      await entry.getAnalysis();
+      expect(entry.isIcebergTable()).toBeTruthy();
+    });
+
+    it('rejects a cachedOnly request if there is no previous promise', async () => {
+      emptyAnalysisApiSpy();
+      const entryA = await getEntry('someDb.someTable');
+      let rejected = false;
+      await entryA.getAnalysis({ cachedOnly: true }).catch(() => {
+        rejected = true;
+      });
+
+      expect(rejected).toBeTruthy();
+    });
+
+    it('should return the same analysis promise for the same entry', async () => {
+      emptyAnalysisApiSpy();
+      const entryA = await getEntry('someDb.someTable');
+      const entryB = await getEntry('someDb.someTable');
+      expect(entryA.getAnalysis()).toEqual(entryB.getAnalysis());
+    });
+
+    it('should not return the same analysis promise for different entries', async () => {
+      emptyAnalysisApiSpy();
+      const entryA = await getEntry('someDb.someTableOne');
+      const entryB = await getEntry('someDb.someTableTwo');
+      expect(entryA.getAnalysis()).not.toEqual(entryB.getAnalysis());
+    });
+
+    it('should keep the analysis promise for future session use', async () => {
+      emptyAnalysisApiSpy();
+      const entryA = await getEntry('someDb.someTable');
+      await entryA.clearCache();
+      const analysisPromise = entryA.getAnalysis();
+      expect(entryA.analysisPromise).toEqual(analysisPromise);
+      const entryB = await getEntry('someDb.someTable');
+      expect(entryB.analysisPromise).toEqual(analysisPromise);
+    });
+
+    it('should not cancel when cancellable option is not set to true', async () => {
+      emptyAnalysisApiSpy();
+      const entryA = await getEntry('someDb.someTable');
+      const analysisPromise = entryA.getAnalysis({ cancellable: false });
+      await analysisPromise.cancel();
+      expect(analysisPromise.cancelled).toBeFalsy();
+      expect(entryA.analysisPromise).toEqual(analysisPromise);
+    });
+
+    it('should not return a cancelled analysis promise', async () => {
+      emptyAnalysisApiSpy();
+      const entryA = await getEntry('someDb.someTable');
+      const cancelledPromise = entryA.getAnalysis({ cancellable: true });
+      await cancelledPromise.cancel();
+      const newPromise = entryA.getAnalysis();
+      expect(cancelledPromise.cancelled).toBeTruthy();
+      expect(newPromise).not.toEqual(cancelledPromise);
+    });
+  });
+
   describe('getSample', () => {
   describe('getSample', () => {
     const emptySampleApiSpy = () => {
     const emptySampleApiSpy = () => {
       jest.spyOn(CatalogApi, 'fetchSample').mockReturnValue(
       jest.spyOn(CatalogApi, 'fetchSample').mockReturnValue(

+ 1 - 3
desktop/core/src/desktop/js/ko/components/contextPopover/dataCatalogContext.js

@@ -124,9 +124,6 @@ class DataCatalogContext {
         .catch(() => {
         .catch(() => {
           self.hasErrors(true);
           self.hasErrors(true);
         })
         })
-        .finally(() => {
-          self.loading(false);
-        })
     );
     );
 
 
     // TODO: Use connector attributes in dataCatalogContext
     // TODO: Use connector attributes in dataCatalogContext
@@ -177,6 +174,7 @@ class DataCatalogContext {
     );
     );
 
 
     $.when.apply($, self.activePromises).always(() => {
     $.when.apply($, self.activePromises).always(() => {
+      self.loading(false);
       self.activePromises.length = 0;
       self.activePromises.length = 0;
     });
     });
   }
   }

+ 25 - 1
desktop/core/src/desktop/js/ko/components/contextPopover/ko.contextPopover.js

@@ -276,7 +276,31 @@ const SUPPORT_TEMPLATES = `
 
 
   <script type="text/html" id="context-catalog-entry-title">
   <script type="text/html" id="context-catalog-entry-title">
     <div class="hue-popover-title">
     <div class="hue-popover-title">
-      <i class="hue-popover-title-icon fa muted" data-bind="css: catalogEntry() && (catalogEntry().isView() || parentIsView()) ? 'fa-eye' : (catalogEntry().isDatabase() ? 'fa-database' : (catalogEntry().isModel() ? 'fa-puzzle-piece' : 'fa-table'))"></i>
+      <!-- ko if: catalogEntry().isTable() -->
+        <span class="hue-popover-title-secondary-icon-container">
+          <!-- ko hueSpinner: { spin: loading, inline: true } --><!-- /ko -->
+          <!-- ko ifnot: loading -->  
+            <!-- ko if:catalogEntry().isIcebergTable() -->  
+              <i class="hue-popover-title-icon fa muted fa-snowflake-o"  title="${I18n(
+                'Iceberg table'
+              )}"></i>
+            <!-- /ko -->
+            <!-- ko ifnot:catalogEntry().isIcebergTable() -->  
+              <i class="hue-popover-title-icon fa muted fa-table"></i>
+            <!-- /ko -->            
+          <!-- /ko -->
+        </span>
+      <!-- /ko -->
+      <!-- ko ifnot: catalogEntry().isTable() -->
+        <i class="hue-popover-title-icon fa muted" data-bind="css: 
+        catalogEntry() && (catalogEntry().isView() || parentIsView()) 
+        ? 'fa-eye' 
+        : (catalogEntry().isDatabase() 
+        ? 'fa-database' 
+        : (catalogEntry().isModel() 
+        ? 'fa-puzzle-piece' 
+        : 'fa-table'))"></i>      
+      <!-- /ko -->
       <span class="hue-popover-title-text" data-bind="foreach: breadCrumbs">
       <span class="hue-popover-title-text" data-bind="foreach: breadCrumbs">
         <!-- ko ifnot: isActive --><div><a href="javascript: void(0);" data-bind="click: makeActive, text: name"></a>.</div><!-- /ko -->
         <!-- ko ifnot: isActive --><div><a href="javascript: void(0);" data-bind="click: makeActive, text: name"></a>.</div><!-- /ko -->
         <!-- ko if: isActive -->
         <!-- ko if: isActive -->

File diff suppressed because it is too large
+ 0 - 0
desktop/core/src/desktop/static/desktop/css/hue.css


File diff suppressed because it is too large
+ 0 - 0
desktop/core/src/desktop/static/desktop/css/hue3-extra.css


+ 14 - 4
desktop/core/src/desktop/static/desktop/less/components/hue-popover.less

@@ -52,7 +52,7 @@
 .hue-popover.hue-popover-top .hue-popover-arrow::after {
 .hue-popover.hue-popover-top .hue-popover-arrow::after {
   bottom: 1px;
   bottom: 1px;
   margin-left: -3px;
   margin-left: -3px;
-  content: "";
+  content: '';
   border-top-color: @hue-primary-color-dark;
   border-top-color: @hue-primary-color-dark;
   border-bottom-width: 0;
   border-bottom-width: 0;
 }
 }
@@ -72,7 +72,7 @@
 .hue-popover.hue-popover-right .hue-popover-arrow::after {
 .hue-popover.hue-popover-right .hue-popover-arrow::after {
   bottom: -3px;
   bottom: -3px;
   left: 1px;
   left: 1px;
-  content: "";
+  content: '';
   border-right-color: @hue-primary-color-dark;
   border-right-color: @hue-primary-color-dark;
   border-left-width: 0;
   border-left-width: 0;
 }
 }
@@ -92,7 +92,7 @@
 .hue-popover.hue-popover-bottom .hue-popover-arrow::after {
 .hue-popover.hue-popover-bottom .hue-popover-arrow::after {
   top: 3px;
   top: 3px;
   margin-left: -3px;
   margin-left: -3px;
-  content: "";
+  content: '';
   border-top-width: 0;
   border-top-width: 0;
   border-bottom-color: @hue-primary-color-dark;
   border-bottom-color: @hue-primary-color-dark;
 }
 }
@@ -112,7 +112,7 @@
 .hue-popover.hue-popover-left .hue-popover-arrow::after {
 .hue-popover.hue-popover-left .hue-popover-arrow::after {
   right: 2px;
   right: 2px;
   bottom: -3px;
   bottom: -3px;
-  content: "";
+  content: '';
   border-right-width: 0;
   border-right-width: 0;
   border-left-color: @hue-primary-color-dark;
   border-left-color: @hue-primary-color-dark;
 }
 }
@@ -131,6 +131,16 @@
     margin-top: 3px;
     margin-top: 3px;
   }
   }
 
 
+  .hue-popover-title-secondary-icon-container {
+    width: 20px;
+    display: inline-block;
+    margin-top: 3px;
+
+    .hue-popover-title-icon {
+      margin-top: 0;
+    }
+  }
+
   .hue-popover-title-text {
   .hue-popover-title-text {
     padding-left: 4px;
     padding-left: 4px;
     font-size: 0;
     font-size: 0;

Some files were not shown because too many files changed in this diff