소스 검색

[frontend] Enable custom name for Hive interpreters

Johan Åhlén 4 년 전
부모
커밋
3c5e91207d

+ 1 - 1
apps/metastore/src/metastore/templates/metastore.mako

@@ -80,7 +80,7 @@ ${ components.menubar(is_embeddable) }
 <script type="text/html" id="metastore-breadcrumbs">
   <div style="font-size: 14px; margin: 0 12px; line-height: 27px;">
     <!-- ko if: sources().length >= 2 -->
-    <div data-bind="component: { name: 'hue-drop-down', params: { value: source, entries: sources, onSelect: sourceChanged, labelAttribute: 'name', searchable: true, linkTitle: '${ _ko('Source') }' } }"
+    <div data-bind="component: { name: 'hue-drop-down', params: { value: source, entries: sources, onSelect: sourceChanged, labelAttribute: 'displayName', searchable: true, linkTitle: '${ _ko('Source') }' } }"
       style="display: inline-block">
     </div>
     <!-- /ko -->

+ 7 - 0
desktop/core/src/desktop/conf.py

@@ -1788,6 +1788,13 @@ IS_K8S_ONLY = Config(
   help=_('Choose whether to pick configs only from [desktop] [[cluster]]')
 )
 
+ENABLE_UNIFIED_ANALYTICS = Config(
+  key='enable_unified_analytics',
+  default=False,
+  type=coerce_bool,
+  help=_('Choose whether Hive represents Unified Analytics or not.')
+)
+
 ENABLE_ORGANIZATIONS = Config(
   key='enable_organizations',
   default=False,

+ 7 - 0
desktop/core/src/desktop/js/apps/editor/EditorViewModel.js

@@ -90,6 +90,13 @@ export default class EditorViewModel {
       () => this.activeConnector() && this.activeConnector().displayName
     );
 
+    this.editorIcon = ko.pureComputed(() => {
+      const dialect = this.activeConnector()?.dialect;
+      return window.ENABLE_UNIFIED_ANALYTICS && dialect === 'hive'
+        ? 'impala'
+        : dialect || this.editorType();
+    });
+
     this.activeConnector.subscribe(connector => {
       if (connector) {
         if (hueUtils.getParameter('type') !== connector.id) {

+ 12 - 4
desktop/core/src/desktop/js/apps/notebook/NotebookViewModel.js

@@ -50,6 +50,14 @@ export default class NotebookViewModel {
     self.isMobile = ko.observable(options.mobile);
     self.isNotificationManager = ko.observable(options.is_notification_manager || false);
     self.editorType = ko.observable(options.editor_type);
+    self.editorIcon = ko.pureComputed(() => {
+      const foundInterpreter = options.languages.find(
+        interpreter => interpreter.type === self.editorType()
+      );
+      return window.ENABLE_UNIFIED_ANALYTICS && foundInterpreter?.dialect === 'hive'
+        ? 'impala'
+        : self.editorType();
+    });
     self.activeConnector = ko.observable();
 
     const updateConnector = id => {
@@ -154,10 +162,10 @@ export default class NotebookViewModel {
       }
     };
     self.editorTypeTitle = ko.pureComputed(() => {
-      const foundInterpreter = $.grep(options.languages, interpreter => {
-        return interpreter.type === self.editorType();
-      });
-      return foundInterpreter.length > 0 ? foundInterpreter[0].name : self.editorType();
+      const foundInterpreter = options.languages.find(
+        interpreter => interpreter.type === self.editorType()
+      );
+      return foundInterpreter?.displayName || foundInterpreter?.name || self.editorType();
     });
     self.autocompleteTimeout = options.autocompleteTimeout;
     self.selectedNotebook = ko.observable();

+ 1 - 0
desktop/core/src/desktop/js/apps/tableBrowser/metastoreSource.js

@@ -33,6 +33,7 @@ class MetastoreSource {
   constructor(options) {
     this.type = options.type;
     this.name = options.name;
+    this.displayName = options.displayName;
     this.metastoreViewModel = options.metastoreViewModel;
 
     this.reloading = ko.observable(false);

+ 2 - 0
desktop/core/src/desktop/js/apps/tableBrowser/metastoreViewModel.js

@@ -100,6 +100,7 @@ class MetastoreViewModel {
             sources.push(
               new MetastoreSource({
                 metastoreViewModel: this,
+                displayName: interpreter.displayName,
                 name: interpreter.name,
                 type: interpreter.type
               })
@@ -110,6 +111,7 @@ class MetastoreViewModel {
           sources.push(
             new MetastoreSource({
               metastoreViewModel: this,
+              displayName: initialSourceType,
               name: initialSourceType,
               type: initialSourceType
             })

+ 3 - 1
desktop/core/src/desktop/js/doc/docSupport.js

@@ -25,7 +25,9 @@ const DOCUMENT_TYPE_I18n = {
   'oozie-bundle2': I18n('Oozie Bundle'),
   'oozie-coordinator2': I18n('Oozie Schedule'),
   'oozie-workflow2': I18n('Oozie Workflow'),
-  'query-hive': I18n('Hive Query'),
+  'query-hive': window.ENABLE_UNIFIED_ANALYTICS
+    ? I18n('Unified Analytics Query')
+    : I18n('Hive Query'),
   'query-impala': I18n('Impala Query'),
   'search-dashboard': I18n('Search Dashboard'),
   'query-mapreduce': I18n('MapReduce Job'),

+ 3 - 3
desktop/core/src/desktop/js/ko/components/assist/ko.assistDbPanel.js

@@ -588,7 +588,7 @@ class AssistDbPanel {
 
     this.breadcrumb = ko.pureComputed(() => {
       if (this.isStreams && this.selectedSource()) {
-        return this.selectedSource().name;
+        return this.selectedSource().connector.displayName;
       }
       if (!this.isSolr && this.selectedSource()) {
         if (this.selectedSource().selectedNamespace()) {
@@ -599,7 +599,7 @@ class AssistDbPanel {
             return this.selectedSource().selectedNamespace().name;
           }
         }
-        return this.selectedSource().name;
+        return this.selectedSource().connector.displayName;
       }
       return null;
     });
@@ -840,7 +840,7 @@ class AssistDbPanel {
           new AssistDbSource({
             i18n: this.i18n,
             type: connector.id, // TODO: Remove redundant
-            name: connector.name, // TODO: Remove redundant
+            name: connector.displayName, // TODO: Remove redundant
             connector: connector,
             nonSqlType: false,
             navigationSettings: navigationSettings

+ 1 - 1
desktop/core/src/desktop/js/ko/components/assist/ko.assistEditorContextPanel.js

@@ -354,7 +354,7 @@ class AssistEditorContextPanel {
             initialNamespace: activeLocations.namespace,
             connector: activeLocations.connector,
             type: activeLocations.connector.id,
-            name: activeLocations.type,
+            name: activeLocations.connector.displayName,
             navigationSettings: navigationSettings
           }),
           databaseIndex: {},

+ 2 - 2
desktop/core/src/desktop/lib/connectors/api.py

@@ -20,7 +20,7 @@ import logging
 import sys
 
 from useradmin.models import update_app_permissions
-from notebook.conf import config_validator, _connector_to_iterpreter
+from notebook.conf import config_validator, _connector_to_interpreter
 
 from desktop.auth.decorators import admin_required
 from desktop.decorators import api_error_handler
@@ -124,7 +124,7 @@ def test_connector(request):
   connector = json.loads(request.POST.get('connector', '{}'))
 
   # Currently only Editor connectors are supported.
-  interpreter = _connector_to_iterpreter(
+  interpreter = _connector_to_interpreter(
       _augment_connector_properties(connector)
   )
   interpreter['type'] = 'hello'  # This is the id of the common health check query

+ 3 - 3
desktop/core/src/desktop/models.py

@@ -49,7 +49,7 @@ from useradmin.organization import _fitered_queryset
 from desktop import appmanager
 from desktop.auth.backend import is_admin
 from desktop.conf import get_clusters, IS_MULTICLUSTER_ONLY, ENABLE_ORGANIZATIONS, ENABLE_PROMETHEUS, \
-    has_connectors, TASK_SERVER, APP_BLACKLIST, ENABLE_SHARING, ENABLE_CONNECTORS
+    has_connectors, TASK_SERVER, APP_BLACKLIST, ENABLE_SHARING, ENABLE_CONNECTORS, ENABLE_UNIFIED_ANALYTICS
 from desktop.lib import fsmanager
 from desktop.lib.connectors.api import _get_installed_connectors
 from desktop.lib.connectors.models import Connector
@@ -1833,7 +1833,7 @@ class ClusterConfig(object):
           'name': interpreter['name'],
           'type': interpreter['type'],  # Connector v1
           'id': interpreter['type'],
-          'displayName': interpreter['name'],
+          'displayName': 'Unified Analytics' if ENABLE_UNIFIED_ANALYTICS.get() and interpreter['dialect'] == 'hive' else  interpreter['name'],
           'buttonName': _('Query'),
           'tooltip': _('%s Query') % interpreter['type'].title(),
           'optimizer': get_optimizer_mode(),
@@ -1889,7 +1889,7 @@ class ClusterConfig(object):
           'name': interpreter['name'],
           'type': interpreter['type'],
           'id': interpreter['type'],
-          'displayName': interpreter['name'],
+          'displayName': 'Unified Analytics' if ENABLE_UNIFIED_ANALYTICS.get() and interpreter['dialect'] == 'hive' else  interpreter['name'],
           'buttonName': _('Query'),
           'tooltip': _('%s Query') % interpreter['type'].title(),
           'page': '/editor/?type=%(type)s' % interpreter,

+ 2 - 1
desktop/core/src/desktop/templates/global_js_constants.mako

@@ -21,7 +21,7 @@
   from desktop.auth.backend import is_admin, is_hue_admin
   from desktop.conf import APP_SWITCHER_ALTUS_BASE_URL, APP_SWITCHER_MOW_BASE_URL, CUSTOM_DASHBOARD_URL, \
       DISPLAY_APP_SWITCHER, IS_K8S_ONLY, IS_MULTICLUSTER_ONLY, USE_DEFAULT_CONFIGURATION, USE_NEW_ASSIST_PANEL, \
-      VCS, ENABLE_GIST, ENABLE_LINK_SHARING, has_channels, has_connectors
+      VCS, ENABLE_GIST, ENABLE_LINK_SHARING, has_channels, has_connectors, ENABLE_UNIFIED_ANALYTICS
   from desktop.models import hue_version, _get_apps, get_cluster_config
 
   from beeswax.conf import DOWNLOAD_BYTES_LIMIT, DOWNLOAD_ROW_LIMIT, LIST_PARTITIONS_LIMIT, CLOSE_SESSIONS
@@ -105,6 +105,7 @@
 
   window.ENABLE_QUERY_BUILDER = '${ ENABLE_QUERY_BUILDER.get() }' === 'True';
   window.ENABLE_QUERY_SCHEDULING = '${ ENABLE_QUERY_SCHEDULING.get() }' === 'True';
+  window.ENABLE_UNIFIED_ANALYTICS = '${ ENABLE_UNIFIED_ANALYTICS.get() }' === 'True';
 
   window.ENABLE_HISTORY_V2 = '${ hasattr(ENABLE_HISTORY_V2, 'get') and ENABLE_HISTORY_V2.get() }' === 'True';
 

+ 26 - 26
desktop/libs/notebook/src/notebook/api_tests.py

@@ -640,39 +640,39 @@ class TestNotebookApiMocked(object):
 def test_get_interpreters_to_show():
   default_interpreters = OrderedDict((
       ('hive', {
-          'name': 'Hive', 'interface': 'hiveserver2', 'type': 'hive', 'is_sql': True, 'options': {}, 'dialect_properties': {},
-          'is_catalog': False, 'category': 'editor', 'dialect': 'hive'
+          'name': 'Hive', 'displayName': 'Hive', 'interface': 'hiveserver2', 'type': 'hive', 'is_sql': True,
+          'options': {}, 'dialect_properties': {}, 'is_catalog': False, 'category': 'editor', 'dialect': 'hive'
       }),
       ('spark', {
-          'name': 'Scala', 'interface': 'livy', 'type': 'spark', 'is_sql': False, 'options': {}, 'dialect_properties': {},
-          'is_catalog': False, 'category': 'editor', 'dialect': 'scala'
+          'name': 'Scala', 'displayName': 'Scala', 'interface': 'livy', 'type': 'spark', 'is_sql': False, 'options': {},
+          'dialect_properties': {}, 'is_catalog': False, 'category': 'editor', 'dialect': 'scala'
       }),
       ('pig', {
-          'name': 'Pig', 'interface': 'pig', 'type': 'pig', 'is_sql': False, 'options': {}, 'dialect_properties': {},
-          'is_catalog': False, 'category': 'editor', 'dialect': 'pig'
+          'name': 'Pig', 'displayName': 'Pig', 'interface': 'pig', 'type': 'pig', 'is_sql': False, 'options': {},
+          'dialect_properties': {}, 'is_catalog': False, 'category': 'editor', 'dialect': 'pig'
       }),
       ('java', {
-          'name': 'Java', 'interface': 'oozie', 'type': 'java', 'is_sql': False, 'options': {}, 'dialect_properties': {},
-          'is_catalog': False, 'category': 'editor', 'dialect': 'java'
+          'name': 'Java', 'displayName': 'Java', 'interface': 'oozie', 'type': 'java', 'is_sql': False, 'options': {},
+          'dialect_properties': {}, 'is_catalog': False, 'category': 'editor', 'dialect': 'java'
       })
     ))
 
   expected_interpreters = OrderedDict((
       ('java', {
-        'name': 'Java', 'interface': 'oozie', 'type': 'java', 'is_sql': False, 'options': {}, 'dialect_properties': {},
-        'is_catalog': False, 'category': 'editor', 'dialect': 'java'
+        'name': 'Java', 'displayName': 'Java', 'interface': 'oozie', 'type': 'java', 'is_sql': False, 'options': {},
+        'dialect_properties': {}, 'is_catalog': False, 'category': 'editor', 'dialect': 'java'
       }),
       ('pig', {
-        'name': 'Pig', 'interface': 'pig', 'is_sql': False, 'type': 'pig', 'options': {}, 'dialect_properties': {},
-        'is_catalog': False, 'category': 'editor', 'dialect': 'pig'
+        'name': 'Pig', 'displayName': 'Pig', 'interface': 'pig', 'is_sql': False, 'type': 'pig', 'options': {},
+        'dialect_properties': {}, 'is_catalog': False, 'category': 'editor', 'dialect': 'pig'
       }),
       ('hive', {
-          'name': 'Hive', 'interface': 'hiveserver2', 'is_sql': True, 'type': 'hive', 'options': {}, 'dialect_properties': {},
-          'is_catalog': False, 'category': 'editor', 'dialect': 'hive'
+          'name': 'Hive', 'displayName': 'Hive', 'interface': 'hiveserver2', 'is_sql': True, 'type': 'hive',
+          'options': {}, 'dialect_properties': {}, 'is_catalog': False, 'category': 'editor', 'dialect': 'hive'
       }),
       ('spark', {
-          'name': 'Scala', 'interface': 'livy', 'type': 'spark', 'is_sql': False, 'options': {}, 'dialect_properties': {},
-          'is_catalog': False, 'category': 'editor', 'dialect': 'scala'
+          'name': 'Scala', 'displayName': 'Scala', 'interface': 'livy', 'type': 'spark', 'is_sql': False, 'options': {},
+          'dialect_properties': {}, 'is_catalog': False, 'category': 'editor', 'dialect': 'scala'
       })
     ))
 
@@ -710,24 +710,24 @@ def test_get_interpreters_to_show():
 def test_get_ordered_interpreters():
   default_interpreters = OrderedDict((
     ('hive', {
-        'name': 'Hive', 'interface': 'hiveserver2', 'type': 'hive', 'is_sql': True, 'options': {}, 'dialect_properties': None,
-        'is_catalog': False, 'category': 'editor', 'dialect': 'hive'
+        'name': 'Hive', 'displayName': 'Hive', 'interface': 'hiveserver2', 'type': 'hive', 'is_sql': True,
+        'options': {}, 'dialect_properties': None, 'is_catalog': False, 'category': 'editor', 'dialect': 'hive'
     }),
     ('impala', {
-        'name': 'Impala', 'interface': 'hiveserver2', 'type': 'impala', 'is_sql': True, 'options': {}, 'dialect_properties': None,
-        'is_catalog': False, 'category': 'editor', 'dialect': 'impala'
+        'name': 'Impala', 'displayName': 'Impala', 'interface': 'hiveserver2', 'type': 'impala', 'is_sql': True,
+        'options': {}, 'dialect_properties': None, 'is_catalog': False, 'category': 'editor', 'dialect': 'impala'
     }),
     ('spark', {
-        'name': 'Scala', 'interface': 'livy', 'type': 'spark', 'is_sql': False, 'options': {}, 'dialect_properties': None,
-        'is_catalog': False, 'category': 'editor', 'dialect': 'scala'
+        'name': 'Scala', 'displayName': 'Scala', 'interface': 'livy', 'type': 'spark', 'is_sql': False, 'options': {},
+        'dialect_properties': None, 'is_catalog': False, 'category': 'editor', 'dialect': 'scala'
     }),
     ('pig', {
-        'name': 'Pig', 'interface': 'pig', 'type': 'pig', 'is_sql': False, 'options': {}, 'dialect_properties': None,
-        'is_catalog': False, 'category': 'editor', 'dialect': 'pig'
+        'name': 'Pig', 'displayName': 'Pig', 'interface': 'pig', 'type': 'pig', 'is_sql': False, 'options': {},
+        'dialect_properties': None, 'is_catalog': False, 'category': 'editor', 'dialect': 'pig'
     }),
     ('java', {
-        'name': 'Java', 'interface': 'oozie', 'type': 'java', 'is_sql': False, 'options': {}, 'dialect_properties': None,
-        'is_catalog': False, 'category': 'editor', 'dialect': 'java'
+        'name': 'Java', 'displayName': 'Java', 'interface': 'oozie', 'type': 'java', 'is_sql': False, 'options': {},
+        'dialect_properties': None, 'is_catalog': False, 'category': 'editor', 'dialect': 'java'
     })
   ))
 

+ 4 - 3
desktop/libs/notebook/src/notebook/conf.py

@@ -25,7 +25,7 @@ from django.test.client import Client
 from django.urls import reverse
 
 from desktop import appmanager
-from desktop.conf import is_oozie_enabled, has_connectors, is_cm_managed
+from desktop.conf import is_oozie_enabled, has_connectors, is_cm_managed, ENABLE_UNIFIED_ANALYTICS
 from desktop.lib.conf import Config, UnspecifiedConfigSection, ConfigSection, coerce_json_dict, coerce_bool, coerce_csv
 
 if sys.version_info[0] > 2:
@@ -64,7 +64,7 @@ def check_has_missing_permission(user, interpreter, user_apps=None):
          (interpreter in ('java', 'spark2', 'mapreduce', 'shell', 'sqoop1', 'distcp') and 'oozie' not in user_apps)
 
 
-def _connector_to_iterpreter(connector):
+def _connector_to_interpreter(connector):
   return {
       'name': connector['nice_name'],
       'type': connector['name'],  # Aka id
@@ -83,7 +83,7 @@ def get_ordered_interpreters(user=None):
   if has_connectors():
     from desktop.lib.connectors.api import _get_installed_connectors
     interpreters = [
-      _connector_to_iterpreter(connector)
+      _connector_to_interpreter(connector)
       for connector in _get_installed_connectors(categories=['editor', 'catalogs'], user=user)
     ]
   else:
@@ -128,6 +128,7 @@ def get_ordered_interpreters(user=None):
 
   return [{
       "name": i.get('nice_name', i['name']),
+      'displayName': 'Unified Analytics' if ENABLE_UNIFIED_ANALYTICS.get() and i.get('dialect', i['name']).lower() == 'hive' else i.get('nice_name', i['name']),
       "type": i['type'],
       "interface": i['interface'],
       "options": i['options'],

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

@@ -784,31 +784,8 @@
             <li class="app-header" style="display:none" data-bind="visible: true">
               <!-- ko if: editorMode -->
               <a data-bind="hueLink: '${ url('notebook:editor') }?type=' + editorType(), attr: { 'title': editorTitle() + '${ _(' Editor') }'}" style="cursor: pointer">
-                <!-- ko template: { name: 'app-icon-template', data: { icon: editorType() } } --><!-- /ko -->
-                <!-- ko switch: editorType() -->
-                <!-- ko case: 'impala' -->Impala<!-- /ko -->
-                <!-- ko case: 'rdbms' -->DB Query<!-- /ko -->
-                <!-- ko case: 'pig' -->Pig<!-- /ko -->
-                <!-- ko case: 'java' -->Java<!-- /ko -->
-                <!-- ko case: 'spark2' -->Spark<!-- /ko -->
-                <!-- ko case: 'sqoop1' -->Sqoop 1<!-- /ko -->
-                <!-- ko case: 'distcp' -->DistCp<!-- /ko -->
-                <!-- ko case: 'shell' -->Shell<!-- /ko -->
-                <!-- ko case: 'mapreduce' -->MapReduce<!-- /ko -->
-                <!-- ko case: ['beeswax', 'hive'] -->Hive<!-- /ko -->
-                <!-- ko case: 'mapreduce' -->MapReduce<!-- /ko -->
-                <!-- ko case: 'spark' -->Scala<!-- /ko -->
-                <!-- ko case: 'pyspark' -->PySpark<!-- /ko -->
-                <!-- ko case: 'r' -->R<!-- /ko -->
-                <!-- ko case: 'jar' -->Spark Submit Jar<!-- /ko -->
-                <!-- ko case: 'py' -->Spark Submit Python<!-- /ko -->
-                <!-- ko case: 'solr' -->Solr SQL<!-- /ko -->
-                <!-- ko case: 'kafkasql' -->Kafka SQL<!-- /ko -->
-                <!-- ko case: 'markdown' -->Markdown<!-- /ko -->
-                <!-- ko case: 'text' -->Text<!-- /ko -->
-                <!-- ko case: 'clickhouse' -->ClickHouse<!-- /ko -->
-                <!-- ko case: $default --><span data-bind="text: editorTitle()"></span><!-- /ko -->
-                <!-- /ko -->
+                <!-- ko template: { name: 'app-icon-template', data: { icon: editorIcon() } } --><!-- /ko -->
+                <span data-bind="text: editorTitle()"></span>
                 <!-- ko component: { name: 'hue-favorite-app', params: { app: 'editor', interpreter: editorType() }} --><!-- /ko -->
               </a>
               <!-- /ko -->

+ 3 - 26
desktop/libs/notebook/src/notebook/templates/editor_components.mako

@@ -143,32 +143,9 @@ ${ sqlSyntaxDropdown.sqlSyntaxDropdown() }
           <li class="app-header" style="display:none" data-bind="visible: true">
             <!-- ko if: editorMode -->
               <a data-bind="hueLink: '${ url('notebook:editor') }?type=' + editorType(), attr: { 'title': editorTypeTitle() + '${ _(' Editor') }'}" style="cursor: pointer">
-              <!-- ko template: { name: 'app-icon-template', data: { icon: editorType() } } --><!-- /ko -->
-
-              <!-- ko switch: editorType() -->
-                <!-- ko case: 'impala' -->Impala<!-- /ko -->
-                <!-- ko case: 'rdbms' -->DB Query<!-- /ko -->
-                <!-- ko case: 'pig' -->Pig<!-- /ko -->
-                <!-- ko case: 'java' -->Java<!-- /ko -->
-                <!-- ko case: 'spark2' -->Spark<!-- /ko -->
-                <!-- ko case: 'sqoop1' -->Sqoop 1<!-- /ko -->
-                <!-- ko case: 'distcp' -->DistCp<!-- /ko -->
-                <!-- ko case: 'shell' -->Shell<!-- /ko -->
-                <!-- ko case: 'mapreduce' -->MapReduce<!-- /ko -->
-                <!-- ko case: ['beeswax', 'hive'] -->Hive<!-- /ko -->
-                <!-- ko case: 'mapreduce' -->MapReduce<!-- /ko -->
-                <!-- ko case: 'spark' -->Scala<!-- /ko -->
-                <!-- ko case: 'pyspark' -->PySpark<!-- /ko -->
-                <!-- ko case: 'r' -->R<!-- /ko -->
-                <!-- ko case: 'jar' -->Spark Submit Jar<!-- /ko -->
-                <!-- ko case: 'py' -->Spark Submit Python<!-- /ko -->
-                <!-- ko case: 'solr' -->Solr SQL<!-- /ko -->
-                <!-- ko case: 'kafkasql' -->Kafka SQL<!-- /ko -->
-                <!-- ko case: 'markdown' -->Markdown<!-- /ko -->
-                <!-- ko case: 'text' -->Text<!-- /ko -->
-                <!-- ko case: 'clickhouse' -->ClickHouse<!-- /ko -->
-                <!-- ko case: $default --><span data-bind="text: editorTypeTitle()"></span><!-- /ko -->
-              <!-- /ko -->
+              <!-- ko template: { name: 'app-icon-template', data: { icon: editorIcon() } } --><!-- /ko -->
+
+              <span data-bind="text: editorTypeTitle"></span>
               <!-- ko component: { name: 'hue-favorite-app', params: { app: 'editor', interpreter: editorType() }} --><!-- /ko -->
               </a>
             <!-- /ko -->