mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2024-11-22 08:46:39 -06:00
Added support to allow tables to be dragged to ERD Tool. Fixes #6241
This commit is contained in:
parent
476d7c5fc9
commit
7f3c3fa6f9
@ -12,6 +12,7 @@ The Entity-Relationship Diagram (ERD) tool is a database design tool that provid
|
|||||||
* Save the diagram and open it later to continue working on it.
|
* Save the diagram and open it later to continue working on it.
|
||||||
* Generate ready to run SQL from the database design.
|
* Generate ready to run SQL from the database design.
|
||||||
* Generate the database diagram for an existing database.
|
* Generate the database diagram for an existing database.
|
||||||
|
* Drag and drop tables from browser tree to the diagram.
|
||||||
|
|
||||||
.. image:: images/erd_tool.png
|
.. image:: images/erd_tool.png
|
||||||
:alt: ERD tool window
|
:alt: ERD tool window
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 82 KiB |
Binary file not shown.
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 84 KiB |
Binary file not shown.
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 47 KiB |
@ -11,6 +11,7 @@ New features
|
|||||||
|
|
||||||
| `Issue #4596 <https://redmine.postgresql.org/issues/4596>`_ - Added support for indent guides in the browser tree.
|
| `Issue #4596 <https://redmine.postgresql.org/issues/4596>`_ - Added support for indent guides in the browser tree.
|
||||||
| `Issue #6081 <https://redmine.postgresql.org/issues/6081>`_ - Added support for advanced table fields like the foreign key, primary key in the ERD tool.
|
| `Issue #6081 <https://redmine.postgresql.org/issues/6081>`_ - Added support for advanced table fields like the foreign key, primary key in the ERD tool.
|
||||||
|
| `Issue #6241 <https://redmine.postgresql.org/issues/6241>`_ - Added support to allow tables to be dragged to ERD Tool.
|
||||||
| `Issue #6529 <https://redmine.postgresql.org/issues/6529>`_ - Added index creation when generating SQL in the ERD tool.
|
| `Issue #6529 <https://redmine.postgresql.org/issues/6529>`_ - Added index creation when generating SQL in the ERD tool.
|
||||||
| `Issue #6657 <https://redmine.postgresql.org/issues/6657>`_ - Added support for authentication via the webserver (REMOTE_USER).
|
| `Issue #6657 <https://redmine.postgresql.org/issues/6657>`_ - Added support for authentication via the webserver (REMOTE_USER).
|
||||||
| `Issue #6794 <https://redmine.postgresql.org/issues/6794>`_ - Added support to enable/disable rules.
|
| `Issue #6794 <https://redmine.postgresql.org/issues/6794>`_ - Added support to enable/disable rules.
|
||||||
|
@ -7,6 +7,8 @@
|
|||||||
//
|
//
|
||||||
//////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
import { generateNodeUrl } from './node_ajax';
|
||||||
|
|
||||||
define('pgadmin.browser', [
|
define('pgadmin.browser', [
|
||||||
'sources/gettext', 'sources/url_for', 'require', 'jquery', 'underscore',
|
'sources/gettext', 'sources/url_for', 'require', 'jquery', 'underscore',
|
||||||
'bootstrap', 'sources/pgadmin', 'pgadmin.alertifyjs', 'bundled_codemirror',
|
'bootstrap', 'sources/pgadmin', 'pgadmin.alertifyjs', 'bundled_codemirror',
|
||||||
@ -61,8 +63,16 @@ define('pgadmin.browser', [
|
|||||||
function(b) {
|
function(b) {
|
||||||
InitTree.initBrowserTree(b).then(() => {
|
InitTree.initBrowserTree(b).then(() => {
|
||||||
b.tree.registerDraggableType({
|
b.tree.registerDraggableType({
|
||||||
'collation domain domain_constraints fts_configuration fts_dictionary fts_parser fts_template synonym table partition type sequence package view mview foreign_table edbvar' : (data, item)=>{
|
'collation domain domain_constraints fts_configuration fts_dictionary fts_parser fts_template synonym table partition type sequence package view mview foreign_table edbvar' : (data, item, treeNodeInfo)=>{
|
||||||
return pgadminUtils.fully_qualify(b, data, item);
|
let text = pgadminUtils.fully_qualify(b, data, item);
|
||||||
|
return {
|
||||||
|
text: text,
|
||||||
|
objUrl: generateNodeUrl.call(pgBrowser.Nodes[data._type], treeNodeInfo, 'properties', data, true),
|
||||||
|
cur: {
|
||||||
|
from: text.length,
|
||||||
|
to: text.length,
|
||||||
|
},
|
||||||
|
};
|
||||||
},
|
},
|
||||||
'schema column database cast event_trigger extension language foreign_data_wrapper foreign_server user_mapping compound_trigger index index_constraint primary_key unique_constraint check_constraint exclusion_constraint foreign_key rule' : (data)=>{
|
'schema column database cast event_trigger extension language foreign_data_wrapper foreign_server user_mapping compound_trigger index index_constraint primary_key unique_constraint check_constraint exclusion_constraint foreign_key rule' : (data)=>{
|
||||||
return pgadminUtils.quote_ident(data._label);
|
return pgadminUtils.quote_ident(data._label);
|
||||||
|
@ -467,7 +467,7 @@ export class Tree {
|
|||||||
* overrides the dragstart event set using element.on('dragstart')
|
* overrides the dragstart event set using element.on('dragstart')
|
||||||
* This will avoid conflict.
|
* This will avoid conflict.
|
||||||
*/
|
*/
|
||||||
let dropDetails = dropDetailsFunc(data, item);
|
let dropDetails = dropDetailsFunc(data, item, this.getTreeNodeHierarchy(item));
|
||||||
|
|
||||||
if(typeof dropDetails == 'string') {
|
if(typeof dropDetails == 'string') {
|
||||||
dropDetails = {
|
dropDetails = {
|
||||||
|
@ -175,11 +175,12 @@ export default class ERDCore {
|
|||||||
|
|
||||||
getModel() {return this.getEngine().getModel();}
|
getModel() {return this.getEngine().getModel();}
|
||||||
|
|
||||||
getNewNode(initData) {
|
getNewNode(initData, dataUrl=null) {
|
||||||
return this.getEngine().getNodeFactories().getFactory('table').generateModel({
|
return this.getEngine().getNodeFactories().getFactory('table').generateModel({
|
||||||
initialConfig: {
|
initialConfig: {
|
||||||
otherInfo: {
|
otherInfo: {
|
||||||
data:initData,
|
data:initData,
|
||||||
|
dataUrl: dataUrl,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -404,6 +405,21 @@ export default class ERDCore {
|
|||||||
this.repaint();
|
this.repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cloneTableData(tableData, name) {
|
||||||
|
const SKIP_CLONE_KEYS = ['foreign_key'];
|
||||||
|
|
||||||
|
if(!tableData) {
|
||||||
|
return tableData;
|
||||||
|
}
|
||||||
|
let newData = {
|
||||||
|
..._.pickBy(tableData, (_v, k)=>(SKIP_CLONE_KEYS.indexOf(k) == -1)),
|
||||||
|
};
|
||||||
|
if(name) {
|
||||||
|
newData['name'] = name;
|
||||||
|
}
|
||||||
|
return newData;
|
||||||
|
}
|
||||||
|
|
||||||
serialize(version) {
|
serialize(version) {
|
||||||
return {
|
return {
|
||||||
version: version||0,
|
version: version||0,
|
||||||
@ -422,7 +438,10 @@ export default class ERDCore {
|
|||||||
let nodesDict = this.getModel().getNodesDict();
|
let nodesDict = this.getModel().getNodesDict();
|
||||||
|
|
||||||
Object.keys(nodesDict).forEach((id)=>{
|
Object.keys(nodesDict).forEach((id)=>{
|
||||||
nodes[id] = nodesDict[id].serializeData();
|
let nodeData = nodesDict[id].serializeData();
|
||||||
|
if(nodeData) {
|
||||||
|
nodes[id] = nodeData;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -18,6 +18,7 @@ import PrimaryKeyIcon from 'top/browser/server_groups/servers/databases/schemas/
|
|||||||
import ForeignKeyIcon from 'top/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/static/img/foreign_key.svg';
|
import ForeignKeyIcon from 'top/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/static/img/foreign_key.svg';
|
||||||
import ColumnIcon from 'top/browser/server_groups/servers/databases/schemas/tables/columns/static/img/column.svg';
|
import ColumnIcon from 'top/browser/server_groups/servers/databases/schemas/tables/columns/static/img/column.svg';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import gettext from 'sources/gettext';
|
||||||
|
|
||||||
const TYPE = 'table';
|
const TYPE = 'table';
|
||||||
|
|
||||||
@ -29,11 +30,35 @@ export class TableNodeModel extends DefaultNodeModel {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this._note = otherInfo.note || '';
|
this._note = otherInfo.note || '';
|
||||||
|
this._metadata = {
|
||||||
this._data = {
|
data_failed: false,
|
||||||
columns: [],
|
...otherInfo.metadata,
|
||||||
...otherInfo.data,
|
is_promise: Boolean(otherInfo.data?.then || (otherInfo.metadata?.data_failed && !otherInfo.data)),
|
||||||
};
|
};
|
||||||
|
this._data = null;
|
||||||
|
if(otherInfo.data?.then) {
|
||||||
|
otherInfo.data.then((data)=>{
|
||||||
|
/* Once the data is available, it is no more a promise */
|
||||||
|
this._data = data;
|
||||||
|
this._metadata = {
|
||||||
|
data_failed: false,
|
||||||
|
is_promise: false,
|
||||||
|
};
|
||||||
|
this.fireEvent(this._metadata, 'dataAvaiable');
|
||||||
|
this.fireEvent({}, 'nodeUpdated');
|
||||||
|
}).catch(()=>{
|
||||||
|
this._metadata = {
|
||||||
|
data_failed: true,
|
||||||
|
is_promise: true,
|
||||||
|
};
|
||||||
|
this.fireEvent(this._metadata, 'dataAvaiable');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this._data = {
|
||||||
|
columns: [],
|
||||||
|
...otherInfo.data,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getPortName(attnum) {
|
getPortName(attnum) {
|
||||||
@ -48,6 +73,10 @@ export class TableNodeModel extends DefaultNodeModel {
|
|||||||
return this._note;
|
return this._note;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getMetadata() {
|
||||||
|
return this._metadata;
|
||||||
|
}
|
||||||
|
|
||||||
addColumn(col) {
|
addColumn(col) {
|
||||||
this._data.columns.push(col);
|
this._data.columns.push(col);
|
||||||
}
|
}
|
||||||
@ -64,17 +93,6 @@ export class TableNodeModel extends DefaultNodeModel {
|
|||||||
this._data['name'] = name;
|
this._data['name'] = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
cloneData(name) {
|
|
||||||
const SKIP_CLONE_KEYS = ['foreign_key'];
|
|
||||||
let newData = {
|
|
||||||
..._.pickBy(this.getData(), (_v, k)=>(SKIP_CLONE_KEYS.indexOf(k) == -1)),
|
|
||||||
};
|
|
||||||
if(name) {
|
|
||||||
newData['name'] = name;
|
|
||||||
}
|
|
||||||
return newData;
|
|
||||||
}
|
|
||||||
|
|
||||||
setData(data) {
|
setData(data) {
|
||||||
let self = this;
|
let self = this;
|
||||||
/* Remove the links if column dropped or primary key removed */
|
/* Remove the links if column dropped or primary key removed */
|
||||||
@ -116,6 +134,7 @@ export class TableNodeModel extends DefaultNodeModel {
|
|||||||
otherInfo: {
|
otherInfo: {
|
||||||
data: this.getData(),
|
data: this.getData(),
|
||||||
note: this.getNote(),
|
note: this.getNote(),
|
||||||
|
metadata: this.getMetadata(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -146,6 +165,10 @@ export class TableNodeWidget extends React.Component {
|
|||||||
toggleDetails: (event) => {
|
toggleDetails: (event) => {
|
||||||
this.setState({show_details: event.show_details});
|
this.setState({show_details: event.show_details});
|
||||||
},
|
},
|
||||||
|
dataAvaiable: ()=>{
|
||||||
|
/* Just re-render */
|
||||||
|
this.setState({});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,26 +215,37 @@ export class TableNodeWidget extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
let tableData = this.props.node.getData();
|
let tableData = this.props.node.getData();
|
||||||
|
let tableMetaData = this.props.node.getMetadata();
|
||||||
return (
|
return (
|
||||||
<div className={'table-node ' + (this.props.node.isSelected() ? 'selected': '') } onDoubleClick={()=>{this.props.node.fireEvent({}, 'editTable');}}>
|
<div className={'table-node ' + (this.props.node.isSelected() ? 'selected': '') } onDoubleClick={()=>{this.props.node.fireEvent({}, 'editTable');}}>
|
||||||
<div className="table-toolbar">
|
<div className="table-toolbar">
|
||||||
<DetailsToggleButton className='btn-xs' showDetails={this.state.show_details} onClick={this.toggleShowDetails} onDoubleClick={(e)=>{e.stopPropagation();}} />
|
<DetailsToggleButton className='btn-xs' showDetails={this.state.show_details}
|
||||||
|
onClick={this.toggleShowDetails} onDoubleClick={(e)=>{e.stopPropagation();}}
|
||||||
|
disabled={tableMetaData.is_promise} />
|
||||||
{this.props.node.getNote() &&
|
{this.props.node.getNote() &&
|
||||||
<IconButton icon="far fa-sticky-note" className="btn-xs btn-warning ml-auto" onClick={()=>{
|
<IconButton icon="far fa-sticky-note" className="btn-xs btn-warning ml-auto" onClick={()=>{
|
||||||
this.props.node.fireEvent({}, 'showNote');
|
this.props.node.fireEvent({}, 'showNote');
|
||||||
}} title="Check note" />}
|
}} title="Check note"/>}
|
||||||
</div>
|
|
||||||
<div className="d-flex table-schema-data">
|
|
||||||
<RowIcon icon={SchemaIcon}/>
|
|
||||||
<div className="table-schema my-auto">{tableData.schema}</div>
|
|
||||||
</div>
|
|
||||||
<div className="d-flex table-name-data">
|
|
||||||
<RowIcon icon={TableIcon} />
|
|
||||||
<div className="table-name my-auto">{tableData.name}</div>
|
|
||||||
</div>
|
|
||||||
<div className="table-cols">
|
|
||||||
{_.map(tableData.columns, (col)=>this.generateColumn(col, tableData))}
|
|
||||||
</div>
|
</div>
|
||||||
|
{tableMetaData.is_promise && <>
|
||||||
|
<div className="d-flex table-name-data">
|
||||||
|
{!tableMetaData.data_failed && <div className="table-name my-auto">{gettext('Fetching...')}</div>}
|
||||||
|
{tableMetaData.data_failed && <div className="table-name my-auto fetch-error">{gettext('Failed to get data. Please delete this table.')}</div>}
|
||||||
|
</div>
|
||||||
|
</>}
|
||||||
|
{!tableMetaData.is_promise && <>
|
||||||
|
<div className="d-flex table-schema-data">
|
||||||
|
<RowIcon icon={SchemaIcon}/>
|
||||||
|
<div className="table-schema my-auto">{tableData.schema}</div>
|
||||||
|
</div>
|
||||||
|
<div className="d-flex table-name-data">
|
||||||
|
<RowIcon icon={TableIcon} />
|
||||||
|
<div className="table-name my-auto">{tableData.name}</div>
|
||||||
|
</div>
|
||||||
|
<div className="table-cols">
|
||||||
|
{_.map(tableData.columns, (col)=>this.generateColumn(col, tableData))}
|
||||||
|
</div>
|
||||||
|
</>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ import url_for from 'sources/url_for';
|
|||||||
import {showERDSqlTool} from 'tools/datagrid/static/js/show_query_tool';
|
import {showERDSqlTool} from 'tools/datagrid/static/js/show_query_tool';
|
||||||
import 'wcdocker';
|
import 'wcdocker';
|
||||||
import Theme from '../../../../../../static/js/Theme';
|
import Theme from '../../../../../../static/js/Theme';
|
||||||
|
import TableSchema from '../../../../../../browser/server_groups/servers/databases/schemas/tables/static/js/table.ui';
|
||||||
|
|
||||||
/* Custom react-diagram action for keyboard events */
|
/* Custom react-diagram action for keyboard events */
|
||||||
export class KeyboardShortcutAction extends Action {
|
export class KeyboardShortcutAction extends Action {
|
||||||
@ -94,7 +95,7 @@ export default class BodyWidget extends React.Component {
|
|||||||
_.bindAll(this, ['onLoadDiagram', 'onSaveDiagram', 'onSaveAsDiagram', 'onSQLClick',
|
_.bindAll(this, ['onLoadDiagram', 'onSaveDiagram', 'onSaveAsDiagram', 'onSQLClick',
|
||||||
'onImageClick', 'onAddNewNode', 'onEditTable', 'onCloneNode', 'onDeleteNode', 'onNoteClick',
|
'onImageClick', 'onAddNewNode', 'onEditTable', 'onCloneNode', 'onDeleteNode', 'onNoteClick',
|
||||||
'onNoteClose', 'onOneToManyClick', 'onManyToManyClick', 'onAutoDistribute', 'onDetailsToggle',
|
'onNoteClose', 'onOneToManyClick', 'onManyToManyClick', 'onAutoDistribute', 'onDetailsToggle',
|
||||||
'onDetailsToggle', 'onHelpClick'
|
'onDetailsToggle', 'onHelpClick', 'onDropNode',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
this.diagram.zoomToFit = this.diagram.zoomToFit.bind(this.diagram);
|
this.diagram.zoomToFit = this.diagram.zoomToFit.bind(this.diagram);
|
||||||
@ -114,8 +115,15 @@ export default class BodyWidget extends React.Component {
|
|||||||
this.realignGrid({backgroundSize: `${bgSize*3}px ${bgSize*3}px`});
|
this.realignGrid({backgroundSize: `${bgSize*3}px ${bgSize*3}px`});
|
||||||
},
|
},
|
||||||
'nodesSelectionChanged': ()=>{
|
'nodesSelectionChanged': ()=>{
|
||||||
|
let singleNodeSelected = false;
|
||||||
|
if(this.diagram.getSelectedNodes().length == 1) {
|
||||||
|
let metadata = this.diagram.getSelectedNodes()[0].getMetadata();
|
||||||
|
if(!metadata.is_promise) {
|
||||||
|
singleNodeSelected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
single_node_selected: this.diagram.getSelectedNodes().length == 1,
|
single_node_selected: singleNodeSelected,
|
||||||
any_item_selected: this.diagram.getSelectedNodes().length > 0 || this.diagram.getSelectedLinks().length > 0,
|
any_item_selected: this.diagram.getSelectedNodes().length > 0 || this.diagram.getSelectedLinks().length > 0,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -361,6 +369,29 @@ export default class BodyWidget extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onDropNode(e) {
|
||||||
|
let nodeDropData = JSON.parse(e.dataTransfer.getData('text'));
|
||||||
|
if(nodeDropData.objUrl) {
|
||||||
|
let matchUrl = `/${this.props.params.sgid}/${this.props.params.sid}/${this.props.params.did}/`;
|
||||||
|
if(nodeDropData.objUrl.indexOf(matchUrl) == -1) {
|
||||||
|
this.props.alertify.error(gettext('Cannot drop table from outside of the current database.'));
|
||||||
|
} else {
|
||||||
|
let dataPromise = new Promise((resolve, reject)=>{
|
||||||
|
axios.get(nodeDropData.objUrl)
|
||||||
|
.then((res)=>{
|
||||||
|
resolve(this.diagram.cloneTableData(TableSchema.getErdSupportedData(res.data)));
|
||||||
|
})
|
||||||
|
.catch((err)=>{
|
||||||
|
console.error(err);
|
||||||
|
reject();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const {x, y} = this.diagram.getEngine().getRelativeMousePoint(e);
|
||||||
|
this.diagram.addNode(dataPromise, [x, y]).setSelected(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onEditTable() {
|
onEditTable() {
|
||||||
const selected = this.diagram.getSelectedNodes();
|
const selected = this.diagram.getSelectedNodes();
|
||||||
if(selected.length == 1) {
|
if(selected.length == 1) {
|
||||||
@ -375,10 +406,12 @@ export default class BodyWidget extends React.Component {
|
|||||||
onCloneNode() {
|
onCloneNode() {
|
||||||
const selected = this.diagram.getSelectedNodes();
|
const selected = this.diagram.getSelectedNodes();
|
||||||
if(selected.length == 1) {
|
if(selected.length == 1) {
|
||||||
let newData = selected[0].cloneData(this.diagram.getNextTableName());
|
let newData = this.diagram.cloneTableData(selected[0].getData(), this.diagram.getNextTableName());
|
||||||
let {x, y} = selected[0].getPosition();
|
if(newData) {
|
||||||
let newNode = this.diagram.addNode(newData, [x+20, y+20]);
|
let {x, y} = selected[0].getPosition();
|
||||||
newNode.setSelected(true);
|
let newNode = this.diagram.addNode(newData, [x+20, y+20]);
|
||||||
|
newNode.setSelected(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -825,7 +858,7 @@ export default class BodyWidget extends React.Component {
|
|||||||
fgcolor={this.props.params.fgcolor} title={this.props.params.title}/>
|
fgcolor={this.props.params.fgcolor} title={this.props.params.title}/>
|
||||||
<FloatingNote open={this.state.note_open} onClose={this.onNoteClose}
|
<FloatingNote open={this.state.note_open} onClose={this.onNoteClose}
|
||||||
reference={this.noteRefEle} noteNode={this.state.note_node} appendTo={this.diagramContainerRef.current} rows={8}/>
|
reference={this.noteRefEle} noteNode={this.state.note_node} appendTo={this.diagramContainerRef.current} rows={8}/>
|
||||||
<div className="diagram-container" ref={this.diagramContainerRef}>
|
<div className="diagram-container" ref={this.diagramContainerRef} onDrop={this.onDropNode} onDragOver={e => {e.preventDefault();}}>
|
||||||
<Loader message={this.state.loading_msg} autoEllipsis={true}/>
|
<Loader message={this.state.loading_msg} autoEllipsis={true}/>
|
||||||
<CanvasWidget className="diagram-canvas flex-grow-1" ref={(ele)=>{this.canvasEle = ele?.ref?.current;}} engine={this.diagram.getEngine()} />
|
<CanvasWidget className="diagram-canvas flex-grow-1" ref={(ele)=>{this.canvasEle = ele?.ref?.current;}} engine={this.diagram.getEngine()} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -135,6 +135,10 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
& .fetch-error {
|
||||||
|
color: $color-danger;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-cols {
|
.table-cols {
|
||||||
|
@ -101,6 +101,7 @@ describe('ERDCore', ()=>{
|
|||||||
initialConfig: {
|
initialConfig: {
|
||||||
otherInfo: {
|
otherInfo: {
|
||||||
data:data,
|
data:data,
|
||||||
|
dataUrl: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -24,6 +24,11 @@ export class FakeNode {
|
|||||||
retVal.name = tabName;
|
retVal.name = tabName;
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
getMetadata() {
|
||||||
|
return {
|
||||||
|
is_promise: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FakeLink {
|
export class FakeLink {
|
||||||
|
@ -63,15 +63,6 @@ describe('ERD TableNodeModel', ()=>{
|
|||||||
expect(modelObj.getData().name).toBe('changedName');
|
expect(modelObj.getData().name).toBe('changedName');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('cloneData', ()=>{
|
|
||||||
modelObj.addColumn({name: 'col1', not_null:false, attnum: 0});
|
|
||||||
expect(modelObj.cloneData('clonedNode')).toEqual({
|
|
||||||
name: 'clonedNode',
|
|
||||||
schema: 'erd',
|
|
||||||
columns: [{name: 'col1', not_null:false, attnum: 0}],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('setData', ()=>{
|
describe('setData', ()=>{
|
||||||
let existPort = jasmine.createSpyObj('port', {
|
let existPort = jasmine.createSpyObj('port', {
|
||||||
'removeAllLinks': jasmine.createSpy('removeAllLinks'),
|
'removeAllLinks': jasmine.createSpy('removeAllLinks'),
|
||||||
@ -196,6 +187,9 @@ describe('ERD TableNodeModel', ()=>{
|
|||||||
schema: 'erd',
|
schema: 'erd',
|
||||||
},
|
},
|
||||||
note: 'some note',
|
note: 'some note',
|
||||||
|
metadata: {
|
||||||
|
data_failed: false, is_promise: false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -178,7 +178,7 @@ describe('ERD BodyWidget', ()=>{
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('event nodesSelectionChanged', (done)=>{
|
it('event nodesSelectionChanged', (done)=>{
|
||||||
spyOn(bodyInstance.diagram, 'getSelectedNodes').and.returnValue([{key:'value'}]);
|
spyOn(bodyInstance.diagram, 'getSelectedNodes').and.returnValue([new FakeNode({key:'value'})]);
|
||||||
bodyInstance.diagram.fireEvent({}, 'nodesSelectionChanged', true);
|
bodyInstance.diagram.fireEvent({}, 'nodesSelectionChanged', true);
|
||||||
setTimeout(()=>{
|
setTimeout(()=>{
|
||||||
expect(body.state().single_node_selected).toBe(true);
|
expect(body.state().single_node_selected).toBe(true);
|
||||||
|
Loading…
Reference in New Issue
Block a user