public inbox for [email protected]  
help / color / mirror / Atom feed
From: Nikhil Mohite <[email protected]>
To: pgadmin-hackers <[email protected]>
Subject: [pgAdmin][RM-7149]: [React] Port preferences dialog to React.
Date: Mon, 7 Mar 2022 17:53:57 +0530
Message-ID: <CAOBg0AMfBEFesek3Uoet9zuNbHP5xA8OzWNGkP8JTt5CrGk9_w@mail.gmail.com> (raw)

Hi Hackers,

Please find attached the patch for RM-7149
<https://redmine.postgresql.org/issues/7149;: [React] Port preferences
dialog to React.

-- 
*Thanks & Regards,*
*Nikhil Mohite*
*Senior Software Engineer.*
*EDB Postgres* <https://www.enterprisedb.com/;
*Mob.No: +91-7798364578.*


Attachments:

  [application/octet-stream] RM-7149.patch (149.0K, 3-RM-7149.patch)
  download | inline diff:
diff --git a/web/pgadmin/browser/register_browser_preferences.py b/web/pgadmin/browser/register_browser_preferences.py
index af7dc4f2..0b0224b3 100644
--- a/web/pgadmin/browser/register_browser_preferences.py
+++ b/web/pgadmin/browser/register_browser_preferences.py
@@ -519,7 +519,7 @@ def register_browser_preferences(self):
 
     self.open_in_new_tab = self.preference.register(
         'tab_settings', 'new_browser_tab_open',
-        gettext("Open in new browser tab"), 'select2', None,
+        gettext("Open in new browser tab"), 'select', None,
         category_label=PREF_LABEL_OPTIONS,
         options=ope_new_tab_options,
         help_str=gettext(
@@ -527,7 +527,7 @@ def register_browser_preferences(self):
             'or PSQL Tool from the drop-down to set '
             'open in new browser tab for that particular module.'
         ),
-        select2={
+        control_props={
             'multiple': True, 'allowClear': False,
             'tags': True, 'first_empty': False,
             'selectOnClose': False, 'emptyOptions': True,
diff --git a/web/pgadmin/browser/server_groups/servers/static/js/binary_path.ui.js b/web/pgadmin/browser/server_groups/servers/static/js/binary_path.ui.js
new file mode 100644
index 00000000..af31cedb
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/static/js/binary_path.ui.js
@@ -0,0 +1,68 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2022, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import gettext from 'sources/gettext';
+import _ from 'lodash';
+import url_for from 'sources/url_for';
+import BaseUISchema from 'sources/SchemaView/base_schema.ui';
+import getApiInstance from '../../../../../static/js/api_instance';
+import Notify from '../../../../../static/js/helpers/Notifier';
+
+export function getBinaryPathSchema() {
+
+  return new BinaryPathSchema();
+}
+
+export default class BinaryPathSchema extends BaseUISchema {
+  constructor() {
+    super({
+      isDefault: false,
+      serverType: undefined,
+      binaryPath: null,
+    });
+  }
+
+  get baseFields() {
+    return [
+      {
+        id: 'isDefault', label: gettext('Set as default'), type: 'switch',
+        cell: 'switch', width: 32,
+        disabled: (state) => {
+          return state.binaryPath ? false : true;
+        }
+      },
+      {
+        id: 'serverType',
+        label: gettext('Database Server'),
+        type: 'text', cell: '',
+        width: 40,
+      },
+      {
+        id: 'binaryPath', label: gettext('Binary Path'), cell: 'file',
+        isvalidate: true, controlProps: { dialogType: 'select_folder', supportedTypes: ['*', 'sql', 'backup'], dialogTitle: 'Select folder' },
+        validate: (data) => {
+          const api = getApiInstance();
+          if (_.isNull(data) || data.trim() === '') {
+            Notify.alert(gettext('Validate Path'), gettext('Path should not be empty.'));
+          }
+
+          api.post(url_for('misc.validate_binary_path'),
+            JSON.stringify({ 'utility_path': data }))
+            .then(function (res) {
+              Notify.alert(gettext('Validate binary path'), gettext(res.data.data));
+            })
+            .catch(function (error) {
+              Notify.pgNotifier(error, gettext('Failed to validate binary path.'));
+            });
+          return true;
+        }
+      },
+    ];
+  }
+}
diff --git a/web/pgadmin/misc/__init__.py b/web/pgadmin/misc/__init__.py
index fb436d70..6cd83404 100644
--- a/web/pgadmin/misc/__init__.py
+++ b/web/pgadmin/misc/__init__.py
@@ -67,7 +67,10 @@ class MiscModule(PgAdminModule):
             'user_language', 'user_language',
             gettext("User language"), 'options', 'en',
             category_label=gettext('User language'),
-            options=lang_options
+            options=lang_options,
+            control_props={
+                'allowClear': False,
+            }
         )
 
         theme_options = []
@@ -90,6 +93,9 @@ class MiscModule(PgAdminModule):
             gettext("Theme"), 'options', 'standard',
             category_label=gettext('Themes'),
             options=theme_options,
+            control_props={
+                'allowClear': False,
+            },
             help_str=gettext(
                 'A refresh is required to apply the theme. Below is the '
                 'preview of the theme'
diff --git a/web/pgadmin/misc/file_manager/__init__.py b/web/pgadmin/misc/file_manager/__init__.py
index d132e46e..ca69e609 100644
--- a/web/pgadmin/misc/file_manager/__init__.py
+++ b/web/pgadmin/misc/file_manager/__init__.py
@@ -167,10 +167,14 @@ class FileManagerModule(PgAdminModule):
         )
         self.file_dialog_view = self.preference.register(
             'options', 'file_dialog_view',
-            gettext("File dialog view"), 'options', 'list',
+            gettext("File dialog view"), 'select', 'list',
             category_label=PREF_LABEL_OPTIONS,
             options=[{'label': gettext('List'), 'value': 'list'},
-                     {'label': gettext('Grid'), 'value': 'grid'}]
+                     {'label': gettext('Grid'), 'value': 'grid'}],
+            control_props={
+                'allowClear': False,
+                'tags': False
+            },
         )
         self.show_hidden_files = self.preference.register(
             'options', 'show_hidden_files',
@@ -236,7 +240,7 @@ def file_manager_config(trans_id):
     """render the required json"""
     data = Filemanager.get_trasaction_selection(trans_id)
     pref = Preferences.module('file_manager')
-    file_dialog_view = pref.preference('file_dialog_view').get()
+    file_dialog_view = pref.preference('file_dialog_view').get()[0]
     show_hidden_files = pref.preference('show_hidden_files').get()
 
     return Response(response=render_template(
diff --git a/web/pgadmin/preferences/__init__.py b/web/pgadmin/preferences/__init__.py
index 9872a672..3f754ada 100644
--- a/web/pgadmin/preferences/__init__.py
+++ b/web/pgadmin/preferences/__init__.py
@@ -37,26 +37,23 @@ class PreferencesModule(PgAdminModule):
     """
 
     def get_own_javascripts(self):
-        return [{
-            'name': 'pgadmin.preferences',
-            'path': url_for('preferences.index') + 'preferences',
-            'when': None
-        }]
+        scripts = list()
+        for name, script in [
+            ['pgadmin.preferences', 'js/preferences']
+        ]:
+            scripts.append({
+                'name': name,
+                'path': url_for('preferences.index') + script,
+                'when': None
+            })
+
+        return scripts
 
     def get_own_stylesheets(self):
         return []
 
     def get_own_menuitems(self):
-        return {
-            'file_items': [
-                MenuItem(name='mnu_preferences',
-                         priority=997,
-                         module="pgAdmin.Preferences",
-                         callback='show',
-                         icon='fa fa-cog',
-                         label=gettext('Preferences'))
-            ]
-        }
+        return {}
 
     def get_exposed_url_endpoints(self):
         """
@@ -149,7 +146,8 @@ def _iterate_categories(pref_d, label, res):
         "label": gettext(pref_d['label']),
         "inode": True,
         "open": True,
-        "branch": []
+        "children": [],
+        "value": gettext(pref_d['label']),
     }
 
     for c in pref_d['categories']:
@@ -162,13 +160,15 @@ def _iterate_categories(pref_d, label, res):
             "id": c['id'],
             "mid": pref_d['id'],
             "label": gettext(c['label']),
+            "value": '{0}{1}'.format(c['id'], gettext(c['label'])),
             "inode": False,
             "open": False,
-            "preferences": sorted(c['preferences'], key=label)
+            "preferences": sorted(c['preferences'], key=label),
+            "showCheckbox": False
         }
 
-        (om['branch']).append(oc)
-    om['branch'] = sorted(om['branch'], key=label)
+        (om['children']).append(oc)
+    om['children'] = sorted(om['children'], key=label)
 
     res.append(om)
 
@@ -194,53 +194,85 @@ def preferences_s():
     )
 
 
[email protected]("/<int:pid>", methods=["PUT"], endpoint="update")
+def get_data():
+    pref_data = request.form if request.form else json.loads(
+        request.data.decode())
+
+    if not pref_data:
+        raise ValueError("Please provide the valid preferences data to save.")
+
+    return pref_data
+
+
[email protected]("/", methods=["PUT"], endpoint="update")
 @login_required
-def save(pid):
+def save():
     """
     Save a specific preference.
     """
-    data = request.form if request.form else json.loads(request.data.decode())
+    pref_data = get_data()
 
-    if data['name'] in ['vw_edt_tab_title_placeholder',
-                        'qt_tab_title_placeholder',
-                        'debugger_tab_title_placeholder'] \
-            and data['value'].isspace():
-        data['value'] = ''
+    for data in pref_data:
+        if data['name'] in ['vw_edt_tab_title_placeholder',
+                            'qt_tab_title_placeholder',
+                            'debugger_tab_title_placeholder'] \
+                and data['value'].isspace():
+            data['value'] = ''
 
-    res, msg = Preferences.save(
-        data['mid'], data['category_id'], data['id'], data['value'])
-    sgm.get_nodes(sgm)
+        if data['name'] == 'pg_bin_dir':
+            check_binary_path_data(data)
 
-    if not res:
-        return internal_server_error(errormsg=msg)
+        res, msg = Preferences.save(
+            data['mid'], data['category_id'], data['id'], data['value'])
+        sgm.get_nodes(sgm)
 
-    response = success_return()
+        if not res:
+            return internal_server_error(errormsg=msg)
 
-    # Set cookie & session for language settings.
-    # This will execute every time as could not find the better way to know
-    # that which preference is getting updated.
+        response = success_return()
 
-    misc_preference = Preferences.module('misc')
-    user_languages = misc_preference.preference(
-        'user_language'
-    )
+        # Set cookie & session for language settings.
+        # This will execute every time as could not find the better way to know
+        # that which preference is getting updated.
+
+        misc_preference = Preferences.module('misc')
+        user_languages = misc_preference.preference(
+            'user_language'
+        )
 
-    language = 'en'
-    if user_languages:
-        language = user_languages.get() or language
+        language = 'en'
+        if user_languages:
+            language = user_languages.get() or language
 
-    domain = dict()
-    if config.COOKIE_DEFAULT_DOMAIN and\
-            config.COOKIE_DEFAULT_DOMAIN != 'localhost':
-        domain['domain'] = config.COOKIE_DEFAULT_DOMAIN
+        domain = dict()
+        if config.COOKIE_DEFAULT_DOMAIN and \
+                config.COOKIE_DEFAULT_DOMAIN != 'localhost':
+            domain['domain'] = config.COOKIE_DEFAULT_DOMAIN
 
-    setattr(session, 'PGADMIN_LANGUAGE', language)
-    response.set_cookie("PGADMIN_LANGUAGE", value=language,
-                        path=config.COOKIE_DEFAULT_PATH,
-                        secure=config.SESSION_COOKIE_SECURE,
-                        httponly=config.SESSION_COOKIE_HTTPONLY,
-                        samesite=config.SESSION_COOKIE_SAMESITE,
-                        **domain)
+        setattr(session, 'PGADMIN_LANGUAGE', language)
+        response.set_cookie("PGADMIN_LANGUAGE", value=language,
+                            path=config.COOKIE_DEFAULT_PATH,
+                            secure=config.SESSION_COOKIE_SECURE,
+                            httponly=config.SESSION_COOKIE_HTTPONLY,
+                            samesite=config.SESSION_COOKIE_SAMESITE,
+                            **domain)
 
     return response
+
+
+def check_binary_path_data(data):
+    pref_val = json.loads(data['value'])
+    pref_data = Preferences.preferences()
+    _data = [el for el in pref_data if el['id'] == data['mid']]
+    if _data:
+        categories = _data[0]['categories']
+        pref = [pref for ct in categories for pref in ct['preferences']
+                if
+                ct['id'] == data['category_id'] and pref['id'] == data[
+                    'id']]
+        if pref:
+            values = json.loads(pref[0]['value'])
+            pref = [pval if pval['version'] == val['version'] else val
+                    for val in values for pval in pref_val]
+            data['value'] = json.dumps(pref)
+    return data
diff --git a/web/pgadmin/preferences/static/js/components/PreferencesComponent.jsx b/web/pgadmin/preferences/static/js/components/PreferencesComponent.jsx
new file mode 100644
index 00000000..a433a835
--- /dev/null
+++ b/web/pgadmin/preferences/static/js/components/PreferencesComponent.jsx
@@ -0,0 +1,511 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2022, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import gettext from 'sources/gettext';
+import url_for from 'sources/url_for';
+import React, { useEffect } from 'react';
+import { Box } from '@material-ui/core';
+import PropTypes from 'prop-types';
+import { makeStyles } from '@material-ui/core/styles';
+import SchemaView from '../../../../static/js/SchemaView';
+import getApiInstance from '../../../../static/js/api_instance';
+import CloseSharpIcon from '@material-ui/icons/CloseSharp';
+import SaveSharpIcon from '@material-ui/icons/SaveSharp';
+import clsx from 'clsx';
+import Notify from '../../../../static/js/helpers/Notifier';
+import pgAdmin from 'sources/pgadmin';
+import { DefaultButton, PrimaryButton } from '../../../../static/js/components/Buttons';
+import BaseUISchema from 'sources/SchemaView/base_schema.ui';
+import { getBinaryPathSchema } from '../../../../browser/server_groups/servers/static/js/binary_path.ui';
+import PreferencesTree from './PreferencesTree';
+import { _set_dynamic_tab} from '../../../../tools/datagrid/static/js/show_query_tool';
+
+class PreferencesSchema extends BaseUISchema {
+  constructor(initValues = {}, schemaFields = []) {
+    super({
+      ...initValues
+    });
+    this.schemaFields = schemaFields;
+    this.category = '';
+  }
+
+  get idAttribute() {
+    return 'id';
+  }
+
+  setSelectedCategory(category) {
+    this.category = category;
+  }
+
+  get baseFields() {
+    return this.schemaFields;
+  }
+}
+
+const useStyles = makeStyles((theme) =>
+  ({
+    root: {
+      display: 'flex',
+      flexDirection: 'column',
+      flexGrow: 1,
+      height: '100%',
+      backgroundColor: theme.palette.background.default,
+      minHeight: 520,
+      minWidth: 700,
+      overflow: 'hidden',
+      '&$disabled': {
+        color: '#ddd',
+      }
+    },
+    body: {
+      display: 'flex',
+      flexDirection: 'column',
+      height: '100%',
+    },
+    preferences: {
+      borderColor: theme.otherVars.borderColor,
+      display: 'flex',
+      flexGrow: 1,
+      height: '100%',
+      minHeight: 0,
+      overflow: 'hidden'
+
+    },
+    treeContainer: {
+      flexBasis: '25%',
+      alignItems: 'flex-start',
+      paddingLeft: '5px',
+      minHeight: 0,
+    },
+    preferencesContainer: {
+      flexBasis: '75%',
+      padding: '5px',
+      borderColor: theme.otherVars.borderColor + '!important',
+      borderLeft: '1px solid',
+      position: 'relative',
+      height: '100%',
+      paddingTop: '5px',
+      overflow: 'auto'
+    },
+    actionBtn: {
+      alignItems: 'flex-start',
+    },
+    buttonMargin: {
+      marginLeft: '0.5em'
+    },
+    footer: {
+      borderTop: '1px solid #dde0e6 !important',
+      padding: '0.5rem',
+      display: 'flex',
+      width: '100%',
+      background: theme.otherVars.headerBg,
+      zIndex: 999,
+    },
+    customTreeClass: {
+      '& .react-checkbox-tree': {
+        height: '100% !important',
+        border: 'none !important',
+      },
+    },
+    preferencesTree: {
+      height: 'calc(100% - 50px)',
+      minHeight: 0
+    }
+  }),
+);
+
+
+function RightPanel({ schema, ...props }) {
+  let initData = () => new Promise((resolve, reject) => {
+    try {
+      resolve(props.initValues);
+    } catch (error) {
+      reject(error);
+    }
+  });
+
+  return (
+    <SchemaView
+      formType={'dialog'}
+      getInitData={initData}
+      viewHelperProps={{ mode: 'edit' }}
+      schema={schema}
+      showFooter={false}
+      isTabView={false}
+      onDataChange={(isChanged, changedData) => {
+        props.onDataChange(changedData);
+      }}
+    />
+  );
+}
+
+RightPanel.propTypes = {
+  schema: PropTypes.object,
+  initValues: PropTypes.object,
+  onDataChange: PropTypes.func
+};
+
+
+export default function PreferencesComponent({ ...props }) {
+  const classes = useStyles();
+  const [disableSave, setDisableSave] = React.useState(true);
+  const prefSchema = React.useRef(new PreferencesSchema({}, []));
+  const prefChangedData = React.useRef({});
+  const [prefTreeData, setPrefTreeData] = React.useState(null);
+  const [initValues, setInitValues] = React.useState({});
+  const [loadTree, setLoadTree] = React.useState(0);
+  const api = getApiInstance();
+
+  useEffect(() => {
+    const pref_url = url_for('preferences.index');
+    api({
+      url: pref_url,
+      method: 'GET',
+    }).then((res) => {
+      let preferencesData = [];
+      let preferencesTreeData = [];
+      let preferencesValues = {};
+      res.data.forEach(node => {
+        let id = Math.floor(Math.random() * 1000);
+        let tdata = {
+          'id': id.toString(),
+          'label': node.label,
+          '_label': node.label,
+          'name': node.label,
+          'icon': '',
+          'inode': true,
+          'type': 2,
+          '_type': node.label.toLowerCase(),
+          '_id': id,
+          '_pid': null,
+          'childrenNodes': [],
+          'expanded': true,
+        };
+
+        node.children.forEach(subNode => {
+          let sid = Math.floor(Math.random() * 1000);
+          let nodeData = {
+            'id': sid.toString(),
+            'label': subNode.label,
+            '_label': subNode.label,
+            'name': subNode.label,
+            'icon': '',
+            'inode': false,
+            '_type': subNode.label.toLowerCase(),
+            '_id': sid,
+            '_pid': node.id,
+            'type': 1,
+            'expanded': false,
+          };
+          subNode.preferences.forEach((element) => {
+            let addNote = false;
+            let note = '';
+            let type = getControlMappedForType(element.type);
+
+            if (type === 'file') {
+              addNote = true;
+              note = gettext('Enter the directory in which the psql, pg_dump, pg_dumpall, and pg_restore utilities can be found for the corresponding database server version.  The default path will be used for server versions that do not have a  path specified.');
+              element.type = 'collection';
+              element.schema = getBinaryPathSchema();
+              element.canAdd = false;
+              element.canDelete = false;
+              element.canEdit = false;
+              element.editable = false;
+              preferencesValues[element.id] = JSON.parse(element.value);
+            }
+            else if (type == 'select') {
+              if (element.control_props !== undefined) {
+                element.controlProps = element.control_props;
+              } else {
+                element.controlProps = {};
+              }
+              preferencesValues[element.id] = element.value;
+              element.type = type;
+            }
+            else if (type === 'keyboardShortcut') {
+              element.type = 'keyboardShortcut';
+              element.canAdd = false;
+              element.canDelete = false;
+              element.canEdit = false;
+              element.editable = false;
+              if (pgAdmin.Browser.get_preference(node.label.toLowerCase(), element.name)?.value) {
+                let temp = pgAdmin.Browser.get_preference(node.label.toLowerCase(), element.name).value;
+                preferencesValues[element.id] = temp;
+              } else {
+                preferencesValues[element.id] = element.value;
+              }
+              delete element.value;
+            } else if(type === 'threshold') {
+              element.type = 'threshold';
+
+              let _val = element.value.split('|');
+              preferencesValues[element.id] = {'warning': _val[0], 'alert': _val[1]};
+            } else {
+              element.type = type;
+              preferencesValues[element.id] = element.value;
+            }
+
+            delete element.value;
+            element.visible = false;
+            element.helpMessage = element?.help_str ? element.help_str : null;
+            preferencesData.push(element);
+
+            if (addNote) {
+              preferencesData.push(
+                {
+                  id: 'note_' + element.id,
+                  type: 'note', text: [
+                    '<ul><li>',
+                    gettext(note),
+                    '</li></ul>',
+                  ].join(''),
+                  visible: false,
+                  'parentId': nodeData['id']
+                },
+              );
+            }
+            element.parentId = nodeData['id'];
+          });
+          tdata['childrenNodes'].push(nodeData);
+        });
+
+        // set Preferences Tree data
+        preferencesTreeData.push(tdata);
+
+      });
+      setPrefTreeData(preferencesTreeData);
+      setInitValues(preferencesValues);
+      // set Preferences schema
+      prefSchema.current = new PreferencesSchema(preferencesValues, preferencesData);
+    }).catch((err) => {
+      Notify.alert(err.response.data);
+    });
+  }, []);
+
+  function getControlMappedForType(type) {
+    switch (type) {
+    case 'text':
+      return 'text';
+    case 'input':
+      return 'text';
+    case 'boolean':
+      return 'switch';
+    case 'node':
+      return 'switch';
+    case 'integer':
+      return 'numeric';
+    case 'numeric':
+      return 'numeric';
+    case 'date':
+      return 'datetimepicker';
+    case 'datetime':
+      return 'datetimepicker';
+    case 'options':
+      return 'select';
+    case 'select':
+      return 'select';
+    case 'select2':
+      return 'select';
+    case 'multiline':
+      return 'multiline';
+    case 'switch':
+      return 'switch';
+    case 'keyboardshortcut':
+      return 'keyboardShortcut';
+    case 'radioModern':
+      return 'toggle';
+    case 'selectFile':
+      return 'file';
+    case 'threshold':
+      return 'threshold';
+    default:
+      if (console && console.warn) {
+        // Warning for developer only.
+        console.warn(
+          'Hmm.. We don\'t know how to render this type - \'\'' + type + '\' of control.'
+        );
+      }
+      return 'input';
+    }
+  }
+
+  function getCollectionValue(_metadata, value) {
+    let val = value;
+    if (typeof (value) == 'object') {
+      if(_metadata[0].type == 'collection' && _metadata[0].schema) {
+        if('binaryPath' in value.changed[0]) {
+          val = JSON.stringify(value.changed);
+        }else {
+          let key_val = {
+            'char': value.changed[0]['key'],
+            'key_code': value.changed[0]['code'],
+          };
+          value.changed[0]['key'] = key_val;
+          val = value.changed[0];
+        }
+      } else if('warning' in value) {
+        val = value['warning'] + '|' + value['alert'];
+      } else if(value?.changed && value.changed.length > 0) {
+        val = JSON.stringify(value.changed);
+      }
+    }
+    return val;
+  }
+
+  function savePreferences(data) {
+    let _data = [];
+    for (const [key, value] of Object.entries(data.current)) {
+      let _metadata = prefSchema.current.schemaFields.filter((el) => { return el.id == key; });
+      if (_metadata.length > 0) {
+        let val = getCollectionValue(_metadata, value);
+        _data.push({
+          'category_id': _metadata[0]['cid'],
+          'id': parseInt(key),
+          'mid': _metadata[0]['mid'],
+          'name': _metadata[0]['name'],
+          'value': val,
+        });
+      }
+    }
+
+    if (_data.length > 0) {
+      save(_data, data);
+    }
+
+  }
+
+  function checkRefreshRequired(pref, requires_refresh) {
+    if (pref.name == 'theme') {
+      requires_refresh = true;
+    }
+
+    if (pref.name == 'user_language') {
+      requires_refresh = true;
+    }
+
+    return requires_refresh;
+  }
+
+  function save(save_data, data) {
+    api({
+      url: url_for('preferences.index'),
+      method: 'PUT',
+      data: save_data,
+    }).then(() => {
+      let requires_refresh = false;
+      /* Find the modules changed */
+      let modulesChanged = {};
+      for (const [key] of Object.entries(data.current)) {
+        let pref = pgAdmin.Browser.get_preference_for_id(Number(key));
+
+        if (pref['name'] == 'dynamic_tabs') {
+          _set_dynamic_tab(pgAdmin.Browser, !pref['value']);
+        }
+
+        if (!modulesChanged[pref.module]) {
+          modulesChanged[pref.module] = true;
+        }
+
+        requires_refresh = checkRefreshRequired(pref, requires_refresh);
+
+        if (pref.name == 'hide_shared_server') {
+          Notify.confirm(
+            gettext('Browser tree refresh required'),
+            gettext('A browser tree refresh is required. Do you wish to refresh the tree?'),
+            function () {
+              pgAdmin.Browser.tree.destroy({
+                success: function () {
+                  pgAdmin.Browser.initializeBrowserTree(pgAdmin.Browser);
+                  return true;
+                },
+              });
+            },
+            function () {
+              return true;
+            },
+            gettext('Refresh'),
+            gettext('Later')
+          );
+        }
+      }
+
+      if (requires_refresh) {
+        Notify.confirm(
+          gettext('Refresh required'),
+          gettext('A page refresh is required to apply the theme. Do you wish to refresh the page now?'),
+          function () {
+            /* If user clicks Yes */
+            location.reload();
+            return true;
+          },
+          function () { props.closeModal(); /*props.panel.close()*/ },
+          gettext('Refresh'),
+          gettext('Later')
+        );
+      }
+      // Refresh preferences cache
+      pgAdmin.Browser.cache_preferences(modulesChanged);
+      props.closeModal(); /*props.panel.close()*/
+    }).catch((err) => {
+      Notify.alert(err.response.data);
+    });
+  }
+
+  return (
+    <Box height={props.isFullScreen ? '100%' : '520px'}>
+      <Box className={classes.root}>
+        <Box className={clsx(classes.preferences)}>
+          <Box className={clsx(classes.treeContainer)}>
+            {prefTreeData &&
+            <PreferencesTree data={prefTreeData} getSelectedItem={(item) => {
+              if (item.type == 1) {
+                prefSchema.current.schemaFields.forEach((field) => {
+                  field.visible = field.parentId.toString() === item._metadata.data.id.toString();
+                });
+              }
+              setLoadTree(Math.floor(Math.random() * 1000));
+            }}
+            ></PreferencesTree>
+            }
+          </Box>
+          <Box className={clsx(classes.preferencesContainer)}>
+            {
+              prefSchema.current && loadTree > 0 ?
+                <RightPanel schema={prefSchema.current} initValues={initValues} onDataChange={(changedData) => {
+                  Object.keys(changedData).length > 0 ? setDisableSave(false) : setDisableSave(true);
+                  prefChangedData.current = changedData;
+                }}></RightPanel>
+                : <></>
+            }
+          </Box>
+        </Box>
+        <Box className={classes.footer}>
+          <Box className={classes.actionBtn} marginLeft="auto">
+            <DefaultButton className={classes.buttonMargin} onClick={() => { props.closeModal(); /*props.panel.close()*/ }} startIcon={<CloseSharpIcon onClick={() => { props.closeModal(); /*props.panel.close()*/ }} />}>
+              {gettext('Cancel')}
+            </DefaultButton>
+            <PrimaryButton className={classes.buttonMargin} startIcon={<SaveSharpIcon />} disabled={disableSave} onClick={() => { savePreferences(prefChangedData); }}>
+              {gettext('Save')}
+            </PrimaryButton>
+          </Box>
+        </Box>
+        {/* </Box> */}
+
+      </Box >
+    </Box>
+  );
+}
+
+PreferencesComponent.propTypes = {
+  schema: PropTypes.array,
+  initValues: PropTypes.object,
+  closeModal: PropTypes.func,
+  isFullScreen: PropTypes.bool,
+
+};
diff --git a/web/pgadmin/preferences/static/js/components/PreferencesTree.jsx b/web/pgadmin/preferences/static/js/components/PreferencesTree.jsx
new file mode 100644
index 00000000..a502323b
--- /dev/null
+++ b/web/pgadmin/preferences/static/js/components/PreferencesTree.jsx
@@ -0,0 +1,135 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2022, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import _ from 'lodash';
+import React, { useRef } from 'react';
+import PropTypes from 'prop-types';
+import { FileTreeItem } from 'pgadmin4-tree/src/FileTreeItem/';
+import { ManagePreferenceTreeNodes } from '../../../../static/js/tree/preference_nodes';
+import { TreeModel, FileTree } from 'react-aspen';
+import { DecorationsManager, Decoration, TargetMatchMode } from 'aspen-decorations';
+import AutoSizer from 'react-virtualized-auto-sizer';
+import { Notificar } from 'notificar';
+
+export class TreeModelX extends TreeModel {
+    decorations = null;
+    constructor(host, mountPath) {
+      super(host, mountPath);
+      this.decorations = new DecorationsManager(this.root);
+      this.activeFileDec = new Decoration('active');
+      this.decorations.addDecoration(this.activeFileDec);
+    }
+}
+
+function getModel(data) {
+  const MOUNT_POINT = '/preferences';
+
+  // Setup host
+  let ptree = new ManagePreferenceTreeNodes(data);
+
+  // Init Tree with the Tree Parent node '/preferences'
+  ptree.init(MOUNT_POINT);
+  const host = {
+    pathStyle: 'unix',
+    getItems: async (path) => {
+      return ptree.readNode(path);
+    },
+  };
+
+  return new TreeModelX(host, MOUNT_POINT);
+}
+
+function setActiveFile(model, item, handler, events, activeFile) {
+  let fileH = item;
+  if (fileH === model.root) { return; }
+  if (_.isUndefined(activeFile.current)) {
+    activeFile.current = fileH;
+  } else {
+    model.activeFileDec.removeTarget(activeFile.current);
+    activeFile.current = fileH;
+  }
+
+  if (fileH) {
+    model.activeFileDec.addTarget(fileH, TargetMatchMode.SelfAndChildren);
+  }
+}
+
+function toggleDirectory(dir, handler) {
+  if (dir.type === 2) {
+    if ((dir).expanded) {
+      handler.current.closeDirectory(dir);
+    } else {
+      dir._children = null;
+      dir.flattenedBranch = null;
+      const ref = FileTreeItem.itemIdToRefMap.get(dir.id);
+      if (ref) {
+        ref.style.background = 'none';
+        const label$ = ref.querySelector('i.directory-toggle');
+        label$.classList.add('loading');
+      }
+      handler.current.openDirectory(dir);
+
+      if (ref) {
+        ref.style.background = 'none';
+        const label$ = ref.querySelector('i.directory-toggle');
+        if (label$) label$.classList.remove('loading');
+      }
+    }
+  }
+}
+
+export default function PreferencesTree({ data, getSelectedItem }) {
+  const model = useRef(getModel(data));
+  const internalHandler = useRef();
+  const events = new Notificar();
+  const activeFile = useRef();
+
+  return (
+    <AutoSizer>
+      {({ width, height }) => (
+        <FileTree
+          model={model.current}
+          height={height}
+          width={width}
+          itemHeight={24}
+          onReady={(handel) => {
+            internalHandler.current = handel;
+            return true;
+          }
+          }>
+          {(props) => <FileTreeItem
+            events={events}
+            item={props.item}
+            itemType={props.itemType}
+            onClick={(ev, item, type) => {
+              if (type === 1) {
+                getSelectedItem(item);
+                setActiveFile(model.current, item, internalHandler, events, activeFile);
+              }
+              if (type === 2) {
+                toggleDirectory(item, internalHandler);
+              }
+            }}
+            onDoubleClick={() => {/*This is intentional (SonarQube)*/}}
+            changeDirectoryCount={() => {/*This is intentional (SonarQube)*/}}
+            decorations={model.current.decorations.getDecorations(props.item)}
+          />}
+        </FileTree>
+      )}
+    </AutoSizer>
+
+  );
+}
+
+PreferencesTree.propTypes = {
+  data: PropTypes.array,
+  getSelectedItem: PropTypes.func,
+  item: PropTypes.object,
+  itemType: PropTypes.number
+};
diff --git a/web/pgadmin/preferences/static/js/index.js b/web/pgadmin/preferences/static/js/index.js
new file mode 100644
index 00000000..2764633f
--- /dev/null
+++ b/web/pgadmin/preferences/static/js/index.js
@@ -0,0 +1,21 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2022, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+import pgAdmin from 'sources/pgadmin';
+import pgBrowser from 'top/browser/static/js/browser';
+import Preferences from './preferences';
+
+if(!pgAdmin.Preferences) {
+  pgAdmin.Preferences = {};
+}
+
+pgAdmin.Preferences = Preferences.getInstance(pgAdmin, pgBrowser);
+
+module.exports = {
+  Preferences: Preferences,
+};
diff --git a/web/pgadmin/preferences/static/js/preferences.js b/web/pgadmin/preferences/static/js/preferences.js
index ebef31ca..dac6e35e 100644
--- a/web/pgadmin/preferences/static/js/preferences.js
+++ b/web/pgadmin/preferences/static/js/preferences.js
@@ -7,624 +7,48 @@
 //
 //////////////////////////////////////////////////////////////
 
+import React from 'react';
+import gettext from 'sources/gettext';
+import PreferencesComponent from './components/PreferencesComponent';
 import Notify from '../../../static/js/helpers/Notifier';
 
-define('pgadmin.preferences', [
-  'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'backbone',
-  'pgadmin.alertifyjs', 'sources/pgadmin', 'pgadmin.backform',
-  'pgadmin.browser', 'sources/modify_animation',
-  'tools/datagrid/static/js/show_query_tool',
-  'sources/tree/pgadmin_tree_save_state',
-], function(
-  gettext, url_for, $, _, Backbone, Alertify, pgAdmin, Backform, pgBrowser,
-  modifyAnimation, showQueryTool
-) {
-  // This defines the Preference/Options Dialog for pgAdmin IV.
-
-  /*
-   * Hmm... this module is already been initialized, we can refer to the old
-   * object from here.
-   */
-  if (pgAdmin.Preferences)
-    return pgAdmin.Preferences;
-
-  pgAdmin.Preferences = {
-    init: function() {
-      if (this.initialized)
-        return;
-
-      this.initialized = true;
-
-      // Declare the Preferences dialog
-      Alertify.dialog('preferencesDlg', function() {
-
-        var jTree, // Variable to create the aci-tree
-          controls = [], // Keep tracking of all the backform controls
-          // created by the dialog.
-          // Dialog containter
-          $container = $('<div class=\'preferences_dialog d-flex flex-row\'></div>');
-
-
-        /*
-         * Preference Model
-         *
-         * This model will be used to keep tracking of the changes done for
-         * an individual option.
-         */
-        var PreferenceModel = Backbone.Model.extend({
-          idAttribute: 'id',
-          defaults: {
-            id: undefined,
-            value: undefined,
-          },
-        });
-
-        /*
-         * Preferences Collection object.
-         *
-         * We will use only one collection object to keep track of all the
-         * preferences.
-         */
-        var changed = {},
-          preferences = this.preferences = new(Backbone.Collection.extend({
-            model: PreferenceModel,
-            url: url_for('preferences.index'),
-            updateAll: function() {
-              // We will send only the modified data to the server.
-              for (var key in changed) {
-                this.get(key).save();
-              }
-
-              return true;
-            },
-          }))(null);
-
-        preferences.on('reset', function() {
-          // Reset the changed variables
-          changed = {};
-        });
-
-        preferences.on('change', function(m) {
-          var id = m.get('id'),
-            dependents = m.get('dependents');
-          if (!(id in changed)) {
-            // Keep track of the original value
-            changed[id] = m._previousAttributes.value;
-          } else if (_.isEqual(m.get('value'), changed[id])) {
-            // Remove unchanged models.
-            delete changed[id];
-          }
-
-          // Check dependents exist or not. If exists then call dependentsFound function.
-          if (!_.isNull(dependents) && Array.isArray(dependents) && dependents.length > 0) {
-            dependentsFound(m.get('name'), m.get('value'), dependents);
-          }
-        });
-
-        /*
-         * Function: dependentsFound
-         *
-         * This method will be used to iterate through all the controls and
-         * dependents. If found then perform the appropriate action.
-         */
-        var dependentsFound = function(pref_name, pref_val, dependents) {
-          // Iterate through all the controls and check the dependents
-          _.each(controls, function(c) {
-            let ctrl_name = c.model.get('name');
-            _.each(dependents, function(deps) {
-              if (ctrl_name === deps) {
-                // Create methods to take appropriate actions and call here.
-                enableDisableMaxWidth(pref_name, pref_val, c);
-              }
-            });
-          });
-        };
-
-        /*
-         * Function: enableDisableMaxWidth
-         *
-         * This method will be used to enable and disable Maximum Width control
-         */
-        var enableDisableMaxWidth = function(pref_name, pref_val, control) {
-          if (pref_name === 'column_data_auto_resize' && pref_val === 'by_name') {
-            control.$el.find('input').prop('disabled', true);
-            control.$el.find('input').val(0);
-          } else if (pref_name === 'column_data_auto_resize' && pref_val === 'by_data') {
-            control.$el.find('input').prop('disabled', false);
-          }
-        };
-
-        /*
-         * Function: renderPreferencePanel
-         *
-         * Renders the preference panel in the content div based on the given
-         * preferences.
-         */
-        var renderPreferencePanel = function(prefs) {
-          /*
-           * Clear the existing html in the preferences content
-           */
-          var content = $container.find('.preferences_content');
-
-          /*
-           * We should clean up the existing controls.
-           */
-          if (controls) {
-            _.each(controls, function(c) {
-              if ('$sel' in c) {
-                if (c.$sel.data('select2').isOpen()) c.$sel.data('select2').close();
-              }
-              c.remove();
-            });
-          }
-          content.empty();
-          controls = [];
-
-          /*
-           * We will create new set of controls and render it based on the
-           * list of preferences using the Backform Field, Control.
-           */
-          _.each(prefs, function(p) {
-
-            var m = preferences.get(p.id);
-            m.errorModel = new Backbone.Model();
-            var f = new Backform.Field(
-                _.extend({}, p, {
-                  id: 'value',
-                  name: 'value',
-                })
-              ),
-              cntr = new(f.get('control'))({
-                field: f,
-                model: m,
-              });
-            content.append(cntr.render().$el);
-
-            // We will keep track of all the controls rendered at the
-            // moment.
-            controls.push(cntr);
-          });
-
-          /* Iterate through all preferences and check if dependents found.
-           * If found then call the dependentsFound method
-           */
-          _.each(prefs, function(p) {
-            let m = preferences.get(p.id);
-            let dependents = m.get('dependents');
-            if (!_.isNull(dependents) && Array.isArray(dependents) && dependents.length > 0) {
-              dependentsFound(m.get('name'), m.get('value'), dependents);
-            }
-          });
-        };
-
-        /*
-         * Function: dialogContentCleanup
-         *
-         * Do the dialog container cleanup on openning.
-         */
-
-        var dialogContentCleanup = function() {
-            // Remove the existing preferences
-            if (!jTree)
-              return;
-
-            /*
-             * Remove the aci-tree (mainly to remove the jquery object of
-             * aciTree from the system for this container).
-             */
-            try {
-              jTree.aciTree('destroy');
-            } catch (ex) {
-              // Sometimes - it fails to destroy the tree properly and throws
-              // exception.
-              console.warn(ex.stack || ex);
-            }
-            jTree.off('acitree', treeEventHandler);
-
-            // We need to reset the data from the preferences too
-            preferences.reset();
-
-            /*
-             * Clean up the existing controls.
-             */
-            if (controls) {
-              _.each(controls, function(c) {
-                c.remove();
-              });
-            }
-            controls = [];
-
-            // Remove all the objects now.
-            $container.empty();
-          },
-          /*
-           * Function: selectFirstCategory
-           *
-           * Whenever a user select a module instead of a category, we should
-           * select the first categroy of it.
-           */
-          selectFirstCategory = function(api, item) {
-            var data = item ? api.itemData(item) : null;
-
-            if (data && data.preferences) {
-              api.select(item);
-              return;
-            }
-            item = api.first(item);
-            selectFirstCategory(api, item);
-          },
-          /*
-           * A map on how to create controls for each datatype in preferences
-           * dialog.
-           */
-          getControlMappedForType = function(p) {
-            switch (p.type) {
-            case 'text':
-              return 'input';
-            case 'boolean':
-              p.options = {
-                onText: gettext('True'),
-                offText: gettext('False'),
-                onColor: 'success',
-                offColor: 'ternary',
-                size: 'mini',
-              };
-              return 'switch';
-            case 'node':
-              p.options = {
-                onText: gettext('Show'),
-                offText: gettext('Hide'),
-                onColor: 'success',
-                offColor: 'ternary',
-                size: 'mini',
-                width: '56',
-              };
-              return 'switch';
-            case 'integer':
-              return 'numeric';
-            case 'numeric':
-              return 'numeric';
-            case 'date':
-              return 'datepicker';
-            case 'datetime':
-              return 'datetimepicker';
-            case 'options':
-              var opts = [],
-                has_value = false;
-                // Convert the array to SelectControl understandable options.
-              _.each(p.options, function(o) {
-                if ('label' in o && 'value' in o) {
-                  let push_var = {
-                    'label': o.label,
-                    'value': o.value,
-                  };
-                  push_var['label'] = o.label;
-                  push_var['value'] = o.value;
-
-                  if('preview_src' in o) {
-                    push_var['preview_src'] = o.preview_src;
-                  }
-                  opts.push(push_var);
-                  if (o.value == p.value)
-                    has_value = true;
-                } else {
-                  opts.push({
-                    'label': o,
-                    'value': o,
-                  });
-                  if (o == p.value)
-                    has_value = true;
-                }
-              });
-              if (p.select2 && p.select2.tags == true && p.value && has_value == false) {
-                opts.push({
-                  'label': p.value,
-                  'value': p.value,
-                });
-              }
-              p.options = opts;
-              return 'select2';
-            case 'select2':
-              var select_opts = [];
-              _.each(p.options, function(o) {
-                if ('label' in o && 'value' in o) {
-                  let push_var = {
-                    'label': o.label,
-                    'value': o.value,
-                  };
-                  push_var['label'] = o.label;
-                  push_var['value'] = o.value;
-
-                  if('preview_src' in o) {
-                    push_var['preview_src'] = o.preview_src;
-                  }
-                  select_opts.push(push_var);
-                } else {
-                  select_opts.push({
-                    'label': o,
-                    'value': o,
-                  });
-                }
-              });
-
-              p.options = select_opts;
-              return 'select2';
-
-            case 'multiline':
-              return 'textarea';
-            case 'switch':
-              return 'switch';
-            case 'keyboardshortcut':
-              return 'keyboardShortcut';
-            case 'radioModern':
-              return 'radioModern';
-            case 'selectFile':
-              return 'binary-paths-grid';
-            case 'threshold':
-              p.warning_label = gettext('Warning');
-              p.alert_label = gettext('Alert');
-              p.unit = gettext('(in minutes)');
-              return 'threshold';
-            default:
-              if (console && console.warn) {
-                // Warning for developer only.
-                console.warn(
-                  'Hmm.. We don\'t know how to render this type - \'\'' + p.type + '\' of control.'
-                );
-              }
-              return 'input';
-            }
-          },
-          /*
-           * function: treeEventHandler
-           *
-           * It is basically a callback, which listens to aci-tree events,
-           * and act accordingly.
-           *
-           * + Selection of the node will existance of the preferences for
-           *   the selected tree-node, if not pass on to select the first
-           *   category under a module, else pass on to the render function.
-           *
-           * + When a new node is added in the tree, it will add the relavent
-           *   preferences in the preferences model collection, which will be
-           *   called during initialization itself.
-           *
-           *
-           */
-          treeEventHandler = function(event, api, item, eventName) {
-            // Look for selected item (if none supplied)!
-            item = item || api.selected();
-
-            // Event tree item has itemData
-            var d = item ? api.itemData(item) : null;
-
-            /*
-             * boolean (switch/checkbox), string, enum (combobox - enumvals),
-             * integer (min-max), font, color
-             */
-            switch (eventName) {
-            case 'selected':
-              if (!d)
-                break;
-
-              if (d.preferences) {
-                /*
-                   * Clear the existing html in the preferences content
-                   */
-                $container.find('.preferences_content');
-
-                renderPreferencePanel(d.preferences);
-
-                break;
-              } else {
-                selectFirstCategory(api, item);
-              }
-              break;
-            case 'added':
-              if (!d)
-                break;
-
-              // We will add the preferences in to the preferences data
-              // collection.
-              if (d.preferences && _.isArray(d.preferences)) {
-                _.each(d.preferences, function(p) {
-                  preferences.add({
-                    'id': p.id,
-                    'value': p.value,
-                    'category_id': d.id,
-                    'mid': d.mid,
-                    'name': p.name,
-                    'dependents': p.dependents,
-                  });
-                  /*
-                     * We don't know until now, how to render the control for
-                     * this preference.
-                     */
-                  if (!p.control) {
-                    p.control = getControlMappedForType(p);
-                  }
-                  if (p.help_str) {
-                    p.helpMessage = p.help_str;
-                  }
-                });
-              }
-              d.sortable = false;
-              break;
-            case 'loaded':
-              // Let's select the first category from the prefrences.
-              // We need to wait for sometime before all item gets loaded
-              // properly.
-              setTimeout(
-                function() {
-                  selectFirstCategory(api, null);
-                }, 300);
-              break;
-            }
-            return true;
-          };
-
-        // Dialog property
-        return {
-          main: function() {
-
-            // Remove the existing content first.
-            dialogContentCleanup();
-
-            $container.append(
-              '<div class=\'pg-el-sm-3 preferences_tree aciTree\'></div>'
-            ).append(
-              '<div class=\'pg-el-sm-9 preferences_content\'>' +
-              gettext('Category is not selected.') +
-              '</div>'
-            );
-
-            // Create the aci-tree for listing the modules and categories of
-            // it.
-            jTree = $container.find('.preferences_tree');
-            jTree.on('acitree', treeEventHandler);
-
-            jTree.aciTree({
-              selectable: true,
-              expand: true,
-              fullRow: true,
-              ajax: {
-                url: url_for('preferences.index'),
-              },
-              animateRoot: true,
-              unanimated: false,
-              show: {duration: 75},
-              hide: {duration: 75},
-              view: {duration: 75},
-            });
-
-            if (jTree.aciTree('api')) modifyAnimation.modifyAcitreeAnimation(pgBrowser, jTree.aciTree('api'));
-
-            this.show();
-          },
-          setup: function() {
-            return {
-              buttons: [{
-                text: '',
-                key: 112,
-                className: 'btn btn-primary-icon pull-left fa fa-question pg-alertify-icon-button',
-                attrs: {
-                  name: 'dialog_help',
-                  type: 'button',
-                  label: gettext('Preferences'),
-                  'aria-label': gettext('Help'),
-                  url: url_for(
-                    'help.static', {
-                      'filename': 'preferences.html',
-                    }
-                  ),
-                },
-              }, {
-                text: gettext('Cancel'),
-                key: 27,
-                className: 'btn btn-secondary fa fa-lg fa-times pg-alertify-button',
-              }, {
-                text: gettext('Save'),
-                key: 13,
-                className: 'btn btn-primary fa fa-lg fa-save pg-alertify-button',
-              }],
-              focus: {
-                element: 0,
-              },
-              options: {
-                padding: !1,
-                overflow: !1,
-                title: gettext('Preferences'),
-                closableByDimmer: false,
-                modal: true,
-                pinnable: false,
-              },
-            };
-          },
-          callback: function(e) {
-            if (e.button.element.name == 'dialog_help') {
-              e.cancel = true;
-              pgBrowser.showHelp(e.button.element.name, e.button.element.getAttribute('url'),
-                null, null);
-              return;
-            }
-
-            if (e.button.text == gettext('Save')) {
-              let requires_refresh = false;
-              preferences.updateAll();
-
-              /* Find the modules changed */
-              let modulesChanged = {};
-              _.each(changed, (val, key)=> {
-                let pref = pgBrowser.get_preference_for_id(Number(key));
-
-                if(pref['name'] == 'dynamic_tabs') {
-                  showQueryTool._set_dynamic_tab(pgBrowser, !pref['value']);
-                }
-
-                if(!modulesChanged[pref.module]) {
-                  modulesChanged[pref.module] = true;
-                }
-
-                if(pref.name == 'theme') {
-                  requires_refresh = true;
-                }
-
-                if(pref.name == 'hide_shared_server') {
-                  Notify.confirm(
-                    gettext('Browser tree refresh required'),
-                    gettext('A browser tree refresh is required. Do you wish to refresh the tree?'),
-                    function() {
-                      pgAdmin.Browser.tree.destroy({
-                        success: function() {
-                          pgAdmin.Browser.initializeBrowserTree(pgAdmin.Browser);
-                          return true;
-                        },
-                      });
-                    },
-                    function() {
-                      preferences.reset();
-                      changed = {};
-                      return true;
-                    },
-                    gettext('Refresh'),
-                    gettext('Later')
-                  );
-                }
-              });
-
-              if(requires_refresh) {
-                Notify.confirm(
-                  gettext('Refresh required'),
-                  gettext('A page refresh is required to apply the theme. Do you wish to refresh the page now?'),
-                  function() {
-                    /* If user clicks Yes */
-                    location.reload();
-                    return true;
-                  },
-                  function() {/* If user clicks No */ return true;},
-                  gettext('Refresh'),
-                  gettext('Later')
-                );
-              }
-              // Refresh preferences cache
-              pgBrowser.cache_preferences(modulesChanged);
-            }
-          },
-          build: function() {
-            this.elements.content.appendChild($container.get(0));
-            Alertify.pgDialogBuild.apply(this);
-          },
-          hooks: {
-            onshow: function() {/* This is intentional (SonarQube) */},
-          },
-        };
-      });
-
-    },
-    show: function() {
-      Alertify.preferencesDlg(true).resizeTo(pgAdmin.Browser.stdW.calc(pgAdmin.Browser.stdW.lg),pgAdmin.Browser.stdH.calc(pgAdmin.Browser.stdH.lg));
-    },
-  };
-
-  return pgAdmin.Preferences;
-});
+export default class Preferences {
+  static instance;
+
+  static getInstance(...args) {
+    if (!Preferences.instance) {
+      Preferences.instance = new Preferences(...args);
+    }
+    return Preferences.instance;
+  }
+
+  constructor(pgAdmin, pgBrowser) {
+    this.pgAdmin = pgAdmin;
+    this.pgBrowser = pgBrowser;
+  }
+
+  init() {
+    if (this.initialized)
+      return;
+    this.initialized = true;
+    // Add Preferences in to file menu
+    var menus = [{
+      name: 'mnu_preferences',
+      module: this,
+      applies: ['file'],
+      callback: 'show',
+      enable: true,
+      priority: 3,
+      label: gettext('Preferences'),
+      icon: 'fa fa-cog',
+    }];
+
+    this.pgBrowser.add_menus(menus);
+  }
+
+  // This is a callback function to show preferences.
+  show() {
+    // Render Preferences component
+    Notify.showModal(gettext('Preferences'), (closeModal, isFullScreen)=> {return <PreferencesComponent closeModal={closeModal} isFullScreen={isFullScreen}/>;}, {isFullScreen: false, showFullScreen: true, maxWidth: 'md', isFullWidth: true});
+  }
+}
diff --git a/web/pgadmin/preferences/tests/__init__.py b/web/pgadmin/preferences/tests/__init__.py
new file mode 100644
index 00000000..6e04daf6
--- /dev/null
+++ b/web/pgadmin/preferences/tests/__init__.py
@@ -0,0 +1,8 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2022, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
diff --git a/web/pgadmin/preferences/tests/preferences_test_data.json b/web/pgadmin/preferences/tests/preferences_test_data.json
new file mode 100644
index 00000000..a4327dd4
--- /dev/null
+++ b/web/pgadmin/preferences/tests/preferences_test_data.json
@@ -0,0 +1,27 @@
+{
+  "get_preferences": [
+    {
+      "name": "Get the all Preferences",
+      "url": "/preferences/",
+      "is_positive_test": true,
+      "mocking_required": false,
+      "mock_data": {
+      },
+      "expected_data": {
+        "status_code": 200
+      }
+    }
+  ],
+    "update_preferences": [
+    {
+      "name": "Update the Preferences",
+      "url": "/preferences/",
+      "is_positive_test": true,
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
+    }
+  ]
+}
diff --git a/web/pgadmin/preferences/tests/test_preferences_get.py b/web/pgadmin/preferences/tests/test_preferences_get.py
new file mode 100644
index 00000000..b7863888
--- /dev/null
+++ b/web/pgadmin/preferences/tests/test_preferences_get.py
@@ -0,0 +1,39 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2022, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import os
+from pgadmin.utils.route import BaseTestGenerator
+from regression.python_test_utils import test_utils as utils
+from regression.test_setup import config_data
+import json
+import config
+
+test_user_details = None
+if config.SERVER_MODE:
+    test_user_details = config_data['pgAdmin4_test_non_admin_credentials']
+
+CURRENT_PATH = os.path.dirname(os.path.realpath(__file__))
+with open(CURRENT_PATH + "/preferences_test_data.json") as data_file:
+    test_cases = json.load(data_file)
+
+
+class GetPreferencesTest(BaseTestGenerator):
+    """
+    This class will fetch all Preferences
+    """
+
+    scenarios = utils.generate_scenarios('get_preferences', test_cases)
+
+    def runTest(self):
+        self.get_preferences()
+
+    def get_preferences(self):
+        response = self.tester.get(self.url,
+                                   content_type='html/json')
+        self.assertTrue(response.status_code, 200)
diff --git a/web/pgadmin/preferences/tests/test_preferences_update.py b/web/pgadmin/preferences/tests/test_preferences_update.py
new file mode 100644
index 00000000..6b9eccc0
--- /dev/null
+++ b/web/pgadmin/preferences/tests/test_preferences_update.py
@@ -0,0 +1,60 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2022, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import os
+from pgadmin.utils.route import BaseTestGenerator
+from regression.python_test_utils import test_utils as utils
+from regression.test_setup import config_data
+from regression import parent_node_dict
+import json
+import config
+
+test_user_details = None
+if config.SERVER_MODE:
+    test_user_details = config_data['pgAdmin4_test_non_admin_credentials']
+
+CURRENT_PATH = os.path.dirname(os.path.realpath(__file__))
+with open(CURRENT_PATH + "/preferences_test_data.json") as data_file:
+    test_cases = json.load(data_file)
+
+
+class GetPreferencesTest(BaseTestGenerator):
+    """
+    This class will fetch all Preferences
+    """
+
+    scenarios = utils.generate_scenarios('update_preferences', test_cases)
+
+    def setUp(self):
+        response = self.tester.get(self.url,
+                                   content_type='html/json')
+        self.assertTrue(response.status_code, 200)
+        parent_node_dict['preferences'] = response.data
+
+    def runTest(self):
+        self.update_preferences()
+
+    def update_preferences(self):
+        if 'preferences' in parent_node_dict:
+            data = \
+                json.loads(parent_node_dict['preferences'])[0]['children'][0][
+                    'preferences'][0]
+            updated_data = [{
+                'id': data['id'],
+                'category_id': data['cid'],
+                'mid': data['mid'],
+                'name': data['name'],
+                'value': not data['value']
+            }]
+            response = self.tester.put(self.url,
+                                       data=json.dumps(updated_data),
+                                       content_type='html/json')
+            self.assertTrue(response.status_code, 200)
+        else:
+            self.fail('Preferences not found')
diff --git a/web/pgadmin/static/js/SchemaView/MappedControl.jsx b/web/pgadmin/static/js/SchemaView/MappedControl.jsx
index a361b828..8018799a 100644
--- a/web/pgadmin/static/js/SchemaView/MappedControl.jsx
+++ b/web/pgadmin/static/js/SchemaView/MappedControl.jsx
@@ -9,18 +9,19 @@
 
 import React, { useCallback } from 'react';
 import _ from 'lodash';
-
-import { FormInputText, FormInputSelect, FormInputSwitch, FormInputCheckbox, FormInputColor,
-  FormInputFileSelect, FormInputToggle, InputSwitch, FormInputSQL, FormNote, FormInputDateTimePicker, PlainString, InputSQL,
-  InputSelect, InputText, InputCheckbox, InputDateTimePicker } from '../components/FormComponents';
+import {
+  FormInputText, FormInputSelect, FormInputSwitch, FormInputCheckbox, FormInputColor,
+  FormInputFileSelect, FormInputToggle, InputSwitch, FormInputSQL, InputSQL, FormNote, FormInputDateTimePicker, PlainString,
+  InputSelect, InputText, InputCheckbox, InputDateTimePicker, InputFileSelect, FormInputKeyboardShortcut, FormInputQueryThreshold
+} from '../components/FormComponents';
 import Privilege from '../components/Privilege';
 import { evalFunc } from 'sources/utils';
 import PropTypes from 'prop-types';
 import CustomPropTypes from '../custom_prop_types';
-import { SelectRefresh} from '../components/SelectRefresh';
+import { SelectRefresh } from '../components/SelectRefresh';
 
 /* Control mapping for form view */
-function MappedFormControlBase({type, value, id, onChange, className, visible, inputRef, noLabel, ...props}) {
+function MappedFormControlBase({ type, value, id, onChange, className, visible, inputRef, noLabel, ...props }) {
   const name = id;
   const onTextChange = useCallback((e) => {
     let val = e;
@@ -34,36 +35,36 @@ function MappedFormControlBase({type, value, id, onChange, className, visible, i
     onChange && onChange(changedValue);
   }, []);
 
-  if(!visible) {
+  if (!visible) {
     return <></>;
   }
 
   /* The mapping uses Form* components as it comes with labels */
   switch (type) {
   case 'int':
-    return <FormInputText name={name} value={value} onChange={onTextChange} className={className} inputRef={inputRef} {...props} type='int'/>;
+    return <FormInputText name={name} value={value} onChange={onTextChange} className={className} inputRef={inputRef} {...props} type='int' />;
   case 'numeric':
-    return <FormInputText name={name} value={value} onChange={onTextChange} className={className} inputRef={inputRef} {...props} type='numeric'/>;
+    return <FormInputText name={name} value={value} onChange={onTextChange} className={className} inputRef={inputRef} {...props} type='numeric' />;
   case 'tel':
-    return <FormInputText name={name} value={value} onChange={onTextChange} className={className} inputRef={inputRef} {...props} type='tel'/>;
+    return <FormInputText name={name} value={value} onChange={onTextChange} className={className} inputRef={inputRef} {...props} type='tel' />;
   case 'text':
-    return <FormInputText name={name} value={value} onChange={onTextChange} className={className} inputRef={inputRef} {...props}/>;
+    return <FormInputText name={name} value={value} onChange={onTextChange} className={className} inputRef={inputRef} {...props} />;
   case 'multiline':
     return <FormInputText name={name} value={value} onChange={onTextChange} className={className}
-      inputRef={inputRef} controlProps={{multiline: true}} {...props}/>;
+      inputRef={inputRef} controlProps={{ multiline: true }} {...props} />;
   case 'password':
-    return <FormInputText name={name} value={value} onChange={onTextChange} className={className} type='password' inputRef={inputRef} {...props}/>;
+    return <FormInputText name={name} value={value} onChange={onTextChange} className={className} type='password' inputRef={inputRef} {...props} />;
   case 'select':
     return <FormInputSelect name={name} value={value} onChange={onTextChange} className={className} inputRef={inputRef} {...props} />;
   case 'select-refresh':
     return <SelectRefresh name={name} value={value} onChange={onTextChange} className={className} {...props} />;
   case 'switch':
     return <FormInputSwitch name={name} value={value}
-      onChange={(e)=>onTextChange(e.target.checked, e.target.name)} className={className}
+      onChange={(e) => onTextChange(e.target.checked, e.target.name)} className={className}
       {...props} />;
   case 'checkbox':
     return <FormInputCheckbox name={name} value={value}
-      onChange={(e)=>onTextChange(e.target.checked, e.target.name)} className={className}
+      onChange={(e) => onTextChange(e.target.checked, e.target.name)} className={className}
       {...props} />;
   case 'toggle':
     return <FormInputToggle name={name} value={value}
@@ -76,9 +77,13 @@ function MappedFormControlBase({type, value, id, onChange, className, visible, i
   case 'sql':
     return <FormInputSQL name={name} value={value} onChange={onSqlChange} className={className} noLabel={noLabel} {...props} />;
   case 'note':
-    return <FormNote className={className} {...props}/>;
+    return <FormNote className={className} {...props} />;
   case 'datetimepicker':
     return <FormInputDateTimePicker name={name} value={value} onChange={onTextChange} className={className} {...props} />;
+  case 'keyboardShortcut':
+    return <FormInputKeyboardShortcut name={name} value={value} onChange={onTextChange} {...props}/>;
+  case 'threshold':
+    return <FormInputQueryThreshold name={name} value={value} onChange={onTextChange} {...props}/>;
   default:
     return <PlainString value={value} {...props} />;
   }
@@ -100,11 +105,11 @@ MappedFormControlBase.propTypes = {
 };
 
 /* Control mapping for grid cell view */
-function MappedCellControlBase({cell, value, id, optionsLoaded, onCellChange, visible, reRenderRow,...props}) {
+function MappedCellControlBase({ cell, value, id, optionsLoaded, onCellChange, visible, reRenderRow, ...props }) {
   const name = id;
   const onTextChange = useCallback((e) => {
     let val = e;
-    if(e && e.target) {
+    if (e && e.target) {
       val = e.target.value;
     }
 
@@ -118,13 +123,13 @@ function MappedCellControlBase({cell, value, id, optionsLoaded, onCellChange, vi
   /* Some grid cells are based on options selected in other cells.
    * lets trigger a re-render for the row if optionsLoaded
    */
-  const optionsLoadedRerender = useCallback((res)=>{
+  const optionsLoadedRerender = useCallback((res) => {
     /* optionsLoaded is called when select options are fetched */
     optionsLoaded && optionsLoaded(res);
     reRenderRow && reRenderRow();
   }, []);
 
-  if(!visible) {
+  if (!visible) {
     return <></>;
   }
 
@@ -142,7 +147,7 @@ function MappedCellControlBase({cell, value, id, optionsLoaded, onCellChange, vi
     return <InputSelect name={name} value={value} onChange={onTextChange} optionsLoaded={optionsLoadedRerender} {...props}/>;
   case 'switch':
     return <InputSwitch name={name} value={value}
-      onChange={(e)=>onTextChange(e.target.checked, e.target.name)} {...props} />;
+      onChange={(e)=>onTextChange(e.target.checked, e.target.name)} disabled={props.disabled} {...props} />;
   case 'checkbox':
     return <InputCheckbox name={name} value={value}
       onChange={(e)=>onTextChange(e.target.checked, e.target.name)} {...props} />;
@@ -152,6 +157,10 @@ function MappedCellControlBase({cell, value, id, optionsLoaded, onCellChange, vi
     return <InputDateTimePicker name={name} value={value} onChange={onTextChange} {...props}/>;
   case 'sql':
     return <InputSQL name={name} value={value} onChange={onSqlChange} {...props} />;
+  case 'file':
+    return <InputFileSelect name={name} value={value} onChange={onTextChange} inputRef={props.inputRef} {...props} />;
+  case 'keyCode':
+    return <InputText name={name} value={value} onChange={onTextChange} {...props} type='text' maxlength={1} />;
   default:
     return <PlainString value={value} {...props} />;
   }
@@ -167,14 +176,16 @@ MappedCellControlBase.propTypes = {
   reRenderRow: PropTypes.func,
   optionsLoaded: PropTypes.func,
   onCellChange: PropTypes.func,
-  visible: PropTypes.bool
+  visible: PropTypes.bool,
+  disabled: PropTypes.bool,
+  inputRef: CustomPropTypes.ref,
 };
 
 const ALLOWED_PROPS_FIELD_COMMON = [
   'mode', 'value', 'readonly', 'disabled', 'hasError', 'id',
   'label', 'options', 'optionsLoaded', 'controlProps', 'schema', 'inputRef',
   'visible', 'autoFocus', 'helpMessage', 'className', 'optionsReloadBasis',
-  'orientation'
+  'orientation', 'isvalidate', 'fields'
 ];
 
 const ALLOWED_PROPS_FIELD_FORM = [
@@ -182,14 +193,14 @@ const ALLOWED_PROPS_FIELD_FORM = [
 ];
 
 const ALLOWED_PROPS_FIELD_CELL = [
-  'cell', 'onCellChange', 'row', 'reRenderRow',
+  'cell', 'onCellChange', 'row', 'reRenderRow', 'validate', 'disabled', 'readonly'
 ];
 
 
-export const MappedFormControl = (props)=>{
-  let newProps = {...props};
+export const MappedFormControl = (props) => {
+  let newProps = { ...props };
   let typeProps = evalFunc(null, newProps.type, newProps.state);
-  if(typeof(typeProps) === 'object') {
+  if (typeof (typeProps) === 'object') {
     newProps = {
       ...newProps,
       ...typeProps,
@@ -199,13 +210,13 @@ export const MappedFormControl = (props)=>{
   }
 
   /* Filter out garbage props if any using ALLOWED_PROPS_FIELD */
-  return <MappedFormControlBase {..._.pick(newProps, _.union(ALLOWED_PROPS_FIELD_COMMON, ALLOWED_PROPS_FIELD_FORM))}/>;
+  return <MappedFormControlBase {..._.pick(newProps, _.union(ALLOWED_PROPS_FIELD_COMMON, ALLOWED_PROPS_FIELD_FORM))} />;
 };
 
-export const MappedCellControl = (props)=>{
-  let newProps = {...props};
+export const MappedCellControl = (props) => {
+  let newProps = { ...props };
   let cellProps = evalFunc(null, newProps.cell, newProps.row);
-  if(typeof(cellProps) === 'object') {
+  if (typeof (cellProps) === 'object') {
     newProps = {
       ...newProps,
       ...cellProps,
@@ -215,5 +226,5 @@ export const MappedCellControl = (props)=>{
   }
 
   /* Filter out garbage props if any using ALLOWED_PROPS_FIELD */
-  return <MappedCellControlBase {..._.pick(newProps, _.union(ALLOWED_PROPS_FIELD_COMMON, ALLOWED_PROPS_FIELD_CELL))}/>;
+  return <MappedCellControlBase {..._.pick(newProps, _.union(ALLOWED_PROPS_FIELD_COMMON, ALLOWED_PROPS_FIELD_CELL))} />;
 };
diff --git a/web/pgadmin/static/js/SchemaView/index.jsx b/web/pgadmin/static/js/SchemaView/index.jsx
index 8b20d5f5..3febb103 100644
--- a/web/pgadmin/static/js/SchemaView/index.jsx
+++ b/web/pgadmin/static/js/SchemaView/index.jsx
@@ -108,7 +108,7 @@ function getChangedData(topSchema, viewHelperProps, sessData, stringify=false, i
 
     /* The comparator and setter */
     const attrChanged = (id, change, force=false)=>{
-      if(isValueEqual(_.get(origVal, id), _.get(sessVal, id)) && !force) {
+      if(isValueEqual(_.get(origVal, id), _.get(sessVal, id)) && !force && (_.isObject(_.get(origVal, id)) && _.isEqual(_.get(origVal, id), _.get(sessData, id)))) {
         return;
       } else {
         change = change || _.get(sessVal, id);
diff --git a/web/pgadmin/static/js/components/FormComponents.jsx b/web/pgadmin/static/js/components/FormComponents.jsx
index cdcfab64..7f69bcf4 100644
--- a/web/pgadmin/static/js/components/FormComponents.jsx
+++ b/web/pgadmin/static/js/components/FormComponents.jsx
@@ -10,8 +10,10 @@
 
 import React, { forwardRef, useCallback, useEffect, useRef, useState } from 'react';
 import { makeStyles } from '@material-ui/core/styles';
-import { Box, FormControl, OutlinedInput, FormHelperText,
-  Grid, IconButton, FormControlLabel, Switch, Checkbox, useTheme, InputLabel, Paper, Select as MuiSelect } from '@material-ui/core';
+import {
+  Box, FormControl, OutlinedInput, FormHelperText,
+  Grid, IconButton, FormControlLabel, Switch, Checkbox, useTheme, InputLabel, Paper, Select as MuiSelect
+} from '@material-ui/core';
 import { ToggleButton, ToggleButtonGroup } from '@material-ui/lab';
 import ErrorRoundedIcon from '@material-ui/icons/ErrorOutlineRounded';
 import InfoRoundedIcon from '@material-ui/icons/InfoRounded';
@@ -20,13 +22,14 @@ import CheckRoundedIcon from '@material-ui/icons/CheckRounded';
 import WarningRoundedIcon from '@material-ui/icons/WarningRounded';
 import FolderOpenRoundedIcon from '@material-ui/icons/FolderOpenRounded';
 import DescriptionIcon from '@material-ui/icons/Description';
-import Select, {components as RSComponents} from 'react-select';
+import AssignmentTurnedIn from '@material-ui/icons/AssignmentTurnedIn';
+import Select, { components as RSComponents } from 'react-select';
 import CreatableSelect from 'react-select/creatable';
 import Pickr from '@simonwep/pickr';
 import clsx from 'clsx';
 import PropTypes from 'prop-types';
 import HTMLReactParse from 'html-react-parser';
-import { KeyboardDateTimePicker, KeyboardDatePicker, KeyboardTimePicker, MuiPickersUtilsProvider} from '@material-ui/pickers';
+import { KeyboardDateTimePicker, KeyboardDatePicker, KeyboardTimePicker, MuiPickersUtilsProvider } from '@material-ui/pickers';
 import DateFnsUtils from '@date-io/date-fns';
 import * as DateFns from 'date-fns';
 
@@ -36,6 +39,8 @@ import { showFileDialog } from '../helpers/legacyConnector';
 import _ from 'lodash';
 import { DefaultButton, PrimaryButton, PgIconButton } from './Buttons';
 import CustomPropTypes from '../custom_prop_types';
+import KeyboardShortcuts from './KeyboardShortcuts';
+import QueryThresholds from './QueryThresholds';
 
 
 const useStyles = makeStyles((theme) => ({
@@ -55,7 +60,7 @@ const useStyles = makeStyles((theme) => ({
     margin: theme.spacing(0.75, 0.75, 0.75, 0.75),
     display: 'flex',
   },
-  formLabelError:  {
+  formLabelError: {
     color: theme.palette.error.main,
   },
   sql: {
@@ -95,17 +100,17 @@ export const MESSAGE_TYPE = {
 };
 
 /* Icon based on MESSAGE_TYPE */
-function FormIcon({type, close=false, ...props}) {
+function FormIcon({ type, close = false, ...props }) {
   let TheIcon = null;
-  if(close) {
+  if (close) {
     TheIcon = CloseIcon;
-  } else if(type === MESSAGE_TYPE.SUCCESS) {
+  } else if (type === MESSAGE_TYPE.SUCCESS) {
     TheIcon = CheckRoundedIcon;
-  } else if(type === MESSAGE_TYPE.ERROR) {
+  } else if (type === MESSAGE_TYPE.ERROR) {
     TheIcon = ErrorRoundedIcon;
-  } else if(type === MESSAGE_TYPE.INFO) {
+  } else if (type === MESSAGE_TYPE.INFO) {
     TheIcon = InfoRoundedIcon;
-  } else if(type === MESSAGE_TYPE.WARNING) {
+  } else if (type === MESSAGE_TYPE.WARNING) {
     TheIcon = WarningRoundedIcon;
   }
 
@@ -117,21 +122,21 @@ FormIcon.propTypes = {
 };
 
 /* Wrapper on any form component to add label, error indicator and help message */
-export function FormInput({children, error, className, label, helpMessage, required, testcid}) {
+export function FormInput({ children, error, className, label, helpMessage, required, testcid }) {
   const classes = useStyles();
   const cid = testcid || _.uniqueId('c');
   const helpid = `h${cid}`;
   return (
     <Grid container spacing={0} className={className}>
       <Grid item lg={3} md={3} sm={3} xs={12}>
-        <InputLabel htmlFor={cid} className={clsx(classes.formLabel, error?classes.formLabelError : null)} required={required}>
+        <InputLabel htmlFor={cid} className={clsx(classes.formLabel, error ? classes.formLabelError : null)} required={required}>
           {label}
-          <FormIcon type={MESSAGE_TYPE.ERROR} style={{marginLeft: 'auto', visibility: error ? 'unset' : 'hidden'}}/>
+          <FormIcon type={MESSAGE_TYPE.ERROR} style={{ marginLeft: 'auto', visibility: error ? 'unset' : 'hidden' }} />
         </InputLabel>
       </Grid>
       <Grid item lg={9} md={9} sm={9} xs={12}>
         <FormControl error={Boolean(error)} fullWidth>
-          {React.cloneElement(children, {cid, helpid})}
+          {React.cloneElement(children, { cid, helpid })}
         </FormControl>
         <FormHelperText id={helpid} variant="outlined">{HTMLReactParse(helpMessage || '')}</FormHelperText>
       </Grid>
@@ -148,17 +153,22 @@ FormInput.propTypes = {
   testcid: PropTypes.any,
 };
 
-export function InputSQL({value, onChange, className, controlProps, ...props}) {
+export function InputSQL({ value, options, onChange, className, controlProps, ...props }) {
   const classes = useStyles();
   const editor = useRef();
 
   return (
     <CodeMirror
-      currEditor={(obj)=>editor.current=obj}
-      value={value||''}
+      currEditor={(obj) => editor.current = obj}
+      value={value || ''}
+      options={{
+        lineNumbers: true,
+        mode: 'text/x-pgsql',
+        ...options,
+      }}
       className={clsx(classes.sql, className)}
       events={{
-        change: (cm)=>{
+        change: (cm) => {
           onChange && onChange(cm.getValue());
         },
       }}
@@ -176,13 +186,13 @@ InputSQL.propTypes = {
   controlProps: PropTypes.object,
 };
 
-export function FormInputSQL({hasError, required, label, className, helpMessage, testcid, value, noLabel, ...props}) {
-  if(noLabel) {
-    return <InputSQL value={value} {...props}/>;
+export function FormInputSQL({ hasError, required, label, className, helpMessage, testcid, value, controlProps, noLabel, ...props }) {
+  if (noLabel) {
+    return <InputSQL value={value} options={controlProps} {...props} />;
   } else {
     return (
       <FormInput required={required} label={label} error={hasError} className={className} helpMessage={helpMessage} testcid={testcid} >
-        <InputSQL value={value} {...props}/>
+        <InputSQL value={value} options={controlProps} {...props} />
       </FormInput>
     );
   }
@@ -208,7 +218,7 @@ const DATE_TIME_FORMAT = {
   TIME_24: 'HH:mm:ss',
 };
 
-export function InputDateTimePicker({value, onChange, readonly, controlProps, ...props}) {
+export function InputDateTimePicker({ value, onChange, readonly, controlProps, ...props }) {
   let format = '';
   let placeholder = '';
   if (controlProps?.pickerType === 'Date') {
@@ -222,15 +232,15 @@ export function InputDateTimePicker({value, onChange, readonly, controlProps, ..
     placeholder = controlProps.placeholder || 'YYYY-MM-DD HH:mm:ss Z';
   }
 
-  const handleChange = (dateVal, stringVal)=> {
+  const handleChange = (dateVal, stringVal) => {
     onChange(stringVal);
   };
 
   /* Value should be a date object instead of string */
   value = _.isUndefined(value) ? null : value;
-  if(!_.isNull(value)) {
+  if (!_.isNull(value)) {
     let parseValue = DateFns.parse(value, format, new Date());
-    if(!DateFns.isValid(parseValue)) {
+    if (!DateFns.isValid(parseValue)) {
       parseValue = DateFns.parseISO(value);
     }
     value = !DateFns.isValid(parseValue) ? value : parseValue;
@@ -238,7 +248,7 @@ export function InputDateTimePicker({value, onChange, readonly, controlProps, ..
 
   if (readonly) {
     return (<InputText value={value ? DateFns.format(value, format) : value}
-      readonly={readonly} controlProps={{placeholder: controlProps.placeholder}} {...props}/>);
+      readonly={readonly} controlProps={{ placeholder: controlProps.placeholder }} {...props} />);
   }
 
   let commonProps = {
@@ -262,20 +272,20 @@ export function InputDateTimePicker({value, onChange, readonly, controlProps, ..
   if (controlProps?.pickerType === 'Date') {
     return (
       <MuiPickersUtilsProvider utils={DateFnsUtils}>
-        <KeyboardDatePicker {...commonProps}/>
+        <KeyboardDatePicker {...commonProps} />
       </MuiPickersUtilsProvider>
     );
   } else if (controlProps?.pickerType === 'Time') {
     return (
       <MuiPickersUtilsProvider utils={DateFnsUtils}>
-        <KeyboardTimePicker {...commonProps}/>
+        <KeyboardTimePicker {...commonProps} />
       </MuiPickersUtilsProvider>
     );
   }
 
   return (
     <MuiPickersUtilsProvider utils={DateFnsUtils}>
-      <KeyboardDateTimePicker {...commonProps}/>
+      <KeyboardDateTimePicker {...commonProps} />
     </MuiPickersUtilsProvider>
   );
 }
@@ -287,10 +297,10 @@ InputDateTimePicker.propTypes = {
   controlProps: PropTypes.object,
 };
 
-export function FormInputDateTimePicker({hasError, required, label, className, helpMessage, testcid, ...props}) {
+export function FormInputDateTimePicker({ hasError, required, label, className, helpMessage, testcid, ...props }) {
   return (
     <FormInput required={required} label={label} error={hasError} className={className} helpMessage={helpMessage} testcid={testcid}>
-      <InputDateTimePicker {...props}/>
+      <InputDateTimePicker {...props} />
     </FormInput>
   );
 }
@@ -308,23 +318,23 @@ FormInputDateTimePicker.propTypes = {
 
 /* Use forwardRef to pass ref prop to OutlinedInput */
 export const InputText = forwardRef(({
-  cid, helpid, readonly, disabled, maxlength=255, value, onChange, controlProps, type, ...props}, ref)=>{
+  cid, helpid, readonly, disabled, maxlength = 255, value, onChange, controlProps, type, ...props }, ref) => {
 
   const classes = useStyles();
   const patterns = {
     'numeric': '^-?[0-9]\\d*\\.?\\d*$',
     'int': '^-?[0-9]\\d*$',
   };
-  let onChangeFinal = (e)=>{
+  let onChangeFinal = (e) => {
     let changeVal = e.target.value;
 
     /* For type number, we set type as tel with number regex to get validity.*/
-    if(['numeric', 'int', 'tel'].indexOf(type) > -1) {
-      if(!e.target.validity.valid && changeVal !== '' && changeVal !== '-') {
+    if (['numeric', 'int', 'tel'].indexOf(type) > -1) {
+      if (!e.target.validity.valid && changeVal !== '' && changeVal !== '-') {
         return;
       }
     }
-    if(controlProps?.formatter) {
+    if (controlProps?.formatter) {
       changeVal = controlProps.formatter.toRaw(changeVal);
     }
     onChange && onChange(changeVal);
@@ -332,11 +342,11 @@ export const InputText = forwardRef(({
 
   let finalValue = (_.isNull(value) || _.isUndefined(value)) ? '' : value;
 
-  if(controlProps?.formatter) {
+  if (controlProps?.formatter) {
     finalValue = controlProps.formatter.fromRaw(finalValue);
   }
 
-  return(
+  return (
     <OutlinedInput
       ref={ref}
       color="primary"
@@ -346,7 +356,7 @@ export const InputText = forwardRef(({
         id: cid,
         maxLength: controlProps?.multiline ? null : maxlength,
         'aria-describedby': helpid,
-        ...(type ? {pattern: !_.isUndefined(controlProps) && !_.isUndefined(controlProps.pattern) ? controlProps.pattern : patterns[type]} : {})
+        ...(type ? { pattern: !_.isUndefined(controlProps) && !_.isUndefined(controlProps.pattern) ? controlProps.pattern : patterns[type] } : {})
       }}
       readOnly={Boolean(readonly)}
       disabled={Boolean(disabled)}
@@ -354,9 +364,12 @@ export const InputText = forwardRef(({
       notched={false}
       value={(_.isNull(finalValue) || _.isUndefined(finalValue)) ? '' : finalValue}
       onChange={onChangeFinal}
+      {
+        ...(controlProps?.onKeyDown && { onKeyDown: controlProps.onKeyDown })
+      }
       {...controlProps}
       {...props}
-      {...(['numeric', 'int'].indexOf(type) > -1 ? {type: 'tel'} : {type: type})}
+      {...(['numeric', 'int'].indexOf(type) > -1 ? { type: 'tel' } : { type: type })}
     />
   );
 });
@@ -374,10 +387,10 @@ InputText.propTypes = {
   type: PropTypes.string,
 };
 
-export function FormInputText({hasError, required, label, className, helpMessage, testcid, ...props}) {
+export function FormInputText({ hasError, required, label, className, helpMessage, testcid, ...props }) {
   return (
     <FormInput required={required} label={label} error={hasError} className={className} helpMessage={helpMessage} testcid={testcid}>
-      <InputText label={label} {...props}/>
+      <InputText label={label} {...props} />
     </FormInput>
   );
 }
@@ -391,16 +404,21 @@ FormInputText.propTypes = {
 };
 
 /* Using the existing file dialog functions using showFileDialog */
-export function InputFileSelect({controlProps, onChange, disabled, readonly, ...props}) {
+export function InputFileSelect({ controlProps, onChange, disabled, readonly, isvalidate = false, validate, ...props }) {
   const inpRef = useRef();
-  const onFileSelect = (value)=>{
+  const onFileSelect = (value) => {
     onChange && onChange(decodeURI(value));
     inpRef.current.focus();
   };
   return (
     <InputText ref={inpRef} disabled={disabled} readonly={readonly} onChange={onChange} {...props} endAdornment={
-      <IconButton onClick={()=>showFileDialog(controlProps, onFileSelect)}
-        disabled={disabled||readonly} aria-label={gettext('Select a file')}><FolderOpenRoundedIcon /></IconButton>
+      <>
+        <IconButton onClick={() => showFileDialog(controlProps, onFileSelect)}
+          disabled={disabled || readonly} aria-label={gettext('Select a file')}><FolderOpenRoundedIcon /></IconButton>
+        {isvalidate &&
+          <PgIconButton title={gettext('Validate')} disabled={!props.value} onClick={() => { validate(props.value); }} icon={<AssignmentTurnedIn />}></PgIconButton>
+        }
+      </>
     } />
   );
 }
@@ -409,14 +427,17 @@ InputFileSelect.propTypes = {
   onChange: PropTypes.func,
   disabled: PropTypes.bool,
   readonly: PropTypes.bool,
+  isvalidate: PropTypes.bool,
+  validate: PropTypes.func,
+  value: PropTypes.string
 };
 
 export function FormInputFileSelect({
-  hasError, required, label, className, helpMessage, testcid, ...props}) {
+  hasError, required, label, className, helpMessage, testcid, ...props }) {
 
   return (
     <FormInput required={required} label={label} error={hasError} className={className} helpMessage={helpMessage} testcid={testcid}>
-      <InputFileSelect required={required} label={label} {...props}/>
+      <InputFileSelect required={required} label={label} {...props} />
     </FormInput>
   );
 }
@@ -429,13 +450,13 @@ FormInputFileSelect.propTypes = {
   testcid: PropTypes.string,
 };
 
-export function InputSwitch({cid, helpid, value, onChange, readonly, controlProps, ...props}) {
+export function InputSwitch({ cid, helpid, value, onChange, readonly, controlProps, ...props }) {
   const classes = useStyles();
   return (
     <Switch color="primary"
       checked={Boolean(value)}
       onChange={
-        readonly ? ()=>{/*This is intentional (SonarQube)*/} : onChange
+        readonly ? () => {/*This is intentional (SonarQube)*/ } : onChange
       }
       id={cid}
       inputProps={{
@@ -457,11 +478,11 @@ InputSwitch.propTypes = {
   controlProps: PropTypes.object,
 };
 
-export function FormInputSwitch({hasError, required, label, className, helpMessage, testcid, ...props}) {
+export function FormInputSwitch({ hasError, required, label, className, helpMessage, testcid, ...props }) {
 
   return (
     <FormInput required={required} label={label} error={hasError} className={className} helpMessage={helpMessage} testcid={testcid}>
-      <InputSwitch {...props}/>
+      <InputSwitch {...props} />
     </FormInput>
   );
 }
@@ -474,7 +495,7 @@ FormInputSwitch.propTypes = {
   testcid: PropTypes.string,
 };
 
-export function InputCheckbox({cid, helpid, value, onChange, controlProps, readonly, ...props}) {
+export function InputCheckbox({ cid, helpid, value, onChange, controlProps, readonly, ...props }) {
   controlProps = controlProps || {};
   return (
     <FormControlLabel
@@ -482,10 +503,10 @@ export function InputCheckbox({cid, helpid, value, onChange, controlProps, reado
         <Checkbox
           id={cid}
           checked={Boolean(value)}
-          onChange={readonly ? ()=>{/*This is intentional (SonarQube)*/} : onChange}
+          onChange={readonly ? () => {/*This is intentional (SonarQube)*/ } : onChange}
           color="primary"
-          inputProps={{'aria-describedby': helpid}}
-          {...props}/>
+          inputProps={{ 'aria-describedby': helpid }}
+          {...props} />
       }
       label={controlProps.label}
     />
@@ -500,12 +521,12 @@ InputCheckbox.propTypes = {
   readonly: PropTypes.bool,
 };
 
-export function FormInputCheckbox({hasError, required, label,
-  className, helpMessage, testcid, ...props}) {
+export function FormInputCheckbox({ hasError, required, label,
+  className, helpMessage, testcid, ...props }) {
 
   return (
     <FormInput required={required} label={label} error={hasError} className={className} helpMessage={helpMessage} testcid={testcid}>
-      <InputCheckbox {...props}/>
+      <InputCheckbox {...props} />
     </FormInput>
   );
 }
@@ -519,23 +540,23 @@ FormInputCheckbox.propTypes = {
 };
 
 
-export const InputToggle = forwardRef(({cid, value, onChange, options, disabled, readonly, ...props}, ref) => {
+export const InputToggle = forwardRef(({ cid, value, onChange, options, disabled, readonly, ...props }, ref) => {
   return (
     <ToggleButtonGroup
       id={cid}
       value={value}
       exclusive
-      onChange={(e, val)=>{val!==null && onChange(val);}}
+      onChange={(e, val) => { val !== null && onChange(val); }}
       {...props}
     >
       {
-        (options||[]).map((option, i)=>{
+        (options || []).map((option, i) => {
           const isSelected = option.value === value;
           const isDisabled = disabled || option.disabled || (readonly && !isSelected);
           return (
-            <ToggleButton ref={i==0 ? ref : null} key={option.label} value={option.value} component={isSelected ? PrimaryButton : DefaultButton}
+            <ToggleButton ref={i == 0 ? ref : null} key={option.label} value={option.value} component={isSelected ? PrimaryButton : DefaultButton}
               disabled={isDisabled} aria-label={option.label}>
-              <CheckRoundedIcon style={{visibility: isSelected ? 'visible': 'hidden'}}/>&nbsp;{option.label}
+              <CheckRoundedIcon style={{ visibility: isSelected ? 'visible' : 'hidden' }} />&nbsp;{option.label}
             </ToggleButton>
           );
         })
@@ -554,11 +575,11 @@ InputToggle.propTypes = {
   readonly: PropTypes.bool,
 };
 
-export function FormInputToggle({hasError, required, label,
-  className, helpMessage, testcid, inputRef, ...props}) {
+export function FormInputToggle({ hasError, required, label,
+  className, helpMessage, testcid, inputRef, ...props }) {
   return (
     <FormInput required={required} label={label} error={hasError} className={className} helpMessage={helpMessage} testcid={testcid}>
-      <InputToggle ref={inputRef} {...props}/>
+      <InputToggle ref={inputRef} {...props} />
     </FormInput>
   );
 }
@@ -575,9 +596,9 @@ FormInputToggle.propTypes = {
 /* react-select package is used for select input
  * Customizing the select styles to fit existing theme
  */
-const customReactSelectStyles = (theme, readonly)=>({
+const customReactSelectStyles = (theme, readonly) => ({
   input: (provided) => {
-    return {...provided, padding: 0, margin: 0, color: 'inherit'};
+    return { ...provided, padding: 0, margin: 0, color: 'inherit' };
   },
   singleValue: (provided) => {
     return {
@@ -593,35 +614,35 @@ const customReactSelectStyles = (theme, readonly)=>({
     borderColor: theme.otherVars.inputBorderColor,
     ...(state.isFocused ? {
       borderColor: theme.palette.primary.main,
-      boxShadow: 'inset 0 0 0 1px '+theme.palette.primary.main,
+      boxShadow: 'inset 0 0 0 1px ' + theme.palette.primary.main,
       '&:hover': {
         borderColor: theme.palette.primary.main,
       }
     } : {}),
   }),
-  dropdownIndicator: (provided)=>({
+  dropdownIndicator: (provided) => ({
     ...provided,
     padding: '0rem 0.25rem',
   }),
-  indicatorsContainer: (provided)=>({
+  indicatorsContainer: (provided) => ({
     ...provided,
-    ...(readonly ? {display: 'none'} : {})
+    ...(readonly ? { display: 'none' } : {})
   }),
-  clearIndicator: (provided)=>({
+  clearIndicator: (provided) => ({
     ...provided,
     padding: '0rem 0.25rem',
   }),
-  valueContainer: (provided)=>({
+  valueContainer: (provided) => ({
     ...provided,
     padding: theme.otherVars.reactSelect.padding,
   }),
-  groupHeading: (provided)=>({
+  groupHeading: (provided) => ({
     ...provided,
     color: 'inherit',
     fontSize: '0.85em',
     textTransform: 'none',
   }),
-  menu: (provided)=>({
+  menu: (provided) => ({
     ...provided,
     backgroundColor: theme.palette.background.default,
     color: theme.palette.text.primary,
@@ -629,12 +650,12 @@ const customReactSelectStyles = (theme, readonly)=>({
     border: '1px solid ' + theme.otherVars.inputBorderColor,
     marginTop: '2px',
   }),
-  menuPortal: (provided)=>({
+  menuPortal: (provided) => ({
     ...provided, zIndex: 9999,
     backgroundColor: 'inherit',
     color: 'inherit',
   }),
-  option: (provided, state)=>{
+  option: (provided, state) => {
     let bgColor = 'inherit';
     if (state.isFocused) {
       bgColor = theme.palette.grey[400];
@@ -648,27 +669,27 @@ const customReactSelectStyles = (theme, readonly)=>({
       backgroundColor: bgColor,
     };
   },
-  multiValue: (provided)=>({
+  multiValue: (provided) => ({
     ...provided,
     backgroundColor: theme.palette.grey[400],
   }),
-  multiValueLabel: (provided)=>({
+  multiValueLabel: (provided) => ({
     ...provided,
     fontSize: '1em',
     zIndex: 99,
     color: theme.palette.text.primary
   }),
-  multiValueRemove: (provided)=>({
+  multiValueRemove: (provided) => ({
     ...provided,
     '&:hover': {
       backgroundColor: 'unset',
       color: theme.palette.error.main,
     },
-    ...(readonly ? {display: 'none'} : {})
+    ...(readonly ? { display: 'none' } : {})
   }),
 });
 
-function OptionView({image, label}) {
+function OptionView({ image, label }) {
   const classes = useStyles();
   return (
     <>
@@ -705,8 +726,8 @@ CustomSelectSingleValue.propTypes = {
 };
 
 export function flattenSelectOptions(options) {
-  return _.flatMap(options, (option)=>{
-    if(option.options) {
+  return _.flatMap(options, (option) => {
+    if (option.options) {
       return option.options;
     } else {
       return option;
@@ -716,28 +737,28 @@ export function flattenSelectOptions(options) {
 
 function getRealValue(options, value, creatable, formatter) {
   let realValue = null;
-  if(_.isArray(value)) {
+  if (_.isArray(value)) {
     realValue = [...value];
     /* If multi select options need to be in some format by UI, use formatter */
-    if(formatter) {
+    if (formatter) {
       realValue = formatter.fromRaw(realValue, options);
     } else {
-      if(creatable) {
-        realValue = realValue.map((val)=>({label:val, value: val}));
+      if (creatable) {
+        realValue = realValue.map((val) => ({ label: val, value: val }));
       } else {
-        realValue = realValue.map((val)=>(_.find(options, (option)=>_.isEqual(option.value, val))));
+        realValue = realValue.map((val) => (_.find(options, (option) => _.isEqual(option.value, val))));
       }
     }
   } else {
     let flatOptions = flattenSelectOptions(options);
-    realValue = _.find(flatOptions, (option)=>option.value==value) ||
-        (creatable && !_.isUndefined(value) && !_.isNull(value) ? {label:value, value: value} : null);
+    realValue = _.find(flatOptions, (option) => option.value == value) ||
+      (creatable && !_.isUndefined(value) && !_.isNull(value) ? { label: value, value: value } : null);
   }
   return realValue;
 }
-export function InputSelectNonSearch({options, ...props}) {
+export function InputSelectNonSearch({ options, ...props }) {
   return <MuiSelect native {...props} variant="outlined">
-    {(options||[]).map((o)=><option key={o.value} value={o.value}>{o.label}</option>)}
+    {(options || []).map((o) => <option key={o.value} value={o.value}>{o.label}</option>)}
   </MuiSelect>;
 }
 InputSelectNonSearch.propTypes = {
@@ -748,7 +769,7 @@ InputSelectNonSearch.propTypes = {
 };
 
 export const InputSelect = forwardRef(({
-  cid, onChange, options, readonly=false, value, controlProps={}, optionsLoaded, optionsReloadBasis, disabled, ...props}, ref) => {
+  cid, onChange, options, readonly = false, value, controlProps = {}, optionsLoaded, optionsReloadBasis, disabled, ...props }, ref) => {
   const [[finalOptions, isLoading], setFinalOptions] = useState([[], true]);
   const theme = useTheme();
 
@@ -757,33 +778,33 @@ export const InputSelect = forwardRef(({
   loading the options. optionsReloadBasis is helpful to avoid repeated
   options load. If optionsReloadBasis value changes, then options will be loaded again.
   */
-  useEffect(()=>{
-    let optPromise = options, umounted=false;
-    if(typeof options === 'function') {
+  useEffect(() => {
+    let optPromise = options, umounted = false;
+    if (typeof options === 'function') {
       optPromise = options();
     }
     setFinalOptions([[], true]);
     Promise.resolve(optPromise)
-      .then((res)=>{
+      .then((res) => {
         /* If component unmounted, dont update state */
-        if(!umounted) {
+        if (!umounted) {
           optionsLoaded && optionsLoaded(res, value);
           /* Auto select if any option has key as selected */
           const flatRes = flattenSelectOptions(res || []);
           let selectedVal;
-          if(controlProps.multiple) {
-            selectedVal = _.filter(flatRes, (o)=>o.selected)?.map((o)=>o.value);
+          if (controlProps.multiple) {
+            selectedVal = _.filter(flatRes, (o) => o.selected)?.map((o) => o.value);
           } else {
-            selectedVal = _.find(flatRes, (o)=>o.selected)?.value;
+            selectedVal = _.find(flatRes, (o) => o.selected)?.value;
           }
 
-          if((!_.isUndefined(selectedVal) && !_.isArray(selectedVal)) || (_.isArray(selectedVal) && selectedVal.length != 0)) {
+          if ((!_.isUndefined(selectedVal) && !_.isArray(selectedVal)) || (_.isArray(selectedVal) && selectedVal.length != 0)) {
             onChange && onChange(selectedVal);
           }
           setFinalOptions([res || [], false]);
         }
       });
-    return ()=>umounted=true;
+    return () => umounted = true;
   }, [optionsReloadBasis]);
 
 
@@ -791,7 +812,7 @@ export const InputSelect = forwardRef(({
   const filteredOptions = (controlProps.filter && controlProps.filter(finalOptions)) || finalOptions;
   const flatFiltered = flattenSelectOptions(filteredOptions);
   let realValue = getRealValue(flatFiltered, value, controlProps.creatable, controlProps.formatter);
-  if(realValue && _.isPlainObject(realValue) && _.isUndefined(realValue.value)) {
+  if (realValue && _.isPlainObject(realValue) && _.isUndefined(realValue.value)) {
     console.error('Undefined option value not allowed', realValue, filteredOptions);
   }
   const otherProps = {
@@ -802,17 +823,17 @@ export const InputSelect = forwardRef(({
 
   const styles = customReactSelectStyles(theme, readonly || disabled);
 
-  const onChangeOption = useCallback((selectVal)=>{
-    if(_.isArray(selectVal)) {
+  const onChangeOption = useCallback((selectVal) => {
+    if (_.isArray(selectVal)) {
       // Check if select all option is selected
       if (!_.isUndefined(selectVal.find(x => x.label === 'Select All'))) {
         selectVal = filteredOptions;
       }
       /* If multi select options need to be in some format by UI, use formatter */
-      if(controlProps.formatter) {
+      if (controlProps.formatter) {
         selectVal = controlProps.formatter.toRaw(selectVal, filteredOptions);
       } else {
-        selectVal = selectVal.map((option)=>option.value);
+        selectVal = selectVal.map((option) => option.value);
       }
       onChange && onChange(selectVal);
     } else {
@@ -838,13 +859,13 @@ export const InputSelect = forwardRef(({
     ...otherProps,
     ...props,
   };
-  if(!controlProps.creatable) {
+  if (!controlProps.creatable) {
     return (
-      <Select ref={ref} {...commonProps}/>
+      <Select ref={ref} {...commonProps} />
     );
   } else {
     return (
-      <CreatableSelect ref={ref} {...commonProps}/>
+      <CreatableSelect ref={ref} {...commonProps} />
     );
   }
 });
@@ -863,10 +884,10 @@ InputSelect.propTypes = {
 
 
 export function FormInputSelect({
-  hasError, required, className, label, helpMessage, testcid, ...props}) {
+  hasError, required, className, label, helpMessage, testcid, ...props }) {
   return (
     <FormInput required={required} label={label} error={hasError} className={className} helpMessage={helpMessage} testcid={testcid}>
-      <InputSelect ref={props.inputRef} {...props}/>
+      <InputSelect ref={props.inputRef} {...props} />
     </FormInput>
   );
 }
@@ -881,7 +902,7 @@ FormInputSelect.propTypes = {
 };
 
 /* React wrapper on color pickr */
-export function InputColor({value, controlProps, disabled, onChange, currObj}) {
+export function InputColor({ value, controlProps, disabled, onChange, currObj }) {
   const pickrOptions = {
     showPalette: true,
     allowEmpty: true,
@@ -896,19 +917,19 @@ export function InputColor({value, controlProps, disabled, onChange, currObj}) {
   const pickrObj = useRef();
   const classes = useStyles();
 
-  const setColor = (newVal)=>{
+  const setColor = (newVal) => {
     pickrObj.current &&
       pickrObj.current.setColor((_.isUndefined(newVal) || newVal == '') ? pickrOptions.defaultColor : newVal);
   };
 
-  const destroyPickr = ()=>{
-    if(pickrObj.current) {
+  const destroyPickr = () => {
+    if (pickrObj.current) {
       pickrObj.current.destroy();
       pickrObj.current = null;
     }
   };
 
-  const initPickr = ()=>{
+  const initPickr = () => {
     /* pickr does not have way to update options, need to
     destroy and recreate pickr to reflect options */
     destroyPickr();
@@ -920,7 +941,7 @@ export function InputColor({value, controlProps, disabled, onChange, currObj}) {
       swatches: [
         '#000', '#666', '#ccc', '#fff', '#f90', '#ff0', '#0f0',
         '#f0f', '#f4cccc', '#fce5cd', '#d0e0e3', '#cfe2f3', '#ead1dc', '#ea9999',
-        '#b6d7a8', '#a2c4c9', '#d5a6bd', '#e06666','#93c47d', '#76a5af', '#c27ba0',
+        '#b6d7a8', '#a2c4c9', '#d5a6bd', '#e06666', '#93c47d', '#76a5af', '#c27ba0',
         '#f1c232', '#6aa84f', '#45818e', '#a64d79', '#bf9000', '#0c343d', '#4c1130',
       ],
       position: pickrOptions.position,
@@ -941,20 +962,20 @@ export function InputColor({value, controlProps, disabled, onChange, currObj}) {
       setColor(value);
       disabled && instance.disable();
 
-      const {lastColor} = instance.getRoot().preview;
-      const {clear} = instance.getRoot().interaction;
+      const { lastColor } = instance.getRoot().preview;
+      const { clear } = instance.getRoot().interaction;
 
       /* Cycle the keyboard navigation within the color picker */
-      clear.addEventListener('keydown', (e)=>{
-        if(e.keyCode === 9) {
+      clear.addEventListener('keydown', (e) => {
+        if (e.keyCode === 9) {
           e.preventDefault();
           e.stopPropagation();
           lastColor.focus();
         }
       });
 
-      lastColor.addEventListener('keydown', (e)=>{
-        if(e.keyCode === 9 && e.shiftKey) {
+      lastColor.addEventListener('keydown', (e) => {
+        if (e.keyCode === 9 && e.shiftKey) {
           e.preventDefault();
           e.stopPropagation();
           clear.focus();
@@ -965,32 +986,32 @@ export function InputColor({value, controlProps, disabled, onChange, currObj}) {
     }).on('change', (color) => {
       onChange && onChange(color.toHEXA().toString());
     }).on('show', (color, instance) => {
-      const {palette} = instance.getRoot().palette;
+      const { palette } = instance.getRoot().palette;
       palette.focus();
     }).on('hide', (instance) => {
       const button = instance.getRoot().button;
       button.focus();
     });
 
-    if(currObj) {
+    if (currObj) {
       currObj(pickrObj.current);
     }
   };
 
-  useEffect(()=>{
+  useEffect(() => {
     initPickr();
-    return ()=>{
+    return () => {
       destroyPickr();
     };
   }, [...Object.values(pickrOptions)]);
 
-  useEffect(()=>{
-    if(pickrObj.current) {
+  useEffect(() => {
+    if (pickrObj.current) {
       setColor(value);
     }
   }, [value]);
 
-  let btnStyles = {backgroundColor: value};
+  let btnStyles = { backgroundColor: value };
   return (
     <PgIconButton ref={eleRef} title={gettext('Select the color')} className={classes.colorBtn} style={btnStyles} disabled={pickrOptions.disabled}
       icon={(_.isUndefined(value) || _.isNull(value) || value === '') && <CloseIcon />}
@@ -1006,11 +1027,11 @@ InputColor.propTypes = {
 };
 
 export function FormInputColor({
-  hasError, required, className, label, helpMessage, testcid, ...props}) {
+  hasError, required, className, label, helpMessage, testcid, ...props }) {
 
   return (
     <FormInput required={required} label={label} error={hasError} className={className} helpMessage={helpMessage} testcid={testcid}>
-      <InputColor {...props}/>
+      <InputColor {...props} />
     </FormInput>
   );
 }
@@ -1023,9 +1044,9 @@ FormInputColor.propTypes = {
   testcid: PropTypes.string,
 };
 
-export function PlainString({controlProps, value}) {
+export function PlainString({ controlProps, value }) {
   let finalValue = value;
-  if(controlProps?.formatter) {
+  if (controlProps?.formatter) {
     finalValue = controlProps.formatter.fromRaw(finalValue);
   }
   return <span>{finalValue}</span>;
@@ -1035,7 +1056,7 @@ PlainString.propTypes = {
   value: PropTypes.any,
 };
 
-export function FormNote({text, className}) {
+export function FormNote({ text, className }) {
   const classes = useStyles();
   return (
     <Box className={className}>
@@ -1051,7 +1072,7 @@ FormNote.propTypes = {
   className: CustomPropTypes.className,
 };
 
-const useStylesFormFooter = makeStyles((theme)=>({
+const useStylesFormFooter = makeStyles((theme) => ({
   root: {
     padding: theme.spacing(0.5),
     position: 'absolute',
@@ -1108,7 +1129,7 @@ const useStylesFormFooter = makeStyles((theme)=>({
 export function FormFooterMessage(props) {
   const classes = useStylesFormFooter();
 
-  if(!props.message) {
+  if (!props.message) {
     return <></>;
   }
   return (
@@ -1122,15 +1143,54 @@ FormFooterMessage.propTypes = {
   message: PropTypes.string,
 };
 
-export function NotifierMessage({type=MESSAGE_TYPE.SUCCESS, message, closable=true, onClose=()=>{/*This is intentional (SonarQube)*/}}) {
+export function FormInputKeyboardShortcut({ hasError, label, className, helpMessage, testcid, onChange, ...props }) {
+  const cid = _.uniqueId('c');
+  const helpid = `h${cid}`;
+  return (
+    <FormInput label={label} error={hasError} className={className} helpMessage={helpMessage} testcid={testcid}>
+      <KeyboardShortcuts cid={cid} helpid={helpid} onChange={onChange} {...props} />
+    </FormInput>
+
+  );
+}
+FormInputKeyboardShortcut.propTypes = {
+  hasError: PropTypes.bool,
+  label: PropTypes.string,
+  className: CustomPropTypes.className,
+  helpMessage: PropTypes.string,
+  testcid: PropTypes.string,
+  onChange: PropTypes.func
+};
+
+export function FormInputQueryThreshold({ hasError, label, className, helpMessage, testcid, onChange, ...props }) {
+  const cid = _.uniqueId('c');
+  const helpid = `h${cid}`;
+  return (
+    <FormInput label={label} error={hasError} className={className} helpMessage={helpMessage} testcid={testcid}>
+      <QueryThresholds cid={cid} helpid={helpid} onChange={onChange} {...props} />
+    </FormInput>
+
+  );
+}
+FormInputQueryThreshold.propTypes = {
+  hasError: PropTypes.bool,
+  label: PropTypes.string,
+  className: CustomPropTypes.className,
+  helpMessage: PropTypes.string,
+  testcid: PropTypes.string,
+  onChange: PropTypes.func
+};
+
+
+export function NotifierMessage({ type = MESSAGE_TYPE.SUCCESS, message, closable = true, onClose = () => {/*This is intentional (SonarQube)*/ } }) {
   const classes = useStylesFormFooter();
 
   return (
     <Box className={clsx(classes.container, classes[`container${type}`])}>
-      <FormIcon type={type} className={classes[`icon${type}`]}/>
+      <FormIcon type={type} className={classes[`icon${type}`]} />
       <Box className={classes.message}>{message}</Box>
       {closable && <IconButton className={clsx(classes.closeButton, classes[`icon${type}`])} onClick={onClose}>
-        <FormIcon close={true}/>
+        <FormIcon close={true} />
       </IconButton>}
     </Box>
   );
diff --git a/web/pgadmin/static/js/components/KeyboardShortcuts.jsx b/web/pgadmin/static/js/components/KeyboardShortcuts.jsx
new file mode 100644
index 00000000..76aed7cb
--- /dev/null
+++ b/web/pgadmin/static/js/components/KeyboardShortcuts.jsx
@@ -0,0 +1,103 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2022, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import _ from 'lodash';
+import { FormGroup, FormControlLabel, makeStyles } from '@material-ui/core';
+import React from 'react';
+import { InputCheckbox, InputText } from './FormComponents';
+import PropTypes from 'prop-types';
+
+const useStyles = makeStyles(() => ({
+  formControlLabel: {
+    padding: '3px',
+  },
+  formInput: {
+    marginLeft: '5px'
+  },
+  formCheckboxControl: {
+    padding: '3px',
+    border: '1px solid',
+    borderRadius: '0.25rem',
+  },
+  formGroup: {
+    padding: '5px'
+  }
+}));
+
+export default function KeyboardShortcuts({ value, onChange, fields }) {
+  const classes = useStyles();
+  const keyCid = _.uniqueId('c');
+  const keyhelpid = `h${keyCid}`;
+  const shiftCid = _.uniqueId('c');
+  const shifthelpid = `h${shiftCid}`;
+  const ctrlCid = _.uniqueId('c');
+  const ctrlhelpid = `h${ctrlCid}`;
+  const altCid = _.uniqueId('c');
+  const althelpid = `h${altCid}`;
+
+  const onKeyDown = (e) => {
+    let newVal = { ...value };
+    let _val = e.key;
+    if (e.keyCode == 32) {
+      _val = 'Space';
+    }
+    newVal.key = {
+      char: _val,
+      key_code: e.keyCode
+    };
+    onChange(newVal);
+  };
+
+  const onShiftChange = (e) => {
+    let newVal = { ...value };
+    newVal.shift = e.target.checked;
+    onChange(newVal);
+  };
+
+  const onCtrlChange = (e) => {
+    let newVal = { ...value };
+    newVal.ctrl = e.target.checked;
+    onChange(newVal);
+  };
+
+  const onAltChange = (e) => {
+    let newVal = { ...value };
+    newVal.alt = e.target.checked;
+    onChange(newVal);
+  };
+
+  return (
+    <FormGroup row={true} className={classes.formGroup}>
+      {fields.map(element => {
+        if (element.type == 'keyCode') {
+          return <FormControlLabel  key={_.uniqueId('c')} labelPlacement="start" className={classes.formControlLabel} control={<InputText cid={keyCid} helpid={keyhelpid} type='text' value={value?.key?.char} controlProps={
+            {
+              onKeyDown: onKeyDown,
+            }
+          }></InputText>} label={element.label} />;
+        } else if (element.name == 'shift') {
+          return <FormControlLabel key={_.uniqueId('c')} labelPlacement="start" className={classes.formCheckboxControl} control={<InputCheckbox cid={shiftCid} helpid={shifthelpid} value={value?.shift} onChange={onShiftChange}></InputCheckbox>} label={element.label} />;
+        } else if (element.name == 'control') {
+          return <FormControlLabel key={_.uniqueId('c')} labelPlacement="start" className={classes.formCheckboxControl} control={<InputCheckbox cid={ctrlCid} helpid={ctrlhelpid} value={value?.ctrl} onChange={onCtrlChange} ></InputCheckbox>} label={element.label} />;
+        } else if (element.name == 'alt') {
+          return <FormControlLabel key={_.uniqueId('c')} labelPlacement="start" className={classes.formCheckboxControl} control={<InputCheckbox cid={altCid} helpid={althelpid} value={value?.alt} onChange={onAltChange}></InputCheckbox>} label={element.label} />;
+        }
+      })
+
+      }
+    </FormGroup>
+  );
+}
+
+KeyboardShortcuts.propTypes = {
+  value: PropTypes.object,
+  onChange: PropTypes.func,
+  controlProps: PropTypes.object,
+  fields: PropTypes.array
+};
diff --git a/web/pgadmin/static/js/components/QueryThresholds.jsx b/web/pgadmin/static/js/components/QueryThresholds.jsx
new file mode 100644
index 00000000..16f5ca76
--- /dev/null
+++ b/web/pgadmin/static/js/components/QueryThresholds.jsx
@@ -0,0 +1,67 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2022, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import gettext from 'sources/gettext';
+import _ from 'lodash';
+import { FormGroup, FormControlLabel, makeStyles } from '@material-ui/core';
+import React from 'react';
+import { InputText } from './FormComponents';
+import PropTypes from 'prop-types';
+
+const useStyles = makeStyles(() => ({
+  formControlLabel: {
+    padding: '3px',
+  },
+  formInput: {
+    marginLeft: '5px'
+  },
+  formCheckboxControl: {
+    padding: '3px',
+    border: '1px solid',
+    borderRadius: '0.25rem',
+  },
+  formGroup: {
+    padding: '5px'
+  }
+}));
+
+export default function QueryThresholds({ value, onChange }) {
+  const classes = useStyles();
+  const warningCid = _.uniqueId('c');
+  const warninghelpid = `h${warningCid}`;
+  const alertCid = _.uniqueId('c');
+  const alerthelpid = `h${alertCid}`;
+  
+  const onWarningChange = (val) => {
+    let new_val = {...value};
+    new_val['warning'] = val;
+    onChange(new_val);
+  };
+
+  const onAlertChange = (val) => {
+    let new_val = {...value};
+    new_val['alert'] = val;
+    onChange(new_val);
+  };
+
+  return (
+    <FormGroup row={true} className={classes.formGroup}>
+      <FormControlLabel  key={_.uniqueId('c')} labelPlacement="start" className={classes.formControlLabel} control={<InputText cid={warningCid} helpid={warninghelpid} type='numeric' value={value?.warning}
+        onChange={onWarningChange}
+      ></InputText>} label={gettext('Warning')} />;
+      <FormControlLabel  key={_.uniqueId('c')} labelPlacement="start" className={classes.formControlLabel} control={<InputText cid={alertCid} helpid={alerthelpid} type='numeric' value={value?.alert} 
+        onChange={onAlertChange}></InputText>} label={gettext('Alert')} />;
+    </FormGroup>
+  );
+}
+
+QueryThresholds.propTypes = {
+  value: PropTypes.object,
+  onChange: PropTypes.func,
+};
diff --git a/web/pgadmin/static/js/helpers/ModalProvider.jsx b/web/pgadmin/static/js/helpers/ModalProvider.jsx
index 7e4fe526..d7b0b261 100644
--- a/web/pgadmin/static/js/helpers/ModalProvider.jsx
+++ b/web/pgadmin/static/js/helpers/ModalProvider.jsx
@@ -8,11 +8,13 @@
 //////////////////////////////////////////////////////////////
 
 import { Box, Dialog, DialogContent, DialogTitle, makeStyles, Paper } from '@material-ui/core';
-import React from 'react';
-import {getEpoch} from 'sources/utils';
+import React, { useState } from 'react';
+import { getEpoch } from 'sources/utils';
 import { DefaultButton, PgIconButton, PrimaryButton } from '../components/Buttons';
 import Draggable from 'react-draggable';
 import CloseIcon from '@material-ui/icons/CloseRounded';
+import FullscreenExitIcon from '@material-ui/icons/FullscreenExit';
+import FullscreenIcon from '@material-ui/icons/Fullscreen';
 import CustomPropTypes from '../custom_prop_types';
 import PropTypes from 'prop-types';
 import gettext from 'sources/gettext';
@@ -25,7 +27,7 @@ const ModalContext = React.createContext({});
 export function useModal() {
   return React.useContext(ModalContext);
 }
-const useAlertStyles = makeStyles((theme)=>({
+const useAlertStyles = makeStyles((theme) => ({
   footer: {
     display: 'flex',
     justifyContent: 'flex-end',
@@ -37,11 +39,11 @@ const useAlertStyles = makeStyles((theme)=>({
   }
 }));
 
-function AlertContent({text, confirm, okLabel=gettext('OK'), cancelLabel=gettext('Cancel'), onOkClick, onCancelClick}) {
+function AlertContent({ text, confirm, okLabel = gettext('OK'), cancelLabel = gettext('Cancel'), onOkClick, onCancelClick }) {
   const classes = useAlertStyles();
   return (
     <Box display="flex" flexDirection="column" height="100%">
-      <Box flexGrow="1" p={2}>{typeof(text) == 'string' ? HTMLReactParser(text) : text}</Box>
+      <Box flexGrow="1" p={2}>{typeof (text) == 'string' ? HTMLReactParser(text) : text}</Box>
       <Box className={classes.footer}>
         {confirm &&
           <DefaultButton startIcon={<CloseIcon />} onClick={onCancelClick} >{cancelLabel}</DefaultButton>
@@ -60,10 +62,10 @@ AlertContent.propTypes = {
   cancelLabel: PropTypes.string,
 };
 
-function alert(title, text, onOkClick, okLabel=gettext('OK')){
+function alert(title, text, onOkClick, okLabel = gettext('OK')) {
   // bind the modal provider before calling
-  this.showModal(title, (closeModal)=>{
-    const onOkClickClose = ()=>{
+  this.showModal(title, (closeModal) => {
+    const onOkClickClose = () => {
       onOkClick && onOkClick();
       closeModal();
     };
@@ -73,45 +75,53 @@ function alert(title, text, onOkClick, okLabel=gettext('OK')){
   });
 }
 
-function confirm(title, text, onOkClick, onCancelClick, okLabel=gettext('Yes'), cancelLabel=gettext('No')) {
+function confirm(title, text, onOkClick, onCancelClick, okLabel = gettext('Yes'), cancelLabel = gettext('No')) {
   // bind the modal provider before calling
-  this.showModal(title, (closeModal)=>{
-    const onCancelClickClose = ()=>{
+  this.showModal(title, (closeModal) => {
+    const onCancelClickClose = () => {
       onCancelClick && onCancelClick();
       closeModal();
     };
-    const onOkClickClose = ()=>{
+    const onOkClickClose = () => {
       onOkClick && onOkClick();
       closeModal();
     };
     return (
-      <AlertContent text={text} confirm onOkClick={onOkClickClose} onCancelClick={onCancelClickClose} okLabel={okLabel} cancelLabel={cancelLabel}/>
+      <AlertContent text={text} confirm onOkClick={onOkClickClose} onCancelClick={onCancelClickClose} okLabel={okLabel} cancelLabel={cancelLabel} />
     );
   });
 }
 
-export default function ModalProvider({children}) {
+export default function ModalProvider({ children }) {
   const [modals, setModals] = React.useState([]);
 
-  const showModal = (title, content, modalOptions)=>{
+  const showModal = (title, content, modalOptions) => {
     let id = getEpoch().toString() + Math.random();
-    setModals((prev)=>[...prev, {
+    setModals((prev) => [...prev, {
       id: id,
       title: title,
       content: content,
       ...modalOptions,
     }]);
   };
-  const closeModal = (id)=>{
-    setModals((prev)=>{
-      return prev.filter((o)=>o.id!=id);
+  const closeModal = (id) => {
+    setModals((prev) => {
+      return prev.filter((o) => o.id != id);
     });
   };
+
+  const fullScreenModal = (fullScreen) => {
+    setModals((prev) => [...prev, {
+      fullScreen: fullScreen,
+    }]);
+  };
+
   const modalContextBase = {
     showModal: showModal,
     closeModal: closeModal,
+    fullScreenModal: fullScreenModal
   };
-  const modalContext = React.useMemo(()=>({
+  const modalContext = React.useMemo(() => ({
     ...modalContextBase,
     confirm: confirm.bind(modalContextBase),
     alert: alert.bind(modalContextBase)
@@ -119,8 +129,8 @@ export default function ModalProvider({children}) {
   return (
     <ModalContext.Provider value={modalContext}>
       {children}
-      {modals.map((modalOptions, i)=>(
-        <ModalContainer key={i} {...modalOptions}/>
+      {modals.map((modalOptions, i) => (
+        <ModalContainer key={i} {...modalOptions} />
       ))}
     </ModalContext.Provider>
   );
@@ -133,28 +143,55 @@ ModalProvider.propTypes = {
 function PaperComponent(props) {
   return (
     <Draggable cancel={'[class*="MuiDialogContent-root"]'}>
-      <Paper {...props} style={{minWidth: '600px'}} />
+      <Paper {...props} style={{ minWidth: '600px' }} />
     </Draggable>
   );
 }
 
-function ModalContainer({id, title, content}) {
+const useModalStyles = makeStyles(() => ({
+  titleBar: {
+    display: 'flex',
+    flexGrow: 1
+  },
+  title: {
+    flexGrow: 1
+  },
+}));
+
+function ModalContainer({ id, title, content, fullScreen = false, maxWidth = 'md', isFullWidth = false, showFullScreen = false }) {
   let useModalRef = useModal();
-  let closeModal = ()=>useModalRef.closeModal(id);
+  const classes = useModalStyles();
+  let closeModal = () => useModalRef.closeModal(id);
+  const [isfullScreen, setIsFullScreen] = useState(fullScreen);
+
   return (
     <Theme>
       <Dialog
         open={true}
         onClose={closeModal}
         PaperComponent={PaperComponent}
+        fullScreen={isfullScreen}
+        maxWidth={maxWidth}
+        fullWidth={isFullWidth}
         disableBackdropClick
       >
         <DialogTitle>
-          <Box marginRight="0.25rem">{title}</Box>
-          <Box marginLeft="auto"><PgIconButton title={gettext('Close')} icon={<CloseIcon />} size="xs" noBorder onClick={closeModal}/></Box>
+          <Box className={classes.titleBar}>
+            <Box className={classes.title} marginRight="0.25rem" >{title}</Box>
+            {
+              showFullScreen && !isfullScreen &&
+            <Box marginLeft="auto"><PgIconButton title={gettext('Maximize')} icon={<FullscreenIcon />} size="xs" noBorder onClick={() => { setIsFullScreen(!isfullScreen);  }} /></Box>
+            }
+            {
+              showFullScreen && isfullScreen &&
+              <Box marginLeft="auto"><PgIconButton title={gettext('Minimize')} icon={<FullscreenExitIcon />} size="xs" noBorder onClick={() => { setIsFullScreen(!isfullScreen); }} /></Box>
+            }
+
+            <Box marginLeft="auto"><PgIconButton title={gettext('Close')} icon={<CloseIcon />} size="xs" noBorder onClick={closeModal} /></Box>
+          </Box>
         </DialogTitle>
-        <DialogContent>
-          {content(closeModal)}
+        <DialogContent height="100%">
+          {content(closeModal, isfullScreen)}
         </DialogContent>
       </Dialog>
     </Theme>
@@ -164,4 +201,8 @@ ModalContainer.propTypes = {
   id: PropTypes.string,
   title: CustomPropTypes.children,
   content: PropTypes.func,
+  fullScreen: PropTypes.bool,
+  maxWidth: PropTypes.string,
+  isFullWidth: PropTypes.bool,
+  showFullScreen: PropTypes.bool
 };
diff --git a/web/pgadmin/static/js/helpers/Notifier.jsx b/web/pgadmin/static/js/helpers/Notifier.jsx
index d67faa38..af11762f 100644
--- a/web/pgadmin/static/js/helpers/Notifier.jsx
+++ b/web/pgadmin/static/js/helpers/Notifier.jsx
@@ -8,6 +8,13 @@
 //////////////////////////////////////////////////////////////
 
 import { useSnackbar, SnackbarProvider, SnackbarContent } from 'notistack';
+import { makeStyles } from '@material-ui/core/styles';
+import {Box} from '@material-ui/core';
+import CloseIcon from '@material-ui/icons/CloseRounded';
+import { DefaultButton, PrimaryButton } from '../components/Buttons';
+import HTMLReactParser from 'html-react-parser';
+import CheckRoundedIcon from '@material-ui/icons/CheckRounded';
+import PropTypes from 'prop-types';
 import React from 'react';
 import ReactDOM from 'react-dom';
 import Theme from 'sources/Theme';
@@ -76,6 +83,41 @@ FinalNotifyContent.propTypes = {
   children: CustomPropTypes.children,
 };
 
+const useModalStyles = makeStyles((theme)=>({
+  footer: {
+    display: 'flex',
+    justifyContent: 'flex-end',
+    padding: '0.5rem',
+    ...theme.mixins.panelBorder.top,
+  },
+  margin: {
+    marginLeft: '0.25rem',
+  }
+}));
+function AlertContent({text, confirm, okLabel=gettext('OK'), cancelLabel=gettext('Cancel'), onOkClick, onCancelClick}) {
+  const classes = useModalStyles();
+  return (
+    <Box display="flex" flexDirection="column" height="100%">
+      <Box flexGrow="1" p={2}>{HTMLReactParser(text)}</Box>
+      <Box className={classes.footer}>
+        {confirm &&
+          <DefaultButton startIcon={<CloseIcon />} onClick={onCancelClick} >{cancelLabel}</DefaultButton>
+        }
+        <PrimaryButton className={classes.margin} startIcon={<CheckRoundedIcon />} onClick={onOkClick} autoFocus={true} >{okLabel}</PrimaryButton>
+      </Box>
+    </Box>
+  );
+}
+AlertContent.propTypes = {
+  text: PropTypes.string,
+  confirm: PropTypes.bool,
+  onOkClick: PropTypes.func,
+  onCancelClick: PropTypes.func,
+  okLabel: PropTypes.string,
+  cancelLabel: PropTypes.string,
+};
+
+
 var Notifier = {
   success(msg, autoHideDuration = AUTO_HIDE_DURATION) {
     this._callNotify(msg, MESSAGE_TYPE.SUCCESS, autoHideDuration);
@@ -195,11 +237,11 @@ var Notifier = {
     }
     modalRef.confirm(title, text, onOkClick, onCancelClick, okLabel, cancelLabel);
   },
-  showModal(title, content) {
+  showModal: (title, content, modalOptions) => {
     if(!modalInitialized) {
       initializeModalProvider();
     }
-    modalRef.showModal(title, content);
+    modalRef.showModal(title, content, modalOptions);
   }
 };
 
diff --git a/web/pgadmin/static/js/tree/preference_nodes.ts b/web/pgadmin/static/js/tree/preference_nodes.ts
new file mode 100644
index 00000000..605f4a03
--- /dev/null
+++ b/web/pgadmin/static/js/tree/preference_nodes.ts
@@ -0,0 +1,253 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2021, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import * as BrowserFS from 'browserfs'
+import pgAdmin from 'sources/pgadmin';
+import _ from 'underscore';
+import { FileType } from 'react-aspen'
+import { findInTree } from './tree';
+
+import { unix } from 'path-fx';
+
+export class ManagePreferenceTreeNodes {
+  constructor(data) {
+    this.tree = {}
+    this.tempTree = new TreeNode(undefined, {});
+    this.treeData = data;
+  }
+
+  public init = (_root: string) => new Promise((res, rej) => {
+    let node = { parent: null, children: [], data: null };
+    this.tree = {};
+    this.tree[_root] = { name: 'root', type: FileType.Directory, metadata: node };
+    res();
+  })
+
+  public updateNode = (_path, _data) => new Promise((res, rej) => {
+    const item = this.findNode(_path);
+    if (item) {
+      item.name = _data.label;
+      item.metadata.data = _data;
+    }
+    res(true);
+  })
+
+  public removeNode = async (_path, _removeOnlyChild) => {
+    const item = this.findNode(_path);
+
+    if (item && item.parentNode) {
+      item.children = [];
+      item.parentNode.children.splice(item.parentNode.children.indexOf(item), 1);
+    }
+    return true;
+  };
+
+  findNode(path) {
+    if (path === null || path === undefined || path.length === 0 || path == '/preferences') {
+      return this.tempTree;
+    }
+    console.log(path)
+    return findInTree(this.tempTree, path);
+  }
+
+  public addNode = (_parent: string, _path: string, _data: []) => new Promise((res, rej) => {
+    _data.type = _data.inode ? FileType.Directory : FileType.File;
+    _data._label = _data.label;
+    _data.label = _.escape(_data.label);
+
+    _data.is_collection = isCollectionNode(_data._type);
+    let nodeData = { parent: _parent, children: _data?.children ? _data.children : [], data: _data };
+
+    let tmpParentNode = this.findNode(_parent);
+    let treeNode = new TreeNode(_data.id, _data, {}, tmpParentNode, nodeData, _data.type);
+
+    if (tmpParentNode !== null && tmpParentNode !== undefined) tmpParentNode.children.push(treeNode);
+
+    res(treeNode);
+  })
+
+  public readNode = (_path: string) => new Promise<string[]>((res, rej) => {
+    let temp_tree_path = _path,
+      node = this.findNode(_path),
+      base_url = '/preferences/';
+    node.children = [];
+
+    if (node && node.children.length > 0) {
+      if (!node.type === FileType.File) {
+        rej("It's a leaf node")
+      }
+      else {
+        if (node?.children.length != 0) res(node.children)
+      }
+    }
+
+    var self = this;
+
+    async function loadData() {
+      const Path = BrowserFS.BFSRequire('path')
+      const fill = async (tree) => {
+        //remove this is code clenup
+        for (let idx in tree) {
+          const _node = tree[idx]
+          const _pathl = Path.join(_path, _node.id)
+          await self.addNode(temp_tree_path, _pathl, _node);
+        }
+      }
+
+      if (node && !_.isUndefined(node.id)) {
+        let _data = self.treeData.find((el) => el.id == node.id);
+        // console.log('Inside sub nodes')
+
+        let subNodes = [];
+
+        _data.childrenNodes.forEach(element => {
+          subNodes.push(element)
+        });
+
+        await fill(subNodes);
+      } else {
+        await fill(self.treeData);
+      }
+
+      if (node?.children.length > 0) return res(node.children);
+      else return res(null);
+
+    }
+    loadData();
+  })
+
+}
+
+
+
+export class TreeNode {
+  constructor(id, data, domNode, parent, metadata, type) {
+    this.id = id;
+    this.data = data;
+    this.setParent(parent);
+    this.children = [];
+    this.domNode = domNode;
+    this.metadata = metadata;
+    this.name = metadata ? metadata.data.label : "";
+    this.type = type ? type : undefined;
+  }
+
+  hasParent() {
+    return this.parentNode !== null && this.parentNode !== undefined;
+  }
+
+  parent() {
+    return this.parentNode;
+  }
+
+  setParent(parent) {
+    this.parentNode = parent;
+    this.path = this.id;
+    if (this.id)
+      if (parent !== null && parent !== undefined && parent.path !== undefined) {
+        this.path = parent.path + '/' + this.id;
+      } else {
+        this.path = '/preferences/' + this.id;
+      }
+  }
+
+  getData() {
+    if (this.data === undefined) {
+      return undefined;
+    } else if (this.data === null) {
+      return null;
+    }
+    return Object.assign({}, this.data);
+  }
+
+  getHtmlIdentifier() {
+    return this.domNode;
+  }
+
+  /*
+   * Find the ancestor with matches this condition
+   */
+  ancestorNode(condition) {
+    let node = this;
+
+    while (node.hasParent()) {
+      node = node.parent();
+      if (condition(node)) {
+        return node;
+      }
+    }
+
+    return null;
+  }
+
+  /**
+   * Given a condition returns true if the current node
+   * or any of the parent nodes condition result is true
+   */
+  anyFamilyMember(condition) {
+    if (condition(this)) {
+      return true;
+    }
+
+    return this.ancestorNode(condition) !== null;
+  }
+  anyParent(condition) {
+    return this.ancestorNode(condition) !== null;
+  }
+
+  reload(tree) {
+    return new Promise((resolve) => {
+      this.unload(tree)
+        .then(() => {
+          tree.setInode(this.domNode);
+          tree.deselect(this.domNode);
+          setTimeout(() => {
+            tree.selectNode(this.domNode);
+          }, 0);
+          resolve();
+        });
+    });
+  }
+
+  unload(tree) {
+    return new Promise((resolve, reject) => {
+      this.children = [];
+      tree.unload(this.domNode)
+        .then(
+          () => {
+            resolve(true);
+          },
+          () => {
+            reject();
+          });
+    });
+  }
+
+
+  open(tree, suppressNoDom) {
+    return new Promise((resolve, reject) => {
+      if (suppressNoDom && (this.domNode == null || typeof (this.domNode) === 'undefined')) {
+        resolve(true);
+      } else if (tree.isOpen(this.domNode)) {
+        resolve(true);
+      } else {
+        tree.open(this.domNode).then(val => resolve(true), err => reject(true));
+      }
+    });
+  }
+
+}
+
+export function isCollectionNode(node) {
+  if (pgAdmin.Browser.Nodes && node in pgAdmin.Browser.Nodes) {
+    if (pgAdmin.Browser.Nodes[node].is_collection !== undefined) return pgAdmin.Browser.Nodes[node].is_collection;
+    else return false;
+  }
+  return false;
+}
diff --git a/web/pgadmin/static/js/tree/preferences_tree.tsx b/web/pgadmin/static/js/tree/preferences_tree.tsx
new file mode 100644
index 00000000..f19db9de
--- /dev/null
+++ b/web/pgadmin/static/js/tree/preferences_tree.tsx
@@ -0,0 +1,53 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2021, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import * as React from 'react';
+import { render } from 'react-dom';
+import { FileTreeX, TreeModelX } from 'pgadmin4-tree';
+import {Tree} from './tree';
+
+import { IBasicFileSystemHost } from 'react-aspen';
+import { ManagePreferenceTreeNodes } from './preference_nodes';
+
+var initPreferencesTree = async (pgBrowser, container, data) => {
+  const MOUNT_POINT = '/preferences'
+
+  // Setup host
+  let ptree = new ManagePreferenceTreeNodes(data);
+
+  // Init Tree with the Tree Parent node '/browser'
+  ptree.init(MOUNT_POINT);
+  const host: IBasicFileSystemHost = {
+    pathStyle: 'unix',
+    getItems: async (path) => {
+      return ptree.readNode(path);
+    },
+  }
+
+  const pTreeModelX = new TreeModelX(host, MOUNT_POINT)
+
+  const itemHandle = function onReady(handler) {
+    // Initialize pgBrowser Tree
+    pgBrowser.ptree = new Tree(handler, ptree, pgBrowser, false);
+    return true;
+  }
+
+  await pTreeModelX.root.ensureLoaded()
+
+  // Render Browser Tree
+  await render(
+      <FileTreeX model={pTreeModelX}
+        onReady={itemHandle} />
+     , container);
+}
+
+module.exports = {
+    initPreferencesTree: initPreferencesTree,
+};
+
diff --git a/web/pgadmin/tools/sqleditor/utils/query_tool_preferences.py b/web/pgadmin/tools/sqleditor/utils/query_tool_preferences.py
index 46a326da..b18145e0 100644
--- a/web/pgadmin/tools/sqleditor/utils/query_tool_preferences.py
+++ b/web/pgadmin/tools/sqleditor/utils/query_tool_preferences.py
@@ -197,7 +197,7 @@ def register_query_tool_preferences(self):
         options=[{'label': gettext('None'), 'value': 'none'},
                  {'label': gettext('All'), 'value': 'all'},
                  {'label': gettext('Strings'), 'value': 'strings'}],
-        select2={
+        control_props={
             'allowClear': False,
             'tags': False
         }
@@ -209,9 +209,9 @@ def register_query_tool_preferences(self):
         category_label=PREF_LABEL_CSV_TXT,
         options=[{'label': '"', 'value': '"'},
                  {'label': '\'', 'value': '\''}],
-        select2={
+        control_props={
             'allowClear': False,
-            'tags': True
+            'tags': False
         }
     )
 
@@ -223,9 +223,9 @@ def register_query_tool_preferences(self):
                  {'label': ',', 'value': ','},
                  {'label': '|', 'value': '|'},
                  {'label': gettext('Tab'), 'value': '\t'}],
-        select2={
+        control_props={
             'allowClear': False,
-            'tags': True
+            'tags': False
         }
     )
 
@@ -247,7 +247,7 @@ def register_query_tool_preferences(self):
         options=[{'label': gettext('None'), 'value': 'none'},
                  {'label': gettext('All'), 'value': 'all'},
                  {'label': gettext('Strings'), 'value': 'strings'}],
-        select2={
+        control_props={
             'allowClear': False,
             'tags': False
         }
@@ -259,9 +259,9 @@ def register_query_tool_preferences(self):
         category_label=PREF_LABEL_RESULTS_GRID,
         options=[{'label': '"', 'value': '"'},
                  {'label': '\'', 'value': '\''}],
-        select2={
+        control_props={
             'allowClear': False,
-            'tags': True
+            'tags': False
         }
     )
 
@@ -273,9 +273,9 @@ def register_query_tool_preferences(self):
                  {'label': ',', 'value': ','},
                  {'label': '|', 'value': '|'},
                  {'label': gettext('Tab'), 'value': '\t'}],
-        select2={
+        control_props={
             'allowClear': False,
-            'tags': True
+            'tags': False
         }
     )
 
diff --git a/web/pgadmin/utils/preferences.py b/web/pgadmin/utils/preferences.py
index 09011804..e07424cb 100644
--- a/web/pgadmin/utils/preferences.py
+++ b/web/pgadmin/utils/preferences.py
@@ -66,10 +66,11 @@ class _Preference(object):
         self.label = label
         self._type = _type
         self.help_str = kwargs.get('help_str', None)
+        self.control_props = kwargs.get('control_props', None)
         self.min_val = kwargs.get('min_val', None)
         self.max_val = kwargs.get('max_val', None)
         self.options = kwargs.get('options', None)
-        self.select2 = kwargs.get('select2', None)
+        self.select = kwargs.get('select', None)
         self.fields = kwargs.get('fields', None)
         self.allow_blanks = kwargs.get('allow_blanks', None)
         self.disabled = kwargs.get('disabled', False)
@@ -146,10 +147,10 @@ class _Preference(object):
             for opt in self.options:
                 if 'value' in opt and opt['value'] == res.value:
                     return True, res.value
-            if self.select2 and self.select2['tags']:
+            if self.select and self.select['tags']:
                 return True, res.value
             return True, self.default
-        if self._type == 'select2':
+        if self._type == 'select':
             if res.value:
                 res.value = res.value.replace('[', '')
                 res.value = res.value.replace(']', '')
@@ -190,7 +191,7 @@ class _Preference(object):
                 has_value = next((True for opt in self.options
                                   if 'value' in opt and opt['value'] == value),
                                  False)
-                assert (has_value or (self.select2 and self.select2['tags']))
+                assert (has_value or (self.select and self.select['tags']))
             elif self._type == 'date':
                 value = parser_map[self._type](value).date()
             else:
@@ -248,10 +249,11 @@ class _Preference(object):
             'label': self.label or self.name,
             'type': self._type,
             'help_str': self.help_str,
+            'control_props': self.control_props,
             'min_val': self.min_val,
             'max_val': self.max_val,
             'options': self.options,
-            'select2': self.select2,
+            'select': self.select,
             'value': self.get(),
             'fields': self.fields,
             'disabled': self.disabled,
@@ -393,7 +395,7 @@ class Preferences(object):
         return res
 
     def register(
-            self, category, name, label, _type, default, **kwargs
+        self, category, name, label, _type, default, **kwargs
     ):
         """
         register
@@ -414,7 +416,7 @@ class Preferences(object):
         :param options:
         :param help_str:
         :param category_label:
-        :param select2: select2 control extra options
+        :param select: select control extra options
         :param fields: field schema (if preference has more than one field to
                         take input from user e.g. keyboardshortcut preference)
         :param allow_blanks: Flag specify whether to allow blank value.
@@ -424,8 +426,9 @@ class Preferences(object):
         max_val = kwargs.get('max_val', None)
         options = kwargs.get('options', None)
         help_str = kwargs.get('help_str', None)
+        control_props = kwargs.get('control_props', {})
         category_label = kwargs.get('category_label', None)
-        select2 = kwargs.get('select2', None)
+        select = kwargs.get('select', None)
         fields = kwargs.get('fields', None)
         allow_blanks = kwargs.get('allow_blanks', None)
         disabled = kwargs.get('disabled', False)
@@ -440,14 +443,15 @@ class Preferences(object):
         assert _type in (
             'boolean', 'integer', 'numeric', 'date', 'datetime',
             'options', 'multiline', 'switch', 'node', 'text', 'radioModern',
-            'keyboardshortcut', 'select2', 'selectFile', 'threshold'
+            'keyboardshortcut', 'select', 'selectFile', 'threshold'
         ), "Type cannot be found in the defined list!"
 
         (cat['preferences'])[name] = res = _Preference(
             cat['id'], name, label, _type, default, help_str=help_str,
             min_val=min_val, max_val=max_val, options=options,
-            select2=select2, fields=fields, allow_blanks=allow_blanks,
-            disabled=disabled, dependents=dependents
+            select=select, fields=fields, allow_blanks=allow_blanks,
+            disabled=disabled, dependents=dependents,
+            control_props=control_props
         )
 
         return res
@@ -483,7 +487,7 @@ class Preferences(object):
 
     @classmethod
     def register_preference(
-            cls, module, category, name, label, _type, **kwargs
+        cls, module, category, name, label, _type, **kwargs
     ):
         """
         register
@@ -503,6 +507,7 @@ class Preferences(object):
         max_val = kwargs.get('max_val', None)
         options = kwargs.get('options', None)
         help_str = kwargs.get('help_str', None)
+        control_props = kwargs.get('control_props', None)
         module_label = kwargs.get('module_label', None)
         category_label = kwargs.get('category_label', None)
 
@@ -516,6 +521,7 @@ class Preferences(object):
         return m.register(
             category, name, label, _type, default, min_val=min_val,
             max_val=max_val, options=options, help_str=help_str,
+            control_props=control_props,
             category_label=category_label
         )
 
diff --git a/web/regression/javascript/components/KeyboardShortcuts.spec.js b/web/regression/javascript/components/KeyboardShortcuts.spec.js
new file mode 100644
index 00000000..1a92ff88
--- /dev/null
+++ b/web/regression/javascript/components/KeyboardShortcuts.spec.js
@@ -0,0 +1,100 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2022, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import jasmineEnzyme from 'jasmine-enzyme';
+import React from 'react';
+import '../helper/enzyme.helper';
+import { withTheme } from '../fake_theme';
+import { createMount } from '@material-ui/core/test-utils';
+import {
+  OutlinedInput,
+} from '@material-ui/core';
+import KeyboardShortcuts from '../../../pgadmin/static/js/components/KeyboardShortcuts';
+
+/* MUI Components need to be wrapped in Theme for theme vars */
+describe('KeyboardShortcuts', () => {
+  let mount;
+  let defult_value = {
+    'ctrl': true,
+    'alt': true,
+    'key': {
+      'char': 'a',
+      'key_code': 97
+    }
+  };
+  let fields = [{
+    type: 'keyCode',
+    label: 'Key'
+  }, {
+    name: 'shift',
+    label: 'Shift',
+    type: 'checkbox'
+  },
+  {
+    name: 'control',
+    label: 'Control',
+    type: 'checkbox'
+  },
+  {
+    name: 'alt',
+    label: 'Alt/Option',
+    type: 'checkbox'
+  }];
+
+  /* Use createMount so that material ui components gets the required context */
+  /* https://material-ui.com/guides/testing/#api */
+  beforeAll(() => {
+    mount = createMount();
+  });
+
+  afterAll(() => {
+    mount.cleanUp();
+  });
+
+  beforeEach(() => {
+    jasmineEnzyme();
+  });
+
+  describe('KeyboardShortcuts', () => {
+    let ThemedFormInputKeyboardShortcuts = withTheme(KeyboardShortcuts), ctrl;
+
+    beforeEach(() => {
+      ctrl = mount(
+        <ThemedFormInputKeyboardShortcuts
+          testcid="inpCid"
+          helpMessage="some help message"
+          /* InputText */
+          readonly={false}
+          disabled={false}
+          maxlength={1}
+          value={defult_value}
+          fields={fields}
+          controlProps={{
+            extraprop: 'test'
+          }}
+        />);
+    });
+
+    it('init', () => {
+      expect(ctrl.find(OutlinedInput).prop('value')).toBe('a');
+    });
+
+    it('Key change', () => {
+      let onChange = () => {/*This is intentional (SonarQube)*/ };
+      ctrl.setProps({
+        controlProps: {
+          onKeyDown: onChange
+        }
+      });
+
+      expect(ctrl.find(OutlinedInput).prop('value')).toBe('a');
+    });
+  });
+
+});
diff --git a/web/regression/javascript/components/QueryThreshold.spec.js b/web/regression/javascript/components/QueryThreshold.spec.js
new file mode 100644
index 00000000..fa259ae0
--- /dev/null
+++ b/web/regression/javascript/components/QueryThreshold.spec.js
@@ -0,0 +1,86 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2022, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import jasmineEnzyme from 'jasmine-enzyme';
+import React from 'react';
+import '../helper/enzyme.helper';
+import { withTheme } from '../fake_theme';
+import { createMount } from '@material-ui/core/test-utils';
+import {
+  OutlinedInput,
+} from '@material-ui/core';
+import QueryThresholds from '../../../pgadmin/static/js/components/QueryThresholds';
+
+/* MUI Components need to be wrapped in Theme for theme vars */
+describe('QueryThresholds', () => {
+  let mount;
+  let defult_value = {
+    'warning': 5,
+    'alert': 6
+  };
+
+  /* Use createMount so that material ui components gets the required context */
+  /* https://material-ui.com/guides/testing/#api */
+  beforeAll(() => {
+    mount = createMount();
+  });
+
+  afterAll(() => {
+    mount.cleanUp();
+  });
+
+  beforeEach(() => {
+    jasmineEnzyme();
+  });
+
+  describe('QueryThresholds', () => {
+    let ThemedFormInputQueryThresholds = withTheme(QueryThresholds), ctrl;
+
+    beforeEach(() => {
+      ctrl = mount(
+        <ThemedFormInputQueryThresholds
+          testcid="inpCid"
+          helpMessage="some help message"
+          /* InputText */
+          readonly={false}
+          disabled={false}
+          maxlength={1}
+          value={defult_value}
+          controlProps={{
+            extraprop: 'test'
+          }}
+        />);
+    });
+
+    it('init Warning', () => {
+      expect(ctrl.find(OutlinedInput).at(0).prop('value')).toBe(5);
+    });
+
+    it('init Alert', () => {
+      expect(ctrl.find(OutlinedInput).at(1).prop('value')).toBe(6);
+    });
+
+    it('warning change', () => {
+      let onChange = () => {/*This is intentional (SonarQube)*/ };
+      ctrl.setProps({
+        onChange: onChange
+      });
+      expect(ctrl.find(OutlinedInput).at(0).prop('value')).toBe(5);
+    });
+
+    it('Alert change', () => {
+      let onChange = () => {/*This is intentional (SonarQube)*/ };
+      ctrl.setProps({
+        onChange: onChange
+      });
+      expect(ctrl.find(OutlinedInput).at(1).prop('value')).toBe(6);
+    });
+  });
+
+});
diff --git a/web/regression/javascript/schema_ui_files/binary_path.ui.spec.js b/web/regression/javascript/schema_ui_files/binary_path.ui.spec.js
new file mode 100644
index 00000000..f51dfefc
--- /dev/null
+++ b/web/regression/javascript/schema_ui_files/binary_path.ui.spec.js
@@ -0,0 +1,44 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2022, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import '../helper/enzyme.helper';
+import { createMount } from '@material-ui/core/test-utils';
+import {genericBeforeEach, getPropertiesView} from '../genericFunctions';
+import {getBinaryPathSchema} from '../../../pgadmin/browser/server_groups/servers/static/js/binary_path.ui';
+
+describe('BinaryPathschema', ()=>{
+  let mount;
+  let schemaObj = getBinaryPathSchema();
+  let getInitData = ()=>Promise.resolve({});
+
+  /* Use createMount so that material ui components gets the required context */
+  /* https://material-ui.com/guides/testing/#api */
+  beforeAll(()=>{
+    mount = createMount();
+  });
+
+  afterAll(() => {
+    mount.cleanUp();
+  });
+
+  beforeEach(()=>{
+    genericBeforeEach();
+  });
+
+  it('edit', ()=>{
+    mount(getPropertiesView(schemaObj, getInitData));
+  });
+
+  it('validate path', ()=>{
+    let validate = _.find(schemaObj.fields, (f)=>f.id=='binaryPath').validate;
+    let status = validate('/test/');
+    expect(status).toBe(true);
+  });
+
+});
diff --git a/web/webpack.shim.js b/web/webpack.shim.js
index f45efde8..a2e8f28e 100644
--- a/web/webpack.shim.js
+++ b/web/webpack.shim.js
@@ -282,7 +282,7 @@ var webpackShimConfig = {
     'pgadmin.node.user_mapping': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/user_mappings/static/js/user_mapping'),
     'pgadmin.node.view': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/view'),
     'pgadmin.node.row_security_policy': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/schemas/tables/row_security_policies/static/js/row_security_policy'),
-    'pgadmin.preferences': path.join(__dirname, './pgadmin/preferences/static/js/preferences'),
+    'pgadmin.preferences': path.join(__dirname, './pgadmin/preferences/static/js/'),
     'pgadmin.settings': path.join(__dirname, './pgadmin/settings/static/js/settings'),
     'pgadmin.server.supported_servers': '/browser/server/supported_servers',
     'pgadmin.sqleditor': path.join(__dirname, './pgadmin/tools/sqleditor/static/js/sqleditor'),


view thread (7+ 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]
  Subject: Re: [pgAdmin][RM-7149]: [React] Port preferences dialog to React.
  In-Reply-To: <CAOBg0AMfBEFesek3Uoet9zuNbHP5xA8OzWNGkP8JTt5CrGk9_w@mail.gmail.com>

* 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