From b0cd028ff83bf683bb4b3bba075dd5c52e1c57a1 Mon Sep 17 00:00:00 2001 From: Ashesh Vashi Date: Wed, 18 Sep 2024 20:55:01 +0530 Subject: [PATCH] Fixed an issue introduced due to reloading of the tree due to changes in particular preferences (#7942) * Fix an issue - when the object browser tree is being recreated due to reloading for changes in some of the preferences. Tree object returns object from the previous instance as 'selected', but - it does not have the 'treeNodeInfo' available. In this special case - we would consider that there is no node selected at that particular moment, and pass information accordingly. * Fixed 'New Connection' dialog issue after connecting a disconnected server. * Disable the 'Add' button in the GridHeader with form, when 'canAdd' flag is set to false. * Convert the access path to string array for correct comparision. * Check the access path type before comparison. When language is 'c', set the 'code' block read-only. * Enabled 'Strict' control for EPAS >= 95 --- .../static/js/fts_configuration.ui.js | 20 +++---- .../functions/static/js/function.ui.js | 52 ++++++++++--------- .../static/js/trigger_function.ui.js | 9 ++-- .../js/SchemaView/DataGridView/formHeader.jsx | 12 +++-- .../js/SchemaView/SchemaState/reducer.js | 2 +- .../js/SchemaView/hooks/useFieldError.js | 11 ++-- .../static/js/helpers/withStandardTabInfo.jsx | 16 ++++-- .../dialogs/NewConnectionDialog.jsx | 12 +++-- 8 files changed, 78 insertions(+), 56 deletions(-) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/static/js/fts_configuration.ui.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/static/js/fts_configuration.ui.js index fc1abdc91..876ead2a9 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/static/js/fts_configuration.ui.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/static/js/fts_configuration.ui.js @@ -16,15 +16,15 @@ class TokenHeaderSchema extends BaseUISchema { constructor(tokenOptions) { super({ token: undefined, - isNew: false, + isNew: true, }); this.tokenOptions = tokenOptions; } set isNewFTSConf(flag) { - if (!this.state) return; - this.state.data = {...this.state.data, isNew: flag}; + if (this.state) + this.state.data = {...this.state.data, isNew: flag}; } getNewData(data) { @@ -35,16 +35,17 @@ class TokenHeaderSchema extends BaseUISchema { } get baseFields() { - let obj = this; return [{ id: 'token', label: gettext('Tokens'), deps: ['isNew'], type: () => ({ type: 'select', options: this.tokenOptions, }), - disabled: function() { return obj.isNewFTSConf; } - }, { - id: 'isNew', visible: false, type: 'text', + disabled: function() { + return this.state ? this.state.data.isNew : true; + } + },{ + id: 'isNew', visible: false, type: 'text', exclude: true, }]; } } @@ -160,12 +161,13 @@ export default class FTSConfigurationSchema extends BaseUISchema { }, { id: 'tokens', label: '', type: 'collection', group: gettext('Tokens'), mode: ['create','edit'], - editable: false, schema: this.tokColumnSchema, + schema: this.tokColumnSchema, headerSchema: this.tokHeaderSchema, headerFormVisible: true, GridHeader: DataGridFormHeader, uniqueCol : ['token'], - canAdd: true, canEdit: false, canDelete: true, + canAdd: (state, helpderProps) => (helpderProps.mode !== 'create'), + canDelete: true, } ]; } diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/static/js/function.ui.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/static/js/function.ui.js index 2168d5b7c..111208280 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/static/js/function.ui.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/static/js/function.ui.js @@ -159,33 +159,21 @@ export default class FunctionSchema extends BaseUISchema { } } - isGreaterThan95(state){ - if ( + isLessThan95ORNonSPL(state) { + return ( + this.inCatalog() || this.node_info['node_info'].server.version < 90500 || this.node_info['node_info']['server'].server_type != 'ppas' || state.lanname != 'edbspl' - ) { - state.provolatile = null; - state.proisstrict = false; - state.procost = null; - state.proleakproof = false; - return true; - } else { - return false; - } + ); } - isGreaterThan96(state){ - if ( + isLessThan96ORNonSPL(state){ + return ( this.node_info['node_info'].server.version < 90600 || this.node_info['node_info']['server'].server_type != 'ppas' || state.lanname != 'edbspl' - ) { - state.proparallel = null; - return true; - } else { - return false; - } + ); } @@ -210,7 +198,7 @@ export default class FunctionSchema extends BaseUISchema { if (this.type !== 'procedure') { obj.inCatalog(state); } else { - obj.isGreaterThan95(state); + obj.isLessThan95ORNonSPL(state); } }, noEmpty: true, @@ -328,7 +316,7 @@ export default class FunctionSchema extends BaseUISchema { {'label': 'VOLATILE', 'value': 'v'}, {'label': 'STABLE', 'value': 's'}, {'label': 'IMMUTABLE', 'value': 'i'}, - ], disabled: (this.type !== 'procedure') ? obj.inCatalog() : obj.isGreaterThan95, + ], disabled: (this.type !== 'procedure') ? obj.inCatalog() : obj.isLessThan95ORNonSPL, controlProps: {allowClear: false}, },{ id: 'proretset', label: gettext('Returns a set?'), type: 'switch', @@ -336,8 +324,22 @@ export default class FunctionSchema extends BaseUISchema { visible: obj.isVisible, readonly: obj.isReadonly, },{ id: 'proisstrict', label: gettext('Strict?'), type: 'switch', - group: gettext('Options'), disabled: obj.inCatalog(), + group: gettext('Options'), + disabled: obj.inCatalog() ? true : obj.isLessThan95ORNonSPL, deps: ['lanname'], + depChange: (state, source) => ( + (source[source.length - 1] !== 'lanname') ? undefined : ( + obj.isLessThan95ORNonSPL(state) + ) ? { + provolatile: null, + proisstrict: false, + procost: null, + proleakproof: false, + proparallel: null, + } : ( + obj.isLessThan95ORNonSPL(state) ? { proparallel: null } : undefined + ) + ), },{ id: 'prosecdef', label: gettext('Security of definer?'), group: gettext('Options'), type: 'switch', @@ -357,13 +359,13 @@ export default class FunctionSchema extends BaseUISchema { {'label': 'RESTRICTED', 'value': 'r'}, {'label': 'SAFE', 'value': 's'}, ], - disabled: (this.type !== 'procedure') ? obj.inCatalog(): obj.isGreaterThan96, + disabled: (this.type !== 'procedure') ? obj.inCatalog(): obj.isLessThan96ORNonSPL, min_version: 90600, controlProps: {allowClear: false}, },{ id: 'procost', label: gettext('Estimated cost'), group: gettext('Options'), cell:'string', type: 'text', deps: ['lanname'], - disabled: (this.type !== 'procedure') ? obj.isDisabled: obj.isGreaterThan95, + disabled: (this.type !== 'procedure') ? obj.isDisabled : obj.isLessThan95ORNonSPL, },{ id: 'prorows', label: gettext('Estimated rows'), type: 'text', deps: ['proretset'], visible: obj.isVisible, @@ -378,7 +380,7 @@ export default class FunctionSchema extends BaseUISchema { },{ id: 'proleakproof', label: gettext('Leak proof?'), group: gettext('Options'), cell:'boolean', type: 'switch', min_version: 90200, - disabled: (this.type !== 'procedure') ? obj.inCatalog(): obj.isGreaterThan95, + disabled: (this.type !== 'procedure') ? obj.inCatalog() : obj.isLessThan95ORNonSPL, deps: ['lanname'], },{ id: 'prosupportfunc', label: gettext('Support function'), diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/static/js/trigger_function.ui.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/static/js/trigger_function.ui.js index 8e9fbd74a..f511c3fe2 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/static/js/trigger_function.ui.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/functions/static/js/trigger_function.ui.js @@ -161,10 +161,7 @@ export default class TriggerFunctionSchema extends BaseUISchema { type: 'sql', isFullTab: true, mode: ['properties', 'create', 'edit'], group: gettext('Code'), deps: ['lanname'], - visible: (state) => { - return state.lanname !== 'c'; - }, - disabled: obj.isDisabled, readonly: obj.isReadonly, + readonly: (state) => (obj.isDisabled() || state.lanname === 'c'), },{ id: 'probin', label: gettext('Object file'), cell: 'string', type: 'text', group: gettext('Definition'), deps: ['lanname'], @@ -260,8 +257,8 @@ export default class TriggerFunctionSchema extends BaseUISchema { if (isEmptyString(state.service)) { - /* code validation*/ - if (isEmptyString(state.prosrc)) { + /* code validation */ + if (isEmptyString(state.prosrc) && state.lanname !== 'c') { errmsg = gettext('Code cannot be empty.'); setError('prosrc', errmsg); return true; diff --git a/web/pgadmin/static/js/SchemaView/DataGridView/formHeader.jsx b/web/pgadmin/static/js/SchemaView/DataGridView/formHeader.jsx index 7b13e9a0f..f88a5e178 100644 --- a/web/pgadmin/static/js/SchemaView/DataGridView/formHeader.jsx +++ b/web/pgadmin/static/js/SchemaView/DataGridView/formHeader.jsx @@ -77,7 +77,7 @@ export function DataGridFormHeader({tableEleRef}) { viewHelperProps, } = useContext(DataGridContext); const { - addOnTop, canAddRow, canEdit, expandEditOnAdd, headerFormVisible + canAdd, addOnTop, canAddRow, canEdit, expandEditOnAdd, headerFormVisible } = options; const rows = table.getRowModel().rows; @@ -85,8 +85,12 @@ export function DataGridFormHeader({tableEleRef}) { const newRowIndex = useRef(-1); const schemaState = useContext(SchemaStateContext); const headerFormData = useRef({}); - const [addDisabled, setAddDisabled] = useState(canAddRow); + const [addDisabled, setAddDisabled] = useState(!canAdd || !canAddRow); const {headerSchema} = field; + const disableAddButton = (flag) => { + if (!canAdd || !canAddRow) return; + setAddDisabled(flag); + }; const onAddClick = useCallback(() => { @@ -154,8 +158,8 @@ export function DataGridFormHeader({tableEleRef}) { showFooter={false} onDataChange={(isDataChanged, dataChanged)=>{ headerFormData.current = dataChanged; - setAddDisabled( - canAddRow && headerSchema.addDisabled(headerFormData.current) + disableAddButton( + headerSchema.addDisabled(headerFormData.current) ); }} hasSQL={false} diff --git a/web/pgadmin/static/js/SchemaView/SchemaState/reducer.js b/web/pgadmin/static/js/SchemaView/SchemaState/reducer.js index dca8a1eac..87b4dc3f6 100644 --- a/web/pgadmin/static/js/SchemaView/SchemaState/reducer.js +++ b/web/pgadmin/static/js/SchemaView/SchemaState/reducer.js @@ -114,7 +114,7 @@ export const sessDataReducer = (state, action) => { return data; case SCHEMA_STATE_ACTIONS.DEFERRED_DEPCHANGE: - data = getDepChange(action.path, data, state, action); + data = getDepChange(action.path, _.cloneDeep(data), state, action); break; } diff --git a/web/pgadmin/static/js/SchemaView/hooks/useFieldError.js b/web/pgadmin/static/js/SchemaView/hooks/useFieldError.js index 282837078..6b8c21ae9 100644 --- a/web/pgadmin/static/js/SchemaView/hooks/useFieldError.js +++ b/web/pgadmin/static/js/SchemaView/hooks/useFieldError.js @@ -9,11 +9,14 @@ import { useEffect } from 'react'; +const convertKeysToString = (arr) => (arr||[]).map((key) => String(key)); const isPathEqual = (path1, path2) => ( - JSON.stringify(path1) === JSON.stringify(path2) + Array.isArray(path1) && + Array.isArray(path2) && + JSON.stringify(convertKeysToString(path1)) === + JSON.stringify(convertKeysToString(path2)) ); - export const useFieldError = (path, schemaState, subscriberManager) => { useEffect(() => { @@ -38,7 +41,9 @@ export const useFieldError = (path, schemaState, subscriberManager) => { }); const errors = schemaState?.errors || {}; - const error = isPathEqual(errors.name, path) ? errors.message : null; + const error = ( + Array.isArray(errors.name) && isPathEqual(errors.name, path) + ) ? errors.message : null; return {hasError: !_.isNull(error), error}; }; diff --git a/web/pgadmin/static/js/helpers/withStandardTabInfo.jsx b/web/pgadmin/static/js/helpers/withStandardTabInfo.jsx index 6521785a3..1a8efdcb1 100644 --- a/web/pgadmin/static/js/helpers/withStandardTabInfo.jsx +++ b/web/pgadmin/static/js/helpers/withStandardTabInfo.jsx @@ -67,13 +67,23 @@ export default function withStandardTabInfo(Component, tabId) { }; }, []); + //////// + // Special case: + // + // When the tree is being recreated during reloading on changes of some + // preferences, it is possible that the tree returns 'selected' node, but - + // it does not have the 'treeNodeInfo' as it was actually part of the + // previous instance of the tree. + // + // In that case - we consider that there is no node selected in the tree. + /////// return ( { + return new Promise((resolve, reject) => { this.api.get(url_for('sqleditor.get_new_connection_servers')) .then(({data: respData})=>{ let groupedOptions = []; @@ -61,7 +61,7 @@ class NewConnectionSchema extends BaseUISchema { return; } /* initial selection */ - let foundServer = _.find(v, (o)=>o.value==obj.params.sid); + let foundServer = _.find(v, (o) => o.value == obj.params.sid); foundServer && (foundServer.selected = true); groupedOptions.push({ label: k, @@ -69,7 +69,7 @@ class NewConnectionSchema extends BaseUISchema { }); }); /* Will be re-used for changing icon when connected */ - this.groupedServers = groupedOptions.map((group)=>{ + this.groupedServers = groupedOptions.map((group) => { return { label: group.label, options: group.options.map((o)=>({...o, selected: false})), @@ -118,18 +118,19 @@ class NewConnectionSchema extends BaseUISchema { optionsLoaded: (res) => self.flatServers = flattenSelectOptions(res), optionsReloadBasis: self.flatServers.map((s) => s.connected).join(''), }), - depChange: (state)=>{ + depChange: (state) => { /* Once the option is selected get the name */ /* Force sid to null, and set only if connected */ let selectedServer = _.find( self.flatServers, (s) => s.value == state.sid ); + return { server_name: selectedServer?.label, did: null, user: null, role: null, - sid: null, + sid: state.sid, fgcolor: selectedServer?.fgcolor, bgcolor: selectedServer?.bgcolor, connected: selectedServer?.connected, @@ -138,6 +139,7 @@ class NewConnectionSchema extends BaseUISchema { deferredDepChange: (state, source, topState, actionObj) => { return new Promise((resolve) => { let sid = actionObj.value; + if(!_.find(self.flatServers, (s) => s.value == sid)?.connected) { this.connectServer(sid, state.user, null, (data) => { self.setServerConnected(sid, data.icon);