mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Add support for one to one relationship in the ERD tool. #5128
This commit is contained in:
parent
bf7f8cdd73
commit
2fc65589c8
@ -207,6 +207,22 @@ The table node shows table details in a graphical representation:
|
||||
* you can click on the node and drag to move on the canvas.
|
||||
* Upon double click on the table node or by clicking the edit button from the toolbar, the table dialog opens where you can change the table details. Refer :ref:`table dialog <table_dialog>` for information on different fields.
|
||||
|
||||
The One to One Link Dialog
|
||||
***************************
|
||||
|
||||
.. image:: images/erd_11_dialog.png
|
||||
:alt: ERD tool 1-1 dialog
|
||||
:align: center
|
||||
|
||||
The one to one link dialog allows you to:
|
||||
|
||||
* Add a one to one relationship between two tables.
|
||||
* *Local Table* is the table that references a table and has the *one* end point.
|
||||
* *Local Column* the column that references.
|
||||
* *Select Constraint* To implement one to one relationship, the *Local Column* must have primaty key or unique constraint. The default is a unique constraint. Please note that this field is visible only when the selected *Local Column* does not have either of the mentioned constraints.
|
||||
* *Referenced Table* is the table that is being referred and has the *one* end point.
|
||||
* *Referenced Column* the column that is being referred.
|
||||
|
||||
The One to Many Link Dialog
|
||||
***************************
|
||||
|
||||
|
BIN
docs/en_US/images/erd_11_dialog.png
Normal file
BIN
docs/en_US/images/erd_11_dialog.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 87 KiB |
@ -246,6 +246,24 @@ class ERDModule(PgAdminModule):
|
||||
fields=shortcut_fields
|
||||
)
|
||||
|
||||
self.preference.register(
|
||||
'keyboard_shortcuts',
|
||||
'one_to_one',
|
||||
gettext('One to one link'),
|
||||
'keyboardshortcut',
|
||||
{
|
||||
'alt': True,
|
||||
'shift': False,
|
||||
'control': True,
|
||||
'key': {
|
||||
'key_code': 66,
|
||||
'char': 'b'
|
||||
}
|
||||
},
|
||||
category_label=PREF_LABEL_KEYBOARD_SHORTCUTS,
|
||||
fields=shortcut_fields
|
||||
)
|
||||
|
||||
self.preference.register(
|
||||
'keyboard_shortcuts',
|
||||
'one_to_many',
|
||||
|
@ -9,6 +9,7 @@ export const ERD_EVENTS = {
|
||||
CLONE_NODE: 'CLONE_NODE',
|
||||
DELETE_NODE: 'DELETE_NODE',
|
||||
SHOW_NOTE: 'SHOW_NOTE',
|
||||
ONE_TO_ONE: 'ONE_TO_ONE',
|
||||
ONE_TO_MANY: 'ONE_TO_MANY',
|
||||
MANY_TO_MANY: 'MANY_TO_MANY',
|
||||
AUTO_DISTRIBUTE: 'AUTO_DISTRIBUTE',
|
||||
|
@ -22,6 +22,8 @@ import ForeignKeySchema from '../../../../../browser/server_groups/servers/datab
|
||||
import diffArray from 'diff-arrays-of-objects';
|
||||
import TableSchema from '../../../../../browser/server_groups/servers/databases/schemas/tables/static/js/table.ui';
|
||||
import ColumnSchema from '../../../../../browser/server_groups/servers/databases/schemas/tables/columns/static/js/column.ui';
|
||||
import UniqueConstraintSchema from '../../../../../browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/static/js/unique_constraint.ui';
|
||||
import PrimaryKeySchema from '../../../../../browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/static/js/primary_key.ui';
|
||||
import { boundingBoxFromPolygons } from '@projectstorm/geometry';
|
||||
|
||||
export default class ERDCore {
|
||||
@ -337,18 +339,19 @@ export default class ERDCore {
|
||||
let tableData = tableNode.getData();
|
||||
/* Remove the links if column dropped or primary key removed */
|
||||
_.differenceWith(oldTableData.columns, tableData.columns, function(existing, incoming) {
|
||||
if(existing.attnum == incoming.attnum && existing.is_primary_key && !incoming.is_primary_key) {
|
||||
return false;
|
||||
}
|
||||
return existing.attnum == incoming.attnum;
|
||||
}).forEach((col)=>{
|
||||
let existPort = tableNode.getPort(tableNode.getPortName(col.attnum));
|
||||
if(existPort) {
|
||||
Object.values(existPort.getLinks()).forEach((link)=>{
|
||||
self.removeOneToManyLink(link);
|
||||
});
|
||||
tableNode.removePort(existPort);
|
||||
}
|
||||
this.getLeftRightPorts(tableNode, col.attnum).forEach(port => {
|
||||
if (port) {
|
||||
Object.values(port.getLinks()).forEach(link => {
|
||||
self.removeOneToManyLink(link);
|
||||
});
|
||||
tableNode.removePort(port);
|
||||
}
|
||||
});
|
||||
});
|
||||
Object.values(tableNode.getLinks()).forEach(link=>{
|
||||
link.fireEvent({},'updateLink');
|
||||
});
|
||||
}
|
||||
|
||||
@ -482,6 +485,31 @@ export default class ERDCore {
|
||||
columns: [col],
|
||||
})
|
||||
);
|
||||
// Below logic is to add one to one relationship
|
||||
if(onetomanyData.constraint_type === 'primary_key') {
|
||||
let newPk = new PrimaryKeySchema({},{});
|
||||
let pkCol = {};
|
||||
let column = _.find(targetNode.getColumns(), (colm)=>colm.attnum==onetomanyData.local_column_attnum);
|
||||
column.is_primary_key = true;
|
||||
pkCol.column =column.name;
|
||||
tableData.primary_key = tableData.primary_key || [];
|
||||
tableData.primary_key.push(
|
||||
newPk.getNewData({
|
||||
columns: [pkCol]
|
||||
})
|
||||
);
|
||||
|
||||
} else if (onetomanyData.constraint_type === 'unique') {
|
||||
let newUk = new UniqueConstraintSchema({},{});
|
||||
let ukCol = {};
|
||||
ukCol.column = _.find(targetNode.getColumns(), (colm)=>colm.attnum==onetomanyData.local_column_attnum).name;
|
||||
tableData.unique_constraint = tableData.unique_constraint || [];
|
||||
tableData.unique_constraint.push(
|
||||
newUk.getNewData({
|
||||
columns: [ukCol]
|
||||
})
|
||||
);
|
||||
}
|
||||
targetNode.setData(tableData);
|
||||
let newLink = this.addLink(onetomanyData, 'onetomany');
|
||||
this.clearSelection();
|
||||
|
@ -145,7 +145,7 @@ export default class ERDTool extends React.Component {
|
||||
|
||||
_.bindAll(this, ['onLoadDiagram', 'onSaveDiagram', 'onSQLClick',
|
||||
'onImageClick', 'onAddNewNode', 'onEditTable', 'onCloneNode', 'onDeleteNode', 'onNoteClick',
|
||||
'onNoteClose', 'onOneToManyClick', 'onManyToManyClick', 'onAutoDistribute', 'onDetailsToggle',
|
||||
'onNoteClose', 'onOneToOneClick', 'onOneToManyClick', 'onManyToManyClick', 'onAutoDistribute', 'onDetailsToggle',
|
||||
'onChangeColors', 'onDropNode', 'onNotationChange', 'closePanel'
|
||||
]);
|
||||
|
||||
@ -220,6 +220,7 @@ export default class ERDTool extends React.Component {
|
||||
this.eventBus.registerListener(ERD_EVENTS.CLONE_NODE, this.onCloneNode);
|
||||
this.eventBus.registerListener(ERD_EVENTS.DELETE_NODE, this.onDeleteNode);
|
||||
this.eventBus.registerListener(ERD_EVENTS.SHOW_NOTE, this.onNoteClick);
|
||||
this.eventBus.registerListener(ERD_EVENTS.ONE_TO_ONE, this.onOneToOneClick);
|
||||
this.eventBus.registerListener(ERD_EVENTS.ONE_TO_MANY, this.onOneToManyClick);
|
||||
this.eventBus.registerListener(ERD_EVENTS.MANY_TO_MANY, this.onManyToManyClick);
|
||||
this.eventBus.registerListener(ERD_EVENTS.AUTO_DISTRIBUTE, this.onAutoDistribute);
|
||||
@ -265,6 +266,9 @@ export default class ERDTool extends React.Component {
|
||||
[this.state.preferences.add_edit_note, ()=>{
|
||||
this.eventBus.fireEvent(ERD_EVENTS.SHOW_NOTE);
|
||||
}],
|
||||
[this.state.preferences.one_to_one, ()=>{
|
||||
this.eventBus.fireEvent(ERD_EVENTS.ONE_TO_ONE);
|
||||
}],
|
||||
[this.state.preferences.one_to_many, ()=>{
|
||||
this.eventBus.fireEvent(ERD_EVENTS.ONE_TO_MANY);
|
||||
}],
|
||||
@ -397,7 +401,7 @@ export default class ERDTool extends React.Component {
|
||||
serverInfo, callback
|
||||
});
|
||||
};
|
||||
} else if(dialogName === 'onetomany_dialog' || dialogName === 'manytomany_dialog') {
|
||||
} else if(dialogName === 'onetomany_dialog' || dialogName === 'manytomany_dialog' || dialogName === 'onetoone_dialog') {
|
||||
return (title, attributes, callback)=>{
|
||||
this.erdDialogs.showRelationDialog(dialogName, {
|
||||
title, attributes, tableNodes: this.diagram.getModel().getNodesDict(),
|
||||
@ -429,6 +433,17 @@ export default class ERDTool extends React.Component {
|
||||
if(this.diagram.anyDuplicateNodeName(newData, oldData)) {
|
||||
return gettext('Table name already exists');
|
||||
}
|
||||
// If a column that is part of a foreign key is removed, the foreign key constraint should also be removed.
|
||||
_.differenceWith(oldData.columns, newData.columns, function(existing, incoming) {
|
||||
return existing.attnum == incoming.attnum;
|
||||
}).forEach(colm=>{
|
||||
newData.foreign_key?.forEach((theFkRow, index)=>{
|
||||
let fkCols = theFkRow.columns[0];
|
||||
if (fkCols.local_column === colm.name) {
|
||||
newData.foreign_key.splice(index,1);
|
||||
}
|
||||
});
|
||||
});
|
||||
node.setData(newData);
|
||||
this.diagram.syncTableLinks(node, oldData);
|
||||
this.diagram.repaint();
|
||||
@ -774,6 +789,14 @@ export default class ERDTool extends React.Component {
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
onOneToOneClick() {
|
||||
let dialog = this.getDialog('onetoone_dialog');
|
||||
let initData = {local_table_uid: this.diagram.getSelectedNodes()[0].getID()};
|
||||
dialog(gettext('One to one relation'), initData, (newData)=>{
|
||||
this.diagram.addOneToManyLink(newData);
|
||||
});
|
||||
}
|
||||
|
||||
onOneToManyClick() {
|
||||
let dialog = this.getDialog('onetomany_dialog');
|
||||
let initData = {local_table_uid: this.diagram.getSelectedNodes()[0].getID()};
|
||||
|
@ -54,6 +54,7 @@ export function MainToolBar({preferences, eventBus, fillColor, textColor, notati
|
||||
'save': true,
|
||||
'edit-table': true,
|
||||
'clone-table': true,
|
||||
'one-to-one': true,
|
||||
'one-to-many': true,
|
||||
'many-to-many': true,
|
||||
'show-note': true,
|
||||
@ -121,6 +122,7 @@ export function MainToolBar({preferences, eventBus, fillColor, textColor, notati
|
||||
[ERD_EVENTS.SINGLE_NODE_SELECTED, (selected)=>{
|
||||
setDisableButton('edit-table', !selected);
|
||||
setDisableButton('clone-table', !selected);
|
||||
setDisableButton('one-to-one', !selected);
|
||||
setDisableButton('one-to-many', !selected);
|
||||
setDisableButton('many-to-many', !selected);
|
||||
setDisableButton('show-note', !selected);
|
||||
@ -210,12 +212,17 @@ export function MainToolBar({preferences, eventBus, fillColor, textColor, notati
|
||||
}} />
|
||||
</PgButtonGroup>
|
||||
<PgButtonGroup size="small">
|
||||
<PgIconButton title={gettext('One-to-Many Relation')} icon={<span style={{letterSpacing: '-1px'}}>1M</span>}
|
||||
<PgIconButton title={gettext('One-to-One Relation')} icon={<span style={{letterSpacing: '-1px'}}>1 - 1</span>}
|
||||
shortcut={preferences.one_to_one} disabled={buttonsDisabled['one-to-one']}
|
||||
onClick={()=>{
|
||||
eventBus.fireEvent(ERD_EVENTS.ONE_TO_ONE);
|
||||
}} />
|
||||
<PgIconButton title={gettext('One-to-Many Relation')} icon={<span style={{letterSpacing: '-1px'}}>1 - M</span>}
|
||||
shortcut={preferences.one_to_many} disabled={buttonsDisabled['one-to-many']}
|
||||
onClick={()=>{
|
||||
eventBus.fireEvent(ERD_EVENTS.ONE_TO_MANY);
|
||||
}} />
|
||||
<PgIconButton title={gettext('Many-to-Many Relation')} icon={<span style={{letterSpacing: '-1px'}}>MM</span>}
|
||||
<PgIconButton title={gettext('Many-to-Many Relation')} icon={<span style={{letterSpacing: '-1px'}}>M - M</span>}
|
||||
shortcut={preferences.many_to_many} disabled={buttonsDisabled['many-to-many']}
|
||||
onClick={()=>{
|
||||
eventBus.fireEvent(ERD_EVENTS.MANY_TO_MANY);
|
||||
|
@ -0,0 +1,106 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2024, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import gettext from 'sources/gettext';
|
||||
import { isEmptyString } from 'sources/validators';
|
||||
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
|
||||
import _ from 'lodash';
|
||||
|
||||
class OneToOneSchema extends BaseUISchema {
|
||||
constructor(fieldOptions={}, initValues={}, localTableData={}) {
|
||||
super({
|
||||
local_table_uid: undefined,
|
||||
local_column_attnum: undefined,
|
||||
referenced_table_uid: undefined,
|
||||
referenced_column_attnum: undefined,
|
||||
constraint_type: undefined,
|
||||
...initValues,
|
||||
});
|
||||
this.fieldOptions = fieldOptions;
|
||||
this.localTableData = localTableData;
|
||||
}
|
||||
|
||||
isVisible (state) {
|
||||
let colName = _.find(this.localTableData.getData().columns, col => col.attnum === state.local_column_attnum)?.name;
|
||||
let {pkCols, ukCols} = this.localTableData.getConstraintCols();
|
||||
return !((pkCols.includes(colName) || ukCols.includes(colName)) || isEmptyString(state.local_column_attnum));
|
||||
}
|
||||
get baseFields() {
|
||||
return [{
|
||||
id: 'local_table_uid', label: gettext('Local Table'),
|
||||
type: 'select', readonly: true, controlProps: {allowClear: false},
|
||||
options: this.fieldOptions.local_table_uid,
|
||||
},{
|
||||
id: 'local_column_attnum', label: gettext('Local Column'),
|
||||
type: 'select', options: this.fieldOptions.local_column_attnum,
|
||||
controlProps: {allowClear: false}, noEmpty: true,
|
||||
},{
|
||||
id: 'constraint_type', label: gettext('Select constraint'),
|
||||
type: 'toggle', deps: ['local_column_attnum'],
|
||||
options: [
|
||||
{label: 'Primary Key', value: 'primary_key'},
|
||||
{label: 'Unique', value: 'unique'},
|
||||
],
|
||||
visible: this.isVisible,
|
||||
depChange: (state, source)=>{
|
||||
if (source[0] === 'local_column_attnum' && this.isVisible(state)) {
|
||||
return {constraint_type: 'unique'};
|
||||
} else if (source[0] === 'local_column_attnum') {
|
||||
return {constraint_type: ''};
|
||||
}
|
||||
}, helpMessage: gettext('A constraint is required to implement One to One relationship.')
|
||||
}, {
|
||||
id: 'referenced_table_uid', label: gettext('Referenced Table'),
|
||||
type: 'select', options: this.fieldOptions.referenced_table_uid,
|
||||
controlProps: {allowClear: false}, noEmpty: true,
|
||||
},{
|
||||
id: 'referenced_column_attnum', label: gettext('Referenced Column'),
|
||||
controlProps: {allowClear: false}, deps: ['referenced_table_uid'], noEmpty: true,
|
||||
type: (state)=>({
|
||||
type: 'select',
|
||||
options: state.referenced_table_uid ? ()=>this.fieldOptions.getRefColumns(state.referenced_table_uid) : [],
|
||||
optionsReloadBasis: state.referenced_table_uid,
|
||||
}),
|
||||
}];
|
||||
}
|
||||
|
||||
validate(state, setError) {
|
||||
let tableData = this.localTableData.getData();
|
||||
if (tableData.primary_key.length && state.constraint_type === 'primary_key') {
|
||||
setError('constraint_type', gettext('Primary key already exists, please select different constraint.'));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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'});
|
||||
});
|
||||
|
||||
return new OneToOneSchema({
|
||||
local_table_uid: tablesData,
|
||||
local_column_attnum: tableNodesDict[attributes.local_table_uid].getColumns().map((col)=>{
|
||||
return {
|
||||
value: col.attnum, label: col.name, 'image': 'icon-column',
|
||||
};
|
||||
}),
|
||||
referenced_table_uid: tablesData,
|
||||
getRefColumns: (uid)=>{
|
||||
return tableNodesDict[uid].getColumns().map((col)=>{
|
||||
return {
|
||||
value: col.attnum, label: col.name, 'image': 'icon-column',
|
||||
};
|
||||
});
|
||||
},
|
||||
}, attributes, tableNodesDict[attributes.local_table_uid]);
|
||||
}
|
@ -10,6 +10,7 @@
|
||||
import {getTableDialogSchema} from './TableDialog';
|
||||
import {getOneToManyDialogSchema} from './OneToManyDialog';
|
||||
import {getManyToManyDialogSchema} from './ManyToManyDialog';
|
||||
import {getOneToOneDialogSchema} from './OneToOneDialog';
|
||||
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import SchemaView from '../../../../../../static/js/SchemaView';
|
||||
@ -67,6 +68,8 @@ export default class ERDDialogs {
|
||||
schema = getOneToManyDialogSchema(params.attributes, params.tableNodes);
|
||||
} else if(dialogName === 'manytomany_dialog') {
|
||||
schema = getManyToManyDialogSchema(params.attributes, params.tableNodes);
|
||||
} else if(dialogName === 'onetoone_dialog') {
|
||||
schema = getOneToOneDialogSchema(params.attributes, params.tableNodes);
|
||||
}
|
||||
|
||||
this.modal.showModal(params.title, (closeModal)=>{
|
||||
|
@ -45,6 +45,10 @@ export class OneToManyLinkModel extends RightAngleLinkModel {
|
||||
this._data = {
|
||||
...data,
|
||||
};
|
||||
this._linkPointType = {
|
||||
sourceType: 'one',
|
||||
targetType: 'many'
|
||||
};
|
||||
}
|
||||
|
||||
getData() {
|
||||
@ -77,6 +81,22 @@ export class OneToManyLinkModel extends RightAngleLinkModel {
|
||||
data: this.getData(),
|
||||
};
|
||||
}
|
||||
|
||||
setPointType(nodesDict) {
|
||||
let data = this.getData();
|
||||
let target = nodesDict[data['local_table_uid']].getData();
|
||||
let colName = _.find(target.columns, (col)=>data.local_column_attnum == col.attnum).name;
|
||||
let {pkCols=[], ukCols=[]} = nodesDict[data['local_table_uid']].getConstraintCols();
|
||||
let targetType = pkCols.includes(colName) || ukCols.includes(colName) ? 'one' : 'many';
|
||||
this._linkPointType = {
|
||||
...this._linkPointType,
|
||||
targetType,
|
||||
};
|
||||
}
|
||||
|
||||
getPointType() {
|
||||
return this._linkPointType;
|
||||
}
|
||||
}
|
||||
|
||||
const svgLinkSelected = keyframes`
|
||||
@ -173,6 +193,22 @@ CustomLinkEndWidget.propTypes = {
|
||||
export class OneToManyLinkWidget extends RightAngleLinkWidget {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
this.setPointType();
|
||||
this.updateLinkListener = this.props.link.registerListener({
|
||||
updateLink: ()=>{
|
||||
this.setPointType();
|
||||
this.setState({});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.link.deregisterListener(this.updateLinkListener);
|
||||
}
|
||||
|
||||
setPointType() {
|
||||
this.props.link.setPointType(this.props.diagramEngine.getModel().getNodesDict());
|
||||
}
|
||||
|
||||
endPointTranslation(alignment) {
|
||||
@ -259,9 +295,10 @@ export class OneToManyLinkWidget extends RightAngleLinkWidget {
|
||||
//ensure id is present for all points on the path
|
||||
let points = this.props.link.getPoints();
|
||||
let paths = [];
|
||||
let {sourceType, targetType} = this.props.link.getPointType();
|
||||
|
||||
let onePoint = this.addCustomWidgetPoint('one', this.props.link.getSourcePort(), points[0]);
|
||||
let manyPoint = this.addCustomWidgetPoint('many', this.props.link.getTargetPort(), points[points.length-1]);
|
||||
let onePoint = this.addCustomWidgetPoint(sourceType, this.props.link.getSourcePort(), points[0]);
|
||||
let manyPoint = this.addCustomWidgetPoint(targetType, this.props.link.getTargetPort(), points[points.length-1]);
|
||||
|
||||
if (!this.state.canDrag && points.length > 2) {
|
||||
// Those points and its position only will be moved
|
||||
|
@ -44,6 +44,7 @@ export class TableNodeModel extends DefaultNodeModel {
|
||||
is_promise: Boolean(otherInfo.data?.then || (otherInfo.metadata?.data_failed && !otherInfo.data)),
|
||||
};
|
||||
this._data = null;
|
||||
this._constraintCols = {};
|
||||
if(otherInfo.data?.then) {
|
||||
otherInfo.data.then((data)=>{
|
||||
/* Once the data is available, it is no more a promise */
|
||||
@ -53,6 +54,7 @@ export class TableNodeModel extends DefaultNodeModel {
|
||||
data_failed: false,
|
||||
is_promise: false,
|
||||
};
|
||||
this.generateOnetoOneData(data);
|
||||
this.fireEvent(this._metadata, 'dataAvaiable');
|
||||
this.fireEvent({}, 'nodeUpdated');
|
||||
this.fireEvent({}, 'selectionChanged');
|
||||
@ -69,6 +71,7 @@ export class TableNodeModel extends DefaultNodeModel {
|
||||
columns: [],
|
||||
...otherInfo.data,
|
||||
};
|
||||
this.generateOnetoOneData(otherInfo.data);
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,6 +135,7 @@ export class TableNodeModel extends DefaultNodeModel {
|
||||
|
||||
setData(data) {
|
||||
this._data = data;
|
||||
this.generateOnetoOneData(data);
|
||||
this.fireEvent({}, 'nodeUpdated');
|
||||
}
|
||||
|
||||
@ -164,6 +168,34 @@ export class TableNodeModel extends DefaultNodeModel {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
setConstraintCols(colsData) {
|
||||
this._constraintCols = colsData;
|
||||
}
|
||||
|
||||
getConstraintCols() {
|
||||
return this._constraintCols;
|
||||
}
|
||||
|
||||
generateOnetoOneData = (tableData) => {
|
||||
if (tableData){
|
||||
let ukCols = [], pkCols = [];
|
||||
(tableData.unique_constraint||[]).forEach((uk)=>{
|
||||
if(uk.columns.length === 1){
|
||||
ukCols.push(...uk.columns.map((c)=>c.column));
|
||||
}
|
||||
});
|
||||
(tableData.primary_key||[]).forEach((pk)=>{
|
||||
if(pk.columns.length === 1){
|
||||
pkCols.push(...pk.columns.map((c)=>c.column));
|
||||
}
|
||||
});
|
||||
this.setConstraintCols({
|
||||
ukCols,
|
||||
pkCols
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function RowIcon({icon}) {
|
||||
@ -239,7 +271,7 @@ export class TableNodeWidget extends React.Component {
|
||||
show_details: true,
|
||||
};
|
||||
|
||||
this.props.node.registerListener({
|
||||
this.tableNodeEventListener = this.props.node.registerListener({
|
||||
toggleDetails: (event) => {
|
||||
this.setState({show_details: event.show_details});
|
||||
},
|
||||
@ -256,6 +288,10 @@ export class TableNodeWidget extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.node.deregisterListener(this.tableNodeEventListener);
|
||||
}
|
||||
|
||||
generateColumn(col, localFkCols, localUkCols) {
|
||||
let leftPort = this.props.node.getPort(this.props.node.getPortName(col.attnum, PortModelAlignment.LEFT));
|
||||
let rightPort = this.props.node.getPort(this.props.node.getPortName(col.attnum, PortModelAlignment.RIGHT));
|
||||
|
@ -15,6 +15,7 @@ import {
|
||||
|
||||
import OneToManyPortModel from 'pgadmin.tools.erd/erd_tool/ports/OneToManyPort';
|
||||
import {OneToManyLinkModel, OneToManyLinkWidget, OneToManyLinkFactory} from 'pgadmin.tools.erd/erd_tool/links/OneToManyLink';
|
||||
import ERDModel from 'pgadmin.tools.erd/erd_tool/ERDModel';
|
||||
import { render } from '@testing-library/react';
|
||||
import Theme from '../../../pgadmin/static/js/Theme';
|
||||
|
||||
@ -108,14 +109,14 @@ describe('ERD OneToManyLinkModel', ()=>{
|
||||
|
||||
describe('ERD OneToManyLinkWidget', ()=>{
|
||||
let linkFactory = new OneToManyLinkFactory();
|
||||
let model = new ERDModel();
|
||||
let engine = {
|
||||
getFactoryForLink: ()=>linkFactory,
|
||||
getModel: ()=>model
|
||||
};
|
||||
let link = null;
|
||||
|
||||
beforeEach(()=>{
|
||||
|
||||
|
||||
link = new OneToManyLinkModel({
|
||||
color: '#000',
|
||||
data: {
|
||||
@ -129,6 +130,45 @@ describe('ERD OneToManyLinkWidget', ()=>{
|
||||
link.setTargetPort(new OneToManyPortModel({options: {}}));
|
||||
});
|
||||
|
||||
jest.spyOn(model, 'getNodes').mockReturnValue([
|
||||
{
|
||||
name: 'test1',
|
||||
getID: function() {
|
||||
return 'id1';
|
||||
},
|
||||
getData: function(){ return {
|
||||
'name': 'table1',
|
||||
'schema': 'erd1',
|
||||
'columns': [
|
||||
{'name': 'col11', attnum: 0},
|
||||
{'name': 'col12', attnum: 1},
|
||||
],
|
||||
};},
|
||||
getConstraintCols: function(){ return {
|
||||
ukCols: [],
|
||||
pkCols: []
|
||||
};}
|
||||
},
|
||||
{
|
||||
name: 'test2',
|
||||
getID: function() {
|
||||
return 'id2';
|
||||
},
|
||||
getData: function(){ return {
|
||||
'name': 'table2',
|
||||
'schema': 'erd2',
|
||||
'columns': [
|
||||
{'name': 'col21', attnum: 0},
|
||||
{'name': 'col22', attnum: 1},
|
||||
],
|
||||
};},
|
||||
getConstraintCols: function(){ return {
|
||||
ukCols: [],
|
||||
pkCols: []
|
||||
};}
|
||||
},
|
||||
]);
|
||||
|
||||
it('render', ()=>{
|
||||
let linkWidget = render(
|
||||
<Theme>
|
||||
|
Loading…
Reference in New Issue
Block a user