Skip to content

Commit eddc9ce

Browse files
committed
Fixed an issue where Geometry Viewer was showing stale data and not auto-updating on query reruns or new query runs with new data or different geometry columns in Query tool. #9392
1 parent 965a27d commit eddc9ce

File tree

2 files changed

+144
-25
lines changed

2 files changed

+144
-25
lines changed

web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx

Lines changed: 91 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -436,25 +436,110 @@ export function GeometryViewer({rows, columns, column}) {
436436

437437
const mapRef = React.useRef();
438438
const contentRef = React.useRef();
439-
const data = parseData(rows, columns, column);
440439
const queryToolCtx = React.useContext(QueryToolContext);
441440

441+
// Track previous state to detect changes
442+
const prevStateRef = React.useRef({
443+
columnKey: null,
444+
columnNames: null,
445+
selectedRowPKs: [],
446+
});
447+
448+
const [mapKey, setMapKey] = React.useState(0);
449+
const currentColumnKey = column?.key;
450+
const currentColumnNames = React.useMemo(
451+
() => columns.map(c => c.key).sort().join(','),
452+
[columns]
453+
);
454+
455+
// Detect when to clear, filter, or re-render the map based on changes in geometry column, columns list, or rows
456+
useEffect(() => {
457+
const prevState = prevStateRef.current;
458+
459+
if (!currentColumnKey) {
460+
setMapKey(prev => prev + 1);
461+
prevStateRef.current = {
462+
columnKey: null,
463+
columnNames: null,
464+
selectedRowPKs: [],
465+
};
466+
return;
467+
}
468+
469+
if (currentColumnKey !== prevState.columnKey ||
470+
currentColumnNames !== prevState.columnNames) {
471+
setMapKey(prev => prev + 1);
472+
prevStateRef.current = {
473+
columnKey: currentColumnKey,
474+
columnNames: currentColumnNames,
475+
selectedRowPKs: [],
476+
};
477+
return;
478+
}
479+
480+
if (currentColumnKey === prevState.columnKey &&
481+
currentColumnNames === prevState.columnNames &&
482+
rows.length > 0) {
483+
484+
// If user previously selected specific rows, filter them from new data
485+
if (prevState.selectedRowPKs.length > 0 && prevState.selectedRowPKs.length < rows.length) {
486+
const newSelectedPKs = rows
487+
.filter(row => prevState.selectedRowPKs.includes(row.__temp_PK))
488+
.map(row => row.__temp_PK);
489+
490+
prevStateRef.current.selectedRowPKs = newSelectedPKs.length > 0 ? newSelectedPKs : rows.map(r => r.__temp_PK);
491+
} else {
492+
// All rows are displayed
493+
const allPKs = rows.map(r => r.__temp_PK);
494+
prevStateRef.current.selectedRowPKs = allPKs;
495+
}
496+
}
497+
}, [currentColumnKey, currentColumnNames, rows]);
498+
499+
const displayRows = React.useMemo(() => {
500+
if (!currentColumnKey || rows.length === 0) return [];
501+
502+
const selectedPKs = prevStateRef.current.selectedRowPKs;
503+
return selectedPKs.length > 0 && selectedPKs.length < rows.length
504+
? rows.filter(row => selectedPKs.includes(row.__temp_PK))
505+
: rows;
506+
}, [rows, currentColumnKey]);
507+
508+
// Parse geometry data only when needed
509+
const data = React.useMemo(() => {
510+
if (!currentColumnKey) {
511+
return {
512+
'geoJSONs': [],
513+
'selectedSRID': 0,
514+
'getPopupContent': undefined,
515+
'infoList': [gettext('Select a geometry/geography column to visualize.')],
516+
};
517+
}
518+
return parseData(displayRows, columns, column);
519+
}, [displayRows, columns, column, currentColumnKey]);
520+
442521
useEffect(()=>{
443522
let timeoutId;
444523
const contentResizeObserver = new ResizeObserver(()=>{
445524
clearTimeout(timeoutId);
446-
if(queryToolCtx.docker.isTabVisible(PANELS.GEOMETRY)) {
525+
if(queryToolCtx?.docker?.isTabVisible(PANELS.GEOMETRY)) {
447526
timeoutId = setTimeout(function () {
448527
mapRef.current?.invalidateSize();
449528
}, 100);
450529
}
451530
});
452-
contentResizeObserver.observe(contentRef.current);
453-
}, []);
531+
if(contentRef.current) {
532+
contentResizeObserver.observe(contentRef.current);
533+
}
534+
return () => {
535+
clearTimeout(timeoutId);
536+
contentResizeObserver.disconnect();
537+
};
538+
}, [queryToolCtx]);
454539

