public inbox for [email protected]  
help / color / mirror / Atom feed
From: Pramod Ahire <[email protected]>
To: Akshay Joshi <[email protected]>
Cc: Dave Page <[email protected]>
Cc: pgadmin-hackers <[email protected]>
Subject: Re: Quick search for menu items & help articles
Date: Mon, 01 Feb 2021 13:32:33 +0530
Message-ID: <[email protected]> (raw)
In-Reply-To: <CANxoLDc3yh3B0MtKVjaN5wAWTM_1UaoCsCmmUVp4vFBR6KdECw@mail.gmail.com>
References: <[email protected]>
	<CA+OCxozgWnBkzORFfLD1o0X4Zq9izG26a+LZUZ8encP-Qn6mew@mail.gmail.com>
	<[email protected]>
	<CA+OCxoweuRoV-NC8Q3Go1oU_MSKy2+ntHF7h_Sinrr9um=XwRw@mail.gmail.com>
	<CANxoLDe2APE-=h3+8NUuoj+ArH_R6U4vUepRWna5gwxaq2MK2Q@mail.gmail.com>
	<CA+OCxozwzdBa5OBj_=gTNo0V6F0M_Ne3H-q9GHPgfP7ZqSF+cQ@mail.gmail.com>
	<CANxoLDfF+-EoPpwC0F-Fh0rJKihaiv9Q1n5r7V0A9_vfqUudyQ@mail.gmail.com>
	<[email protected]>
	<[email protected]>
	<CANxoLDc3yh3B0MtKVjaN5wAWTM_1UaoCsCmmUVp4vFBR6KdECw@mail.gmail.com>

Hi Akshay,

 

Please find the rebased patch as attached with this email.

 

Please do let me know if need anything else.

 

Pramod Ahire

Software Engineer

 

 

C: +91-020-66449600/601

D: +91-9028697679

edbpostgres.com

 

 

From: Akshay Joshi <[email protected]>
Date: Monday, 25 January 2021 at 3:53 PM
To: Pramod Ahire <[email protected]>
Cc: Dave Page <[email protected]>, pgadmin-hackers <[email protected]>
Subject: Re: Quick search for menu items & help articles

 

Hi Pramod

 

The patch is not applied, maybe you need to rebase and send the patch again.

 

On Wed, Jan 20, 2021 at 4:36 PM Pramod Ahire <[email protected]> wrote:

Hi Team,

 

Sorry for the inconvenience, kindly please discard previous email.

 

Please review patch attached to this email. 

Changes as below :

1. Added complete quick search menu to help dropdown
2. Added message to each disabled menu item to describe why it can be disabled

3. Info icon design to menu items results if disabled
4. Standard/dark/high contrast theme supports

NOTE: “Initial backend search mechanism patch by: Murtuza Zabuawala”

 

Thanks !

 

Pramod Ahire

Software Engineer

 

 

C: +91-020-66449600/601

D: +91-9028697679

edbpostgres.com

 

 

From: Pramod Ahire <[email protected]>
Date: Wednesday, 20 January 2021 at 4:19 PM
To: Akshay Joshi <[email protected]>, Dave Page <[email protected]>
Cc: pgadmin-hackers <[email protected]>
Subject: Re: Quick search for menu items & help articles

 

Hi Team,

 

As per inputs from you all, made changes & attached patch with this email.

 

Kindly please have a review & do let me know if missed anything.

 

Thanks !

 

Pramod Ahire

Software Engineer

 

 

C: +91-020-66449600/601

D: +91-9028697679

edbpostgres.com

 

 

From: Akshay Joshi <[email protected]>
Date: Friday, 15 January 2021 at 3:34 PM
To: Dave Page <[email protected]>
Cc: Pramod Ahire <[email protected]>, pgadmin-hackers <[email protected]>
Subject: Re: Quick search for menu items & help articles

 

 

 

On Fri, Jan 15, 2021 at 3:15 PM Dave Page <[email protected]> wrote:

 

 

On Fri, Jan 15, 2021 at 9:43 AM Akshay Joshi <[email protected]> wrote:

Hi Dave

 

On Fri, Jan 15, 2021 at 2:46 PM Dave Page <[email protected]> wrote:

Hi

 

On Fri, Jan 15, 2021 at 7:23 AM Pramod Ahire <[email protected]> wrote:

Hi Dave,

 

Thank you for comments ! I had described points below. Please do let me know if anything missing.

 

From: Dave Page <[email protected]>
Date: Thursday, 14 January 2021 at 10:26 PM
To: Pramod Ahire <[email protected]>
Cc: pgadmin-hackers <[email protected]>
Subject: Re: Quick search for menu items & help articles

 

Hi

 

On Thu, Jan 14, 2021 at 4:48 PM Pramod Ahire <[email protected]> wrote:

Hi Team, 

 

Please find the attached designs & patch that contains complete functionality except below to do for quick search.

 

To Do: 

 
Unit test cases are not that sufficient to cover complete code, but will be working in background to cover up those one
In pgadmin, for disabled menu items we need to add info that will describe why menu has disabled & how it will be enabled. Either another way to enable all of them & show respective reason in popup that menu is disabled for. 
 

Please do let me know if I missed anything or suggestion of yours.

 

Looks very good. I haven't done an extensive code review/test, but two things spring to mind immediately:

 

1) I think the search box should be the top item on the Help menu. I do not think it should be on the far end of the menu bar, as it looks too much like it will search for data (think of search on a website).

 

- As we are showing menu items as well in search results, it can be redundant for end user & increase duplications of menu items. Please advise your thoughts on this.

 

The search box could be on the Help menu, with results shown in a sub-menu under it.

 

Another more simple option would be to move the search icon to be directly after the Help menu (which would hint that it's related to the menus), but I'm also concerned about the number of items on the top menu - we're getting to the point that 'common' window sizes would show the hamburger menu by default.

 

    I personally feel we should not add the search box on the Help menu, it should be kept separate with a placeholder indicating what is it for. We can move the search icon after the Help menu.

 

What is your reasoning? It is, after all, specifically designed to help people.

 

    OK, I have seen a couple of applications where the search box is inside the Help menu, agree with your point. 

 

 

 

2) Do we need another loading icon? Surely there's one in the source tree already that we can use? 

 

- As we are loading help articles count in background, so I have added background loading icon to show near to count of results. Our existing icon is of blue & white circle combination, which will be more useful to show foreground loading.

 

Hmm, OK.

 

 

Please do let me know your valuable inputs on this. 

 

 

Pramod Ahire

Software Engineer

 

 

C: +91-020-66449600/601

D: +91-9028697679

edbpostgres.com

 

 

 

 


 

-- 

Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake

EDB: http://www.enterprisedb.com


 

-- 

Thanks & Regards

Akshay Joshi

pgAdmin Hacker | Principal Software Architect

EDB Postgres

Mobile: +91 976-788-8246


 

-- 

Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake

EDB: http://www.enterprisedb.com


 

-- 

Thanks & Regards

Akshay Joshi

pgAdmin Hacker | Principal Software Architect

EDB Postgres

Mobile: +91 976-788-8246


 

-- 

Thanks & Regards

Akshay Joshi

pgAdmin Hacker | Principal Software Architect

EDB Postgres

Mobile: +91 976-788-8246



Attachments:

  [application/octet-stream] quick-search-v4.patch (58.3K, 3-quick-search-v4.patch)
  download | inline diff:
diff --git a/web/pgadmin/browser/register_browser_preferences.py b/web/pgadmin/browser/register_browser_preferences.py
index 7f909c41f..235db0271 100644
--- a/web/pgadmin/browser/register_browser_preferences.py
+++ b/web/pgadmin/browser/register_browser_preferences.py
@@ -443,6 +443,19 @@ def register_browser_preferences(self):
         fields=fields
     )
 
+    self.preference.register(
+        'keyboard_shortcuts', 'open_quick_search',
+        gettext('Quick Search'), 'keyboardshortcut',
+        {
+            'alt': False,
+            'shift': True,
+            'control': True,
+            'key': {'key_code': 70, 'char': 'f'}
+        },
+        category_label=PREF_LABEL_KEYBOARD_SHORTCUTS,
+        fields=fields
+    )
+
     self.dynamic_tab_title = self.preference.register(
         'tab_settings', 'dynamic_tabs',
         gettext("Dynamic tab size"), 'boolean', False,
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/packages/static/js/package.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/packages/static/js/package.js
index a62ad0b80..e47b54cb1 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/packages/static/js/package.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/packages/static/js/package.js
@@ -48,19 +48,19 @@ define('pgadmin.node.package', [
           name: 'create_package_on_coll', node: 'coll-package', module: this,
           applies: ['object', 'context'], callback: 'show_obj_properties',
           category: 'create', priority: 4, label: gettext('Package...'),
-          icon: 'wcTabIcon icon-package', data: {action: 'create', check: true},
+          icon: 'wcTabIcon icon-package', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')},
           enable: 'canCreate',
         },{
           name: 'create_package', node: 'package', module: this,
           applies: ['object', 'context'], callback: 'show_obj_properties',
           category: 'create', priority: 4, label: gettext('Package...'),
-          icon: 'wcTabIcon icon-package', data: {action: 'create', check: true},
+          icon: 'wcTabIcon icon-package', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')},
           enable: 'canCreate',
         },{
           name: 'create_package', node: 'schema', module: this,
           applies: ['object', 'context'], callback: 'show_obj_properties',
           category: 'create', priority: 4, label: gettext('Package...'),
-          icon: 'wcTabIcon icon-package', data: {action: 'create', check: true},
+          icon: 'wcTabIcon icon-package', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')},
           enable: 'canCreate',
         },
         ]);
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/synonyms/static/js/synonym.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/synonyms/static/js/synonym.js
index 0523709a0..316f3d70f 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/synonyms/static/js/synonym.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/synonyms/static/js/synonym.js
@@ -47,19 +47,19 @@ define('pgadmin.node.synonym', [
           name: 'create_synonym_on_coll', node: 'coll-synonym', module: this,
           applies: ['object', 'context'], callback: 'show_obj_properties',
           category: 'create', priority: 4, label: gettext('Synonym...'),
-          icon: 'wcTabIcon icon-synonym', data: {action: 'create', check: true},
+          icon: 'wcTabIcon icon-synonym', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')},
           enable: 'canCreate',
         },{
           name: 'create_synonym', node: 'synonym', module: this,
           applies: ['object', 'context'], callback: 'show_obj_properties',
           category: 'create', priority: 4, label: gettext('Synonym...'),
-          icon: 'wcTabIcon icon-synonym', data: {action: 'create', check: true},
+          icon: 'wcTabIcon icon-synonym', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')},
           enable: 'canCreate',
         },{
           name: 'create_synonym', node: 'schema', module: this,
           applies: ['object', 'context'], callback: 'show_obj_properties',
           category: 'create', priority: 4, label: gettext('Synonym...'),
-          icon: 'wcTabIcon icon-synonym', data: {action: 'create', check: true},
+          icon: 'wcTabIcon icon-synonym', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')},
           enable: 'canCreate',
         },
         ]);
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/js/compound_trigger.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/js/compound_trigger.js
index 57d01b5a0..6fa2613d0 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/js/compound_trigger.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/compound_triggers/static/js/compound_trigger.js
@@ -54,25 +54,25 @@ define('pgadmin.node.compound_trigger', [
           name: 'create_compound_trigger_on_coll', node: 'coll-compound_trigger', module: this,
           applies: ['object', 'context'], callback: 'show_obj_properties',
           category: 'create', priority: 4, label: gettext('Compound Trigger...'),
-          icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true},
+          icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')},
           enable: 'canCreate',
         },{
           name: 'create_compound_trigger', node: 'compound_trigger', module: this,
           applies: ['object', 'context'], callback: 'show_obj_properties',
           category: 'create', priority: 4, label: gettext('Compound Trigger...'),
-          icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true},
+          icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')},
           enable: 'canCreate',
         },{
           name: 'create_compound_trigger_onTable', node: 'table', module: this,
           applies: ['object', 'context'], callback: 'show_obj_properties',
           category: 'create', priority: 4, label: gettext('Compound Trigger...'),
-          icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true},
+          icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')},
           enable: 'canCreate',
         },{
           name: 'create_compound_trigger_onPartition', node: 'partition', module: this,
           applies: ['object', 'context'], callback: 'show_obj_properties',
           category: 'create', priority: 4, label: gettext('Compound Trigger...'),
-          icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true},
+          icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')},
           enable: 'canCreate',
         },{
           name: 'enable_compound_trigger', node: 'compound_trigger', module: this,
@@ -88,7 +88,7 @@ define('pgadmin.node.compound_trigger', [
           name: 'create_compound_trigger_onView', node: 'view', module: this,
           applies: ['object', 'context'], callback: 'show_obj_properties',
           category: 'create', priority: 4, label: gettext('Compound Trigger...'),
-          icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true},
+          icon: 'wcTabIcon icon-compound_trigger', data: {action: 'create', check: true, 'data_disabled': gettext('This option is only available on EPAS servers.')},
           enable: 'canCreate',
         },
         ]);
