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
This commit is contained in:
Ashesh Vashi 2024-09-18 20:55:01 +05:30 committed by GitHub
parent 100f59f78b
commit b0cd028ff8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 78 additions and 56 deletions

View File

@ -16,15 +16,15 @@ class TokenHeaderSchema extends BaseUISchema {
constructor(tokenOptions) { constructor(tokenOptions) {
super({ super({
token: undefined, token: undefined,
isNew: false, isNew: true,
}); });
this.tokenOptions = tokenOptions; this.tokenOptions = tokenOptions;
} }
set isNewFTSConf(flag) { set isNewFTSConf(flag) {
if (!this.state) return; if (this.state)
this.state.data = {...this.state.data, isNew: flag}; this.state.data = {...this.state.data, isNew: flag};
} }
getNewData(data) { getNewData(data) {
@ -35,16 +35,17 @@ class TokenHeaderSchema extends BaseUISchema {
} }
get baseFields() { get baseFields() {
let obj = this;
return [{ return [{
id: 'token', label: gettext('Tokens'), deps: ['isNew'], id: 'token', label: gettext('Tokens'), deps: ['isNew'],
type: () => ({ type: () => ({
type: 'select', type: 'select',
options: this.tokenOptions, options: this.tokenOptions,
}), }),
disabled: function() { return obj.isNewFTSConf; } disabled: function() {
}, { return this.state ? this.state.data.isNew : true;
id: 'isNew', visible: false, type: 'text', }
},{
id: 'isNew', visible: false, type: 'text', exclude: true,
}]; }];
} }
} }
@ -160,12 +161,13 @@ export default class FTSConfigurationSchema extends BaseUISchema {
}, { }, {
id: 'tokens', label: '', type: 'collection', id: 'tokens', label: '', type: 'collection',
group: gettext('Tokens'), mode: ['create','edit'], group: gettext('Tokens'), mode: ['create','edit'],
editable: false, schema: this.tokColumnSchema, schema: this.tokColumnSchema,
headerSchema: this.tokHeaderSchema, headerSchema: this.tokHeaderSchema,
headerFormVisible: true, headerFormVisible: true,
GridHeader: DataGridFormHeader, GridHeader: DataGridFormHeader,
uniqueCol : ['token'], uniqueCol : ['token'],
canAdd: true, canEdit: false, canDelete: true, canAdd: (state, helpderProps) => (helpderProps.mode !== 'create'),
canDelete: true,
} }
]; ];
} }

View File

