From 5e96f0fd61cd29c8eff63fe5edfd0f12ca25830a Mon Sep 17 00:00:00 2001 From: Ashesh Vashi Date: Mon, 16 Sep 2024 00:04:37 +0530 Subject: [PATCH] Fixes the Variable Schema UI issues and InlineView bug reported in #7884 * Show the icon for the 'Reset' button. (Reference #7884) * Reload the server list after connecting to a server in the 'New connection' dialog (QueryTool). (Reference: #7884) * Pass the grid path during the bulk update (click on a radio action) * Don't assign the cell value to the 'rowValue' variable. * Don't rely on the 'optionsLoaded' for setting the variable types as it is loaded asynchronously, and variable types data may not be available while rendering the 'value' cell. (Fixes #7884) * Fixed a type while checking for the 'inline-group'. fixes (#7884) * 'vnameOptions' can be a Promise function too, hence - taken care accrodingly. * Introduced a parameter 'reloadOnDepChanges' in the BaseSchemaUI field to force reload the control on value change for one of the dependencies. * Reload on the components in case of dependent value changes. * Introduced 'useSchemaStateSubscriber', which generates a state subscriber mananager instance. It helps multiple subscribers in a single control as we could have multiple subscribe within a control. (For example - value, options, errors, etc). * Fixed all the issues reported (#7884) --- .../foreign_key/static/js/foreign_key.ui.js | 3 +- .../servers/static/js/variable.ui.js | 56 ++++++----- .../js/Dialogs/ChangePasswordContent.jsx | 8 +- .../js/SchemaView/DataGridView/grid.jsx | 14 ++- .../js/SchemaView/DataGridView/mappedCell.jsx | 13 ++- .../static/js/SchemaView/FieldSetView.jsx | 10 +- web/pgadmin/static/js/SchemaView/FormView.jsx | 18 ++-- .../static/js/SchemaView/MappedControl.jsx | 29 +++--- .../static/js/SchemaView/hooks/index.js | 2 + .../js/SchemaView/hooks/useFieldError.js | 30 ++++-- .../js/SchemaView/hooks/useFieldOptions.js | 15 ++- .../js/SchemaView/hooks/useFieldSchema.js | 20 ++-- .../js/SchemaView/hooks/useFieldValue.js | 15 ++- .../hooks/useSchemaStateSubscriber.js | 92 +++++++++++++++++++ .../SchemaView/utils/createFieldControls.jsx | 6 +- .../js/SchemaView/utils/listenDepChanges.js | 31 ++++--- .../static/js/components/FormComponents.jsx | 1 - .../js/components/dialogs/MacrosDialog.jsx | 7 +- .../dialogs/NewConnectionDialog.jsx | 6 +- .../static/js/UserManagementDialog.jsx | 7 +- 20 files changed, 262 insertions(+), 121 deletions(-) create mode 100644 web/pgadmin/static/js/SchemaView/hooks/useSchemaStateSubscriber.js diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/static/js/foreign_key.ui.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/static/js/foreign_key.ui.js index eb2c9cb06..e797ec79b 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/static/js/foreign_key.ui.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/static/js/foreign_key.ui.js @@ -96,7 +96,8 @@ class ForeignKeyHeaderSchema extends BaseUISchema { return state._disable_references; } },{ - id: 'referenced', label: gettext('Referencing'), editable: false, deps: ['references'], + id: 'referenced', label: gettext('Referencing'), editable: false, + deps: ['references'], type: (state)=>{ return { type: 'select', diff --git a/web/pgadmin/browser/server_groups/servers/static/js/variable.ui.js b/web/pgadmin/browser/server_groups/servers/static/js/variable.ui.js index 0f01355e9..187a9b696 100644 --- a/web/pgadmin/browser/server_groups/servers/static/js/variable.ui.js +++ b/web/pgadmin/browser/server_groups/servers/static/js/variable.ui.js @@ -22,7 +22,7 @@ export function getNodeVariableSchema(nodeObj, treeNodeInfo, itemNodeData, hasDa keys.push('role'); } return new VariableSchema( - ()=>getNodeAjaxOptions('vopts', nodeObj, treeNodeInfo, itemNodeData, null, (vars)=>{ + () => getNodeAjaxOptions('vopts', nodeObj, treeNodeInfo, itemNodeData, null, (vars)=>{ let res = []; _.each(vars, function(v) { res.push({ @@ -38,8 +38,8 @@ export function getNodeVariableSchema(nodeObj, treeNodeInfo, itemNodeData, hasDa return res; }), - ()=>getNodeListByName('database', treeNodeInfo, itemNodeData), - ()=>getNodeListByName('role', treeNodeInfo, itemNodeData), + () => getNodeListByName('database', treeNodeInfo, itemNodeData), + () => getNodeListByName('role', treeNodeInfo, itemNodeData), keys ); } @@ -59,6 +59,8 @@ export default class VariableSchema extends BaseUISchema { this.varTypes = {}; this.keys = keys; this.allReadOnly = false; + + setTimeout(() => this.setVarTypes(vnameOptions), 0); } setAllReadOnly(isReadOnly) { @@ -66,10 +68,18 @@ export default class VariableSchema extends BaseUISchema { } setVarTypes(options) { - options.forEach((option)=>{ - this.varTypes[option.value] = { - ...option, - }; + let optPromise = options; + + if (typeof options === 'function') { + optPromise = options(); + } + + Promise.resolve(optPromise).then((res) => { + res.forEach((option) => { + this.varTypes[option.value] = { + ...option, + }; + }); }); } @@ -145,13 +155,10 @@ export default class VariableSchema extends BaseUISchema { }, { id: 'name', label: gettext('Name'), type:'text', - editable: function(state) { - return obj.isNew(state) || !obj.allReadOnly; - }, + editable: (state) => (obj.isNew(state) || !obj.allReadOnly), cell: () => ({ cell: 'select', - options: this.vnameOptions, - optionsLoaded: (options)=>{obj.setVarTypes(options);}, + options: obj.vnameOptions, controlProps: { allowClear: false }, }), }, @@ -165,9 +172,9 @@ export default class VariableSchema extends BaseUISchema { { id: 'value', label: gettext('Value'), type: 'text', deps: ['name'], editable: !obj.allReadOnly, - depChange: (state, source)=>{ + depChange: (state, source) => { if(source[source.length-1] == 'name') { - let variable = this.varTypes[state.name]; + let variable = obj.varTypes[state.name]; if(variable.vartype === 'bool'){ return { value: false, @@ -178,19 +185,20 @@ export default class VariableSchema extends BaseUISchema { }; } }, - cell: (row)=>{ - let variable = this.varTypes[row.name]; - return this.getValueFieldProps(variable); + cell: (row) => { + let variable = obj.varTypes[row.name]; + return obj.getValueFieldProps(variable); } }, - {id: 'database', label: gettext('Database'), type: 'text', - cell: ()=>({cell: 'select', options: this.databaseOptions }), + { + id: 'database', label: gettext('Database'), type: 'text', + cell: ()=>({cell: 'select', options: obj.databaseOptions }), }, - {id: 'role', label: gettext('Role'), type: 'text', - cell: ()=>({cell: 'select', options: this.roleOptions, - controlProps: { - allowClear: false, - } + { + id: 'role', label: gettext('Role'), type: 'text', + cell: () => ({ + cell: 'select', options: obj.roleOptions, + controlProps: { allowClear: false }, }), }, ]; diff --git a/web/pgadmin/static/js/Dialogs/ChangePasswordContent.jsx b/web/pgadmin/static/js/Dialogs/ChangePasswordContent.jsx index 149bc309f..6ce2b46c9 100644 --- a/web/pgadmin/static/js/Dialogs/ChangePasswordContent.jsx +++ b/web/pgadmin/static/js/Dialogs/ChangePasswordContent.jsx @@ -65,10 +65,16 @@ class ChangePasswordSchema extends BaseUISchema { export default function ChangePasswordContent({getInitData=() => { /*This is intentional (SonarQube)*/ }, onSave, onClose, hasCsrfToken=false, showUser=true}) { + const schema=React.useRef(null); + if (!schema.current) + schema.current = new ChangePasswordSchema( + '', false, hasCsrfToken, showUser + ); + return { - setRefreshKey(newKey); - } + [...accessPath, 'length'], schemaState, subscriberManager ); useEffect(() => { diff --git a/web/pgadmin/static/js/SchemaView/DataGridView/mappedCell.jsx b/web/pgadmin/static/js/SchemaView/DataGridView/mappedCell.jsx index a8081c697..6c422d09e 100644 --- a/web/pgadmin/static/js/SchemaView/DataGridView/mappedCell.jsx +++ b/web/pgadmin/static/js/SchemaView/DataGridView/mappedCell.jsx @@ -16,7 +16,9 @@ import { evalFunc } from 'sources/utils'; import { MappedCellControl } from '../MappedControl'; import { SCHEMA_STATE_ACTIONS, SchemaStateContext } from '../SchemaState'; import { flatternObject } from '../common'; -import { useFieldOptions, useFieldValue } from '../hooks'; +import { + useFieldOptions, useFieldValue, useSchemaStateSubscriber +} from '../hooks'; import { listenDepChanges } from '../utils'; import { DataGridContext, DataGridRowContext } from './context'; @@ -25,14 +27,17 @@ import { DataGridContext, DataGridRowContext } from './context'; export function getMappedCell({field}) { const Cell = ({reRenderRow, getValue}) => { - const [key, setKey] = useState(0); + const [, setKey] = useState(0); + const subscriberManager = useSchemaStateSubscriber(setKey); const schemaState = useContext(SchemaStateContext); const { dataDispatch, accessPath } = useContext(DataGridContext); const { rowAccessPath, row } = useContext(DataGridRowContext); const colAccessPath = schemaState.accessPath(rowAccessPath, field.id); - let colOptions = useFieldOptions(colAccessPath, schemaState, key, setKey); - let value = useFieldValue(colAccessPath, schemaState, key, setKey); + let colOptions = useFieldOptions( + colAccessPath, schemaState, subscriberManager + ); + let value = useFieldValue(colAccessPath, schemaState, subscriberManager); let rowValue = useFieldValue(rowAccessPath, schemaState); listenDepChanges(colAccessPath, field, true, schemaState); diff --git a/web/pgadmin/static/js/SchemaView/FieldSetView.jsx b/web/pgadmin/static/js/SchemaView/FieldSetView.jsx index 556b92305..ead9295ab 100644 --- a/web/pgadmin/static/js/SchemaView/FieldSetView.jsx +++ b/web/pgadmin/static/js/SchemaView/FieldSetView.jsx @@ -15,7 +15,9 @@ import CustomPropTypes from 'sources/custom_prop_types'; import { FieldControl } from './FieldControl'; import { SchemaStateContext } from './SchemaState'; -import { useFieldSchema, useFieldValue } from './hooks'; +import { + useFieldSchema, useFieldValue, useSchemaStateSubscriber, +} from './hooks'; import { registerView } from './registry'; import { createFieldControls, listenDepChanges } from './utils'; @@ -23,13 +25,15 @@ import { createFieldControls, listenDepChanges } from './utils'; export default function FieldSetView({ field, accessPath, dataDispatch, viewHelperProps, controlClassName, }) { - const [key, setRefreshKey] = useState(0); + const [, setKey] = useState(0); + const subscriberManager = useSchemaStateSubscriber(setKey); const schema = field.schema; const schemaState = useContext(SchemaStateContext); const value = useFieldValue(accessPath, schemaState); const options = useFieldSchema( - field, accessPath, value, viewHelperProps, schemaState, key, setRefreshKey + field, accessPath, value, viewHelperProps, schemaState, subscriberManager ); + const label = field.label; listenDepChanges(accessPath, field, options.visible, schemaState); diff --git a/web/pgadmin/static/js/SchemaView/FormView.jsx b/web/pgadmin/static/js/SchemaView/FormView.jsx index fce33f112..0d47b25b7 100644 --- a/web/pgadmin/static/js/SchemaView/FormView.jsx +++ b/web/pgadmin/static/js/SchemaView/FormView.jsx @@ -27,7 +27,9 @@ import { FieldControl } from './FieldControl'; import { SQLTab } from './SQLTab'; import { FormContentBox } from './StyledComponents'; import { SchemaStateContext } from './SchemaState'; -import { useFieldSchema, useFieldValue } from './hooks'; +import { + useFieldSchema, useFieldValue, useSchemaStateSubscriber, +} from './hooks'; import { registerView, View } from './registry'; import { createFieldControls, listenDepChanges } from './utils'; @@ -62,10 +64,11 @@ export default function FormView({ showError=false, resetKey, focusOnFirstInput=false }) { const [key, setKey] = useState(0); + const subscriberManager = useSchemaStateSubscriber(setKey); const schemaState = useContext(SchemaStateContext); const value = useFieldValue(accessPath, schemaState); const { visible } = useFieldSchema( - field, accessPath, value, viewHelperProps, schemaState, key, setKey + field, accessPath, value, viewHelperProps, schemaState, subscriberManager ); const [tabValue, setTabValue] = useState(0); @@ -106,13 +109,12 @@ export default function FormView({ useEffect(() => { // Refresh on message changes. - return schemaState.subscribe( - ['errors', 'message'], + return subscriberManager.current?.add( + schemaState, ['errors', 'message'], 'states', (newState, prevState) => { - if (_.isUndefined(newState) || _.isUndefined(prevState)); - setKey(Date.now()); - }, - 'states' + if (_.isUndefined(newState) || _.isUndefined(prevState)) + subscriberManager.current?.signal(); + } ); }, [key]); diff --git a/web/pgadmin/static/js/SchemaView/MappedControl.jsx b/web/pgadmin/static/js/SchemaView/MappedControl.jsx index 1bdd98800..9aed32e05 100644 --- a/web/pgadmin/static/js/SchemaView/MappedControl.jsx +++ b/web/pgadmin/static/js/SchemaView/MappedControl.jsx @@ -28,7 +28,7 @@ import { evalFunc } from 'sources/utils'; import { SchemaStateContext } from './SchemaState'; import { isValueEqual } from './common'; import { - useFieldOptions, useFieldValue, useFieldError + useFieldOptions, useFieldValue, useFieldError, useSchemaStateSubscriber, } from './hooks'; import { listenDepChanges } from './utils'; @@ -339,22 +339,15 @@ export const MappedFormControl = ({ }) => { const checkIsMounted = useIsMounted(); const [key, setKey] = useState(0); + const subscriberManager = useSchemaStateSubscriber(setKey); const schemaState = useContext(SchemaStateContext); const state = schemaState.data; - const avoidRenderingWhenNotMounted = (newKey) => { - if (checkIsMounted()) { - setKey(newKey); - } + const value = useFieldValue(accessPath, schemaState, subscriberManager); + const options = useFieldOptions(accessPath, schemaState, subscriberManager); + const {hasError} = useFieldError(accessPath, schemaState, subscriberManager); + const avoidRenderingWhenNotMounted = (...args) => { + if (checkIsMounted()) subscriberManager.current?.signal(...args); }; - const value = useFieldValue( - accessPath, schemaState, key, avoidRenderingWhenNotMounted - ); - const options = useFieldOptions( - accessPath, schemaState, key, avoidRenderingWhenNotMounted - ); - const { hasError } = useFieldError( - accessPath, schemaState, key, avoidRenderingWhenNotMounted - ); const origOnChange = onChange; @@ -369,7 +362,10 @@ export const MappedFormControl = ({ if (!isValueEqual(changedValue, currValue)) origOnChange(changedValue); }; - listenDepChanges(accessPath, field, options.visible, schemaState); + const depVals = listenDepChanges( + accessPath, field, options.visible, schemaState, state, + avoidRenderingWhenNotMounted + ); let newProps = { ...props, @@ -394,14 +390,17 @@ export const MappedFormControl = ({ newProps.onClick = ()=>{ origOnClick?.(); }; + // FIXME:: Get this list from the option registry. const memDeps = ['disabled', 'visible', 'readonly'].map( option => options[option] ); + memDeps.push(value); memDeps.push(hasError); memDeps.push(key); memDeps.push(JSON.stringify(accessPath)); + memDeps.push(depVals); // Filter out garbage props if any using ALLOWED_PROPS_FIELD. return useMemo( diff --git a/web/pgadmin/static/js/SchemaView/hooks/index.js b/web/pgadmin/static/js/SchemaView/hooks/index.js index c294172fd..e64238d1d 100644 --- a/web/pgadmin/static/js/SchemaView/hooks/index.js +++ b/web/pgadmin/static/js/SchemaView/hooks/index.js @@ -12,6 +12,7 @@ import { useFieldOptions } from './useFieldOptions'; import { useFieldValue } from './useFieldValue'; import { useSchemaState } from './useSchemaState'; import { useFieldSchema } from './useFieldSchema'; +import { useSchemaStateSubscriber } from './useSchemaStateSubscriber'; export { @@ -20,4 +21,5 @@ export { useFieldValue, useFieldSchema, useSchemaState, + useSchemaStateSubscriber, }; diff --git a/web/pgadmin/static/js/SchemaView/hooks/useFieldError.js b/web/pgadmin/static/js/SchemaView/hooks/useFieldError.js index 075427250..282837078 100644 --- a/web/pgadmin/static/js/SchemaView/hooks/useFieldError.js +++ b/web/pgadmin/static/js/SchemaView/hooks/useFieldError.js @@ -9,26 +9,36 @@ import { useEffect } from 'react'; +const isPathEqual = (path1, path2) => ( + JSON.stringify(path1) === JSON.stringify(path2) +); + + +export const useFieldError = (path, schemaState, subscriberManager) => { -export const useFieldError = ( - path, schemaState, key, setRefreshKey -) => { useEffect(() => { - if (!schemaState || !setRefreshKey) return; + if (!schemaState || !subscriberManager?.current) return; const checkPathError = (newState, prevState) => { - if (prevState.name !== path && newState.name !== path) return; // We don't need to redraw the control on message change. - if (prevState.name === newState.name) return; + if (( + !isPathEqual(prevState.name, path) && + !isPathEqual(newState.name, path) + ) || ( + isPathEqual(prevState.name, newState.name) && + prevState.message == newState.message + )) return; - setRefreshKey({id: Date.now()}); + subscriberManager.current?.signal(); }; - return schemaState.subscribe(['errors'], checkPathError, 'states'); - }, [key, schemaState?._id]); + return subscriberManager.current?.add( + schemaState, ['errors'], 'states', checkPathError + ); + }); const errors = schemaState?.errors || {}; - const error = errors.name === path ? errors.message : null; + const error = isPathEqual(errors.name, path) ? errors.message : null; return {hasError: !_.isNull(error), error}; }; diff --git a/web/pgadmin/static/js/SchemaView/hooks/useFieldOptions.js b/web/pgadmin/static/js/SchemaView/hooks/useFieldOptions.js index 5763edc24..b4c6d5ecc 100644 --- a/web/pgadmin/static/js/SchemaView/hooks/useFieldOptions.js +++ b/web/pgadmin/static/js/SchemaView/hooks/useFieldOptions.js @@ -10,16 +10,13 @@ import { useEffect } from 'react'; -export const useFieldOptions = ( - path, schemaState, key, setRefreshKey -) => { - useEffect(() => { - if (!schemaState) return; +export const useFieldOptions = (path, schemaState, subscriberManager) => { - return schemaState.subscribe( - path, () => setRefreshKey?.({id: Date.now()}), 'options' - ); - }, [key, schemaState?._id]); + useEffect(() => { + if (!schemaState || !subscriberManager?.current) return; + + return subscriberManager.current?.add(schemaState, path, 'options'); + }); return schemaState?.options(path) || {visible: true}; }; diff --git a/web/pgadmin/static/js/SchemaView/hooks/useFieldSchema.js b/web/pgadmin/static/js/SchemaView/hooks/useFieldSchema.js index 0cbd2bd94..5a1b7d126 100644 --- a/web/pgadmin/static/js/SchemaView/hooks/useFieldSchema.js +++ b/web/pgadmin/static/js/SchemaView/hooks/useFieldSchema.js @@ -14,32 +14,32 @@ import { booleanEvaluator } from '../options'; export const useFieldSchema = ( - field, accessPath, value, viewHelperProps, schemaState, key, setRefreshKey + field, accessPath, value, viewHelperProps, schemaState, subscriberManager ) => { + useEffect(() => { - if (!schemaState || !field) return; + if (!schemaState || !field || !subscriberManager?.current) return; // It already has 'id', 'options' is already evaluated. if (field.id) - return schemaState.subscribe( - accessPath, () => setRefreshKey?.({id: Date.now()}), 'options' - ); + return subscriberManager.current?.add(schemaState, accessPath, 'options'); // There are no dependencies. if (!_.isArray(field?.deps)) return; // Subscribe to all the dependents. const unsubscribers = field.deps.map((dep) => ( - schemaState.subscribe( - accessPath.concat(dep), () => setRefreshKey?.({id: Date.now()}), - 'value' + subscriberManager.current?.add( + schemaState, accessPath.concat(dep), 'value' ) )); return () => { - unsubscribers.forEach(unsubscribe => unsubscribe()); + unsubscribers.forEach( + unsubscribe => subscriberManager.current?.remove(unsubscribe) + ); }; - }, [key, schemaState?._id]); + }); if (!field) return { visible: true }; if (field.id) return schemaState?.options(accessPath); diff --git a/web/pgadmin/static/js/SchemaView/hooks/useFieldValue.js b/web/pgadmin/static/js/SchemaView/hooks/useFieldValue.js index 0e92ae9e4..4e58330aa 100644 --- a/web/pgadmin/static/js/SchemaView/hooks/useFieldValue.js +++ b/web/pgadmin/static/js/SchemaView/hooks/useFieldValue.js @@ -10,16 +10,13 @@ import { useEffect } from 'react'; -export const useFieldValue = ( - path, schemaState, key, setRefreshKey -) => { - useEffect(() => { - if (!schemaState || !setRefreshKey) return; +export const useFieldValue = (path, schemaState, subscriberManager) => { - return schemaState.subscribe( - path, () => setRefreshKey({id: Date.now()}), 'value' - ); - }, [key, schemaState?._id]); + useEffect(() => { + if (!schemaState || !subscriberManager?.current) return; + + return subscriberManager.current?.add(schemaState, path, 'value'); + }); return schemaState?.value(path); }; diff --git a/web/pgadmin/static/js/SchemaView/hooks/useSchemaStateSubscriber.js b/web/pgadmin/static/js/SchemaView/hooks/useSchemaStateSubscriber.js new file mode 100644 index 000000000..62a687829 --- /dev/null +++ b/web/pgadmin/static/js/SchemaView/hooks/useSchemaStateSubscriber.js @@ -0,0 +1,92 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2024, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import React from 'react'; + +///////// +// +// A class to handle the ScheamState subscription for a control to avoid +// rendering multiple times. +// +class SubscriberManager { + + constructor(refreshKeyCallback) { + this.mounted = true; + this.callback = refreshKeyCallback; + this.unsubscribers = new Set(); + this._id = Date.now(); + } + + add(schemaState, accessPath, kind, callback) { + if (!schemaState) return; + + callback = callback || (() => this.signal()); + + return this._add(schemaState.subscribe(accessPath, callback, kind)); + } + + _add(unsubscriber) { + if (!unsubscriber) return; + // Avoid reinsertion of same unsubscriber. + if (this.unsubscribers.has(unsubscriber)) return; + this.unsubscribers.add(unsubscriber); + + return () => this.remove(unsubscriber); + } + + remove(unsubscriber) { + if (!unsubscriber) return; + if (!this.unsubscribers.has(unsubscriber)) return; + this.unsubscribers.delete(unsubscriber); + unsubscriber(); + } + + signal() { + // Do nothing - if already work is in progress. + if (!this.mounted) return; + this.mounted = false; + this.release(); + this.callback(Date.now()); + } + + release () { + const unsubscribers = this.unsubscribers; + this.unsubscribers = new Set(); + this.mounted = true; + + setTimeout(() => { + Set.prototype.forEach.call( + unsubscribers, (unsubscriber) => unsubscriber() + ); + }, 0); + } + + mount() { + this.mounted = true; + } +} + +export function useSchemaStateSubscriber(refreshKeyCallback) { + const subscriberManager = React.useRef(null); + + React.useEffect(() => { + if (!subscriberManager.current) return; + + return () => { + subscriberManager.current?.release(); + }; + }, []); + + if (!subscriberManager.current) + subscriberManager.current = new SubscriberManager(refreshKeyCallback); + else + subscriberManager.current.mount(); + + return subscriberManager; +} diff --git a/web/pgadmin/static/js/SchemaView/utils/createFieldControls.jsx b/web/pgadmin/static/js/SchemaView/utils/createFieldControls.jsx index 18f2c98c0..0bf5867da 100644 --- a/web/pgadmin/static/js/SchemaView/utils/createFieldControls.jsx +++ b/web/pgadmin/static/js/SchemaView/utils/createFieldControls.jsx @@ -17,7 +17,7 @@ import { View, hasView } from '../registry'; import { StaticMappedFormControl, MappedFormControl } from '../MappedControl'; -const DEFAULT_TAB = 'general'; +const DEFAULT_TAB = gettext('General'); export const createFieldControls = ({ schema, schemaState, accessPath, viewHelperProps, dataDispatch @@ -50,13 +50,13 @@ export const createFieldControls = ({ }; // Create default group - 'General'. - createGroup(DEFAULT_TAB, gettext('General'), true); + createGroup(DEFAULT_TAB, DEFAULT_TAB, true); schema?.fields?.forEach((field) => { if (!isModeSupportedByField(field, viewHelperProps)) return; let inlineGroup = null; - const inlineGroupId = field[inlineGroup]; + const inlineGroupId = field['inlineGroup']; if(field.type === 'group') { diff --git a/web/pgadmin/static/js/SchemaView/utils/listenDepChanges.js b/web/pgadmin/static/js/SchemaView/utils/listenDepChanges.js index 6214da305..5c7d92f18 100644 --- a/web/pgadmin/static/js/SchemaView/utils/listenDepChanges.js +++ b/web/pgadmin/static/js/SchemaView/utils/listenDepChanges.js @@ -13,7 +13,16 @@ import _ from 'lodash'; import { evalFunc } from 'sources/utils'; -export const listenDepChanges = (accessPath, field, visible, schemaState) => { +export const listenDepChanges = ( + accessPath, field, visible, schemaState, data, setRefreshKey +) => { + const deps = field?.deps ? (evalFunc(null, field.deps) || []) : null; + const parentPath = accessPath ? [...accessPath] : []; + + // Remove the last element. + if (field?.id && field.id === parentPath[parentPath.length - 1]) { + parentPath.pop(); + } useEffect(() => { if (!visible || !schemaState || !field) return; @@ -26,25 +35,22 @@ export const listenDepChanges = (accessPath, field, visible, schemaState) => { } if (field.deps) { - const parentPath = [...accessPath]; - - // Remove the last element. - if (field.id && field.id === parentPath[parentPath.length - 1]) { - parentPath.pop(); - } - - (evalFunc(null, field.deps) || []).forEach((dep) => { - + deps.forEach((dep) => { // When dep is a string then prepend the complete accessPath, // but - when dep is an array, then the intention is to provide // the exact accesspath. let source = _.isArray(dep) ? dep : parentPath.concat(dep); - if(field.depChange || field.deferredDepChange) { + if (field.depChange || field.deferredDepChange) { schemaState.addDepListener( source, accessPath, field.depChange, field.deferredDepChange ); } + + if (setRefreshKey) + schemaState.subscribe( + source, () => setRefreshKey(Date.now()), 'value' + ); }); } @@ -54,4 +60,7 @@ export const listenDepChanges = (accessPath, field, visible, schemaState) => { }; }, []); + return deps?.map((dep) => schemaState.value( + _.isArray(dep) ? dep : parentPath.concat(dep) + )); }; diff --git a/web/pgadmin/static/js/components/FormComponents.jsx b/web/pgadmin/static/js/components/FormComponents.jsx index 95b49307a..834f3db91 100644 --- a/web/pgadmin/static/js/components/FormComponents.jsx +++ b/web/pgadmin/static/js/components/FormComponents.jsx @@ -955,7 +955,6 @@ export const InputSelect = forwardRef(({ return () => umounted = true; }, [optionsReloadBasis]); - /* Apply filter if any */ const filteredOptions = (controlProps.filter?.(finalOptions)) || finalOptions; const flatFiltered = flattenSelectOptions(filteredOptions); diff --git a/web/pgadmin/tools/sqleditor/static/js/components/dialogs/MacrosDialog.jsx b/web/pgadmin/tools/sqleditor/static/js/components/dialogs/MacrosDialog.jsx index 8cd994faa..86cb2b35a 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/dialogs/MacrosDialog.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/dialogs/MacrosDialog.jsx @@ -192,6 +192,11 @@ export default function MacrosDialog({onClose, onSave}) { return <>; } + const schema = React.useRef(null); + + if (!schema.current) + schema.current = new MacrosSchema(keyOptions); + return ( Boolean(m.name))}); }} - schema={new MacrosSchema(keyOptions)} + schema={schema.current} viewHelperProps={{ mode: 'edit', }} diff --git a/web/pgadmin/tools/sqleditor/static/js/components/dialogs/NewConnectionDialog.jsx b/web/pgadmin/tools/sqleditor/static/js/components/dialogs/NewConnectionDialog.jsx index fa2884aba..d92e98523 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/dialogs/NewConnectionDialog.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/dialogs/NewConnectionDialog.jsx @@ -172,9 +172,10 @@ class NewConnectionSchema extends BaseUISchema { } }, { id: 'user', label: gettext('User'), deps: ['sid', 'connected'], - noEmpty: true, controlProps: { allowClear: false }, + noEmpty: true, type: (state) => ({ type: 'select', + controlProps: { allowClear: false }, options: () => this.getOtherOptions( state.sid, 'get_new_connection_user' ), @@ -182,8 +183,9 @@ class NewConnectionSchema extends BaseUISchema { }), }, { id: 'role', label: gettext('Role'), deps: ['sid', 'connected'], - type: (state)=>({ + type: (state) => ({ type: 'select', + controlProps: { allowClear: false }, options: () => this.getOtherOptions( state.sid, 'get_new_connection_role' ), diff --git a/web/pgadmin/tools/user_management/static/js/UserManagementDialog.jsx b/web/pgadmin/tools/user_management/static/js/UserManagementDialog.jsx index 08ff1724a..4a0fcbe32 100644 --- a/web/pgadmin/tools/user_management/static/js/UserManagementDialog.jsx +++ b/web/pgadmin/tools/user_management/static/js/UserManagementDialog.jsx @@ -399,6 +399,11 @@ function UserManagementDialog({onClose}) { window.open(url_for('help.static', { 'filename': 'user_management.html' }), 'pgadmin_help'); }; + const schema = React.useRef(null); + + if (!schema.current) + schema.current = new UserManagementSchema(authSourcesOptions, roleOptions); + return { return new Promise((resolve, reject)=>{ @@ -410,7 +415,7 @@ function UserManagementDialog({onClose}) { reject(err instanceof Error ? err : Error(gettext('Something went wrong'))); }); }); }} - schema={new UserManagementSchema(authSourcesOptions, roleOptions)} + schema={schema.current} viewHelperProps={{ mode: 'edit', }}