455-
// Dyanmic CRS is not supported. Use srid as key and recreate the map on change
540+
// Dyanmic CRS is not supported. Use srid and mapKey as key and recreate the map on change
456541
return (
457-
<StyledBox ref={contentRef} width="100%" height="100%" key={data.selectedSRID}>
542+
<StyledBox ref={contentRef} width="100%" height="100%" key={`${data.selectedSRID}-${mapKey}`}>
458543
<MapContainer
459544
crs={data.selectedSRID === 4326 ? CRS.EPSG3857 : CRS.Simple}
460545
zoom={2} center={[20, 100]}

web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx

Lines changed: 53 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -876,6 +876,9 @@ export function ResultSet() {
876876
rsu.current.setLoaderText = setLoaderText;
877877

878878
const isDataChangedRef = useRef(false);
879+
const prevRowsRef = React.useRef(null);
880+
const prevColumnsRef = React.useRef(null);
881+
879882
useEffect(()=>{
880883
isDataChangedRef.current = Boolean(_.size(dataChangeStore.updated) || _.size(dataChangeStore.added) || _.size(dataChangeStore.deleted));
881884
}, [dataChangeStore]);
@@ -1460,30 +1463,61 @@ export function ResultSet() {
14601463
return ()=>eventBus.deregisterListener(QUERY_TOOL_EVENTS.TRIGGER_ADD_ROWS, triggerAddRows);
14611464
}, [columns, selectedRows.size]);
14621465

1466+
const getFilteredRowsForGeometryViewer = React.useCallback(() => {
1467+
let selRowsData = rows;
1468+
if(selectedRows.size != 0) {
1469+
selRowsData = rows.filter((r)=>selectedRows.has(rowKeyGetter(r)));
1470+
} else if(selectedColumns.size > 0) {
1471+
let selectedCols = _.filter(columns, (_c, i)=>selectedColumns.has(i+1));
1472+
selRowsData = _.map(rows, (r)=>_.pick(r, _.map(selectedCols, (c)=>c.key)));
1473+
} else if(selectedRange.current) {
1474+
let [,, startRowIdx, endRowIdx] = getRangeIndexes();
1475+
selRowsData = rows.slice(startRowIdx, endRowIdx+1);
1476+
} else if(selectedCell.current?.[0]) {
1477+
selRowsData = [selectedCell.current[0]];
1478+
}
1479+
return selRowsData;
1480+
}, [rows, columns, selectedRows, selectedColumns]);
1481+
1482+
const openGeometryViewerTab = React.useCallback((column, rowsData) => {
1483+
layoutDocker.openTab({
1484+
id: PANELS.GEOMETRY,
1485+
title: gettext('Geometry Viewer'),
1486+
content: <GeometryViewer rows={rowsData} columns={columns} column={column} />,
1487+
closable: true,
1488+
}, PANELS.MESSAGES, 'after-tab', true);
1489+
}, [layoutDocker, columns]);
1490+
1491+
// Handle manual Geometry Viewer opening
14631492
useEffect(()=>{
14641493
const renderGeometries = (column)=>{
1465-
let selRowsData = rows;
1466-
if(selectedRows.size != 0) {
1467-
selRowsData = rows.filter((r)=>selectedRows.has(rowKeyGetter(r)));
1468-
} else if(selectedColumns.size > 0) {
1469-
let selectedCols = _.filter(columns, (_c, i)=>selectedColumns.has(i+1));
1470-
selRowsData = _.map(rows, (r)=>_.pick(r, _.map(selectedCols, (c)=>c.key)));
1471-
} else if(selectedRange.current) {
1472-
let [,, startRowIdx, endRowIdx] = getRangeIndexes();
1473-
selRowsData = rows.slice(startRowIdx, endRowIdx+1);
1474-
} else if(selectedCell.current?.[0]) {
1475-
selRowsData = [selectedCell.current[0]];
1476-
}
1477-
layoutDocker.openTab({
1478-
id: PANELS.GEOMETRY,
1479-
title:gettext('Geometry Viewer'),
1480-
content: <GeometryViewer rows={selRowsData} columns={columns} column={column} />,
1481-
closable: true,
1482-
}, PANELS.MESSAGES, 'after-tab', true);
1494+
const selRowsData = getFilteredRowsForGeometryViewer();
1495+
openGeometryViewerTab(column, selRowsData);
14831496
};
14841497
eventBus.registerListener(QUERY_TOOL_EVENTS.TRIGGER_RENDER_GEOMETRIES, renderGeometries);
14851498
return ()=>eventBus.deregisterListener(QUERY_TOOL_EVENTS.TRIGGER_RENDER_GEOMETRIES, renderGeometries);
1486-
}, [rows, columns, selectedRows.size, selectedColumns.size]);
1499+
}, [getFilteredRowsForGeometryViewer, openGeometryViewerTab, eventBus]);
1500+
1501+
// Auto-update Geometry Viewer when rows/columns change
1502+
useEffect(()=>{
1503+
const rowsChanged = prevRowsRef.current !== rows;
1504+
const columnsChanged = prevColumnsRef.current !== columns;
1505+
const currentGeometryColumn = columns.find(col => col.cell === 'geometry' || col.cell === 'geography');
1506+
1507+
if((rowsChanged || columnsChanged) && layoutDocker.isTabOpen(PANELS.GEOMETRY)) {
1508+
1509+
if(currentGeometryColumn) {
1510+
const selRowsData = getFilteredRowsForGeometryViewer();
1511+
openGeometryViewerTab(currentGeometryColumn, selRowsData);
1512+
} else {
1513+
// No geometry column
1514+
openGeometryViewerTab(null, []);
1515+
}
1516+
}
1517+
1518+
prevRowsRef.current = rows;
1519+
prevColumnsRef.current = columns;
1520+
}, [rows, columns, getFilteredRowsForGeometryViewer, openGeometryViewerTab, layoutDocker]);
14871521

14881522
const triggerResetScroll = () => {
14891523
// Reset the scroll position to previously saved location.

0 commit comments

Comments
 (0)