* 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>pull/9661/head
parent
3995ba9a95
commit
6d0d387f53
|
|
@ -416,7 +416,7 @@ export default class TableSchema extends BaseUISchema {
|
|||
static getErdSupportedData(data) {
|
||||
let newData = {...data};
|
||||
const SUPPORTED_KEYS = [
|
||||
'name', 'schema', 'description', 'rlspolicy', 'forcerlspolicy', 'fillfactor',
|
||||
'oid', 'name', 'schema', 'description', 'rlspolicy', 'forcerlspolicy', 'fillfactor',
|
||||
'toast_tuple_target', 'parallel_workers', 'relhasoids', 'relpersistence',
|
||||
'columns', 'primary_key', 'foreign_key', 'unique_constraint',
|
||||
];
|
||||
|
|
@ -428,14 +428,18 @@ export default class TableSchema extends BaseUISchema {
|
|||
return c;
|
||||
});
|
||||
|
||||
/* Make autoindex as true if there is coveringindex since ERD works in create mode */
|
||||
newData.foreign_key = (newData.foreign_key||[]).map((fk)=>{
|
||||
newData.original_foreign_keys = (newData.foreign_key||[]).map((fk)=>{
|
||||
/* Make autoindex as true if there is coveringindex since ERD works in create mode */
|
||||
fk.autoindex = false;
|
||||
|
||||
if(fk.coveringindex) {
|
||||
fk.autoindex = true;
|
||||
}
|
||||
|
||||
return fk;
|
||||
});
|
||||
|
||||
newData.foreign_key = [];
|
||||
return newData;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -437,6 +437,20 @@ class ERDModule(PgAdminModule):
|
|||
)
|
||||
)
|
||||
|
||||
self.preference.register(
|
||||
'options',
|
||||
'insert_table_with_relations',
|
||||
gettext('Insert Table With Relations'),
|
||||
'boolean',
|
||||
False,
|
||||
category_label=PREF_LABEL_OPTIONS,
|
||||
help_str=gettext(
|
||||
'Whether inserting a table via drag and drop should '
|
||||
'also insert its relations to the existing tables in '
|
||||
'the diagram.'
|
||||
)
|
||||
)
|
||||
|
||||
self.preference.register(
|
||||
'options', 'cardinality_notation',
|
||||
gettext('Cardinality Notation'), 'radioModern', 'crows',
|
||||
|
|
|
|||
|
|
@ -400,6 +400,7 @@ export default class ERDCore {
|
|||
|
||||
const addLink = (theFk)=>{
|
||||
if(!theFk) return;
|
||||
|
||||
let newData = {
|
||||
local_table_uid: tableNode.getID(),
|
||||
local_column_attnum: undefined,
|
||||
|
|
@ -590,7 +591,7 @@ export default class ERDCore {
|
|||
}
|
||||
|
||||
cloneTableData(tableData, name) {
|
||||
const SKIP_CLONE_KEYS = ['foreign_key'];
|
||||
const SKIP_CLONE_KEYS = ['oid', 'foreign_key', 'original_foreign_keys'];
|
||||
|
||||
if(!tableData) {
|
||||
return tableData;
|
||||
|
|
@ -635,43 +636,95 @@ export default class ERDCore {
|
|||
|
||||
deserializeData(data){
|
||||
let oidUidMap = {};
|
||||
let newNodes = [];
|
||||
|
||||
/* Add the nodes */
|
||||
data.forEach((nodeData)=>{
|
||||
let newNode = this.addNode(TableSchema.getErdSupportedData(nodeData));
|
||||
const newNode = this.addNode(TableSchema.getErdSupportedData(nodeData));
|
||||
oidUidMap[nodeData.oid] = newNode.getID();
|
||||
newNodes.push(newNode);
|
||||
});
|
||||
|
||||
/* Lets use the oidUidMap for creating the links */
|
||||
let tableNodesDict = this.getModel().getNodesDict();
|
||||
// When generating for schema, there may be a reference to another schema table
|
||||
// We'll remove the FK completely in such cases
|
||||
newNodes.forEach((node) => {
|
||||
const nodeData = node.getData();
|
||||
nodeData.original_foreign_keys = nodeData.original_foreign_keys?.filter(fk =>
|
||||
fk.columns?.[0]?.references && oidUidMap[fk.columns[0].references]
|
||||
);
|
||||
});
|
||||
|
||||
this.addLinksBetweenNodes(oidUidMap);
|
||||
}
|
||||
|
||||
addNodeWithLinks(nodeData, position=[50,50], metadata={}){
|
||||
const tableNodesDict = this.getModel().getNodesDict();
|
||||
const oidExists = Object.values(tableNodesDict).some(node => node.getData().oid === nodeData.oid);
|
||||
|
||||
if (oidExists) {
|
||||
delete nodeData.oid;
|
||||
}
|
||||
|
||||
let oidUidMap = {};
|
||||
const newNode = this.addNode(nodeData, position, metadata);
|
||||
|
||||
if (!oidExists) {
|
||||
oidUidMap[nodeData.oid] = newNode.getID();
|
||||
}
|
||||
|
||||
_.forIn(tableNodesDict, (node, uid)=>{
|
||||
let nodeData = node.getData();
|
||||
if(nodeData.foreign_key) {
|
||||
nodeData.foreign_key = nodeData.foreign_key.filter((theFk)=>{
|
||||
delete theFk.oid;
|
||||
theFk = theFk.columns[0];
|
||||
theFk.references = oidUidMap[theFk.references];
|
||||
let newData = {
|
||||
local_table_uid: uid,
|
||||
local_column_attnum: undefined,
|
||||
referenced_table_uid: theFk.references,
|
||||
referenced_column_attnum: undefined,
|
||||
};
|
||||
let sourceNode = tableNodesDict[newData.referenced_table_uid];
|
||||
let targetNode = tableNodesDict[newData.local_table_uid];
|
||||
// When generating for schema, there may be a reference to another schema table
|
||||
// We'll remove the FK completely in such cases.
|
||||
if(!sourceNode || !targetNode) {
|
||||
return false;
|
||||
}
|
||||
const oid = node.getData().oid;
|
||||
if (!oid) return;
|
||||
|
||||
newData.local_column_attnum = _.find(targetNode.getColumns(), (col)=>col.name==theFk.local_column).attnum;
|
||||
newData.referenced_column_attnum = _.find(sourceNode.getColumns(), (col)=>col.name==theFk.referenced).attnum;
|
||||
oidUidMap[oid] = uid;
|
||||
});
|
||||
|
||||
this.addLink(newData, 'onetomany');
|
||||
return true;
|
||||
});
|
||||
}
|
||||
this.addLinksBetweenNodes(oidUidMap, [newNode.getID()]);
|
||||
return newNode;
|
||||
}
|
||||
|
||||
addLinksBetweenNodes(oidUidMap, newNodesUids = null) {
|
||||
const tableNodesDict = this.getModel().getNodesDict();
|
||||
|
||||
_.forIn(tableNodesDict, (node, uid)=>{
|
||||
const nodeData = node.getData();
|
||||
|
||||
nodeData.original_foreign_keys?.forEach((theFk)=>{
|
||||
const theFkColumn = theFk.columns[0];
|
||||
let referencesUid = oidUidMap[theFkColumn.references];
|
||||
|
||||
/* Incomplete reference to missing table */
|
||||
if (!referencesUid) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Avoid creating duplicate links */
|
||||
if (
|
||||
newNodesUids
|
||||
&& !newNodesUids.includes(uid)
|
||||
&& !newNodesUids.includes(referencesUid)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newData = {
|
||||
local_table_uid: uid,
|
||||
local_column_attnum: _.find(
|
||||
tableNodesDict[uid].getColumns(),
|
||||
(col) => col.name == theFkColumn.local_column
|
||||
).attnum,
|
||||
referenced_table_uid: referencesUid,
|
||||
referenced_column_attnum: _.find(
|
||||
tableNodesDict[referencesUid].getColumns(),
|
||||
(col) => col.name == theFkColumn.referenced
|
||||
).attnum,
|
||||
};
|
||||
|
||||
const newForeignKey = _.cloneDeep(theFk);
|
||||
newForeignKey.columns[0].references = referencesUid;
|
||||
nodeData.foreign_key.push(newForeignKey);
|
||||
this.addLink(newData, 'onetomany');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -598,21 +598,26 @@ export default class ERDTool extends React.Component {
|
|||
if(nodeDropData.objUrl.indexOf(matchUrl) == -1) {
|
||||
pgAdmin.Browser.notifier.error(gettext('Cannot drop table from outside of the current database.'));
|
||||
} else {
|
||||
let dataPromise = new Promise((resolve, reject)=>{
|
||||
this.apiObj.get(nodeDropData.objUrl)
|
||||
.then((res)=>{
|
||||
resolve(this.diagram.cloneTableData(TableSchema.getErdSupportedData(res.data)));
|
||||
})
|
||||
.catch((err)=>{
|
||||
console.error(err);
|
||||
reject(err instanceof Error ? err : Error(gettext('Something went wrong')));
|
||||
});
|
||||
});
|
||||
const {x, y} = this.diagram.getEngine().getRelativeMousePoint(e);
|
||||
this.diagram.addNode(dataPromise, [x, y], {
|
||||
fillColor: this.state.fill_color,
|
||||
textColor: this.state.text_color,
|
||||
}).setSelected(true);
|
||||
this.apiObj.get(nodeDropData.objUrl)
|
||||
.then((res)=>{
|
||||
const data = TableSchema.getErdSupportedData(res.data);
|
||||
const {x, y} = this.diagram.getEngine().getRelativeMousePoint(e);
|
||||
const position = [x,y];
|
||||
const metadata = {
|
||||
fillColor: this.state.fill_color,
|
||||
textColor: this.state.text_color,
|
||||
};
|
||||
|
||||
const newNode = this.state.preferences.insert_table_with_relations
|
||||
? this.diagram.addNodeWithLinks(data, position, metadata)
|
||||
: this.diagram.addNode(this.diagram.cloneTableData(data), position, metadata);
|
||||
|
||||
newNode.setSelected(true);
|
||||
})
|
||||
.catch((err)=>{
|
||||
console.error(err);
|
||||
throw (err instanceof Error ? err : Error(gettext('Something went wrong')));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import ERDCore from 'pgadmin.tools.erd/erd_tool/ERDCore';
|
|||
import TEST_TABLES_DATA from './test_tables';
|
||||
import { FakeLink, FakeNode } from './fake_item';
|
||||
import { PortModelAlignment } from '@projectstorm/react-diagrams';
|
||||
import TableSchema from 'pgadmin.tables.js/table.ui';
|
||||
|
||||
describe('ERDCore', ()=>{
|
||||
let eleFactory = {
|
||||
|
|
@ -247,7 +248,7 @@ describe('ERDCore', ()=>{
|
|||
/*This is intentional (SonarQube)*/
|
||||
},
|
||||
getData: function() {
|
||||
return table;
|
||||
return TableSchema.getErdSupportedData(table);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue