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)
This commit is contained in:
Ashesh Vashi 2024-09-16 00:04:37 +05:30 committed by GitHub
parent 98d703645c
commit 5e96f0fd61
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 262 additions and 121 deletions

View File

@ -96,7 +96,8 @@ class ForeignKeyHeaderSchema extends BaseUISchema {
return state._disable_references; return state._disable_references;
} }
},{ },{
id: 'referenced', label: gettext('Referencing'), editable: false, deps: ['references'], id: 'referenced', label: gettext('Referencing'), editable: false,
deps: ['references'],
type: (state)=>{ type: (state)=>{
return { return {
type: 'select', type: 'select',

View File

@ -22,7 +22,7 @@ export function getNodeVariableSchema(nodeObj, treeNodeInfo, itemNodeData, hasDa
keys.push('role'); keys.push('role');
} }
return new VariableSchema( return new VariableSchema(
()=>getNodeAjaxOptions('vopts', nodeObj, treeNodeInfo, itemNodeData, null, (vars)=>{ () => getNodeAjaxOptions('vopts', nodeObj, treeNodeInfo, itemNodeData, null, (vars)=>{
let res = []; let res = [];
_.each(vars, function(v) { _.each(vars, function(v) {
res.push({ res.push({
@ -38,8 +38,8 @@ export function getNodeVariableSchema(nodeObj, treeNodeInfo, itemNodeData, hasDa
return res; return res;
}), }),
()=>getNodeListByName('database', treeNodeInfo, itemNodeData), () => getNodeListByName('database', treeNodeInfo, itemNodeData),
()=>getNodeListByName('role', treeNodeInfo, itemNodeData), () => getNodeListByName('role', treeNodeInfo, itemNodeData),
keys keys
); );
} }
@ -59,6 +59,8 @@ export default class VariableSchema extends BaseUISchema {
this.varTypes = {}; this.varTypes = {};
this.keys = keys; this.keys = keys;
this.allReadOnly = false; this.allReadOnly = false;
setTimeout(() => this.setVarTypes(vnameOptions), 0);
} }
setAllReadOnly(isReadOnly) { setAllReadOnly(isReadOnly) {
@ -66,10 +68,18 @@ export default class VariableSchema extends BaseUISchema {
} }
setVarTypes(options) { setVarTypes(options) {
options.forEach((option)=>{ let optPromise = options;
this.varTypes[option.value] = {
...option, 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', id: 'name', label: gettext('Name'), type:'text',
editable: function(state) { editable: (state) => (obj.isNew(state) || !obj.allReadOnly),
return obj.isNew(state) || !obj.allReadOnly;
},
cell: () => ({ cell: () => ({
cell: 'select', cell: 'select',
options: this.vnameOptions, options: obj.vnameOptions,
optionsLoaded: (options)=>{obj.setVarTypes(options);},
controlProps: { allowClear: false }, controlProps: { allowClear: false },
}), }),
}, },
@ -165,9 +172,9 @@ export default class VariableSchema extends BaseUISchema {
{ {
id: 'value', label: gettext('Value'), type: 'text', id: 'value', label: gettext('Value'), type: 'text',
deps: ['name'], editable: !obj.allReadOnly, deps: ['name'], editable: !obj.allReadOnly,
depChange: (state, source)=>{ depChange: (state, source) => {
if(source[source.length-1] == 'name') { if(source[source.length-1] == 'name') {
let variable = this.varTypes[state.name]; let variable = obj.varTypes[state.name];
if(variable.vartype === 'bool'){ if(variable.vartype === 'bool'){
return { return {
value: false, value: false,
@ -178,19 +185,20 @@ export default class VariableSchema extends BaseUISchema {
}; };
} }
}, },
cell: (row)=>{ cell: (row) => {
let variable = this.varTypes[row.name]; let variable = obj.varTypes[row.name];
return this.getValueFieldProps(variable); 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, id: 'role', label: gettext('Role'), type: 'text',
controlProps: { cell: () => ({
allowClear: false, cell: 'select', options: obj.roleOptions,
} controlProps: { allowClear: false },
}), }),
}, },
]; ];

View File

@ -65,10 +65,16 @@ class ChangePasswordSchema extends BaseUISchema {
export default function ChangePasswordContent({getInitData=() => { /*This is intentional (SonarQube)*/ }, export default function ChangePasswordContent({getInitData=() => { /*This is intentional (SonarQube)*/ },
onSave, onClose, hasCsrfToken=false, showUser=true}) { onSave, onClose, hasCsrfToken=false, showUser=true}) {
const schema=React.useRef(null);
if (!schema.current)
schema.current = new ChangePasswordSchema(
'', false, hasCsrfToken, showUser
);
return <SchemaView return <SchemaView
formType={'dialog'} formType={'dialog'}
getInitData={getInitData} getInitData={getInitData}
schema={new ChangePasswordSchema('', false, hasCsrfToken, showUser)} schema={schema.current}
viewHelperProps={{ viewHelperProps={{
mode: 'create', mode: 'create',
}} }}

View File

@ -33,7 +33,9 @@ import CustomPropTypes from 'sources/custom_prop_types';
import { StyleDataGridBox } from '../StyledComponents'; import { StyleDataGridBox } from '../StyledComponents';
import { SchemaStateContext } from '../SchemaState'; import { SchemaStateContext } from '../SchemaState';
import { useFieldOptions, useFieldValue } from '../hooks'; import {
useFieldOptions, useFieldValue, useSchemaStateSubscriber,
} from '../hooks';
import { registerView } from '../registry'; import { registerView } from '../registry';
import { listenDepChanges } from '../utils'; import { listenDepChanges } from '../utils';
@ -49,20 +51,16 @@ export default function DataGridView({
}) { }) {
const pgAdmin = usePgAdmin(); const pgAdmin = usePgAdmin();
const [refreshKey, setRefreshKey] = useState(0); const [refreshKey, setRefreshKey] = useState(0);
const subscriberManager = useSchemaStateSubscriber(setRefreshKey);
const schemaState = useContext(SchemaStateContext); const schemaState = useContext(SchemaStateContext);
const options = useFieldOptions( const options = useFieldOptions(accessPath, schemaState, subscriberManager);
accessPath, schemaState, refreshKey, setRefreshKey
);
const value = useFieldValue(accessPath, schemaState); const value = useFieldValue(accessPath, schemaState);
const schema = field.schema; const schema = field.schema;
const features = useRef(); const features = useRef();
// Update refresh key on changing the number of rows. // Update refresh key on changing the number of rows.
useFieldValue( useFieldValue(
[...accessPath, 'length'], schemaState, refreshKey, [...accessPath, 'length'], schemaState, subscriberManager
(newKey) => {
setRefreshKey(newKey);
}
); );
useEffect(() => { useEffect(() => {

View File

@ -16,7 +16,9 @@ import { evalFunc } from 'sources/utils';
import { MappedCellControl } from '../MappedControl'; import { MappedCellControl } from '../MappedControl';
import { SCHEMA_STATE_ACTIONS, SchemaStateContext } from '../SchemaState'; import { SCHEMA_STATE_ACTIONS, SchemaStateContext } from '../SchemaState';
import { flatternObject } from '../common'; import { flatternObject } from '../common';
import { useFieldOptions, useFieldValue } from '../hooks'; import {
useFieldOptions, useFieldValue, useSchemaStateSubscriber
} from '../hooks';
import { listenDepChanges } from '../utils'; import { listenDepChanges } from '../utils';
import { DataGridContext, DataGridRowContext } from './context'; import { DataGridContext, DataGridRowContext } from './context';
@ -25,14 +27,17 @@ import { DataGridContext, DataGridRowContext } from './context';
export function getMappedCell({field}) { export function getMappedCell({field}) {
const Cell = ({reRenderRow, getValue}) => { const Cell = ({reRenderRow, getValue}) => {
const [key, setKey] = useState(0); const [, setKey] = useState(0);
const subscriberManager = useSchemaStateSubscriber(setKey);
const schemaState = useContext(SchemaStateContext); const schemaState = useContext(SchemaStateContext);
const { dataDispatch, accessPath } = useContext(DataGridContext); const { dataDispatch, accessPath } = useContext(DataGridContext);
const { rowAccessPath, row } = useContext(DataGridRowContext); const { rowAccessPath, row } = useContext(DataGridRowContext);
const colAccessPath = schemaState.accessPath(rowAccessPath, field.id); const colAccessPath = schemaState.accessPath(rowAccessPath, field.id);
let colOptions = useFieldOptions(colAccessPath, schemaState, key, setKey); let colOptions = useFieldOptions(
let value = useFieldValue(colAccessPath, schemaState, key, setKey); colAccessPath, schemaState, subscriberManager
);
let value = useFieldValue(colAccessPath, schemaState, subscriberManager);
let rowValue = useFieldValue(rowAccessPath, schemaState); let rowValue = useFieldValue(rowAccessPath, schemaState);
listenDepChanges(colAccessPath, field, true, schemaState); listenDepChanges(colAccessPath, field, true, schemaState);

View File

@ -15,7 +15,9 @@ import CustomPropTypes from 'sources/custom_prop_types';
import { FieldControl } from './FieldControl'; import { FieldControl } from './FieldControl';
import { SchemaStateContext } from './SchemaState'; import { SchemaStateContext } from './SchemaState';
import { useFieldSchema, useFieldValue } from './hooks'; import {
useFieldSchema, useFieldValue, useSchemaStateSubscriber,
} from './hooks';
import { registerView } from './registry'; import { registerView } from './registry';
import { createFieldControls, listenDepChanges } from './utils'; import { createFieldControls, listenDepChanges } from './utils';
@ -23,13 +25,15 @@ import { createFieldControls, listenDepChanges } from './utils';
export default function FieldSetView({ export default function FieldSetView({
field, accessPath, dataDispatch, viewHelperProps, controlClassName, field, accessPath, dataDispatch, viewHelperProps, controlClassName,
}) { }) {
const [key, setRefreshKey] = useState(0); const [, setKey] = useState(0);
const subscriberManager = useSchemaStateSubscriber(setKey);
const schema = field.schema; const schema = field.schema;
const schemaState = useContext(SchemaStateContext); const schemaState = useContext(SchemaStateContext);
const value = useFieldValue(accessPath, schemaState); const value = useFieldValue(accessPath, schemaState);
const options = useFieldSchema( const options = useFieldSchema(
field, accessPath, value, viewHelperProps, schemaState, key, setRefreshKey field, accessPath, value, viewHelperProps, schemaState, subscriberManager
); );
const label = field.label; const label = field.label;
listenDepChanges(accessPath, field, options.visible, schemaState); listenDepChanges(accessPath, field, options.visible, schemaState);

View File

@ -27,7 +27,9 @@ import { FieldControl } from './FieldControl';
import { SQLTab } from './SQLTab'; import { SQLTab } from './SQLTab';
import { FormContentBox } from './StyledComponents'; import { FormContentBox } from './StyledComponents';
import { SchemaStateContext } from './SchemaState'; import { SchemaStateContext } from './SchemaState';
import { useFieldSchema, useFieldValue } from './hooks'; import {
useFieldSchema, useFieldValue, useSchemaStateSubscriber,
} from './hooks';
import { registerView, View } from './registry'; import { registerView, View } from './registry';
import { createFieldControls, listenDepChanges } from './utils'; import { createFieldControls, listenDepChanges } from './utils';
@ -62,10 +64,11 @@ export default function FormView({
showError=false, resetKey, focusOnFirstInput=false showError=false, resetKey, focusOnFirstInput=false
}) { }) {
const [key, setKey] = useState(0); const [key, setKey] = useState(0);
const subscriberManager = useSchemaStateSubscriber(setKey);
const schemaState = useContext(SchemaStateContext); const schemaState = useContext(SchemaStateContext);
const value = useFieldValue(accessPath, schemaState); const value = useFieldValue(accessPath, schemaState);
const { visible } = useFieldSchema( const { visible } = useFieldSchema(
field, accessPath, value, viewHelperProps, schemaState, key, setKey field, accessPath, value, viewHelperProps, schemaState, subscriberManager
); );
const [tabValue, setTabValue] = useState(0); const [tabValue, setTabValue] = useState(0);
@ -106,13 +109,12 @@ export default function FormView({
useEffect(() => { useEffect(() => {
// Refresh on message changes. // Refresh on message changes.
return schemaState.subscribe( return subscriberManager.current?.add(
['errors', 'message'], schemaState, ['errors', 'message'], 'states',
(newState, prevState) => { (newState, prevState) => {
if (_.isUndefined(newState) || _.isUndefined(prevState)); if (_.isUndefined(newState) || _.isUndefined(prevState))
setKey(Date.now()); subscriberManager.current?.signal();
}, }
'states'
); );
}, [key]); }, [key]);

View File

@ -28,7 +28,7 @@ import { evalFunc } from 'sources/utils';
import { SchemaStateContext } from './SchemaState'; import { SchemaStateContext } from './SchemaState';
import { isValueEqual } from './common'; import { isValueEqual } from './common';
import { import {
useFieldOptions, useFieldValue, useFieldError useFieldOptions, useFieldValue, useFieldError, useSchemaStateSubscriber,
} from './hooks'; } from './hooks';
import { listenDepChanges } from './utils'; import { listenDepChanges } from './utils';
@ -339,22 +339,15 @@ export const MappedFormControl = ({
}) => { }) => {
const checkIsMounted = useIsMounted(); const checkIsMounted = useIsMounted();
const [key, setKey] = useState(0); const [key, setKey] = useState(0);
const subscriberManager = useSchemaStateSubscriber(setKey);
const schemaState = useContext(SchemaStateContext); const schemaState = useContext(SchemaStateContext);
const state = schemaState.data; const state = schemaState.data;
const avoidRenderingWhenNotMounted = (newKey) => { const value = useFieldValue(accessPath, schemaState, subscriberManager);
if (checkIsMounted()) { const options = useFieldOptions(accessPath, schemaState, subscriberManager);
setKey(newKey); 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; const origOnChange = onChange;
@ -369,7 +362,10 @@ export const MappedFormControl = ({
if (!isValueEqual(changedValue, currValue)) origOnChange(changedValue); if (!isValueEqual(changedValue, currValue)) origOnChange(changedValue);
}; };
listenDepChanges(accessPath, field, options.visible, schemaState); const depVals = listenDepChanges(
accessPath, field, options.visible, schemaState, state,
avoidRenderingWhenNotMounted
);
let newProps = { let newProps = {
...props, ...props,
@ -394,14 +390,17 @@ export const MappedFormControl = ({
newProps.onClick = ()=>{ newProps.onClick = ()=>{
origOnClick?.(); origOnClick?.();
}; };
// FIXME:: Get this list from the option registry. // FIXME:: Get this list from the option registry.
const memDeps = ['disabled', 'visible', 'readonly'].map( const memDeps = ['disabled', 'visible', 'readonly'].map(
option => options[option] option => options[option]
); );
memDeps.push(value); memDeps.push(value);
memDeps.push(hasError); memDeps.push(hasError);
memDeps.push(key); memDeps.push(key);
memDeps.push(JSON.stringify(accessPath)); memDeps.push(JSON.stringify(accessPath));
memDeps.push(depVals);
// Filter out garbage props if any using ALLOWED_PROPS_FIELD. // Filter out garbage props if any using ALLOWED_PROPS_FIELD.
return useMemo( return useMemo(

View File

@ -12,6 +12,7 @@ import { useFieldOptions } from './useFieldOptions';
import { useFieldValue } from './useFieldValue'; import { useFieldValue } from './useFieldValue';
import { useSchemaState } from './useSchemaState'; import { useSchemaState } from './useSchemaState';
import { useFieldSchema } from './useFieldSchema'; import { useFieldSchema } from './useFieldSchema';
import { useSchemaStateSubscriber } from './useSchemaStateSubscriber';
export { export {
@ -20,4 +21,5 @@ export {
useFieldValue, useFieldValue,
useFieldSchema, useFieldSchema,
useSchemaState, useSchemaState,
useSchemaStateSubscriber,
}; };

View File

@ -9,26 +9,36 @@
import { useEffect } from 'react'; 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(() => { useEffect(() => {
if (!schemaState || !setRefreshKey) return; if (!schemaState || !subscriberManager?.current) return;
const checkPathError = (newState, prevState) => { const checkPathError = (newState, prevState) => {
if (prevState.name !== path && newState.name !== path) return;
// We don't need to redraw the control on message change. // 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'); return subscriberManager.current?.add(
}, [key, schemaState?._id]); schemaState, ['errors'], 'states', checkPathError
);
});
const errors = schemaState?.errors || {}; 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}; return {hasError: !_.isNull(error), error};
}; };

View File

@ -10,16 +10,13 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
export const useFieldOptions = ( export const useFieldOptions = (path, schemaState, subscriberManager) => {
path, schemaState, key, setRefreshKey
) => {
useEffect(() => {
if (!schemaState) return;
return schemaState.subscribe( useEffect(() => {
path, () => setRefreshKey?.({id: Date.now()}), 'options' if (!schemaState || !subscriberManager?.current) return;
);
}, [key, schemaState?._id]); return subscriberManager.current?.add(schemaState, path, 'options');
});
return schemaState?.options(path) || {visible: true}; return schemaState?.options(path) || {visible: true};
}; };

View File

@ -14,32 +14,32 @@ import { booleanEvaluator } from '../options';
export const useFieldSchema = ( export const useFieldSchema = (
field, accessPath, value, viewHelperProps, schemaState, key, setRefreshKey field, accessPath, value, viewHelperProps, schemaState, subscriberManager
) => { ) => {
useEffect(() => { useEffect(() => {
if (!schemaState || !field) return; if (!schemaState || !field || !subscriberManager?.current) return;
// It already has 'id', 'options' is already evaluated. // It already has 'id', 'options' is already evaluated.
if (field.id) if (field.id)
return schemaState.subscribe( return subscriberManager.current?.add(schemaState, accessPath, 'options');
accessPath, () => setRefreshKey?.({id: Date.now()}), 'options'
);
// There are no dependencies. // There are no dependencies.
if (!_.isArray(field?.deps)) return; if (!_.isArray(field?.deps)) return;
// Subscribe to all the dependents. // Subscribe to all the dependents.
const unsubscribers = field.deps.map((dep) => ( const unsubscribers = field.deps.map((dep) => (
schemaState.subscribe( subscriberManager.current?.add(
accessPath.concat(dep), () => setRefreshKey?.({id: Date.now()}), schemaState, accessPath.concat(dep), 'value'
'value'
) )
)); ));
return () => { return () => {
unsubscribers.forEach(unsubscribe => unsubscribe()); unsubscribers.forEach(
unsubscribe => subscriberManager.current?.remove(unsubscribe)
);
}; };
}, [key, schemaState?._id]); });
if (!field) return { visible: true }; if (!field) return { visible: true };
if (field.id) return schemaState?.options(accessPath); if (field.id) return schemaState?.options(accessPath);

View File

@ -10,16 +10,13 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
export const useFieldValue = ( export const useFieldValue = (path, schemaState, subscriberManager) => {
path, schemaState, key, setRefreshKey
) => {
useEffect(() => {
if (!schemaState || !setRefreshKey) return;
return schemaState.subscribe( useEffect(() => {
path, () => setRefreshKey({id: Date.now()}), 'value' if (!schemaState || !subscriberManager?.current) return;
);
}, [key, schemaState?._id]); return subscriberManager.current?.add(schemaState, path, 'value');
});
return schemaState?.value(path); return schemaState?.value(path);
}; };

View File

@ -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;
}

View File

@ -17,7 +17,7 @@ import { View, hasView } from '../registry';
import { StaticMappedFormControl, MappedFormControl } from '../MappedControl'; import { StaticMappedFormControl, MappedFormControl } from '../MappedControl';
const DEFAULT_TAB = 'general'; const DEFAULT_TAB = gettext('General');
export const createFieldControls = ({ export const createFieldControls = ({
schema, schemaState, accessPath, viewHelperProps, dataDispatch schema, schemaState, accessPath, viewHelperProps, dataDispatch
@ -50,13 +50,13 @@ export const createFieldControls = ({
}; };
// Create default group - 'General'. // Create default group - 'General'.
createGroup(DEFAULT_TAB, gettext('General'), true); createGroup(DEFAULT_TAB, DEFAULT_TAB, true);
schema?.fields?.forEach((field) => { schema?.fields?.forEach((field) => {
if (!isModeSupportedByField(field, viewHelperProps)) return; if (!isModeSupportedByField(field, viewHelperProps)) return;
let inlineGroup = null; let inlineGroup = null;
const inlineGroupId = field[inlineGroup]; const inlineGroupId = field['inlineGroup'];
if(field.type === 'group') { if(field.type === 'group') {

View File

@ -13,7 +13,16 @@ import _ from 'lodash';
import { evalFunc } from 'sources/utils'; 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(() => { useEffect(() => {
if (!visible || !schemaState || !field) return; if (!visible || !schemaState || !field) return;
@ -26,25 +35,22 @@ export const listenDepChanges = (accessPath, field, visible, schemaState) => {
} }
if (field.deps) { if (field.deps) {
const parentPath = [...accessPath]; deps.forEach((dep) => {
// Remove the last element.
if (field.id && field.id === parentPath[parentPath.length - 1]) {
parentPath.pop();
}
(evalFunc(null, field.deps) || []).forEach((dep) => {
// When dep is a string then prepend the complete accessPath, // When dep is a string then prepend the complete accessPath,
// but - when dep is an array, then the intention is to provide // but - when dep is an array, then the intention is to provide
// the exact accesspath. // the exact accesspath.
let source = _.isArray(dep) ? dep : parentPath.concat(dep); let source = _.isArray(dep) ? dep : parentPath.concat(dep);
if(field.depChange || field.deferredDepChange) { if (field.depChange || field.deferredDepChange) {
schemaState.addDepListener( schemaState.addDepListener(
source, accessPath, field.depChange, field.deferredDepChange 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)
));
}; };

View File

@ -955,7 +955,6 @@ export const InputSelect = forwardRef(({
return () => umounted = true; return () => umounted = true;
}, [optionsReloadBasis]); }, [optionsReloadBasis]);
/* Apply filter if any */ /* Apply filter if any */
const filteredOptions = (controlProps.filter?.(finalOptions)) || finalOptions; const filteredOptions = (controlProps.filter?.(finalOptions)) || finalOptions;
const flatFiltered = flattenSelectOptions(filteredOptions); const flatFiltered = flattenSelectOptions(filteredOptions);

View File

@ -192,6 +192,11 @@ export default function MacrosDialog({onClose, onSave}) {
return <></>; return <></>;
} }
const schema = React.useRef(null);
if (!schema.current)
schema.current = new MacrosSchema(keyOptions);
return ( return (
<StyledBox> <StyledBox>
<SchemaView <SchemaView
@ -202,7 +207,7 @@ export default function MacrosDialog({onClose, onSave}) {
} }
return Promise.resolve({macro: userMacrosData.filter((m)=>Boolean(m.name))}); return Promise.resolve({macro: userMacrosData.filter((m)=>Boolean(m.name))});
}} }}
schema={new MacrosSchema(keyOptions)} schema={schema.current}
viewHelperProps={{ viewHelperProps={{
mode: 'edit', mode: 'edit',
}} }}

View File

@ -172,9 +172,10 @@ class NewConnectionSchema extends BaseUISchema {
} }
}, { }, {
id: 'user', label: gettext('User'), deps: ['sid', 'connected'], id: 'user', label: gettext('User'), deps: ['sid', 'connected'],
noEmpty: true, controlProps: { allowClear: false }, noEmpty: true,
type: (state) => ({ type: (state) => ({
type: 'select', type: 'select',
controlProps: { allowClear: false },
options: () => this.getOtherOptions( options: () => this.getOtherOptions(
state.sid, 'get_new_connection_user' state.sid, 'get_new_connection_user'
), ),
@ -182,8 +183,9 @@ class NewConnectionSchema extends BaseUISchema {
}), }),
}, { }, {
id: 'role', label: gettext('Role'), deps: ['sid', 'connected'], id: 'role', label: gettext('Role'), deps: ['sid', 'connected'],
type: (state)=>({ type: (state) => ({
type: 'select', type: 'select',
controlProps: { allowClear: false },
options: () => this.getOtherOptions( options: () => this.getOtherOptions(
state.sid, 'get_new_connection_role' state.sid, 'get_new_connection_role'
), ),

View File

@ -399,6 +399,11 @@ function UserManagementDialog({onClose}) {
window.open(url_for('help.static', { 'filename': 'user_management.html' }), 'pgadmin_help'); 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 <StyledBox><SchemaView return <StyledBox><SchemaView
formType={'dialog'} formType={'dialog'}
getInitData={()=>{ return new Promise((resolve, reject)=>{ getInitData={()=>{ return new Promise((resolve, reject)=>{
@ -410,7 +415,7 @@ function UserManagementDialog({onClose}) {
reject(err instanceof Error ? err : Error(gettext('Something went wrong'))); reject(err instanceof Error ? err : Error(gettext('Something went wrong')));
}); });
}); }} }); }}
schema={new UserManagementSchema(authSourcesOptions, roleOptions)} schema={schema.current}
viewHelperProps={{ viewHelperProps={{
mode: 'edit', mode: 'edit',
}} }}