Added the ability to search for tables and automatically bring them into view in the ERD tool. #4306
parent
08379d6ae0
commit
986ba41ba9
|
|
@ -86,6 +86,9 @@ Editing Options
|
|||
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
|
||||
| Icon | Behavior | Shortcut |
|
||||
+======================+===================================================================================================+================+
|
||||
| *Search table* | Click to search for a table in the diagram. Selecting a table from the search results will bring | Option/Alt + |
|
||||
| | it into view and highlight it. | Ctrl + F |
|
||||
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
|
||||
| *Add table* | Click this button to add a new table to the diagram. On clicking, this will open a table dialog | Option/Alt + |
|
||||
| | where you can put the table details. | Ctrl + A |
|
||||
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
|
||||
|
|
@ -109,11 +112,14 @@ Table Relationship Options
|
|||
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
|
||||
| Icon | Behavior | Shortcut |
|
||||
+======================+===================================================================================================+================+
|
||||
| *1M* | Click this button to open a one-to-many relationship dialog to add a relationship between the | Option/Alt + |
|
||||
| *1-1* | Click this button to open a one-to-one relationship dialog to add a relationship between the | Option/Alt + |
|
||||
| | two tables. The selected table becomes the referencing table. | Ctrl + B |
|
||||
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
|
||||
| *1-M* | Click this button to open a one-to-many relationship dialog to add a relationship between the | Option/Alt + |
|
||||
| | two tables. The selected table becomes the referencing table and will have the *many* endpoint of | Ctrl + O |
|
||||
| | the link. | |
|
||||
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
|
||||
| *MM* | Click this button to open a many-to-many relationship dialog to add a relationship between the | Option/Alt + |
|
||||
| *M-M* | Click this button to open a many-to-many relationship dialog to add a relationship between the | Option/Alt + |
|
||||
| | two tables. This option will create a new table based on the selected columns for the two relating| Ctrl + M |
|
||||
| | tables and link them. | |
|
||||
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 340 KiB After Width: | Height: | Size: 679 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
|
@ -20,6 +20,7 @@ Bundled PostgreSQL Utilities
|
|||
New features
|
||||
************
|
||||
|
||||
| `Issue #4306 <https://github.com/pgadmin-org/pgadmin4/issues/4306>`_ - Added the ability to search for tables and automatically bring them into view in the ERD tool.
|
||||
| `Issue #6698 <https://github.com/pgadmin-org/pgadmin4/issues/6698>`_ - Add support for setting image download resolution in the ERD tool.
|
||||
| `Issue #7885 <https://github.com/pgadmin-org/pgadmin4/issues/7885>`_ - Add support for displaying detailed Citus query plans instead of 'Custom Scan' placeholder.
|
||||
| `Issue #8912 <https://github.com/pgadmin-org/pgadmin4/issues/8912>`_ - Add support for formatting .pgerd ERD project file.
|
||||
|
|
@ -34,4 +35,6 @@ Bug fixes
|
|||
|
||||
| `Issue #8504 <https://github.com/pgadmin-org/pgadmin4/issues/8504>`_ - Fixed an issue where data output column resize is not sticking in Safari.
|
||||
| `Issue #9117 <https://github.com/pgadmin-org/pgadmin4/issues/9117>`_ - Fixed an issue where Schema Diff does not ignore Tablespace for indexes.
|
||||
| `Issue #9132 <https://github.com/pgadmin-org/pgadmin4/issues/9132>`_ - Fixed an issue where the 2FA window redirected to the login page after session expiration.
|
||||
| `Issue #9240 <https://github.com/pgadmin-org/pgadmin4/issues/9240>`_ - Fixed an issue where the Debian build process failed with a "Sphinx module not found" error when using a Python virtual environment.
|
||||
| `Issue #9304 <https://github.com/pgadmin-org/pgadmin4/issues/9304>`_ - Fixed an issue that prevented assigning multiple users to an RLS policy.
|
||||
|
|
@ -295,7 +295,7 @@ const StyleDialog = styled(Dialog)(({theme}) => ({
|
|||
},
|
||||
}));
|
||||
|
||||
function ModalContainer({ id, title, content, dialogHeight, dialogWidth, onClose, fullScreen = false, isFullWidth = false, showFullScreen = false, isResizeable = false, minHeight = MIN_HEIGHT, minWidth = MIN_WIDTH, showTitle=true }) {
|
||||
function ModalContainer({ id, title, content, dialogHeight, dialogWidth, onClose, fullScreen = false, isFullWidth = false, showFullScreen = false, isResizeable = false, minHeight = MIN_HEIGHT, minWidth = MIN_WIDTH, showTitle=true, ...props }) {
|
||||
let useModalRef = useModal();
|
||||
let closeModal = (_e, reason) => {
|
||||
if(reason == 'backdropClick' && showTitle) {
|
||||
|
|
@ -321,6 +321,7 @@ function ModalContainer({ id, title, content, dialogHeight, dialogWidth, onClose
|
|||
fullScreen={isFullScreen}
|
||||
fullWidth={isFullWidth}
|
||||
disablePortal
|
||||
{...props}
|
||||
>
|
||||
{ showTitle &&
|
||||
<DialogTitle className='modal-drag-area'>
|
||||
|
|
|
|||
|
|
@ -158,6 +158,24 @@ class ERDModule(PgAdminModule):
|
|||
fields=shortcut_fields
|
||||
)
|
||||
|
||||
self.preference.register(
|
||||
'keyboard_shortcuts',
|
||||
'search_table',
|
||||
gettext('Search table'),
|
||||
'keyboardshortcut',
|
||||
{
|
||||
'alt': True,
|
||||
'shift': False,
|
||||
'control': True,
|
||||
'key': {
|
||||
'key_code': 70,
|
||||
'char': 'f'
|
||||
}
|
||||
},
|
||||
category_label=PREF_LABEL_KEYBOARD_SHORTCUTS,
|
||||
fields=shortcut_fields
|
||||
)
|
||||
|
||||
self.preference.register(
|
||||
'keyboard_shortcuts',
|
||||
'add_table',
|
||||
|
|
|
|||
|
|
@ -4,10 +4,13 @@ export const ERD_EVENTS = {
|
|||
TRIGGER_SHOW_SQL: 'TRIGGER_SHOW_SQL',
|
||||
SHOW_SQL: 'SHOW_SQL',
|
||||
DOWNLOAD_IMAGE: 'DOWNLOAD_IMAGE',
|
||||
|
||||
SEARCH_NODE: 'SEARCH_NODE',
|
||||
ADD_NODE: 'ADD_NODE',
|
||||
EDIT_NODE: 'EDIT_NODE',
|
||||
CLONE_NODE: 'CLONE_NODE',
|
||||
DELETE_NODE: 'DELETE_NODE',
|
||||
|
||||
SHOW_NOTE: 'SHOW_NOTE',
|
||||
ONE_TO_ONE: 'ONE_TO_ONE',
|
||||
ONE_TO_MANY: 'ONE_TO_MANY',
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ import { useApplicationState } from '../../../../../../settings/static/Applicati
|
|||
import { connectServerModal, connectServer } from '../../../../../sqleditor/static/js/components/connectServer';
|
||||
import { useEffect } from 'react';
|
||||
import { FileManagerUtils } from '../../../../../../misc/file_manager/static/js/components/FileManager';
|
||||
import SearchNode from './SearchNode';
|
||||
|
||||
/* Custom react-diagram action for keyboard events */
|
||||
export class KeyboardShortcutAction extends Action {
|
||||
|
|
@ -178,9 +179,9 @@ export default class ERDTool extends React.Component {
|
|||
this.eventBus = new EventBus();
|
||||
|
||||
_.bindAll(this, ['onLoadDiagram', 'onSaveDiagram', 'onSQLClick',
|
||||
'onImageClick', 'onAddNewNode', 'onEditTable', 'onCloneNode', 'onDeleteNode', 'onNoteClick',
|
||||
'onImageClick', 'onSearchNode', 'onAddNewNode', 'onEditTable', 'onCloneNode', 'onDeleteNode', 'onNoteClick',
|
||||
'onNoteClose', 'onOneToOneClick', 'onOneToManyClick', 'onManyToManyClick', 'onAutoDistribute', 'onDetailsToggle',
|
||||
'onChangeColors', 'onDropNode', 'onNotationChange', 'closePanel'
|
||||
'onChangeColors', 'onDropNode', 'onNotationChange', 'closePanel', 'scrollToNode'
|
||||
]);
|
||||
|
||||
this.diagram.zoomToFit = this.diagram.zoomToFit.bind(this.diagram);
|
||||
|
|
@ -249,6 +250,7 @@ export default class ERDTool extends React.Component {
|
|||
this.eventBus.registerListener(ERD_EVENTS.SAVE_DIAGRAM, this.onSaveDiagram);
|
||||
this.eventBus.registerListener(ERD_EVENTS.SHOW_SQL, this.onSQLClick);
|
||||
this.eventBus.registerListener(ERD_EVENTS.DOWNLOAD_IMAGE, this.onImageClick);
|
||||
this.eventBus.registerListener(ERD_EVENTS.SEARCH_NODE, this.onSearchNode);
|
||||
this.eventBus.registerListener(ERD_EVENTS.ADD_NODE, this.onAddNewNode);
|
||||
this.eventBus.registerListener(ERD_EVENTS.EDIT_NODE, this.onEditTable);
|
||||
this.eventBus.registerListener(ERD_EVENTS.CLONE_NODE, this.onCloneNode);
|
||||
|
|
@ -285,6 +287,9 @@ export default class ERDTool extends React.Component {
|
|||
[this.state.preferences.download_image, ()=>{
|
||||
this.eventBus.fireEvent(ERD_EVENTS.DOWNLOAD_IMAGE);
|
||||
}],
|
||||
[this.state.preferences.search_table, ()=>{
|
||||
this.eventBus.fireEvent(ERD_EVENTS.SEARCH_NODE);
|
||||
}],
|
||||
[this.state.preferences.add_table, ()=>{
|
||||
this.eventBus.fireEvent(ERD_EVENTS.ADD_NODE);
|
||||
}],
|
||||
|
|
@ -488,12 +493,69 @@ export default class ERDTool extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
scrollToNode(node) {
|
||||
const engine = this.diagram.getEngine();
|
||||
const model = engine.getModel();
|
||||
const container = this.canvasEle;
|
||||
if (!node || !container) return;
|
||||
|
||||
const { x, y } = node.getPosition();
|
||||
const zoom = model.getZoomLevel() / 100;
|
||||
const offsetX = model.getOffsetX();
|
||||
const offsetY = model.getOffsetY();
|
||||
|
||||
const viewportWidth = container.clientWidth;
|
||||
const viewportHeight = container.clientHeight;
|
||||
|
||||
const nodeWidth = node.width; // Approximate width of a table node
|
||||
const nodeHeight = node.height; // Approximate height of a table node
|
||||
|
||||
// Node screen bounds
|
||||
const nodeLeft = x * zoom + offsetX;
|
||||
const nodeRight = nodeLeft + nodeWidth * zoom;
|
||||
const nodeTop = y * zoom + offsetY;
|
||||
const nodeBottom = nodeTop + nodeHeight * zoom;
|
||||
|
||||
let newOffsetX = offsetX;
|
||||
let newOffsetY = offsetY;
|
||||
|
||||
// Check horizontal visibility
|
||||
if (nodeLeft < 0) {
|
||||
newOffsetX += -nodeLeft + 20; // 20px padding
|
||||
} else if (nodeRight > viewportWidth) {
|
||||
newOffsetX -= nodeRight - viewportWidth + 20;
|
||||
}
|
||||
|
||||
// Check vertical visibility
|
||||
if (nodeHeight * zoom >= viewportHeight) {
|
||||
// Node taller than viewport: snap top of node to top of viewport
|
||||
newOffsetY = offsetY + viewportHeight / 2 - (nodeHeight * zoom) / 2;
|
||||
newOffsetY = offsetY - (nodeTop - 20); // aligns top
|
||||
} else {
|
||||
// Node fits in viewport: ensure fully visible
|
||||
if (nodeTop < 0) {
|
||||
newOffsetY += -nodeTop + 20;
|
||||
} else if (nodeBottom > viewportHeight) {
|
||||
newOffsetY -= nodeBottom - viewportHeight + 20;
|
||||
}
|
||||
}
|
||||
|
||||
// Update offset only if needed
|
||||
if (newOffsetX !== offsetX || newOffsetY !== offsetY) {
|
||||
model.setOffset(newOffsetX, newOffsetY);
|
||||
}
|
||||
|
||||
this.diagram.repaint();
|
||||
node.setSelected(true);
|
||||
node.fireEvent({}, 'highlightFlash');
|
||||
};
|
||||
|
||||
|
||||
addEditTable(node) {
|
||||
let dialog = this.getDialog('table_dialog');
|
||||
if(node) {
|
||||
let [schema, table] = node.getSchemaTableName();
|
||||
let oldData = node.getData();
|
||||
dialog(gettext('Table: %s (%s)', _.escape(table),_.escape(schema)), oldData, false, (newData)=>{
|
||||
dialog(gettext('Table: %s', node.getDisplayName()), oldData, false, (newData)=>{
|
||||
if(this.diagram.anyDuplicateNodeName(newData, oldData)) {
|
||||
return gettext('Table name already exists');
|
||||
}
|
||||
|
|
@ -560,6 +622,12 @@ export default class ERDTool extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
onSearchNode() {
|
||||
this.context.showModal(gettext('Search'), (closeModal)=>(
|
||||
<SearchNode tableNodes={this.diagram.getModel().getNodesDict()} onClose={closeModal} scrollToNode={this.scrollToNode} />
|
||||
), {id: 'id-erd-search-node', showTitle: false, disableRestoreFocus: true});
|
||||
}
|
||||
|
||||
onAddNewNode() {
|
||||
this.addEditTable();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,8 +62,7 @@ export default function FloatingNote({open, onClose, anchorEl, rows, noteNode})
|
|||
|
||||
const header = useMemo(()=>{
|
||||
if(noteNode) {
|
||||
let [schema, name] = noteNode.getSchemaTableName();
|
||||
return `${name} (${schema})`;
|
||||
return noteNode.getDisplayName();
|
||||
}
|
||||
return '';
|
||||
}, [open]);
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import ImageRoundedIcon from '@mui/icons-material/ImageRounded';
|
|||
import FormatColorFillRoundedIcon from '@mui/icons-material/FormatColorFillRounded';
|
||||
import FormatColorTextRoundedIcon from '@mui/icons-material/FormatColorTextRounded';
|
||||
import AccountTreeOutlinedIcon from '@mui/icons-material/AccountTreeOutlined';
|
||||
import SearchOutlinedIcon from '@mui/icons-material/SearchOutlined';
|
||||
|
||||
import { PgMenu, PgMenuItem, usePgMenuGroup } from '../../../../../../static/js/components/Menu';
|
||||
import gettext from 'sources/gettext';
|
||||
|
|
@ -201,6 +202,11 @@ export function MainToolBar({preferences, eventBus, fillColor, textColor, notati
|
|||
}} />
|
||||
</PgButtonGroup>
|
||||
<PgButtonGroup size="small">
|
||||
<PgIconButton title={gettext('Search Table')} icon={<SearchOutlinedIcon />}
|
||||
shortcut={preferences.search_table}
|
||||
onClick={()=>{
|
||||
eventBus.fireEvent(ERD_EVENTS.SEARCH_NODE);
|
||||
}} />
|
||||
<PgIconButton title={gettext('Add Table')} icon={<AddBoxIcon />}
|
||||
shortcut={preferences.add_table}
|
||||
onClick={()=>{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2025, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import { InputSelect } from '../../../../../../static/js/components/FormComponents';
|
||||
|
||||
|
||||
export default function SearchNode({tableNodes, onClose, scrollToNode}) {
|
||||
const onSelectChange = (val) => {
|
||||
let node = tableNodes[val];
|
||||
if(node) {
|
||||
scrollToNode(node);
|
||||
}
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<InputSelect
|
||||
options={Object.values(tableNodes).map(node => ({
|
||||
value: node.getID(),
|
||||
label: node.getDisplayName(),
|
||||
}))}
|
||||
onChange={onSelectChange}
|
||||
autoFocus
|
||||
placeholder="Select a table"
|
||||
openMenuOnFocus
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
SearchNode.propTypes = {
|
||||
tableNodes: PropTypes.object.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
|
@ -50,8 +50,7 @@ class ManyToManySchema extends BaseUISchema {
|
|||
export function getManyToManyDialogSchema(attributes, tableNodesDict) {
|
||||
let tablesData = [];
|
||||
_.forEach(tableNodesDict, (node, uid)=>{
|
||||
let [schema, name] = node.getSchemaTableName();
|
||||
tablesData.push({value: uid, label: `(${schema}) ${name}`, image: 'icon-table'});
|
||||
tablesData.push({value: uid, label: node.getDisplayName(), image: 'icon-table'});
|
||||
});
|
||||
|
||||
return new ManyToManySchema({
|
||||
|
|
|
|||
|
|
@ -50,8 +50,7 @@ class OneToManySchema extends BaseUISchema {
|
|||
export function getOneToManyDialogSchema(attributes, tableNodesDict) {
|
||||
let tablesData = [];
|
||||
_.forEach(tableNodesDict, (node, uid)=>{
|
||||
let [schema, name] = node.getSchemaTableName();
|
||||
tablesData.push({value: uid, label: `(${schema}) ${name}`, image: 'icon-table'});
|
||||
tablesData.push({value: uid, label: node.getDisplayName(), image: 'icon-table'});
|
||||
});
|
||||
|
||||
return new OneToManySchema({
|
||||
|
|
|
|||
|
|
@ -83,8 +83,7 @@ class OneToOneSchema extends BaseUISchema {
|
|||
export function getOneToOneDialogSchema(attributes, tableNodesDict) {
|
||||
let tablesData = [];
|
||||
_.forEach(tableNodesDict, (node, uid)=>{
|
||||
let [schema, name] = node.getSchemaTableName();
|
||||
tablesData.push({value: uid, label: `(${schema}) ${name}`, image: 'icon-table'});
|
||||
tablesData.push({value: uid, label: node.getDisplayName(), image: 'icon-table'});
|
||||
});
|
||||
|
||||
return new OneToOneSchema({
|
||||
|
|
|
|||
|
|
@ -61,8 +61,7 @@ export function getTableDialogSchema(attributes, isNew, tableNodesDict, colTypes
|
|||
references: ()=>{
|
||||
let retOpts = [];
|
||||
_.forEach(tableNodesDict, (node, uid)=>{
|
||||
let [schema, name] = node.getSchemaTableName();
|
||||
retOpts.push({value: uid, label: `(${schema}) ${name}`});
|
||||
retOpts.push({value: uid, label: node.getDisplayName()});
|
||||
});
|
||||
return retOpts;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -147,6 +147,10 @@ export class TableNodeModel extends DefaultNodeModel {
|
|||
return [this._data.schema, this._data.name];
|
||||
}
|
||||
|
||||
getDisplayName() {
|
||||
return `(${this._data.schema}) ${this._data.name}`;
|
||||
}
|
||||
|
||||
remove() {
|
||||
Object.values(this.getPorts()).forEach((port)=>{
|
||||
port.removeAllLinks();
|
||||
|
|
@ -218,6 +222,19 @@ const StyledDiv = styled('div')(({theme})=>({
|
|||
position: 'relative',
|
||||
width: `${TABLE_WIDTH}px`,
|
||||
fontSize: '0.8em',
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
|
||||
'&.flash': {
|
||||
animation: 'flash 2s ease-in-out',
|
||||
},
|
||||
'@keyframes flash': {
|
||||
'0%': {
|
||||
boxShadow: `0 0 10px 5px ${theme.palette.primary.main}`,
|
||||
},
|
||||
'100%': {
|
||||
boxShadow: 'none',
|
||||
},
|
||||
},
|
||||
|
||||
'& .TableNode-tableContent': {
|
||||
backgroundColor: theme.palette.background.default,
|
||||
|
|
@ -263,10 +280,21 @@ const StyledDiv = styled('div')(({theme})=>({
|
|||
padding: '0.125rem 0.25rem',
|
||||
wordBreak: 'break-all',
|
||||
},
|
||||
|
||||
'&:last-child': {
|
||||
borderBottom: 'none',
|
||||
},
|
||||
},
|
||||
},
|
||||
'&.TableNode-tableNodeSelected': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
'& .TableNode-tableToolbar': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
},
|
||||
'& .TableNode-tableContent': {
|
||||
borderLeftColor: theme.palette.primary.main,
|
||||
borderRightColor: theme.palette.primary.main,
|
||||
borderBottomColor: theme.palette.primary.main,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
|
|
@ -276,6 +304,7 @@ export class TableNodeWidget extends React.Component {
|
|||
|
||||
this.state = {
|
||||
show_details: true,
|
||||
flash: false,
|
||||
};
|
||||
|
||||
this.tableNodeEventListener = this.props.node.registerListener({
|
||||
|
|
@ -291,6 +320,12 @@ export class TableNodeWidget extends React.Component {
|
|||
dataAvaiable: ()=>{
|
||||
/* Just re-render */
|
||||
this.setState({});
|
||||
},
|
||||
highlightFlash: ()=>{
|
||||
this.setState({flash: true});
|
||||
setTimeout(()=>{
|
||||
this.setState({flash: false});
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -368,13 +403,21 @@ export class TableNodeWidget extends React.Component {
|
|||
(tableData.unique_constraint||[]).forEach((uk)=>{
|
||||
localUkCols.push(...uk.columns.map((c)=>c.column));
|
||||
});
|
||||
const styles = {
|
||||
const contentStyles = {
|
||||
backgroundColor: tableMetaData.fillColor,
|
||||
color: tableMetaData.textColor,
|
||||
};
|
||||
|
||||
let classList = ['TableNode-tableNode'];
|
||||
if(this.props.node.isSelected()) {
|
||||
classList.push('TableNode-tableNodeSelected');
|
||||
}
|
||||
if(this.state.flash) {
|
||||
classList.push('flash');
|
||||
}
|
||||
return (
|
||||
<StyledDiv className={['TableNode-tableNode', (this.props.node.isSelected() ? 'TableNode-tableNodeSelected': '')].join(' ')}
|
||||
onDoubleClick={()=>{this.props.node.fireEvent({}, 'editTable');}} style={styles}>
|
||||
<StyledDiv className={classList.join(' ')}
|
||||
onDoubleClick={()=>{this.props.node.fireEvent({}, 'editTable');}}>
|
||||
<div className={'TableNode-tableToolbar'}>
|
||||
<PgIconButton size="xs" title={gettext('Show Details')} icon={this.state.show_details ? <VisibilityRoundedIcon /> : <VisibilityOffRoundedIcon />}
|
||||
onClick={this.toggleShowDetails} onDoubleClick={(e)=>{e.stopPropagation();}} />
|
||||
|
|
@ -386,7 +429,7 @@ export class TableNodeWidget extends React.Component {
|
|||
}}
|
||||
/>}
|
||||
</div>
|
||||
<div className='TableNode-tableContent'>
|
||||
<div className='TableNode-tableContent' style={contentStyles}>
|
||||
{tableMetaData.is_promise &&
|
||||
<div className='TableNode-tableSection'>
|
||||
{!tableMetaData.data_failed && <div className='TableNode-tableNameText'>{gettext('Fetching...')}</div>}
|
||||
|
|
|
|||
|
|
@ -65,18 +65,25 @@ describe('SchemaView', ()=>{
|
|||
});
|
||||
},
|
||||
simulateValidData = async ()=>{
|
||||
|
||||
// Wait for focus
|
||||
await act(async ()=>{
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
});
|
||||
await user.type(ctrl.container.querySelector('[name="field1"]'), 'val1');
|
||||
await user.type(ctrl.container.querySelector('[name="field2"]'), '2');
|
||||
await user.type(ctrl.container.querySelector('[name="field5"]'), 'val5');
|
||||
/* Add a row */
|
||||
await user.click(ctrl.container.querySelector('button[data-test="add-row"]'));
|
||||
await user.click(ctrl.container.querySelector('button[data-test="add-row"]'));
|
||||
// Wait for focus
|
||||
await act(async ()=>{
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
});
|
||||
await user.type(ctrl.container.querySelectorAll('[name="field5"]')[0], 'rval51');
|
||||
await user.type(ctrl.container.querySelectorAll('[name="field5"]')[1], 'rval52');
|
||||
// Wait for validations to run
|
||||
await act(async ()=>{
|
||||
await new Promise(resolve => setTimeout(resolve));
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export class FakeNode {
|
|||
getColumnAt(pos) {return _.find(this.getColumns()||[], (c)=>c.attnum==pos);}
|
||||
remove() {/*This is intentional (SonarQube)*/}
|
||||
getSchemaTableName() {return [this.data.schema, this.data.name];}
|
||||
getDisplayName() {return `(${this.data.schema}) ${this.data.name}`;}
|
||||
cloneData(tabName) {
|
||||
let retVal = {...this.data};
|
||||
retVal.name = tabName;
|
||||
|
|
|
|||
|
|
@ -26,6 +26,9 @@ describe('ERD FloatingNote', ()=>{
|
|||
getSchemaTableName: function() {
|
||||
return ['schema1', 'table1'];
|
||||
},
|
||||
getDisplayName: function() {
|
||||
return '(schema1) table1';
|
||||
}
|
||||
};
|
||||
const user = userEvent.setup();
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue