Browse Source

[ui][sidebar] Make visibility of the help menu in the sidebar configurable (#3722)

Co-authored-by: tarunjangid <tj@cloudera.com>
Tarun Jangid 1 year ago
parent
commit
f678becc03

+ 4 - 0
desktop/conf.dist/hue.ini

@@ -334,6 +334,10 @@ http_500_debug_mode=false
 # default value is false
 ## disable_local_storage = false
 
+# Whether or not to show the Help menu in the Sidebar.
+# Default value is true
+## enable_help_menu=true
+
 # Administrators
 # ----------------
 [[django_admins]]

+ 4 - 0
desktop/conf/pseudo-distributed.ini.tmpl

@@ -339,6 +339,10 @@
   # default value is false
   ## disable_local_storage = false
 
+  # Whether or not to show the Help menu in the Sidebar.
+  # Default value is true
+  ## enable_help_menu=true
+
   # Administrators
   # ----------------
   [[django_admins]]

+ 101 - 36
desktop/core/src/desktop/conf.py

@@ -1,5 +1,5 @@
 #!/usr/bin/env python
-## -*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
 # Licensed to Cloudera, Inc. under one
 # or more contributor license agreements.  See the NOTICE file
 # distributed with this work for additional information
@@ -16,29 +16,39 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import datetime
-import glob
-import logging
 import os
-import socket
-import stat
 import sys
-
+import glob
+import stat
+import socket
+import logging
+import datetime
 from collections import OrderedDict
 
 from django.db import connection
 
-from metadata.metadata_sites import get_navigator_audit_log_dir, get_navigator_audit_max_file_size
-
 from desktop import appmanager
-from desktop.redaction.engine import parse_redaction_policy_from_file
-from desktop.lib.conf import Config, ConfigSection, UnspecifiedConfigSection, coerce_bool, coerce_csv, coerce_json_dict, \
-    validate_path, list_of_compiled_res, coerce_str_lowercase, coerce_password_from_script, coerce_string
+from desktop.lib.conf import (
+  Config,
+  ConfigSection,
+  UnspecifiedConfigSection,
+  coerce_bool,
+  coerce_csv,
+  coerce_json_dict,
+  coerce_password_from_script,
+  coerce_str_lowercase,
+  coerce_string,
+  list_of_compiled_res,
+  validate_path,
+)
 from desktop.lib.i18n import force_unicode
 from desktop.lib.paths import get_desktop_root, get_run_root
+from desktop.redaction.engine import parse_redaction_policy_from_file
+from metadata.metadata_sites import get_navigator_audit_log_dir, get_navigator_audit_max_file_size
 
 if sys.version_info[0] > 2:
   from builtins import str as new_str
+
   from django.utils.translation import gettext_lazy as _
 else:
   new_str = unicode
@@ -52,6 +62,7 @@ def is_oozie_enabled():
   """Oozie needs to be available as it is the backend."""
   return len([app for app in appmanager.DESKTOP_MODULES if app.name == 'oozie']) > 0
 
+
 def coerce_database(database):
   if database == 'mysql':
     return 'django.db.backends.mysql'
@@ -84,6 +95,7 @@ def coerce_file(path):
 def coerce_timedelta(value):
   return datetime.timedelta(seconds=int(value))
 
+
 def get_dn(fqdn=None):
   """This function returns fqdn(if possible)"""
   val = []
@@ -102,11 +114,12 @@ def get_dn(fqdn=None):
     else:
       LOG.warning("allowed_hosts value to '*'. It is a security risk")
       val.append('*')
-  except:
+  except Exception as e:
     LOG.warning("allowed_hosts value to '*'. It is a security risk")
     val.append('*')
   return val
 
+
 def coerce_positive_integer(integer):
   integer = int(integer)
 
@@ -115,10 +128,12 @@ def coerce_positive_integer(integer):
 
   return integer
 
+
 def is_lb_enabled():
   """Check for Hue Load Balancer is available"""
   return bool(HUE_LOAD_BALANCER.get())
 
+
 def coerce_zero_or_positive_integer(integer):
   integer = int(integer)
 
@@ -127,34 +142,42 @@ def coerce_zero_or_positive_integer(integer):
 
   return integer
 
+
 def is_https_enabled():
   """Hue is configured for HTTPS."""
   return bool(SSL_CERTIFICATE.get() and SSL_PRIVATE_KEY.get())
 
+
 def get_slack_client_id():
   """Returns Slack Client ID if set as environment variable else None"""
   return os.environ.get('SLACK_CLIENT_ID')
 
+
 def get_slack_client_secret():
   """Returns Slack Client Secret if set as environment variable else None"""
   return os.environ.get('SLACK_CLIENT_SECRET')
 
+
 def get_slack_verification_token():
   """Returns Slack Verification Token if set as environment variable else None"""
   return os.environ.get('SLACK_VERIFICATION_TOKEN')
 
+
 def get_slack_bot_user_token():
   """Returns Slack Bot User Token if set as environment variable else None"""
   return os.environ.get('SLACK_BOT_USER_TOKEN')
 
+
 def is_python2():
   """Hue is running on Python 2."""
   return sys.version_info[0] == 2
 
+
 def is_custom_jwt_auth_enabled():
   """Returns True if key server url is set else returns False"""
   return bool(AUTH.JWT.KEY_SERVER_URL.get())
 
+
 USE_CHERRYPY_SERVER = Config(
   key="use_cherrypy_server",
   help=_("If set to true, CherryPy will be used. Otherwise, Gunicorn will be used as the webserver."),
@@ -280,9 +303,11 @@ SSL_CIPHER_LIST = Config(
     '!KRB5-DES-CBC3-SHA',
   ]))
 
