Skip to content

Commit 6d0d387

Browse files
LancearPirni
andauthored
ERD Tool: Insert table with relations via drag-and-drop. #5578 #8198
* Add preference for insert with relations Co-authored-by: Christian P. <pirnichristian@gmail.com> * Insert tables with relations on drag and drop Co-authored-by: Christian P. <pirnichristian@gmail.com> * Fix test mock not returning Erd Supported Data Co-authored-by: Christian P. <pirnichristian@gmail.com> --------- Co-authored-by: Christian P. <pirnichristian@gmail.com>
1 parent 3995ba9 commit 6d0d387

File tree

5 files changed

+125
-48
lines changed

5 files changed

+125
-48
lines changed

web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.ui.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,7 @@ export default class TableSchema extends BaseUISchema {
416416
static getErdSupportedData(data) {
417417
let newData = {...data};
418418
const SUPPORTED_KEYS = [
419-
'name', 'schema', 'description', 'rlspolicy', 'forcerlspolicy', 'fillfactor',
419+
'oid', 'name', 'schema', 'description', 'rlspolicy', 'forcerlspolicy', 'fillfactor',
420420
'toast_tuple_target', 'parallel_workers', 'relhasoids', 'relpersistence',
421421
'columns', 'primary_key', 'foreign_key', 'unique_constraint',
422422
];
@@ -428,14 +428,18 @@ export default class TableSchema extends BaseUISchema {
428428
return c;
429429
});
430430

431-
/* Make autoindex as true if there is coveringindex since ERD works in create mode */
432-
newData.foreign_key = (newData.foreign_key||[]).map((fk)=>{
431+
newData.original_foreign_keys = (newData.foreign_key||[]).map((fk)=>{
432+
/* Make autoindex as true if there is coveringindex since ERD works in create mode */
433433
fk.autoindex = false;
434+
434435
if(fk.coveringindex) {
435436
fk.autoindex = true;
436437
}
438+
437439
return fk;
438440
});
441+
442+
newData.foreign_key = [];
439443
return newData;
440444
}
441445

web/pgadmin/tools/erd/__init__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,20 @@ def register_preferences(self):
437437
)
438438
)
439439

