Fix broken checkbox selection in backup dialog objects tree. #9649

pull/9675/head
Rohit Bhati 2026-02-27 17:01:11 +05:30 committed by GitHub
parent e9e52905cc
commit caafc6bb1b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 54 additions and 13 deletions

View File

@ -80,39 +80,80 @@ export default function PgTreeView({ data = [], hasCheckbox = false,
const [checkedState, setCheckedState] = React.useState({});
const { ref: containerRef, width, height } = useResizeObserver();
// Handle checkbox toggle and collect all checked nodes
// to pass complete selection state to the backup dialog
const toggleCheck = (node, isChecked) => {
const newState = { ...checkedState };
const selectedChNodes = [];
// Update the node itself and all descendants
// Update the clicked node and all its descendants with the new checked value
const updateDescendants = (n, val) => {
newState[n.id] = val;
if (val) {
selectedChNodes.push(n);
}
n.children?.forEach(child => updateDescendants(child, val));
n.children?.forEach(child => { updateDescendants(child, val); });
};
updateDescendants(node, isChecked);
// Update ancestors (Indeterminate logic)
// Update ancestor nodes to reflect the correct state (checked/unchecked/indeterminate)
// This ensures parent nodes show proper visual feedback based on children's state
let parent = node.parent;
while (parent && parent.id !== '__root__') {
const allChecked = parent.children.every(c => newState[c.id]);
// Check if ALL children are fully checked (state must be exactly true,
// not 'indeterminate') to mark parent as fully checked
const allChecked = parent.children.every(c => newState[c.id] === true);
// Check if ALL children are unchecked (falsy value: false, undefined, or null)
const noneChecked = parent.children.every(c => !newState[c.id]);
if (allChecked) {
// All children checked -> parent is fully checked
newState[parent.id] = true;
// logic for custom indeterminate property if needed
} else if (noneChecked) {
// No children checked -> parent is unchecked
newState[parent.id] = false;
} else {
newState[parent.id] = 'indeterminate'; // Store string for 3rd state
// Some children checked, some not -> parent shows indeterminate state
newState[parent.id] = 'indeterminate';
}
parent = parent.parent;
}
setCheckedState(newState);
selectionChange?.(selectedChNodes);
// Collect all checked/indeterminate nodes from the entire tree
// to provide complete selection state to selectionChange callback.
// We use wrapper objects to avoid mutating the original node data.
const allCheckedNodes = [];
const collectAllCheckedNodes = (n) => {
if (!n) return;
const state = newState[n.id];
if (state === true || state === 'indeterminate') {
// Pass wrapper object with isIndeterminate flag to differentiate
// full schema selection from partial selection in backup dialog
allCheckedNodes.push({
node: n,
isIndeterminate: state === 'indeterminate'
});
}
// Recursively check all children
n.children?.forEach(child => { collectAllCheckedNodes(child); });
};
// Navigate up to find the root level of the tree (parent of root nodes is '__root__')
let rootNode = node;
while (rootNode.parent && rootNode.parent.id !== '__root__') {
rootNode = rootNode.parent;
}
// Traverse all root-level nodes to collect checked nodes from entire tree
const rootParent = rootNode.parent;
if (rootParent && rootParent.children) {
// Iterate through all sibling root nodes to collect all checked nodes
rootParent.children.forEach(root => { collectAllCheckedNodes(root); });
} else {
// Fallback: if we can't find siblings, just traverse from the found root
collectAllCheckedNodes(rootNode);
}
// Pass all checked nodes to callback with current selection state.
selectionChange?.(allCheckedNodes);
};
return (<Root ref={containerRef} className={'PgTree-tree'}>

View File

@ -758,8 +758,8 @@ export default class BackupSchema extends BaseUISchema {
'foreign table': [],
'materialized view': [],
};
state?.objects?.forEach((node)=> {
if(node.data.is_schema && !node.data?.isIndeterminate) {
state?.objects?.forEach(({ node, isIndeterminate })=> {
if(node.data.is_schema && !isIndeterminate) {
selectedNodeCollection['schema'].push(node.data.name);
} else if(['table', 'view', 'materialized view', 'foreign table', 'sequence'].includes(node.data.type) &&
!node.data.is_collection && !selectedNodeCollection['schema'].includes(node.data.schema)) {