+
 def has_ssl_no_renegotiation():
   return sys.version_info[:2] >= (3, 7)
 
+
 SSL_NO_RENEGOTIATION = Config(
   key="ssl_no_renegotiation",
   type=coerce_bool,
@@ -349,12 +374,12 @@ SECURE_CONTENT_SECURITY_POLICY = Config(
   help=_('X-Content-Type-Options: nosniff. This is a HTTP response header feature that helps prevent attacks '
     'based on MIME-type confusion.'),
   type=str,
-  default="script-src 'self' 'unsafe-inline' 'unsafe-eval' *.googletagmanager.com *.doubleclick.net data:;"+
-          "img-src 'self' *.doubleclick.net http://*.tile.osm.org *.tile.osm.org *.gstatic.com data:;"+
-          "style-src 'self' 'unsafe-inline' fonts.googleapis.com;"+
-          "connect-src 'self' *.google-analytics.com;"+
-          "frame-src *;"+
-          "child-src 'self' data: *.vimeo.com;"+
+  default="script-src 'self' 'unsafe-inline' 'unsafe-eval' *.googletagmanager.com *.doubleclick.net data:;" +
+          "img-src 'self' *.doubleclick.net http://*.tile.osm.org *.tile.osm.org *.gstatic.com data:;" +
+          "style-src 'self' 'unsafe-inline' fonts.googleapis.com;" +
+          "connect-src 'self' *.google-analytics.com;" +
+          "frame-src *;" +
+          "child-src 'self' data: *.vimeo.com;" +
           "object-src 'none'")
 
 SECURE_SSL_REDIRECT = Config(
@@ -397,15 +422,18 @@ LDAP_USERNAME = Config(
   private=True,
   default="hue")
 
+
 def get_auth_username():
   """Backward compatibility"""
   return LDAP_USERNAME.get()
 
+
 AUTH_USERNAME = Config(
   key="auth_username",
   help=_("Auth username of the hue user used for authentications. For example for LDAP Authentication with HiveServer2/Impala."),
   dynamic_default=get_auth_username)
 
+
 def get_auth_password():
   """Get from script or backward compatibility"""
 
@@ -421,12 +449,13 @@ def get_auth_password():
   if password is not None:
     return password
 
-  password = LDAP_PASSWORD.get() # 2 levels for backward compatibility
+  password = LDAP_PASSWORD.get()  # 2 levels for backward compatibility
   if password:
     return password
 
   return LDAP_PASSWORD_SCRIPT.get()
 
+
 AUTH_PASSWORD = Config(
   key="auth_password",
   help=_("LDAP/PAM/.. password of the hue user used for authentications. Inactive if empty. "
@@ -534,9 +563,9 @@ REDIRECT_WHITELIST = Config(
   key="redirect_whitelist",
   help=_("Comma-separated list of regular expressions, which match the redirect URL."
          "For example, to restrict to your local domain and FQDN, the following value can be used:"
-         "  ^\/.*$,^http:\/\/www.mydomain.com\/.*$"),
+         r"  ^\/.*$,^http:\/\/www.mydomain.com\/.*$"),
   type=list_of_compiled_res(skip_empty=True),
-  default='^(\/[a-zA-Z0-9]+.*|\/)$')
+  default=r'^(\/[a-zA-Z0-9]+.*|\/)$')
 
 USE_X_FORWARDED_HOST = Config(
   key="use_x_forwarded_host",
@@ -575,7 +604,7 @@ CLUSTER_ID = Config(
   private=False,
   default='default')
 
-DEMO_ENABLED = Config( # Internal and Temporary
+DEMO_ENABLED = Config(  # Internal and Temporary
   key="demo_enabled",
   help=_("To set to true in combination when using Hue demo backend."),
   type=coerce_bool,
@@ -643,18 +672,23 @@ def default_secure_cookie():
   """Enable secure cookies if HTTPS is enabled."""
   return is_https_enabled()
 
+
 def default_ssl_cacerts():
   """Path to default Certificate Authority certificates"""
   return SSL_CACERTS.get()
 
+
 def default_ssl_validate():
   """Choose whether Hue should validate certificates received from the server."""
   return SSL_VALIDATE.get()
 
+
 #
 # Email (SMTP) settings
 #
 _default_from_email = None
+
+
 def default_from_email():
   """Email for hue@<host-fqdn>"""
   global _default_from_email
@@ -676,6 +710,7 @@ def default_database_options():
   else:
     return {}
 
+
 def get_deprecated_login_lock_out_by_combination_browser_user_agent():
   """Return value of deprecated LOGIN_LOCK_OUT_BY_COMBINATION_BROWSER_USER_AGENT_AND_IP config"""
   return AUTH.LOGIN_LOCK_OUT_BY_COMBINATION_BROWSER_USER_AGENT_AND_IP.get()
@@ -753,11 +788,13 @@ METRICS = ConfigSection(
   )
 )
 
+
 def is_gunicorn_report_enabled():
   return 'rungunicornserver' in sys.argv \
     and METRICS.LOCATION.get() is not None \
     and METRICS.COLLECTION_INTERVAL.get() is not None
 
+
 SLACK = ConfigSection(
   key='slack',
   help=_("""Configuration options for slack """),
@@ -905,7 +942,7 @@ SESSION = ConfigSection(
       key='ttl',
       help=_("The cookie containing the users' session ID will expire after this amount of time in seconds."),
       type=int,
-      default=60*60*24*14,
+      default=60 * 60 * 24 * 14,
     ),
     SECURE=Config(
       key='secure',
@@ -929,7 +966,7 @@ SESSION = ConfigSection(
       key='csrf_cookie_age',
       help=_("CRSF cookie age defaults to 1 year. If the value is set to 0, it means per session. Time in seconds"),
       type=int,
-      default=60*60*24*7*52,
+      default=60 * 60 * 24 * 7 * 52,
     ),
     CONCURRENT_USER_SESSION_LIMIT=Config(
       key="concurrent_user_session_limit",
@@ -990,7 +1027,7 @@ KERBEROS = ConfigSection(
       key='reinit_frequency',
       help=_("Frequency in seconds with which Hue will renew its keytab."),
       type=int,
-      default=60*60), #1h
+      default=60 * 60),  # 1h
     CCACHE_PATH=Config(
       key='ccache_path',
       help=_("Path to keep Kerberos credentials cached."),
@@ -1008,7 +1045,7 @@ KERBEROS = ConfigSection(
       key='kinit_path',
       help=_("Path to Kerberos 'kinit' command."),
       type=str,
-      default="kinit", # use PATH!
+      default="kinit",  # use PATH!
     ),
     MUTUAL_AUTHENTICATION=Config(
       key='mutual_authentication',
@@ -1022,7 +1059,7 @@ KERBEROS = ConfigSection(
 SASL_MAX_BUFFER = Config(
   key="sasl_max_buffer",
   help=_("This property specifies the maximum size of the receive buffer in bytes in thrift sasl communication."),
-  default=2*1024*1024,  # 2 MB
+  default=2 * 1024 * 1024,  # 2 MB
   type=int
 )
 
@@ -1669,6 +1706,7 @@ def default_feedback_url():
   """A version-specific URL."""
   return "http://groups.google.com/a/cloudera.org/group/hue-user"
 
+
 FEEDBACK_URL = Config(
   key="feedback_url",
   help=_("Link for 'feedback' tab."),
@@ -1752,10 +1790,12 @@ HTTP_500_DEBUG_MODE = Config(
   default=True
 )
 
+
 def get_instrumentation_default():
   """If django_debug_mode is True, this is automatically enabled"""
   return DJANGO_DEBUG_MODE.get()
 
+
 INSTRUMENTATION = Config(
   key='instrumentation',
   help=_('Enable or disable instrumentation. If django_debug_mode is True, this is automatically enabled.'),
@@ -1838,8 +1878,10 @@ ENABLE_NEW_STORAGE_BROWSER = Config(
   default=False
 )
 
+
 def is_chunked_fileuploader_enabled():
-  return ENABLE_CHUNKED_FILE_UPLOADER.get();
+  return ENABLE_CHUNKED_FILE_UPLOADER.get()
+
 
 ENABLE_CHUNKED_FILE_UPLOADER = Config(
   key="enable_chunked_file_uploader",
@@ -1848,7 +1890,7 @@ ENABLE_CHUNKED_FILE_UPLOADER = Config(
   default=False
 )
 
-USE_NEW_EDITOR = Config( # To remove in Hue 4
+USE_NEW_EDITOR = Config(  # To remove in Hue 4
   key='',
   default=True,
   type=coerce_bool,
@@ -1948,6 +1990,7 @@ TRACING = ConfigSection(
     ),
 ))
 
+
 def task_server_default_result_directory():
   """Local directory to store task results."""
   return 'file://%s' % get_run_root('logs')
@@ -2083,6 +2126,7 @@ def has_multi_clusters():
   '''If Hue is configured to talk to more than one completely independent clusters'''
   return len(CLUSTERS.get()) > 1
 
+
 def has_connectors():
   '''When the connector feature is turned on'''
   return ENABLE_CONNECTORS.get()
@@ -2123,10 +2167,12 @@ ENABLE_GIST = Config(
   help=_('Turn on the Gist snippet sharing.')
 )
 
+
 def default_gist_preview():
   """Gist preview only enabled automatically in private setups."""
   return not ENABLE_ORGANIZATIONS.get()
 
+
 ENABLE_GIST_PREVIEW = Config(
   key='enable_gist_preview',
   dynamic_default=default_gist_preview,
@@ -2156,6 +2202,13 @@ DISABLE_LOCAL_STORAGE = Config(
          "please make this value true in case local storage should not be used.")
 )
 
+ENABLE_HELP_MENU = Config(
+  key='enable_help_menu',
+  default=True,
+  type=coerce_bool,
+  help=_("Whether or not to show the Help menu in the Sidebar")
+)
+
 ENABLE_CONNECTORS = Config(
   key='enable_connectors',
   default=False,
@@ -2219,11 +2272,13 @@ CONNECTORS = UnspecifiedConfigSection(
   )
 )
 
+
 def _get_raz_url():
   """Check if we can guess if Raz is configured"""
   from hadoop.core_site import get_raz_api_url  # Avoid circular import
   return get_raz_api_url()
 
+
 def has_raz_url():
   """Check if we can guess if Raz is configured"""
   return bool(_get_raz_url())
@@ -2242,10 +2297,12 @@ SDXAAS = ConfigSection(
   )
 )
 
+
 def is_sdxaas_jwt_enabled():
   """Check if SDXaaS token url is configured"""
   return bool(SDXAAS.TOKEN_URL.get())
 
+
 def handle_raz_api_auth():
   """Return RAZ authentication type from JWT (if SDXaaS token URL is set) or KERBEROS"""
   return 'jwt' if is_sdxaas_jwt_enabled() else 'kerberos'
@@ -2355,7 +2412,7 @@ def validate_ldap(user, config):
       bind_password = get_ldap_bind_password(config)
 
       if bool(bind_dn) != bool(bind_password):
-        if bind_dn == None:
+        if bind_dn is None:
           res.append((LDAP.BIND_DN,
                     new_str(_("If you set bind_password, then you must set bind_dn."))))
         else:
@@ -2383,6 +2440,7 @@ def validate_ldap(user, config):
 
   return res
 
+
 def validate_database(user):
   res = []
   cursor = connection.cursor()
@@ -2440,6 +2498,7 @@ def validate_database(user):
 
   return res
 
+
 def config_validator(user):
   """
   config_validator() -> [ (config_variable, error_message) ]
@@ -2448,8 +2507,8 @@ def config_validator(user):
   """
   from beeswax.models import QueryHistory, SavedQuery, Session
   from desktop.lib import i18n
-  from desktop.models import Document, Document2 # Avoid cyclic loop
-  from desktop.settings import DOCUMENT2_MAX_ENTRIES # Avoid cyclic loop
+  from desktop.models import Document, Document2  # Avoid cyclic loop
+  from desktop.settings import DOCUMENT2_MAX_ENTRIES  # Avoid cyclic loop
   from oozie.models import Job
 
   res = []
@@ -2530,14 +2589,14 @@ def config_validator(user):
   except Exception as e:
     LOG.warning('Config check failed because Oozie app not installed %s' % e)
 
-  from notebook.models import make_notebook
   from notebook.api import _save_notebook
+  from notebook.models import make_notebook
 
   notebook = make_notebook(name='test', editor_type='hive', statement='select "ทดสอบ"', status='ready')
   notebook_doc = None
   try:
     notebook_doc, save_as = _save_notebook(notebook.get_data(), user)
-  except:
+  except Exception as e:
     res.append(('DATABASE_CHARACTER_SET', new_str(
       _('Character set of <i>search</i> field in <i>desktop_document2</i> table is not UTF-8. <br>'
         '<b>NOTE:</b> Configure the database for character set AL32UTF8 and national character set UTF8.'))
@@ -2551,6 +2610,7 @@ def config_validator(user):
 
   return res
 
+
 def get_redaction_policy():
   """
   Return the configured redaction policy.
@@ -2618,6 +2678,7 @@ def get_ldap_bind_password(ldap_config):
 
   return password
 
+
 PERMISSION_ACTION_GS = "gs_access"
 
 GC_ACCOUNTS = UnspecifiedConfigSection(
@@ -2635,21 +2696,24 @@ GC_ACCOUNTS = UnspecifiedConfigSection(
   )
 )
 
+
 def is_cm_managed():
   return 'cloudera-scm-agent' in os.path.realpath(os.getenv("HUE_CONF_DIR", get_desktop_root("conf")))
 
 
 def is_gs_enabled():
-  from desktop.lib.idbroker import conf as conf_idbroker # Circular dependencies  desktop.conf -> idbroker.conf -> desktop.conf
+  from desktop.lib.idbroker import conf as conf_idbroker  # Circular dependencies  desktop.conf -> idbroker.conf -> desktop.conf
 
   return ('default' in list(GC_ACCOUNTS.keys()) and GC_ACCOUNTS['default'].JSON_CREDENTIALS.get()) or is_raz_gs() or \
     conf_idbroker.is_idbroker_enabled('gs')
 
+
 def has_gs_access(user):
   from desktop.auth.backend import is_admin
   return user.is_authenticated and user.is_active and (
     is_admin(user) or user.has_hue_permission(action="gs_access", app="filebrowser") or is_raz_gs())
 
+
 def is_raz_gs():
   from desktop.conf import RAZ  # Must be imported dynamically in order to have proper value
 
@@ -2717,6 +2781,7 @@ OZONE = UnspecifiedConfigSection(
   )
 )
 
+
 def is_ofs_enabled():
   return ('default' in list(OZONE.keys()) and OZONE['default'].get_raw() and OZONE['default'].WEBHDFS_URL.get())
 

+ 14 - 6
desktop/core/src/desktop/js/components/sidebar/HueSidebar.vue

@@ -213,15 +213,15 @@
       userDrawerItem: UserDrawerItem;
       userDrawerChildren: SidebarAccordionSubItem[];
 
-      helpDrawerItem: HelpDrawerItem;
-      helpDrawerChildren: SidebarAccordionSubItem[];
+      helpDrawerItem?: HelpDrawerItem;
+      helpDrawerChildren?: SidebarAccordionSubItem[];
 
       lastEditorDatabase?: EditorDatabaseDetails;
       lastAssistDatabase?: AssistDatabaseDetails;
 
       subTracker: SubscriptionTracker;
     } {
-      return {
+      let sideBarList = {
         userDrawerItem: {
           displayName: (<hueWindow>window).LOGGED_USERNAME || '',
           logoutLabel: I18n('Log Out'),
@@ -229,14 +229,22 @@
         },
         userDrawerChildren: USER_DRAWER_CHILDREN,
 
+        subTracker: new SubscriptionTracker()
+      };
+
+      const helpDrawerList = {
         helpDrawerItem: {
           displayName: I18n('Help'),
           iconHtml: getIconHtml('support')
         },
-        helpDrawerChildren: HELP_DRAWER_CHILDREN,
-
-        subTracker: new SubscriptionTracker()
+        helpDrawerChildren: HELP_DRAWER_CHILDREN
       };
+
+      if ((<hueWindow>window).ENABLE_HELP_MENU) {
+        sideBarList = { ...sideBarList, ...helpDrawerList };
+      }
+
+      return sideBarList;
     },
 
     data(): {

+ 1 - 0
desktop/core/src/desktop/js/types/types.ts

@@ -68,4 +68,5 @@ export interface hueWindow {
   hueDebug?: HueDebug;
   hueDebugAnalytics?: boolean;
   gtag?: (type: string, name: string, parameters: { action: string; version: string }) => void;
+  ENABLE_HELP_MENU?: boolean;
 }

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

@@ -798,6 +798,7 @@
   window.IS_LDAP_SETUP = '${ 'desktop.auth.backend.LdapBackend' in conf.AUTH.BACKEND.get() }' === 'True';
   window.LOGGED_USERNAME = '${ user.username }';
   window.LOGGED_USER_ID = ${ user.id };
+  window.ENABLE_HELP_MENU = '${conf.ENABLE_HELP_MENU.get()}' === 'True';
 
   <%
     # TODO remove