440+
self.preference.register(
441+
'options',
442+
'insert_table_with_relations',
443+
gettext('Insert Table With Relations'),
444+
'boolean',
445+
False,
446+
category_label=PREF_LABEL_OPTIONS,
447+
help_str=gettext(
448+
'Whether inserting a table via drag and drop should '
449+
'also insert its relations to the existing tables in '
450+
'the diagram.'
451+
)
452+
)
453+
440454
self.preference.register(
441455
'options', 'cardinality_notation',
442456
gettext('Cardinality Notation'), 'radioModern', 'crows',

web/pgadmin/tools/erd/static/js/erd_tool/ERDCore.js

Lines changed: 82 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,7 @@ export default class ERDCore {
400400

401401
const addLink = (theFk)=>{
402402
if(!theFk) return;
403+
403404
let newData = {
404405
local_table_uid: tableNode.getID(),
405406
local_column_attnum: undefined,
@@ -590,7 +591,7 @@ export default class ERDCore {
590591
}
591592

592593
cloneTableData(tableData, name) {
593-
const SKIP_CLONE_KEYS = ['foreign_key'];
594+
const SKIP_CLONE_KEYS = ['oid', 'foreign_key', 'original_foreign_keys'];
594595

595596
if(!tableData) {
596597
return tableData;
@@ -635,43 +636,95 @@ export default class ERDCore {
635636

636637
deserializeData(data){
637638
let oidUidMap = {};
639+
let newNodes = [];
638640

639641
/* Add the nodes */
640642
data.forEach((nodeData)=>{
641-
let newNode = this.addNode(TableSchema.getErdSupportedData(nodeData));
643+
const newNode = this.addNode(TableSchema.getErdSupportedData(nodeData));
642644
oidUidMap[nodeData.oid] = newNode.getID();
645+
newNodes.push(newNode);
643646
});
644647

645-
/* Lets use the oidUidMap for creating the links */
646-
let tableNodesDict = this.getModel().getNodesDict();
648+
// When generating for schema, there may be a reference to another schema table
649+
// We'll remove the FK completely in such cases
650+
newNodes.forEach((node) => {
651+
const nodeData = node.getData();
652+
nodeData.original_foreign_keys = nodeData.original_foreign_keys?.filter(fk =>
653+
fk.columns?.[0]?.references && oidUidMap[fk.columns[0].references]
654+
);
655+
});
656+
657+
this.addLinksBetweenNodes(oidUidMap);
658+
}
659+
660+
addNodeWithLinks(nodeData, position=[50,50], metadata={}){
661+
const tableNodesDict = this.getModel().getNodesDict();
662+
const oidExists = Object.values(tableNodesDict).some(node => node.getData().oid === nodeData.oid);
663+
664+
if (oidExists) {
665+
delete nodeData.oid;
666+
}
667+
668+
let oidUidMap = {};
669+
const newNode = this.addNode(nodeData, position, metadata);
670+
671+
if (!oidExists) {
672+
oidUidMap[nodeData.oid] = newNode.getID();
673+
}
674+
647675
_.forIn(tableNodesDict, (node, uid)=>{
648-
let nodeData = node.getData();
649-
if(nodeData.foreign_key) {
650-
nodeData.foreign_key = nodeData.foreign_key.filter((theFk)=>{
651-
delete theFk.oid;
652-
theFk = theFk.columns[0];
653-
theFk.references = oidUidMap[theFk.references];
654-
let newData = {
655-
local_table_uid: uid,
656-
local_column_attnum: undefined,
657-
referenced_table_uid: theFk.references,
658-
referenced_column_attnum: undefined,
659-
};
660-
let sourceNode = tableNodesDict[newData.referenced_table_uid];
661-
let targetNode = tableNodesDict[newData.local_table_uid];
662-
// When generating for schema, there may be a reference to another schema table
663-
// We'll remove the FK completely in such cases.
664-
if(!sourceNode || !targetNode) {
665-
return false;
666-
}
676+
const oid = node.getData().oid;
677+
if (!oid) return;
667678

668-
newData.local_column_attnum = _.find(targetNode.getColumns(), (col)=>col.name==theFk.local_column).attnum;
669-
newData.referenced_column_attnum = _.find(sourceNode.getColumns(), (col)=>col.name==theFk.referenced).attnum;
679+
oidUidMap[oid] = uid;
680+
});
670681

671-
this.addLink(newData, 'onetomany');
672-
return true;
673-
});
674-
}
682+
this.addLinksBetweenNodes(oidUidMap, [newNode.getID()]);
683+
return newNode;
684+
}
685+
686+
addLinksBetweenNodes(oidUidMap, newNodesUids = null) {
687+
const tableNodesDict = this.getModel().getNodesDict();
688+
689+
_.forIn(tableNodesDict, (node, uid)=>{
690+
const nodeData = node.getData();
691+
692+
nodeData.original_foreign_keys?.forEach((theFk)=>{
693+
const theFkColumn = theFk.columns[0];
694+
let referencesUid = oidUidMap[theFkColumn.references];
695+
696+
/* Incomplete reference to missing table */
697+
if (!referencesUid) {
698+
return;
699+
}
700+
701+
/* Avoid creating duplicate links */
702+
if (
703+
newNodesUids
704+
&& !newNodesUids.includes(uid)
705+
&& !newNodesUids.includes(referencesUid)
706+
) {
707+
return;
708+
}
709+
710+
const newData = {
711+
local_table_uid: uid,
712+
local_column_attnum: _.find(
713+
tableNodesDict[uid].getColumns(),
714+
(col) => col.name == theFkColumn.local_column
715+
).attnum,
716+
referenced_table_uid: referencesUid,
717+
referenced_column_attnum: _.find(
718+
tableNodesDict[referencesUid].getColumns(),
719+
(col) => col.name == theFkColumn.referenced
720+
).attnum,
721+
};
722+
723+
const newForeignKey = _.cloneDeep(theFk);
724+
newForeignKey.columns[0].references = referencesUid;
725+
nodeData.foreign_key.push(newForeignKey);
726+
this.addLink(newData, 'onetomany');
727+
});
675728
});
676729
}
677730

web/pgadmin/tools/erd/static/js/erd_tool/components/ERDTool.jsx

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -598,21 +598,26 @@ export default class ERDTool extends React.Component {
598598
if(nodeDropData.objUrl.indexOf(matchUrl) == -1) {
599599
pgAdmin.Browser.notifier.error(gettext('Cannot drop table from outside of the current database.'));
600600
} else {
601-
let dataPromise = new Promise((resolve, reject)=>{
602-
this.apiObj.get(nodeDropData.objUrl)
603-
.then((res)=>{
604-
resolve(this.diagram.cloneTableData(TableSchema.getErdSupportedData(res.data)));
605-
})
606-
.catch((err)=>{
607-
console.error(err);
608-
reject(err instanceof Error ? err : Error(gettext('Something went wrong')));
609-
});
610-
});
611-
const {x, y} = this.diagram.getEngine().getRelativeMousePoint(e);
612-
this.diagram.addNode(dataPromise, [x, y], {
613-
fillColor: this.state.fill_color,
614-
textColor: this.state.text_color,
615-
}).setSelected(true);
601+
this.apiObj.get(nodeDropData.objUrl)
602+
.then((res)=>{
603+
const data = TableSchema.getErdSupportedData(res.data);
604+
const {x, y} = this.diagram.getEngine().getRelativeMousePoint(e);
605+
const position = [x,y];
606+
const metadata = {
607+
fillColor: this.state.fill_color,
608+
textColor: this.state.text_color,
609+
};
610+
611+
const newNode = this.state.preferences.insert_table_with_relations
612+
? this.diagram.addNodeWithLinks(data, position, metadata)
613+
: this.diagram.addNode(this.diagram.cloneTableData(data), position, metadata);
614+
615+
newNode.setSelected(true);
616+
})
617+
.catch((err)=>{
618+
console.error(err);
619+
throw (err instanceof Error ? err : Error(gettext('Something went wrong')));
620+
});
616621
}
617622
}
618623
}

web/regression/javascript/erd/erd_core_spec.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import ERDCore from 'pgadmin.tools.erd/erd_tool/ERDCore';
1010
import TEST_TABLES_DATA from './test_tables';
1111
import { FakeLink, FakeNode } from './fake_item';
1212
import { PortModelAlignment } from '@projectstorm/react-diagrams';
13+
import TableSchema from 'pgadmin.tables.js/table.ui';
1314

1415
describe('ERDCore', ()=>{
1516
let eleFactory = {
@@ -247,7 +248,7 @@ describe('ERDCore', ()=>{
247248
/*This is intentional (SonarQube)*/
248249
},
249250
getData: function() {
250-
return table;
251+
return TableSchema.getErdSupportedData(table);
251252
}
252253
};
253254
});

0 commit comments

Comments
 (0)