@ -159,33 +159,21 @@ export default class FunctionSchema extends BaseUISchema {
} }
} }
isGreaterThan95(state){ isLessThan95ORNonSPL(state) {
if ( return (
this.inCatalog() ||
this.node_info['node_info'].server.version < 90500 || this.node_info['node_info'].server.version < 90500 ||
this.node_info['node_info']['server'].server_type != 'ppas' || this.node_info['node_info']['server'].server_type != 'ppas' ||
state.lanname != 'edbspl' state.lanname != 'edbspl'
) { );
state.provolatile = null;
state.proisstrict = false;
state.procost = null;
state.proleakproof = false;
return true;
} else {
return false;
}
} }
isGreaterThan96(state){ isLessThan96ORNonSPL(state){
if ( return (
this.node_info['node_info'].server.version < 90600 || this.node_info['node_info'].server.version < 90600 ||
this.node_info['node_info']['server'].server_type != 'ppas' || this.node_info['node_info']['server'].server_type != 'ppas' ||
state.lanname != 'edbspl' 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') { if (this.type !== 'procedure') {
obj.inCatalog(state); obj.inCatalog(state);
} else { } else {
obj.isGreaterThan95(state); obj.isLessThan95ORNonSPL(state);
} }
}, },
noEmpty: true, noEmpty: true,
@ -328,7 +316,7 @@ export default class FunctionSchema extends BaseUISchema {
{'label': 'VOLATILE', 'value': 'v'}, {'label': 'VOLATILE', 'value': 'v'},
{'label': 'STABLE', 'value': 's'}, {'label': 'STABLE', 'value': 's'},
{'label': 'IMMUTABLE', 'value': 'i'}, {'label': 'IMMUTABLE', 'value': 'i'},
], disabled: (this.type !== 'procedure') ? obj.inCatalog() : obj.isGreaterThan95, ], disabled: (this.type !== 'procedure') ? obj.inCatalog() : obj.isLessThan95ORNonSPL,
controlProps: {allowClear: false}, controlProps: {allowClear: false},
},{ },{
id: 'proretset', label: gettext('Returns a set?'), type: 'switch', 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, visible: obj.isVisible, readonly: obj.isReadonly,
},{ },{
id: 'proisstrict', label: gettext('Strict?'), type: 'switch', id: 'proisstrict', label: gettext('Strict?'), type: 'switch',
group: gettext('Options'), disabled: obj.inCatalog(), group: gettext('Options'),
disabled: obj.inCatalog() ? true : obj.isLessThan95ORNonSPL,
deps: ['lanname'], 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?'), id: 'prosecdef', label: gettext('Security of definer?'),
group: gettext('Options'), type: 'switch', group: gettext('Options'), type: 'switch',
@ -357,13 +359,13 @@ export default class FunctionSchema extends BaseUISchema {
{'label': 'RESTRICTED', 'value': 'r'}, {'label': 'RESTRICTED', 'value': 'r'},
{'label': 'SAFE', 'value': 's'}, {'label': 'SAFE', 'value': 's'},
], ],
disabled: (this.type !== 'procedure') ? obj.inCatalog(): obj.isGreaterThan96, disabled: (this.type !== 'procedure') ? obj.inCatalog(): obj.isLessThan96ORNonSPL,
min_version: 90600, min_version: 90600,
controlProps: {allowClear: false}, controlProps: {allowClear: false},
},{ },{
id: 'procost', label: gettext('Estimated cost'), group: gettext('Options'), id: 'procost', label: gettext('Estimated cost'), group: gettext('Options'),
cell:'string', type: 'text', deps: ['lanname'], 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', id: 'prorows', label: gettext('Estimated rows'), type: 'text',
deps: ['proretset'], visible: obj.isVisible, deps: ['proretset'], visible: obj.isVisible,
@ -378,7 +380,7 @@ export default class FunctionSchema extends BaseUISchema {
},{ },{
id: 'proleakproof', label: gettext('Leak proof?'), id: 'proleakproof', label: gettext('Leak proof?'),
group: gettext('Options'), cell:'boolean', type: 'switch', min_version: 90200, 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'], deps: ['lanname'],
},{ },{
id: 'prosupportfunc', label: gettext('Support function'), id: 'prosupportfunc', label: gettext('Support function'),

View File

@ -161,10 +161,7 @@ export default class TriggerFunctionSchema extends BaseUISchema {
type: 'sql', isFullTab: true, type: 'sql', isFullTab: true,
mode: ['properties', 'create', 'edit'], mode: ['properties', 'create', 'edit'],
group: gettext('Code'), deps: ['lanname'], group: gettext('Code'), deps: ['lanname'],
visible: (state) => { readonly: (state) => (obj.isDisabled() || state.lanname === 'c'),
return state.lanname !== 'c';
},
disabled: obj.isDisabled, readonly: obj.isReadonly,
},{ },{
id: 'probin', label: gettext('Object file'), cell: 'string', id: 'probin', label: gettext('Object file'), cell: 'string',
type: 'text', group: gettext('Definition'), deps: ['lanname'], type: 'text', group: gettext('Definition'), deps: ['lanname'],
@ -260,8 +257,8 @@ export default class TriggerFunctionSchema extends BaseUISchema {
if (isEmptyString(state.service)) { if (isEmptyString(state.service)) {
/* code validation*/ /* code validation */
if (isEmptyString(state.prosrc)) { if (isEmptyString(state.prosrc) && state.lanname !== 'c') {
errmsg = gettext('Code cannot be empty.'); errmsg = gettext('Code cannot be empty.');
setError('prosrc', errmsg); setError('prosrc', errmsg);
return true; return true;

View File

@ -77,7 +77,7 @@ export function DataGridFormHeader({tableEleRef}) {
viewHelperProps, viewHelperProps,
} = useContext(DataGridContext); } = useContext(DataGridContext);
const { const {
addOnTop, canAddRow, canEdit, expandEditOnAdd, headerFormVisible canAdd, addOnTop, canAddRow, canEdit, expandEditOnAdd, headerFormVisible
} = options; } = options;
const rows = table.getRowModel().rows; const rows = table.getRowModel().rows;
@ -85,8 +85,12 @@ export function DataGridFormHeader({tableEleRef}) {
const newRowIndex = useRef(-1); const newRowIndex = useRef(-1);
const schemaState = useContext(SchemaStateContext); const schemaState = useContext(SchemaStateContext);
const headerFormData = useRef({}); const headerFormData = useRef({});
const [addDisabled, setAddDisabled] = useState(canAddRow); const [addDisabled, setAddDisabled] = useState(!canAdd || !canAddRow);
const {headerSchema} = field; const {headerSchema} = field;
const disableAddButton = (flag) => {
if (!canAdd || !canAddRow) return;
setAddDisabled(flag);
};
const onAddClick = useCallback(() => { const onAddClick = useCallback(() => {
@ -154,8 +158,8 @@ export function DataGridFormHeader({tableEleRef}) {
showFooter={false} showFooter={false}
onDataChange={(isDataChanged, dataChanged)=>{ onDataChange={(isDataChanged, dataChanged)=>{
headerFormData.current = dataChanged; headerFormData.current = dataChanged;
setAddDisabled( disableAddButton(
canAddRow && headerSchema.addDisabled(headerFormData.current) headerSchema.addDisabled(headerFormData.current)
); );
}} }}
hasSQL={false} hasSQL={false}

View File

@ -114,7 +114,7 @@ export const sessDataReducer = (state, action) => {
return data; return data;
case SCHEMA_STATE_ACTIONS.DEFERRED_DEPCHANGE: case SCHEMA_STATE_ACTIONS.DEFERRED_DEPCHANGE:
data = getDepChange(action.path, data, state, action); data = getDepChange(action.path, _.cloneDeep(data), state, action);
break; break;
} }

View File

@ -9,11 +9,14 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
const convertKeysToString = (arr) => (arr||[]).map((key) => String(key));
const isPathEqual = (path1, path2) => ( 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) => { export const useFieldError = (path, schemaState, subscriberManager) => {
useEffect(() => { useEffect(() => {
@ -38,7 +41,9 @@ export const useFieldError = (path, schemaState, subscriberManager) => {
}); });
const errors = schemaState?.errors || {}; 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}; return {hasError: !_.isNull(error), error};
}; };

View File

@ -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 (
<ErrorBoundary> <ErrorBoundary>
<Component <Component
{...props} {...props}
nodeItem={nodeItem} nodeItem={treeNodeInfo ? nodeItem : undefined}
nodeData={nodeData} nodeData={treeNodeInfo ? nodeData : undefined}
node={node} node={treeNodeInfo ? node : undefined}
treeNodeInfo={treeNodeInfo} treeNodeInfo={treeNodeInfo}
isActive={isActive} isActive={isActive}
isStale={isStale} isStale={isStale}

View File

@ -52,7 +52,7 @@ class NewConnectionSchema extends BaseUISchema {
if(this.groupedServers?.length != 0) { if(this.groupedServers?.length != 0) {
return Promise.resolve(this.groupedServers); return Promise.resolve(this.groupedServers);
} }
return new Promise((resolve, reject)=>{ return new Promise((resolve, reject) => {
this.api.get(url_for('sqleditor.get_new_connection_servers')) this.api.get(url_for('sqleditor.get_new_connection_servers'))
.then(({data: respData})=>{ .then(({data: respData})=>{
let groupedOptions = []; let groupedOptions = [];
@ -61,7 +61,7 @@ class NewConnectionSchema extends BaseUISchema {
return; return;
} }
/* initial selection */ /* 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); foundServer && (foundServer.selected = true);
groupedOptions.push({ groupedOptions.push({
label: k, label: k,
@ -69,7 +69,7 @@ class NewConnectionSchema extends BaseUISchema {
}); });
}); });
/* Will be re-used for changing icon when connected */ /* Will be re-used for changing icon when connected */
this.groupedServers = groupedOptions.map((group)=>{ this.groupedServers = groupedOptions.map((group) => {
return { return {
label: group.label, label: group.label,
options: group.options.map((o)=>({...o, selected: false})), options: group.options.map((o)=>({...o, selected: false})),
@ -118,18 +118,19 @@ class NewConnectionSchema extends BaseUISchema {
optionsLoaded: (res) => self.flatServers = flattenSelectOptions(res), optionsLoaded: (res) => self.flatServers = flattenSelectOptions(res),
optionsReloadBasis: self.flatServers.map((s) => s.connected).join(''), optionsReloadBasis: self.flatServers.map((s) => s.connected).join(''),
}), }),
depChange: (state)=>{ depChange: (state) => {
/* Once the option is selected get the name */ /* Once the option is selected get the name */
/* Force sid to null, and set only if connected */ /* Force sid to null, and set only if connected */
let selectedServer = _.find( let selectedServer = _.find(
self.flatServers, (s) => s.value == state.sid self.flatServers, (s) => s.value == state.sid
); );
return { return {
server_name: selectedServer?.label, server_name: selectedServer?.label,
did: null, did: null,
user: null, user: null,
role: null, role: null,
sid: null, sid: state.sid,
fgcolor: selectedServer?.fgcolor, fgcolor: selectedServer?.fgcolor,
bgcolor: selectedServer?.bgcolor, bgcolor: selectedServer?.bgcolor,
connected: selectedServer?.connected, connected: selectedServer?.connected,
@ -138,6 +139,7 @@ class NewConnectionSchema extends BaseUISchema {
deferredDepChange: (state, source, topState, actionObj) => { deferredDepChange: (state, source, topState, actionObj) => {
return new Promise((resolve) => { return new Promise((resolve) => {
let sid = actionObj.value; let sid = actionObj.value;
if(!_.find(self.flatServers, (s) => s.value == sid)?.connected) { if(!_.find(self.flatServers, (s) => s.value == sid)?.connected) {
this.connectServer(sid, state.user, null, (data) => { this.connectServer(sid, state.user, null, (data) => {
self.setServerConnected(sid, data.icon); self.setServerConnected(sid, data.icon);