diff --git a/web/pgadmin/browser/server_groups/servers/databases/static/js/database.js b/web/pgadmin/browser/server_groups/servers/databases/static/js/database.js
index f322d0888..7e07f443e 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/static/js/database.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/static/js/database.js
@@ -76,12 +76,16 @@ define('pgadmin.node.database', [
           name: 'connect_database', node: 'database', module: this,
           applies: ['object', 'context'], callback: 'connect_database',
           category: 'connect', priority: 4, label: gettext('Connect Database...'),
-          icon: 'fa fa-link', enable : 'is_not_connected',
+          icon: 'fa fa-link', enable : 'is_not_connected', data: {
+            'data_disabled': gettext('Selected database is already connected.'),
+          },
         },{
           name: 'disconnect_database', node: 'database', module: this,
           applies: ['object', 'context'], callback: 'disconnect_database',
           category: 'drop', priority: 5, label: gettext('Disconnect Database...'),
-          icon: 'fa fa-unlink', enable : 'is_connected',
+          icon: 'fa fa-unlink', enable : 'is_connected',data: {
+            'data_disabled': gettext('Selected database is already disconnected.'),
+          },
         },{
           name: 'generate_erd', node: 'database', module: this,
           applies: ['object', 'context'], callback: 'generate_erd',
diff --git a/web/pgadmin/browser/server_groups/servers/resource_groups/static/js/resource_group.js b/web/pgadmin/browser/server_groups/servers/resource_groups/static/js/resource_group.js
index 50156ca73..053072e5c 100644
--- a/web/pgadmin/browser/server_groups/servers/resource_groups/static/js/resource_group.js
+++ b/web/pgadmin/browser/server_groups/servers/resource_groups/static/js/resource_group.js
@@ -48,7 +48,7 @@ define('pgadmin.node.resource_group', [
           name: 'create_resourcegroup_on_server', node: 'server', module: this,
           applies: ['object', 'context'], callback: 'show_obj_properties',
           category: 'create', priority: 4, label: gettext('Resource Group...'),
-          icon: 'wcTabIcon icon-resource_group', data: {action: 'create'},
+          icon: 'wcTabIcon icon-resource_group', data: {action: 'create', 'data_disabled': gettext('This option is only available on EPAS servers.')},
           /* Function is used to check the server type and version.
            * Resource Group only supported in PPAS 9.4 and above.
            */
@@ -62,12 +62,12 @@ define('pgadmin.node.resource_group', [
           name: 'create_resource_group_on_coll', node: 'coll-resource_group', module: this,
           applies: ['object', 'context'], callback: 'show_obj_properties',
           category: 'create', priority: 4, label: gettext('Resource Group...'),
-          icon: 'wcTabIcon icon-resource_group', data: {action: 'create'},
+          icon: 'wcTabIcon icon-resource_group', data: {action: 'create', 'data_disabled': gettext('This option is only available on EPAS servers.')},
         },{
           name: 'create_resource_group', node: 'resource_group', module: this,
           applies: ['object', 'context'], callback: 'show_obj_properties',
           category: 'create', priority: 4, label: gettext('Resource Group...'),
-          icon: 'wcTabIcon icon-resource_group', data: {action: 'create'},
+          icon: 'wcTabIcon icon-resource_group', data: {action: 'create', 'data_disabled': gettext('This option is only available on EPAS servers.')},
         },
         ]);
       },
diff --git a/web/pgadmin/browser/server_groups/servers/static/js/server.js b/web/pgadmin/browser/server_groups/servers/static/js/server.js
index 13c00c4dc..72da6ca2d 100644
--- a/web/pgadmin/browser/server_groups/servers/static/js/server.js
+++ b/web/pgadmin/browser/server_groups/servers/static/js/server.js
@@ -100,12 +100,16 @@ define('pgadmin.node.server', [
           name: 'reload_configuration', node: 'server', module: this,
           applies: ['tools', 'context'], callback: 'reload_configuration',
           category: 'reload', priority: 6, label: gettext('Reload Configuration'),
-          icon: 'fa fa-redo-alt', enable : 'enable_reload_config',
+          icon: 'fa fa-redo-alt', enable : 'enable_reload_config',data: {
+            'data_disabled': gettext('Please select a server from the browser tree to reload the configuration files.'),
+          },
         },{
           name: 'restore_point', node: 'server', module: this,
           applies: ['tools', 'context'], callback: 'restore_point',
           category: 'restore', priority: 9, label: gettext('Add Named Restore Point...'),
-          icon: 'fa fa-anchor', enable : 'is_applicable',
+          icon: 'fa fa-anchor', enable : 'is_applicable',data: {
+            'data_disabled': gettext('Please select any server from the browser tree to Add Named Restore Point.'),
+          },
         },{
           name: 'change_password', node: 'server', module: this,
           applies: ['object'], callback: 'change_password',
@@ -115,12 +119,16 @@ define('pgadmin.node.server', [
           name: 'wal_replay_pause', node: 'server', module: this,
           applies: ['tools', 'context'], callback: 'pause_wal_replay',
           category: 'wal_replay_pause', priority: 7, label: gettext('Pause Replay of WAL'),
-          icon: 'fa fa-pause-circle', enable : 'wal_pause_enabled',
+          icon: 'fa fa-pause-circle', enable : 'wal_pause_enabled',data: {
+            'data_disabled': gettext('Please select a connected database as a Super user and run in Recovery mode to Pause Replay of WAL.'),
+          },
         },{
           name: 'wal_replay_resume', node: 'server', module: this,
           applies: ['tools', 'context'], callback: 'resume_wal_replay',
           category: 'wal_replay_resume', priority: 8, label: gettext('Resume Replay of WAL'),
-          icon: 'fa fa-play-circle', enable : 'wal_resume_enabled',
+          icon: 'fa fa-play-circle', enable : 'wal_resume_enabled',data: {
+            'data_disabled': gettext('Please select a connected database as a Super user and run in Recovery mode to Resume Replay of WAL.'),
+          },
         },{
           name: 'clear_saved_password', node: 'server', module: this,
           applies: ['object', 'context'], callback: 'clear_saved_password',
@@ -145,6 +153,9 @@ define('pgadmin.node.server', [
             }
             return false;
           },
+          data: {
+            'data_disabled': gettext('SSH Tunnel password is not saved for selected server.'),
+          },
         }]);
 
         _.bindAll(this, 'connection_lost');
diff --git a/web/pgadmin/browser/static/js/browser.js b/web/pgadmin/browser/static/js/browser.js
index 475412bf7..6ae092bbb 100644
--- a/web/pgadmin/browser/static/js/browser.js
+++ b/web/pgadmin/browser/static/js/browser.js
@@ -875,6 +875,10 @@ define('pgadmin.browser', [
         var $mnu = navbar.children(o.id).first(),
           $dropdown = $mnu.children('.dropdown-menu').first();
         $dropdown.empty();
+        if(o.menu == 'help'){
+          $dropdown.append('<div id="quick-search-component"></div>');
+          $dropdown.append('<div class="menu-groups"><span class="fa fa-list" style="font-weight:900 !important;"></span> &nbsp;SUGGESTED SITES</div>');
+        }
 
         if (pgAdmin.Browser.MenuCreator(
           obj.Nodes, $dropdown, obj.menus[o.menu], obj.menu_categories
@@ -902,6 +906,7 @@ define('pgadmin.browser', [
       } else if(type == 'dialog_help') {
         window.open(url, 'pgadmin_help');
       }
+      $('#live-search-field').focus();
     },
     _findTreeChildNode: function(_i, _d, _o) {
       var loaded = _o.t.wasLoad(_i),
@@ -2224,6 +2229,8 @@ define('pgadmin.browser', [
   if (pgBrowser.utils.useSpaces == 'True') {
     pgAdmin.Browser.editor_shortcut_keys.Tab = 'insertSoftTab';
   }
-
+  setTimeout(function(){
+    $('#mnu_about').closest('li').before('<li class="dropdown-divider"></li>');
+  }, 100);
   return pgAdmin.Browser;
 });
diff --git a/web/pgadmin/browser/static/js/keyboard.js b/web/pgadmin/browser/static/js/keyboard.js
index 3381162d0..a69c7009f 100644
--- a/web/pgadmin/browser/static/js/keyboard.js
+++ b/web/pgadmin/browser/static/js/keyboard.js
@@ -44,6 +44,7 @@ _.extend(pgBrowser.keyboardNavigation, {
         'drop_multiple_objects': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'grid_menu_drop_multiple').value),
         'drop_cascade_multiple_objects': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'grid_menu_drop_cascade_multiple').value),
         'add_grid_row': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'add_grid_row').value),
+        'open_quick_search': commonUtils.parseShortcutValue(pgBrowser.get_preference('browser', 'open_quick_search').value),
 
       };
       this.shortcutMethods = {
@@ -66,6 +67,7 @@ _.extend(pgBrowser.keyboardNavigation, {
         'bindDropMultipleObjects': {'shortcuts': this.keyboardShortcut.drop_multiple_objects}, // Grid Menu Drop Multiple
         'bindDropCascadeMultipleObjects': {'shortcuts': this.keyboardShortcut.drop_cascade_multiple_objects}, // Grid Menu Drop Cascade Multiple
         'bindAddGridRow': {'shortcuts': this.keyboardShortcut.add_grid_row}, // Subnode Grid Add Row
+        'bindOpenQuickSearch': {'shortcuts': this.keyboardShortcut.open_quick_search}, // Subnode Grid Refresh Row
       };
       this.bindShortcuts();
     }
@@ -383,6 +385,9 @@ _.extend(pgBrowser.keyboardNavigation, {
 
     return new dialogTabNavigator.dialogTabNavigator(dialogContainer, backward_shortcut, forward_shortcut);
   },
+  bindOpenQuickSearch: function() {
+    $('#search_icon').trigger('click');
+  },
 });
 
 module.exports = pgAdmin.Browser.keyboardNavigation;
diff --git a/web/pgadmin/browser/static/js/menu.js b/web/pgadmin/browser/static/js/menu.js
index 53d40dcbe..b2f12ff47 100644
--- a/web/pgadmin/browser/static/js/menu.js
+++ b/web/pgadmin/browser/static/js/menu.js
@@ -67,12 +67,17 @@ define([
         }, this.menu_items);
         this.$el = create_submenu.$el;
       } else {
+        var data_disabled = null;
+        if(this.data != undefined && this.data.data_disabled != undefined){
+          data_disabled = this.data.data_disabled;
+        }
         var url = $('<a></a>', {
           'id': this.name,
           'href': this.url,
           'target': this.target,
           'data-toggle': 'pg-menu',
           'role': 'menuitem',
+          'data-disabled': data_disabled,
         }).data('pgMenu', {
           module: this.module || pgAdmin.Browser,
           cb: this.callback,
diff --git a/web/pgadmin/browser/static/js/node.js b/web/pgadmin/browser/static/js/node.js
index 12226fe2f..21ff1bf87 100644
--- a/web/pgadmin/browser/static/js/node.js
+++ b/web/pgadmin/browser/static/js/node.js
@@ -150,6 +150,7 @@ define('pgadmin.browser.node', [
           label: (self.dropAsRemove) ? gettext('Remove %s', self.label) : gettext('Delete/Drop'),
           data: {
             'url': 'drop',
+            'data_disabled': gettext('The selected tree node does not support this option.'),
           },
           icon: 'fa fa-trash-alt',
           enable: _.isFunction(self.canDrop) ?
@@ -232,6 +233,7 @@ define('pgadmin.browser.node', [
             category: gettext('Scripts'),
             data: {
               'script': stype,
+              'data_disabled': gettext('The selected tree node does not support this option.'),
             },
             icon: 'fa fa-pencil-alt',
             enable: self.check_user_permission,
diff --git a/web/pgadmin/browser/static/js/quick_search.js b/web/pgadmin/browser/static/js/quick_search.js
new file mode 100644
index 000000000..5f0120407
--- /dev/null
+++ b/web/pgadmin/browser/static/js/quick_search.js
@@ -0,0 +1,37 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2020, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import React from 'react';
+import ReactDOM from 'react-dom';
+import {Search} from './quick_search/trigger_search';
+
+// TODO: GUI, Add the logic to show loading screen while fetching result
+const onResultFetch = (url, data) => {
+  // URL can be used for displaying all the result in new page
+  // data will be array of search <name> -> <link>
+  console.warn('URL = ' + url);
+  console.warn(data);
+};
+
+setTimeout(function(){
+  if (document.getElementById('quick-search-component')) {
+    ReactDOM.render(
+      <Search onResult={onResultFetch} />,
+      document.getElementById('quick-search-component')
+    );
+  }
+},500);
+
+// Entry point - Quick search functionality
+/*if (document.getElementById('quick-search-component')) {
+  ReactDOM.render(
+    <Search onResult={onResultFetch} />,
+    document.getElementById('quick-search-component')
+  );
+}*/
diff --git a/web/pgadmin/browser/static/js/quick_search/iframe_component.js b/web/pgadmin/browser/static/js/quick_search/iframe_component.js
new file mode 100644
index 000000000..bf9f8c059
--- /dev/null
+++ b/web/pgadmin/browser/static/js/quick_search/iframe_component.js
@@ -0,0 +1,44 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2020, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import React, {Component} from 'react';
+import PropTypes from 'prop-types';
+
+// Allow us to render IFrame using React
+// Here we will add the event listener on Iframe load event
+export class Iframe extends Component {
+  static get propTypes() {
+    return {
+      id: PropTypes.string.isRequired,
+      srcURL: PropTypes.string.isRequired,
+      onLoad: PropTypes.func.isRequired,
+    };
+  }
+
+  render () {
+    const iframeStyle = {
+      border: '0',
+      display: 'block',
+      position:'absolute',
+      opacity:'0',
+    };
+    const {id, srcURL, onLoad} = this.props;
+
+    return (
+      <iframe
+        id={id}
+        src={srcURL}
+        onLoad={onLoad}
+        width={'20'}
+        height={'20'}
+        style={iframeStyle}
+      />
+    );
+  }
+}
diff --git a/web/pgadmin/browser/static/js/quick_search/menuitems_help.js b/web/pgadmin/browser/static/js/quick_search/menuitems_help.js
new file mode 100644
index 000000000..f3d08aecc
--- /dev/null
+++ b/web/pgadmin/browser/static/js/quick_search/menuitems_help.js
@@ -0,0 +1,91 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2020, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+import gettext from 'sources/gettext';
+
+
+
+//  Allow us to
+const getMenuName = (item) => {
+  let aLinks = item.getElementsByTagName('a');
+  let name;
+  if (aLinks.length > 0) {
+    name = (aLinks[0].text).trim();
+    name = name.replace(/\.+$/g, '');
+  }
+  return name;
+};
+
+export function menuSearch(param, props) {
+  let LAST_MENU;
+  param = param.trim();
+  const setState = props.setState;
+  let result = [];
+
+  if(window.pgAdmin.Browser.utils.app_name){
+    LAST_MENU = gettext('About '+ window.pgAdmin.Browser.utils.app_name);
+  }
+
+  // Here we will add the matches
+  const parseLI = (_menu, path) => {
+    let _name = getMenuName(_menu);
+    if (_name && _name.toLowerCase().indexOf(param.toLowerCase()) != -1) {
+      let _res = {};
+      _res[_name] = path;
+      _res['element'] = _menu.children[0];
+      result.push(_res);
+    }
+    // Check if last menu then update the parent component's state
+    if (_name === LAST_MENU) {
+      setState(state => ({
+        ...state,
+        fetched: true,
+        data: result,
+      }));
+    }
+  };
+
+  // Recursive function to search in UL
+  const parseUL = (menu, path) => {
+    const menus = Array.from(menu.children);
+    menus.forEach((_menu) => {
+      let _name, _path;
+      if (_menu.tagName == 'UL') {
+        _name = getMenuName(_menu);
+        _path = `${path}/${_name}`;
+        iterItem(_menu, _path);
+      } else if (_menu.tagName == 'LI') {
+        if (_menu.classList.contains('dropdown-submenu')) {
+          _name = getMenuName(_menu);
+          _path = `${path}/${_name}`;
+          iterItem(_menu, _path);
+        } else {
+          parseLI(_menu, path);
+        }
+      }
+    });
+  };
+
+  // Expects LI of menus which contains A & UL
+  const iterItem = (menu, path) => {
+    const subMenus = Array.from(menu.children);
+    subMenus.forEach((_menu) => {
+      if (_menu.tagName == 'UL') {
+        parseUL(_menu, path);
+      }
+    });
+  };
+
+  // Starting Point
+  const navbar = document.querySelector('.navbar-nav');
+  const mainMenus = Array.from(navbar.children);
+
+  mainMenus.forEach((menu) => {
+    iterItem(menu, getMenuName(menu));
+  });
+}
diff --git a/web/pgadmin/browser/static/js/quick_search/online_help.js b/web/pgadmin/browser/static/js/quick_search/online_help.js
new file mode 100644
index 000000000..9808ad007
--- /dev/null
+++ b/web/pgadmin/browser/static/js/quick_search/online_help.js
@@ -0,0 +1,103 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2020, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+import React from 'react';
+import ReactDOM from 'react-dom';
+import {Iframe} from './iframe_component';
+import url_for from 'sources/url_for';
+
+const extractSearchResult = (list) => {
+  let result = {};
+  for (let idx = 0; idx < list.length; idx++) {
+    let link = list[idx].getElementsByTagName('A');
+    // we are not going to display more than first 10 result as per design
+    if (link.length == 0) {
+      break;
+    }
+    let topicName  = link[0].text;
+    let topicLink  = url_for('help.static', {
+      'filename': link[0].getAttribute('href'),
+    });
+    result[topicName] = topicLink;
+  }
+  return result;
+};
+
+export function onlineHelpSearch(param, props) {
+  param = param.split(' ').join('+');
+  const setState = props.setState;
+  const helpURL = url_for('help.static', {
+    'filename': 'search.html',
+  });
+  const srcURL = `${helpURL}?q=${param}`;
+  let isIFrameLoaded = false;
+  if(document.getElementById('hidden-quick-search-iframe')){
+    document.getElementById('hidden-quick-search-iframe').contentDocument.location.reload(true);
+  }
+
+  // Below function will be called when the page will be loaded in Iframe
+  const _iframeLoaded = () => {
+    if (isIFrameLoaded) {
+      return false;
+    }
+    isIFrameLoaded = true;
+    let iframe = document.getElementById('hidden-quick-search-iframe');
+    let content = (iframe.contentWindow || iframe.contentDocument);
+    let iframeHTML = content.document;
+    window.pooling = setInterval(() => {
+      let resultEl = iframeHTML.getElementById('search-results');
+      let searchResultsH2Tags = resultEl.getElementsByTagName('h2');
+      let list = resultEl && resultEl.getElementsByTagName('LI');
+      let pooling = window.pooling;
+      if ((list && list.length > 0 )) {
+        let res = extractSearchResult(list);
+        // After getting the data, we need to call the Parent component function
+        // which will render the data on the screen
+        if(searchResultsH2Tags[0]['childNodes'][0]['textContent'] != 'Searching'){
+          window.clearInterval(pooling);
+          setState(state => ({
+            ...state,
+            fetched: true,
+            clearedPooling: true,
+            url: srcURL,
+            data: res,
+          }));
+          isIFrameLoaded = false;
+          ReactDOM.unmountComponentAtNode(document.getElementById('quick-search-iframe-container'));
+        }else{
+          setState(state => ({
+            ...state,
+            fetched: true,
+            clearedPooling: false,
+            url: srcURL,
+            data: res,
+          }));
+        }
+
+
+      }else if(searchResultsH2Tags[0]['childNodes'][0]['textContent'] == 'Search Results'){
+        setState(state => ({
+          ...state,
+          fetched: true,
+          clearedPooling: true,
+          url: srcURL,
+          data: {},
+        }));
+        ReactDOM.unmountComponentAtNode(document.getElementById('quick-search-iframe-container'));
+        isIFrameLoaded = false;
+        window.clearInterval(pooling);
+      }
+    }, 500);
+  };
+
+  // Render IFrame
+  ReactDOM.render(
+    <Iframe id='hidden-quick-search-iframe' srcURL={srcURL} onLoad={_iframeLoaded}/>,
+    document.getElementById('quick-search-iframe-container'),
+  );
+}
diff --git a/web/pgadmin/browser/static/js/quick_search/trigger_search.js b/web/pgadmin/browser/static/js/quick_search/trigger_search.js
new file mode 100644
index 000000000..7c94dbd93
--- /dev/null
+++ b/web/pgadmin/browser/static/js/quick_search/trigger_search.js
@@ -0,0 +1,262 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2020, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+import React, {useRef,useState, useEffect} from 'react';
+import {useDelayDebounce} from 'sources/custom_hooks';
+import {onlineHelpSearch} from './online_help';
+import {menuSearch} from './menuitems_help';
+import $ from 'jquery';
+import gettext from 'sources/gettext';
+
+export function Search() {
+  const wrapperRef = useRef(null);
+  const [searchTerm, setSearchTerm] = useState('');
+  const [isShowMinLengthMsg, setIsShowMinLengthMsg] = useState(false);
+  let helpLinkTitles = [];
+  let helpLinks = [];
+  const [isMenuLoading, setIsMenuLoading] = useState(false);
+  const [isHelpLoading, setIsHelpLoading] = useState(false);
+  const [menuSearchResult, setMenuSearchResult] = useState({
+    fetched: false,
+    data: [],
+  });
+  const [helpSearchResult, setHelpSearchResult] = useState({
+    fetched: false,
+    clearedPooling: true,
+    url: '',
+    data: [],
+  });
+
+  const [showResults, setShowResults] = useState(false);
+
+  const resetSearchState = () => {
+    setMenuSearchResult(state => ({
+      ...state,
+      fetched: false,
+      data: [],
+    }));
+    setHelpSearchResult(state => ({
+      ...state,
+      fetched: false,
+      clearedPooling: true,
+      url: '',
+      data: {},
+    }));
+  };
+
+  // Below will be called when any changes has been made to state
+  useEffect(() => {
+    helpLinkTitles = Object.keys(helpSearchResult.data);
+    for(let i = 0; i<helpLinkTitles.length;i++){
+      helpLinks.push(<a href={''} target='_blank' rel='noreferrer'>helpLinkTitles[i]</a>);
+    }
+
+    if(menuSearchResult.fetched == true){
+      setIsMenuLoading(false);
+    }
+
+    if(helpSearchResult.fetched == true){
+      setIsHelpLoading(false);
+    }
+  }, [menuSearchResult, helpSearchResult]);
+
+  const initSearch = (param) => {
+    setIsMenuLoading(true);
+    setIsHelpLoading(true);
+
+    onlineHelpSearch(param,  {
+      state: helpSearchResult,
+      setState: setHelpSearchResult,
+    });
+    menuSearch(param, {
+      state: menuSearchResult,
+      setState: setMenuSearchResult,
+    });
+  };
+
+
+  // Debounse logic to avoid multiple re-render with each keypress
+  useDelayDebounce(initSearch, searchTerm, 1000);
+
+  const toggleDropdownMenu = () => {
+    let pooling = window.pooling;
+    if(pooling){
+      window.clearInterval(pooling);
+    }
+    document.getElementsByClassName('live-search-field')[0].value = '';
+    setTimeout(function(){
+      document.getElementById('live-search-field').focus();
+    },100);
+    resetSearchState();
+    setShowResults(!showResults);
+    setIsMenuLoading(false);
+    setIsHelpLoading(false);
+    setIsShowMinLengthMsg(false);
+  };
+
+  const refactorMenuItems = (items) => {
+    if(items.length > 0){
+      let menuItemsHtmlElement = [];
+      for(let i=0; i < items.length; i++){
+        Object.keys(items[i]).map( (value) => {
+          if(value != 'element' && value != 'No object selected'){
+            menuItemsHtmlElement.push( <li key={ 'li-menu-' + i }><a tabIndex='0' id={ 'li-menu-' + i } href={'#'} className={ (items[i]['element'].classList.contains('disabled') == true ? 'dropdown-item menu-groups-a disabled':'dropdown-item menu-groups-a')} key={ 'menu-' + i } onClick={() => {items[i]['element'].click(); toggleDropdownMenu();}}>
+              {value}
+              <span key={ 'menu-span-' + i }>{refactorPathToMenu(items[i][value])}</span>
+            </a>
+            { ((items[i]['element'].classList.contains('disabled') == true && items[i]['element'].getAttribute('data-disabled') != undefined) ? <i className='fa fa-info-circle quick-search-tooltip' data-toggle='tooltip' title={items[i]['element'].getAttribute('data-disabled')} aria-label='Test data tooltip' aria-hidden='true'></i> : '' )}
+            </li>);
+          }
+        });
+      }
+      $('[data-toggle="tooltip"]').tooltip();
+      return menuItemsHtmlElement;
+    }
+  };
+
+  const refactorPathToMenu = (path) => {
+    if(path){
+      let pathArray = path.split('/');
+      let spanElement = [];
+      for(let i = 0; i < pathArray.length; i++ ){
+        if(i == (pathArray.length -1)){
+          spanElement.push(pathArray[i]);
+        }else{
+          spanElement.push(<span key={ 'menu-span-sub' + i }> {pathArray[i]} <i className='fa fa-angle-right' aria-hidden='true'></i> </span>);
+        }
+      }
+      return spanElement;
+    }
+  };
+
+  const onInputValueChange = (value) => {
+    let pooling = window.pooling;
+    if(pooling){
+      window.clearInterval(pooling);
+    }
+    resetSearchState();
+    setSearchTerm('');
+    if(value.length >= 3){
+      setSearchTerm(value);
+      setIsMenuLoading(true);
+      setIsHelpLoading(true);
+      setIsShowMinLengthMsg(false);
+    }else{
+      setIsMenuLoading(false);
+      setIsHelpLoading(false);
+    }
+
+    if(value.length < 3 && value.length > 0){
+      setIsShowMinLengthMsg(true);
+    }
+
+    if(value.length == 0){
+      setIsShowMinLengthMsg(false);
+    }
+  };
+
+  const useOutsideAlerter = (ref) => {
+    useEffect(() => {
+      /**
+           * Alert if clicked on outside of element
+           */
+      function handleClickOutside(event) {
+        if (ref.current && !ref.current.contains(event.target)) {
+          let input_element = document.getElementById('live-search-field');
+          let input_value = input_element.value;
+          if(input_value && input_value.length > 0){
+            toggleDropdownMenu();
+          }
+          return;
+        }
+      }
+      // Bind the event listener
+      document.addEventListener('mousedown', handleClickOutside);
+      return () => {
+        // Unbind the event listener on clean up
+        document.removeEventListener('mousedown', handleClickOutside);
+      };
+    }, [ref]);
+  };
+
+  useOutsideAlerter(wrapperRef);
+
+  return (
+    <div id='quick-search-container' onClick={setSearchTerm}></div>,
+    <ul id='quick-search-container' ref={wrapperRef} className='test' role="menu">
+      <li>
+        <ul id='myDropdown'>
+          <li className='dropdown-item-input'>
+            <input tabIndex='0' autoFocus type='text' autoComplete='off' className='form-control live-search-field'
+              aria-label='live-search-field' id='live-search-field' placeholder={gettext('Quick Search')} onChange={(e) => {onInputValueChange(e.target.value);} } />
+          </li>
+          <div style={{marginBottom:0}}>
+            <div>
+
+              { isShowMinLengthMsg
+                ? (<div className='pad-12 no-results'>
+                  <span className='fa fa-info-circle'></span>
+                   &nbsp;Please enter minimum 3 characters to search
+                </div>)
+                :''}
+              <div >
+
+                { (menuSearchResult.fetched == true && isMenuLoading == false ) ?
+                  <div>
+                    <div className='menu-groups'>
+                      <span className='fa fa-window-maximize'></span> &nbsp;{gettext('MENU ITEMS')} ({menuSearchResult.data.length})
+                    </div>
+
+
+                    {refactorMenuItems(menuSearchResult.data)}
+                  </div> : ( (isMenuLoading) ? (<div className='pad-12'><div className="search-icon">{gettext('Searching...')}</div></div>) : '')}
+
+                {(menuSearchResult.data.length == 0 && menuSearchResult.fetched == true && isMenuLoading == false) ? (<div className='pad-12 no-results'><span className='fa fa-info-circle'></span> {gettext('No search results')}</div>):''}
+
+                { (helpSearchResult.fetched == true && isHelpLoading == false) ?
+                  <div>
+                    <div className='help-groups'>
+                      <span className='fa fa-question-circle'></span> &nbsp;{gettext('HELP ARTICLES')} {Object.keys(helpSearchResult.data).length > 10 ?
+                        <span>(10 of {Object.keys(helpSearchResult.data).length} )
+                        </span>:
+                        '(' + Object.keys(helpSearchResult.data).length + ')'}&nbsp;
+                      { !helpSearchResult.clearedPooling ? <img src='/static/img/loading.gif' alt={gettext('Loading...')} className='help_loading_icon'/> :''}
+                      { Object.keys(helpSearchResult.data).length > 10 ? <a href={helpSearchResult.url} className='pull-right no-padding' target='_blank' rel='noreferrer'>{gettext('Show all')} &nbsp;<span className='fas fa-external-link-alt' ></span></a> : ''}
+                    </div>
+
+                    {Object.keys(helpSearchResult.data).map( (value, index) => {
+                      if(index <= 9) {  return <li key={ 'li-help-' + index }><a tabIndex='0' href={helpSearchResult.data[value]} key={ 'help-' + index } className='dropdown-item' target='_blank' rel='noreferrer'>{value}</a></li>; }
+                    })}
+
+                    {(Object.keys(helpSearchResult.data).length == 0) ? (<div className='pad-12 no-results'><span className='fa fa-info-circle'></span> {gettext('No search results')}</div>):''}
+                  </div> : ( (isHelpLoading && isMenuLoading == false) ? (
+                    <div>
+                      <div className='help-groups'>
+                        <span className='fa fa-question-circle'></span>
+                          &nbsp;HELP ARTICLES
+                        {Object.keys(helpSearchResult.data).length > 10
+                          ? '(10 of ' + Object.keys(helpSearchResult.data).length + ')'
+                          : '(' + Object.keys(helpSearchResult.data).length + ')'
+                        }
+                        { Object.keys(helpSearchResult.data).length > 10
+                          ? <a href={helpSearchResult.url} className='pull-right no-padding' target='_blank' rel='noreferrer'>
+                          Show all &nbsp;<span className='fas fa-external-link-alt' ></span></a> : ''
+                        }
+                      </div>
+                      <div className='pad-12'><div className="search-icon">{gettext('Searching...')}</div></div>
+                    </div>) : '')}
+              </div>
+            </div>
+          </div>
+        </ul>
+      </li>
+      <div id='quick-search-iframe-container' />
+    </ul>
+  );
+
+}
diff --git a/web/pgadmin/browser/static/scss/_quick_search.scss b/web/pgadmin/browser/static/scss/_quick_search.scss
new file mode 100644
index 000000000..ea90a62d2
--- /dev/null
+++ b/web/pgadmin/browser/static/scss/_quick_search.scss
@@ -0,0 +1,151 @@
+#myInput {
+  box-sizing: border-box;
+  background-position: 14px 12px;
+  background-repeat: no-repeat;
+  font-size: 16px;
+  padding: 14px 20px 12px 45px;
+  border: none;
+  border-bottom: 1px solid #ddd;
+}
+
+#myInput:focus {outline: 3px solid #ddd;}
+
+.custom-dropdown {
+  position: relative;
+  display: inline-block;
+}
+
+.custom-dropdown-content {
+  background-color: $color-bg;
+  min-width: 376px;
+  overflow: auto;
+  z-index: 1;
+}
+
+.custom-dropdown-content a {
+  color: $dropdown-link-color;
+  padding: 6px 12px 6px 16px;
+  text-decoration: none;
+  display: block;
+  cursor:pointer;
+}
+
+.custom-dropdown-content a:hover {
+  color: $black;
+}
+
+#myDropdown a:hover {background-color: $dropdown-link-hover-bg; color:$color-danger-fg !important;}
+
+.search_icon{
+  color: $white;
+  cursor: pointer;
+  padding-right: 8px;
+}
+.hidden { display:none; }
+
+.visible { display:block; }
+
+.menu-groups, .help-groups{
+  background-color: $color-gray-light;
+  padding: 6px;
+  font-size: 12px;
+  font-weight: 600;
+}
+
+.menu-groups .fa, .fas{
+  font-weight:normal !important;
+}
+
+.help-groups .fa, .fas{
+  font-weight:600 !important;
+}
+
+.pad-12{
+  padding:12px;
+}
+
+.no-results{
+  font-size: 14px;
+  color: #697986;
+  text-align: center;
+}
+
+.no-padding{
+  padding:0 !important;
+}
+
+.menu-groups-a{
+  display:flex !important;
+  flex-direction:column;
+  padding: 6px;
+  color: $dropdown-link-color;
+  padding: 6px 16px;
+}
+
+.menu-groups-a span{
+  font-size: 0.9em;
+  font-weight: 100;
+  color: $quick-search-span-text;
+}
+
+#myDropdown a:hover span{
+  color: $color-danger-fg !important;
+}
+
+.search-icon{
+  background: $loader-icon-small center center no-repeat;
+  margin: auto !important;
+  height: 22px !important;
+  width: 130px !important;
+  background-position: left !important;
+  font-size: 14px;
+  color: $dropdown-link-color;
+  padding-left: 30px;
+}
+
+.help_loading_icon{
+  height: 16px;
+}
+#myDropdown ul {
+  list-style: none;
+}
+
+.border-right-search-icon{
+  border-right: 2px solid #fff;
+}
+
+.help_submenu{
+  left: 100%;
+  width: 20rem;
+  top:-0.4rem;
+}
+
+.help_menu{
+  min-width:300px;
+}
+
+.dropdown-item-input{
+  padding:4px;
+}
+
+.menu-groups-a span:focus{
+  color:$color-primary-fg;
+}
+
+.dropdown-item:hover, .dropdown-item:focus span{
+  color: $color-danger-fg !important;
+}
+
+.quick-search-tooltip{
+  position: absolute;
+  right: 10px;
+  margin-top: -2.1em;
+  font-size: 16px;
+  cursor:pointer;
+  color: $quick-search-info-icon;
+}
+
+#myDropdown .dropdown-divider{
+  height: auto;
+  border-top: 0;
+}
diff --git a/web/pgadmin/browser/templates/browser/index.html b/web/pgadmin/browser/templates/browser/index.html
index c571df246..c1387dad5 100644
--- a/web/pgadmin/browser/templates/browser/index.html
+++ b/web/pgadmin/browser/templates/browser/index.html
@@ -133,7 +133,7 @@ window.onload = function(e){
             <li id="mnu_help" class="nav-item active dropdown d-none">
                 <a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">{{
                     _('Help') }} <span class="caret"></span></a>
-                <ul class="dropdown-menu" role="menu"></ul>
+                <ul class="dropdown-menu help_menu" role="menu"></ul>
             </li>
         </ul>
         {% if config.SERVER_MODE %}
diff --git a/web/pgadmin/static/img/loading.gif b/web/pgadmin/static/img/loading.gif
new file mode 100644
index 0000000000000000000000000000000000000000..3288d1035d70bb86517e2c233f1a904e41f06b29
GIT binary patch
literal 3208
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
Bt<?Yk

literal 0
HcmV?d00001

diff --git a/web/pgadmin/static/js/custom_hooks.js b/web/pgadmin/static/js/custom_hooks.js
index 9163f0d6c..b99dcbab4 100644
--- a/web/pgadmin/static/js/custom_hooks.js
+++ b/web/pgadmin/static/js/custom_hooks.js
@@ -26,4 +26,16 @@ export function usePrevious(value) {
     ref.current = value;
   });
   return ref.current;
-}
\ No newline at end of file
+}
+
+export function useDelayDebounce(callback, args, delay) {
+  useEffect(() => {
+    const delayDebounceFn = setTimeout(() => {
+      if (args) {
+        callback(args);
+      }
+    }, delay);
+    return () => clearTimeout(delayDebounceFn);
+  }, [args]);
+}
+
diff --git a/web/pgadmin/static/scss/_pgadmin.style.scss b/web/pgadmin/static/scss/_pgadmin.style.scss
index f8f2bcf99..2e8f46ad8 100644
--- a/web/pgadmin/static/scss/_pgadmin.style.scss
+++ b/web/pgadmin/static/scss/_pgadmin.style.scss
@@ -1129,3 +1129,10 @@ select:-webkit-autofill:focus {
 .pull-left{
   float:left
 }
+
+.menu-groups-a:hover span{
+  color: $white !important;
+}
+
+#myDropdown a:hover {background-color: $dropdown-link-hover-bg; color:$white !important;}
+
diff --git a/web/pgadmin/static/scss/resources/_default.variables.scss b/web/pgadmin/static/scss/resources/_default.variables.scss
index 46dcb1d6f..1e357b312 100644
--- a/web/pgadmin/static/scss/resources/_default.variables.scss
+++ b/web/pgadmin/static/scss/resources/_default.variables.scss
@@ -4,6 +4,9 @@ $enable-flex: true;
 $white: #fff;
 $black: #000;
 
+$span-text-color: #6B6B6B !default;
+$span-text-color-hover: #6B6B6B !default;
+
 $color-bg: $white !default;
 $color-fg: #222222 !default;
 
@@ -349,6 +352,10 @@ $grid-hover-fg-color: $color-fg !default;
 
 $btn-copied-color-fg: $active-color !default;
 
+$quick-search-a-text-color: $black !default;
+$quick-search-span-text: $span-text-color !default;
+$quick-search-span-text-hover: $span-text-color-hover !default;
+$quick-search-info-icon: #646C82 !default;
 /** ERD **/
 $erd-row-padding: 0.25rem;
 $erd-node-border-color: $border-color !default;
diff --git a/web/pgadmin/static/scss/resources/dark/_theme.variables.scss b/web/pgadmin/static/scss/resources/dark/_theme.variables.scss
index d8ca65b50..6912060bb 100644
--- a/web/pgadmin/static/scss/resources/dark/_theme.variables.scss
+++ b/web/pgadmin/static/scss/resources/dark/_theme.variables.scss
@@ -122,6 +122,11 @@ $datagrid-selected-color: $color-primary-fg;
 
 $select2-placeholder: #999;
 
+$span-text-color: #9D9FA1 !default;
+$span-text-color-hover: $white !default;
+$quick-search-a-text-color: $white !default;
+$quick-search-info-icon: #8A8A8A !default;
+
 /* ERD */
 $erd-row-padding: 0.25rem;
 $erd-node-border-color: $border-color;
diff --git a/web/pgadmin/static/scss/resources/high_contrast/_theme.variables.scss b/web/pgadmin/static/scss/resources/high_contrast/_theme.variables.scss
index 75daf4a35..2d26af1fe 100644
--- a/web/pgadmin/static/scss/resources/high_contrast/_theme.variables.scss
+++ b/web/pgadmin/static/scss/resources/high_contrast/_theme.variables.scss
@@ -1,4 +1,5 @@
 $white: #FFFFFF;
+$black: #000;
 $color-bg: #010B15;
 $color-fg: $white;
 
@@ -200,4 +201,7 @@ $grid-hover-fg-color: #010B15;
 
 $btn-copied-color-fg: #010B15;
 
-
+$span-text-color: #9D9FA1 !default;
+$span-text-color-hover: $black !default;
+$quick-search-a-text-color: $black !default;
+$quick-search-info-icon: #8A8A8A !default;
diff --git a/web/pgadmin/tools/backup/static/js/backup.js b/web/pgadmin/tools/backup/static/js/backup.js
index 1552166d2..ef9116958 100644
--- a/web/pgadmin/tools/backup/static/js/backup.js
+++ b/web/pgadmin/tools/backup/static/js/backup.js
@@ -582,6 +582,9 @@ define([
         label: gettext('Backup Globals...'),
         icon: 'fa fa-save',
         enable: menuUtils.menuEnabledServer,
+        data: {
+          'data_disabled': gettext('Please select any server from the browser tree to take Backup of global objects.'),
+        },
       }, {
         name: 'backup_server',
         module: this,
@@ -591,6 +594,9 @@ define([
         label: gettext('Backup Server...'),
         icon: 'fa fa-save',
         enable: menuUtils.menuEnabledServer,
+        data: {
+          'data_disabled': gettext('Please select any server from the browser tree to take Server Backup.'),
+        },
       }, {
         name: 'backup_global_ctx',
         module: this,
@@ -601,6 +607,9 @@ define([
         label: gettext('Backup Globals...'),
         icon: 'fa fa-save',
         enable: menuUtils.menuEnabledServer,
+        data: {
+          'data_disabled': gettext('Please select any db/schema/table from the browser tree to take Backup.'),
+        },
       }, {
         name: 'backup_server_ctx',
         module: this,
@@ -611,6 +620,9 @@ define([
         label: gettext('Backup Server...'),
         icon: 'fa fa-save',
         enable: menuUtils.menuEnabledServer,
+        data: {
+          'data_disabled': gettext('Please select any server from the browser tree to take Server Backup.'),
+        },
       }, {
         name: 'backup_object',
         module: this,
@@ -622,6 +634,9 @@ define([
         enable: supportedNodes.enabled.bind(
           null, pgBrowser.treeMenu, menuUtils.backupSupportedNodes
         ),
+        data: {
+          'data_disabled': gettext('Please select any db/schema/table from the browser tree to take Backup.'),
+        },
       }];
 
       for (var idx = 0; idx < menuUtils.backupSupportedNodes.length; idx++) {
diff --git a/web/pgadmin/tools/datagrid/static/js/datagrid.js b/web/pgadmin/tools/datagrid/static/js/datagrid.js
index c9b8b5b7e..6db4c9922 100644
--- a/web/pgadmin/tools/datagrid/static/js/datagrid.js
+++ b/web/pgadmin/tools/datagrid/static/js/datagrid.js
@@ -113,6 +113,9 @@ define('pgadmin.datagrid', [
           priority: 1,
           label: gettext('Query Tool'),
           icon: 'pg-font-icon icon-query-tool',
+          data:{
+            'data_disabled': gettext('Please select a database from the browser tree to access Query Tool.'),
+          },
         }];
 
         // Create context menu
diff --git a/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.js b/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.js
index be420826d..c7c7c459c 100644
--- a/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.js
+++ b/web/pgadmin/tools/grant_wizard/static/js/grant_wizard.js
@@ -177,6 +177,9 @@ define([
         enable: supportedNodes.enabled.bind(
           null, pgBrowser.treeMenu, menuUtils.supportedNodes
         ),
+        data: {
+          'data_disabled': gettext('Please select any database, schema or schema objects from the browser tree to access Grant Wizard Tool.'),
+        },
       }];
 
       // Add supported menus into the menus list
diff --git a/web/pgadmin/tools/import_export/static/js/import_export.js b/web/pgadmin/tools/import_export/static/js/import_export.js
index bef95d114..d225183ce 100644
--- a/web/pgadmin/tools/import_export/static/js/import_export.js
+++ b/web/pgadmin/tools/import_export/static/js/import_export.js
@@ -412,6 +412,9 @@ define([
         enable: supportedNodes.enabled.bind(
           null, pgBrowser.treeMenu, ['table']
         ),
+        data: {
+          'data_disabled': gettext('Please select any table from the browser tree to Import/Export data.'),
+        },
       }]);
     },
 
diff --git a/web/pgadmin/tools/maintenance/static/js/maintenance.js b/web/pgadmin/tools/maintenance/static/js/maintenance.js
index a8f331094..cf850c76d 100644
--- a/web/pgadmin/tools/maintenance/static/js/maintenance.js
+++ b/web/pgadmin/tools/maintenance/static/js/maintenance.js
@@ -184,6 +184,9 @@ define([
           enable: supportedNodes.enabled.bind(
             null, pgBrowser.treeMenu, menuUtils.maintenanceSupportedNodes
           ),
+          data: {
+            'data_disabled': gettext('Please select any database from the browser tree to do Maintenance.'),
+          },
         });
       }
       pgBrowser.add_menus(menus);
diff --git a/web/pgadmin/tools/restore/static/js/restore.js b/web/pgadmin/tools/restore/static/js/restore.js
index e365b5a76..6c9ff8fda 100644
--- a/web/pgadmin/tools/restore/static/js/restore.js
+++ b/web/pgadmin/tools/restore/static/js/restore.js
@@ -398,6 +398,9 @@ define('tools.restore', [
         enable: supportedNodes.enabled.bind(
           null, pgBrowser.treeMenu, menuUtils.restoreSupportedNodes
         ),
+        data: {
+          'data_disabled': gettext('Please select any schema/table from the browser tree to Restore data.'),
+        },
       }];
 
       for (var idx = 0; idx < menuUtils.restoreSupportedNodes.length; idx++) {
diff --git a/web/pgadmin/tools/search_objects/static/js/search_objects.js b/web/pgadmin/tools/search_objects/static/js/search_objects.js
index fcd52fd7f..ab898bf38 100644
--- a/web/pgadmin/tools/search_objects/static/js/search_objects.js
+++ b/web/pgadmin/tools/search_objects/static/js/search_objects.js
@@ -36,6 +36,9 @@ define([
         enable: this.search_objects_enabled,
         priority: 1,
         label: gettext('Search Objects...'),
+        data: {
+          'data_disabled': gettext('Please select a database from the browser tree to search the database objects.'),
+        },
       }, {
         name: 'search_objects',
         module: this,
diff --git a/web/regression/javascript/quick_search/quick_search_spec.js b/web/regression/javascript/quick_search/quick_search_spec.js
new file mode 100644
index 000000000..d7f59563f
--- /dev/null
+++ b/web/regression/javascript/quick_search/quick_search_spec.js
@@ -0,0 +1,44 @@
+//////////////////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2020, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////////////////
+
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { act } from 'react-dom/test-utils';
+import { Search } from 'browser/quick_search/trigger_search';
+
+let container;
+
+describe('quick search test cases', function () {
+  beforeEach(() => {
+    container = document.createElement('div');
+    document.body.appendChild(container);
+    act(() => {
+      ReactDOM.render(<Search />, container);
+    });
+  });
+
+  afterEach(() => {
+    document.body.removeChild(container);
+    container = null;
+  });
+
+  it('should have rendered quick-search-container', () => {
+    expect(container.firstChild.id).toEqual('quick-search-container');
+  });
+
+  it('should have 2 childs in quick-search-container', () => {
+    expect(container.firstChild.childNodes.length).toEqual(2);
+  });
+
+  it('element should be html element', () => {
+    let inputElement = document.getElementById('live-search-field');
+    expect(inputElement instanceof HTMLElement).toBeTruthy();
+  });
+
+});
diff --git a/web/webpack.config.js b/web/webpack.config.js
index c225b31b8..883b59006 100644
--- a/web/webpack.config.js
+++ b/web/webpack.config.js
@@ -422,6 +422,7 @@ module.exports = [{
       use: {
         loader: 'imports-loader?' +
         'pgadmin.dashboard' +
+        ',pgadmin.browser.quick_search' +
         ',pgadmin.tools.user_management' +
         ',pgadmin.browser.object_statistics' +
         ',pgadmin.browser.dependencies' +
diff --git a/web/webpack.shim.js b/web/webpack.shim.js
index cc3b76a92..a2a5a92db 100644
--- a/web/webpack.shim.js
+++ b/web/webpack.shim.js
@@ -186,6 +186,7 @@ var webpackShimConfig = {
     'pgadmin.browser.preferences': path.join(__dirname, './pgadmin/browser/static/js/preferences'),
     'pgadmin.browser.menu': path.join(__dirname, './pgadmin/browser/static/js/menu'),
     'pgadmin.browser.activity': path.join(__dirname, './pgadmin/browser/static/js/activity'),
+    'pgadmin.browser.quick_search': path.join(__dirname, './pgadmin/browser/static/js/quick_search'),
     'pgadmin.browser.messages': '/browser/js/messages',
     'pgadmin.browser.node': path.join(__dirname, './pgadmin/browser/static/js/node'),
     'pgadmin.browser.node.ui': path.join(__dirname, './pgadmin/browser/static/js/node.ui'),


  [image/png] image001.png (68.4K, 4-image001.png)
  download | view image

  [image/png] image002.png (68.4K, 5-image002.png)
  download | view image

  [image/png] image003.png (68.4K, 6-image003.png)
  download | view image

  [image/png] image004.png (68.4K, 7-image004.png)
  download | view image

view thread (17+ messages)  latest in thread

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: [email protected]
  Cc: [email protected], [email protected], [email protected]
  Subject: Re: Quick search for menu items & help articles
  In-Reply-To: <[email protected]>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox