Browse Source

[frontend] V1 of new assist panel

Johan Åhlén 4 years ago
parent
commit
e89652d70b

+ 2 - 2
desktop/core/src/desktop/js/catalog/dataCatalog.ts

@@ -61,13 +61,13 @@ export interface GetEntryOptions {
   temporaryOnly?: boolean;
   temporaryOnly?: boolean;
 }
 }
 
 
-interface GetMultiTableEntryOptions {
+export interface GetMultiTableEntryOptions {
   namespace: Namespace;
   namespace: Namespace;
   compute: Compute;
   compute: Compute;
   paths: string[][];
   paths: string[][];
 }
 }
 
 
-interface AddTemporaryTableOptions {
+export interface AddTemporaryTableOptions {
   name: string;
   name: string;
   namespace: Namespace;
   namespace: Namespace;
   compute: Compute;
   compute: Compute;

+ 32 - 6
desktop/core/src/desktop/js/components/SqlContextSelector.vue

@@ -1,6 +1,11 @@
 <template>
 <template>
+  <ul v-if="listView && connectors.length > 1">
+    <li v-for="connector in connectors" :key="connector.id">
+      <div @click="connectorSelected(connector)"><ConnectorIcon /> {{ connector.displayName }}</div>
+    </li>
+  </ul>
   <DropdownMenu
   <DropdownMenu
-    :if="connectors.length > 1"
+    v-if="!listView && connectors.length > 1"
     :link="true"
     :link="true"
     :text="modelValue?.connector.name || I18n('Source')"
     :text="modelValue?.connector.name || I18n('Source')"
   >
   >
@@ -19,6 +24,7 @@
 
 
   import DropdownMenuButton from './dropdown/DropdownMenuButton.vue';
   import DropdownMenuButton from './dropdown/DropdownMenuButton.vue';
   import DropdownMenu from './dropdown/DropdownMenu.vue';
   import DropdownMenu from './dropdown/DropdownMenu.vue';
+  import ConnectorIcon from './icons/ConnectorIcon';
   import { SqlContext } from './SqlContextSelector';
   import { SqlContext } from './SqlContextSelector';
   import SubscriptionTracker from './utils/SubscriptionTracker';
   import SubscriptionTracker from './utils/SubscriptionTracker';
   import { EditorInterpreter } from 'config/types';
   import { EditorInterpreter } from 'config/types';
@@ -29,16 +35,28 @@
 
 
   export default defineComponent({
   export default defineComponent({
     name: 'SqlContextSelector',
     name: 'SqlContextSelector',
-    components: { DropdownMenuButton, DropdownMenu },
+    components: { ConnectorIcon, DropdownMenuButton, DropdownMenu },
     props: {
     props: {
+      allowNull: {
+        type: Boolean,
+        default: false
+      },
       modelValue: {
       modelValue: {
         type: Object as PropType<SqlContext | null>,
         type: Object as PropType<SqlContext | null>,
         default: null
         default: null
+      },
+      listView: {
+        type: Boolean,
+        default: false
+      },
+      filterDialect: {
+        type: String as PropType<string | null>,
+        default: null
       }
       }
     },
     },
     emits: ['update:model-value'],
     emits: ['update:model-value'],
     setup(props, { emit }) {
     setup(props, { emit }) {
-      const { modelValue } = toRefs(props);
+      const { modelValue, allowNull, filterDialect } = toRefs(props);
       const subTracker = new SubscriptionTracker();
       const subTracker = new SubscriptionTracker();
       const connectors = ref<EditorInterpreter[]>([]);
       const connectors = ref<EditorInterpreter[]>([]);
 
 
@@ -58,9 +76,12 @@
       };
       };
 
 
       const updateConnectors = () => {
       const updateConnectors = () => {
-        connectors.value = filterEditorConnectors(connector => connector.is_sql);
+        connectors.value = filterEditorConnectors(
+          connector =>
+            connector.is_sql && (!filterDialect.value || connector.dialect === filterDialect.value)
+        );
 
 
-        let updatedConnector: EditorInterpreter | undefined = undefined;
+        let updatedConnector: EditorInterpreter | null = null;
         // Set the activeConnector to 1. updated version, 2. same dialect, 3. first connector
         // Set the activeConnector to 1. updated version, 2. same dialect, 3. first connector
         if (modelValue.value) {
         if (modelValue.value) {
           updatedConnector =
           updatedConnector =
@@ -69,9 +90,14 @@
               connector => connector.dialect === modelValue.value!.connector.dialect
               connector => connector.dialect === modelValue.value!.connector.dialect
             );
             );
         }
         }
-        if (!updatedConnector && connectors.value.length) {
+        if (!allowNull.value && !updatedConnector && connectors.value.length) {
           updatedConnector = connectors.value[0];
           updatedConnector = connectors.value[0];
         }
         }
+
+        if (!updatedConnector && connectors.value.length === 1) {
+          updatedConnector = connectors.value[0];
+        }
+
         connectorSelected(updatedConnector || null);
         connectorSelected(updatedConnector || null);
       };
       };
 
 

+ 29 - 0
desktop/core/src/desktop/js/components/assist/AssistPanel.scss

@@ -0,0 +1,29 @@
+/*
+ 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.
+*/
+
+@import '../styles/colors';
+@import '../styles/mixins';
+
+.hue-assist-panel {
+  @include fillAbsolute;
+
+  padding-top: 5px;
+  padding-left: 5px;
+
+  background-color: $fluid-gray-050;
+}

+ 37 - 0
desktop/core/src/desktop/js/components/assist/AssistPanel.vue

@@ -0,0 +1,37 @@
+<!--
+  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="hue-assist-panel">
+    <HueIcons />
+    <SqlAssistPanel :use-breadcrumbs="true" />
+  </div>
+</template>
+
+<script lang="ts">
+  import { defineComponent } from 'vue';
+
+  import './AssistPanel.scss';
+  import SqlAssistPanel from './SqlAssistPanel.vue';
+  import HueIcons from 'components/icons/HueIcons.vue';
+
+  export default defineComponent({
+    name: 'AssistPanel',
+    components: { HueIcons, SqlAssistPanel }
+  });
+</script>

+ 20 - 0
desktop/core/src/desktop/js/components/assist/AssistPanelWebComponent.ts

@@ -0,0 +1,20 @@
+// 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.
+
+import { wrap } from 'vue/webComponentWrap';
+import AssistPanel from './AssistPanel.vue';
+
+wrap('assist-panel-web-component', AssistPanel);

+ 115 - 0
desktop/core/src/desktop/js/components/assist/SqlAssistEntry.vue

@@ -0,0 +1,115 @@
+<!--
+  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
+    v-if="!root"
+    v-bind="hasPossibleChildren ? { role: 'button', tabIndex: 0 } : {}"
+    :class="{ leaf: !hasPossibleChildren }"
+    @click="onEntryClick"
+  >
+    <span>
+      <DatabaseIcon v-if="entry.isDatabase()" />
+      <ViewIcon v-else-if="entry.isView()" />
+      <TableIcon v-else-if="entry.isTable()" />
+      <ColumnIcon v-else-if="entry.isField()" />
+    </span>
+    {{ entry.getDisplayName() }}
+  </div>
+  <div v-if="open">
+    <ul v-if="loadingChildren">
+      <li class="spinner-container">
+        <Spinner :spin="true" :inline="true" :size="'large'" />
+      </li>
+    </ul>
+    <ul v-if="!loadingChildren && children && children.length">
+      <li v-for="childEntry in children" :key="childEntry.getQualifiedPath()">
+        <SqlAssistEntry :entry="childEntry" />
+      </li>
+    </ul>
+  </div>
+</template>
+
+<script lang="ts">
+  import { defineComponent, PropType, ref, toRefs, watch } from 'vue';
+
+  import ColumnIcon from '../icons/ColumnIcon';
+  import DatabaseIcon from '../icons/DatabaseIcon';
+  import TableIcon from '../icons/TableIcon';
+  import ViewIcon from '../icons/ViewIcon';
+  import Spinner from '../Spinner.vue';
+  import DataCatalogEntry from 'catalog/DataCatalogEntry';
+
+  export default defineComponent({
+    name: 'SqlAssistEntry',
+    components: { ColumnIcon, ViewIcon, TableIcon, DatabaseIcon, Spinner },
+    props: {
+      entry: {
+        type: Object as PropType<DataCatalogEntry>,
+        required: true
+      },
+      root: {
+        type: Boolean,
+        default: false
+      }
+    },
+    setup(props) {
+      const { entry, root } = toRefs(props);
+      const open = ref(false);
+      const loadingChildren = ref(false);
+      const children = ref<DataCatalogEntry[] | null>(null);
+      const hasPossibleChildren = ref(true);
+
+      watch(
+        entry,
+        () => {
+          hasPossibleChildren.value = !entry.value || entry.value.hasPossibleChildren();
+        },
+        { immediate: true }
+      );
+
+      const onEntryClick = async (): Promise<void> => {
+        if (!hasPossibleChildren.value) {
+          return;
+        }
+        open.value = !open.value;
+        if (open.value && !loadingChildren.value && children.value === null) {
+          loadingChildren.value = true;
+          try {
+            children.value = await entry.value.getChildren();
+          } catch (err) {
+            console.warn(err);
+          }
+          loadingChildren.value = false;
+        }
+      };
+
+      if (root.value) {
+        onEntryClick();
+      }
+
+      return {
+        children,
+        hasPossibleChildren,
+        loadingChildren,
+        onEntryClick,
+        open
+      };
+    }
+  });
+</script>

+ 81 - 0
desktop/core/src/desktop/js/components/assist/SqlAssistPanel.scss

@@ -0,0 +1,81 @@
+/*
+ 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.
+*/
+
+@import '../styles/colors';
+@import '../styles/mixins';
+
+.hue-sql-assist-panel {
+  .sql-assist-breadcrumb {
+    cursor: pointer;
+    font-size: 16px;
+
+    .hi {
+      font-size: 18px;
+    }
+
+    &:hover,
+    &:focus {
+      text-decoration: none;
+      outline: none;
+    }
+  }
+
+  .spinner-container {
+    line-height: 40px;
+  }
+
+  ul {
+    font-size: 12px;
+    margin: 0;
+
+    .hi {
+      font-size: 16px;
+    }
+
+    li {
+      list-style: none;
+      margin: 0;
+      overflow-x: hidden;
+      line-height: 30px;
+
+      > div:first-child {
+        padding-left: 12px;
+
+        > span:first-child {
+          margin-right: 1px;
+        }
+      }
+
+      > div:first-child:not(.leaf) {
+        cursor: pointer;
+
+        &:hover {
+          background-color: $fluid-gray-100;
+        }
+
+        &:focus {
+          outline: none;
+        }
+      }
+    }
+  }
+
+  ul:first-child ul li {
+    padding-left: 20px;
+  }
+}

+ 130 - 0
desktop/core/src/desktop/js/components/assist/SqlAssistPanel.vue

@@ -0,0 +1,130 @@
+<!--
+  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="hue-sql-assist-panel">
+    <div
+      v-if="useBreadcrumbs && activeSqlContext && !rootEntry"
+      class="sql-assist-breadcrumb"
+      role="button"
+      tabindex="0"
+      @click="activeSqlContext = null"
+    >
+      <ChevronLeftIcon /> <ConnectorIcon /> {{ activeSqlContext.connector.displayName }}
+    </div>
+
+    <SqlContextSelector
+      v-if="!activeSqlContext"
+      v-model="activeSqlContext"
+      :list-view="true"
+      :allow-null="true"
+    />
+
+    <div
+      v-if="useBreadcrumbs && rootEntry && rootEntry.isDatabase()"
+      class="sql-assist-breadcrumb"
+      role="button"
+      tabindex="0"
+      @click="rootEntry = null"
+    >
+      <ChevronLeftIcon /> <DatabaseIcon /> {{ rootEntry.name }}
+    </div>
+
+    <spinner :spin="activeSqlContext && loadingDatabases" :center="true" :size="'xlarge'" />
+    <ul v-if="activeSqlContext && !loadingDatabases && !rootEntry">
+      <li v-for="database in databases" :key="database.name">
+        <div role="button" tabindex="0" @click="onDatabaseClick(database)">
+          <DatabaseIcon /> {{ database.name }}
+        </div>
+      </li>
+    </ul>
+    <SqlAssistEntry v-if="rootEntry" :entry="rootEntry" :root="true" />
+  </div>
+</template>
+
+<script lang="ts">
+  import { defineComponent, ref, toRefs, watch } from 'vue';
+
+  import './SqlAssistPanel.scss';
+  import SqlAssistEntry from './SqlAssistEntry.vue';
+  import ChevronLeftIcon from '../icons/ChevronLeftIcon';
+  import ConnectorIcon from '../icons/ConnectorIcon';
+  import Spinner from '../Spinner.vue';
+  import DatabaseIcon from '../icons/DatabaseIcon';
+  import dataCatalog from 'catalog/dataCatalog';
+  import DataCatalogEntry from 'catalog/DataCatalogEntry';
+  import { SqlContext } from 'components/SqlContextSelector';
+  import SqlContextSelector from 'components/SqlContextSelector.vue';
+  import I18n from 'utils/i18n';
+
+  export default defineComponent({
+    name: 'SqlAssistPanel',
+    components: {
+      ConnectorIcon,
+      ChevronLeftIcon,
+      SqlAssistEntry,
+      Spinner,
+      DatabaseIcon,
+      SqlContextSelector
+    },
+    props: {
+      useBreadcrumbs: {
+        type: Boolean,
+        default: true
+      }
+    },
+    setup(props) {
+      const { useBreadcrumbs } = toRefs(props);
+      const activeSqlContext = ref<SqlContext | null>(null);
+      const rootEntry = ref<DataCatalogEntry | null>(null);
+      const loadingDatabases = ref(false);
+      const databases = ref<DataCatalogEntry[]>([]);
+
+      watch(activeSqlContext, async sqlContext => {
+        if (sqlContext) {
+          loadingDatabases.value = true;
+          try {
+            const sourceEntry = await dataCatalog.getEntry({
+              path: [],
+              ...sqlContext
+            });
+            if (useBreadcrumbs.value) {
+              databases.value = await sourceEntry.getChildren();
+            } else {
+              rootEntry.value = sourceEntry;
+            }
+          } catch {}
+          loadingDatabases.value = false;
+        }
+      });
+
+      const onDatabaseClick = (database: DataCatalogEntry): void => {
+        rootEntry.value = database;
+      };
+
+      return {
+        rootEntry,
+        activeSqlContext,
+        databases,
+        loadingDatabases,
+        onDatabaseClick,
+        I18n
+      };
+    }
+  });
+</script>