mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
1) Added support for advanced table fields like the foreign key, primary key in the ERD tool. Fixes #6081
2) Added index creation when generating SQL in the ERD tool. Fixes #6529
This commit is contained in:
committed by
Akshay Joshi
parent
9796f50362
commit
a92c1b43a2
@@ -532,6 +532,35 @@ def prequisite(trans_id, sgid, sid, did):
|
||||
)
|
||||
|
||||
|
||||
def translate_foreign_keys(tab_fks, tab_data, all_nodes):
|
||||
"""
|
||||
This function will take the from table foreign keys and translate
|
||||
it into non oid based format. It will allow creating FK sql even
|
||||
if table is not already created.
|
||||
:param tab_fks: Table foreign keyss
|
||||
:param tab_data: Table data
|
||||
:param all_nodes: All the nodes info from ERD
|
||||
:return: Translated foreign key data
|
||||
"""
|
||||
for tab_fk in tab_fks:
|
||||
if 'columns' not in tab_fk:
|
||||
continue
|
||||
print(tab_data)
|
||||
remote_table = all_nodes[tab_fk['columns'][0]['references']]
|
||||
tab_fk['schema'] = tab_data['schema']
|
||||
tab_fk['table'] = tab_data['name']
|
||||
tab_fk['remote_schema'] = remote_table['schema']
|
||||
tab_fk['remote_table'] = remote_table['name']
|
||||
|
||||
new_column = {
|
||||
'local_column': tab_fk['columns'][0]['local_column'],
|
||||
'referenced': tab_fk['columns'][0]['referenced']
|
||||
}
|
||||
tab_fk['columns'][0] = new_column
|
||||
|
||||
return tab_fks
|
||||
|
||||
|
||||
@blueprint.route('/sql/<int:trans_id>/<int:sgid>/<int:sid>/<int:did>',
|
||||
methods=["POST"],
|
||||
endpoint='sql')
|
||||
@@ -542,12 +571,16 @@ def sql(trans_id, sgid, sid, did):
|
||||
conn = _get_connection(sid, did, trans_id)
|
||||
|
||||
sql = ''
|
||||
for tab_key, tab_data in data.get('nodes', {}).items():
|
||||
tab_foreign_keys = []
|
||||
all_nodes = data.get('nodes', {})
|
||||
for tab_key, tab_data in all_nodes.items():
|
||||
tab_fks = tab_data.pop('foreign_key', [])
|
||||
tab_foreign_keys.extend(translate_foreign_keys(tab_fks, tab_data, all_nodes))
|
||||
sql += '\n\n' + helper.get_table_sql(tab_data)
|
||||
|
||||
for link_key, link_data in data.get('links', {}).items():
|
||||
link_sql, name = fkey_utils.get_sql(conn, link_data, None)
|
||||
sql += '\n\n' + link_sql
|
||||
for tab_fk in tab_foreign_keys:
|
||||
fk_sql, name = fkey_utils.get_sql(conn, tab_fk, None)
|
||||
sql += '\n\n' + fk_sql
|
||||
|
||||
return make_json_response(
|
||||
data=sql,
|
||||
|
||||
@@ -13,11 +13,16 @@
|
||||
import createEngine from '@projectstorm/react-diagrams';
|
||||
import {DagreEngine, PathFindingLinkFactory, PortModelAlignment} from '@projectstorm/react-diagrams';
|
||||
import { ZoomCanvasAction } from '@projectstorm/react-canvas-core';
|
||||
import _ from 'lodash';
|
||||
|
||||
import {TableNodeFactory, TableNodeModel } from './nodes/TableNode';
|
||||
import {OneToManyLinkFactory, OneToManyLinkModel } from './links/OneToManyLink';
|
||||
import { OneToManyPortFactory } from './ports/OneToManyPort';
|
||||
import ERDModel from './ERDModel';
|
||||
import ForeignKeySchema from '../../../../../browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/static/js/foreign_key.ui';
|
||||
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';
|
||||
|
||||
export default class ERDCore {
|
||||
constructor() {
|
||||
@@ -66,8 +71,8 @@ export default class ERDCore {
|
||||
else if(e.function === 'showNote') {
|
||||
this.fireEvent({node: e.entity}, 'showNote', true);
|
||||
}
|
||||
else if(e.function === 'editNode') {
|
||||
this.fireEvent({node: e.entity}, 'editNode', true);
|
||||
else if(e.function === 'editTable') {
|
||||
this.fireEvent({node: e.entity}, 'editTable', true);
|
||||
}
|
||||
else if(e.function === 'nodeUpdated') {
|
||||
this.fireEvent({}, 'nodesUpdated', true);
|
||||
@@ -232,6 +237,188 @@ export default class ERDCore {
|
||||
return newLink;
|
||||
}
|
||||
|
||||
removePortLinks(port) {
|
||||
let links = port.getLinks();
|
||||
Object.values(links).forEach((link)=>{
|
||||
link.getTargetPort().remove();
|
||||
link.getSourcePort().remove();
|
||||
link.setSelected(false);
|
||||
link.remove();
|
||||
});
|
||||
}
|
||||
|
||||
syncTableLinks(tableNode, oldTableData) {
|
||||
let tableData = tableNode.getData();
|
||||
let tableNodesDict = this.getModel().getNodesDict();
|
||||
|
||||
const addLink = (theFk)=>{
|
||||
let newData = {
|
||||
local_table_uid: tableNode.getID(),
|
||||
local_column_attnum: undefined,
|
||||
referenced_table_uid: theFk.references,
|
||||
referenced_column_attnum: undefined,
|
||||
};
|
||||
let sourceNode = tableNodesDict[newData.referenced_table_uid];
|
||||
|
||||
newData.local_column_attnum = _.find(tableNode.getColumns(), (col)=>col.name==theFk.local_column).attnum;
|
||||
newData.referenced_column_attnum = _.find(sourceNode.getColumns(), (col)=>col.name==theFk.referenced).attnum;
|
||||
|
||||
this.addLink(newData, 'onetomany');
|
||||
};
|
||||
|
||||
const removeLink = (theFk)=>{
|
||||
let attnum = _.find(tableNode.getColumns(), (col)=>col.name==theFk.local_column).attnum;
|
||||
let existPort = tableNode.getPort(tableNode.getPortName(attnum));
|
||||
if(existPort && existPort.getSubtype() == 'many') {
|
||||
existPort.removeAllLinks();
|
||||
tableNode.removePort(existPort);
|
||||
}
|
||||
};
|
||||
|
||||
const changeDiff = diffArray(
|
||||
oldTableData?.foreign_key || [],
|
||||
tableData?.foreign_key || [],
|
||||
'cid'
|
||||
);
|
||||
|
||||
changeDiff.added.forEach((theFk)=>{
|
||||
addLink(theFk.columns[0]);
|
||||
});
|
||||
changeDiff.removed.forEach((theFk)=>{
|
||||
removeLink(theFk.columns[0]);
|
||||
});
|
||||
|
||||
if(changeDiff.updated.length > 0) {
|
||||
for(const changedRow of changeDiff.updated) {
|
||||
let rowIndx = _.findIndex(tableData.foreign_key, (f)=>f.cid==changedRow.cid);
|
||||
const changeDiffCols = diffArray(
|
||||
oldTableData.foreign_key[rowIndx].columns,
|
||||
tableData.foreign_key[rowIndx].columns,
|
||||
'cid'
|
||||
);
|
||||
if(changeDiffCols.removed.length > 0 || changeDiffCols.added.length > 0) {
|
||||
removeLink(changeDiffCols.removed[0]);
|
||||
addLink(changeDiffCols.added[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addOneToManyLink(onetomanyData) {
|
||||
let newFk = new ForeignKeySchema({}, {}, ()=>{}, {autoindex: false});
|
||||
let tableNodesDict = this.getModel().getNodesDict();
|
||||
let fkColumn = {};
|
||||
let sourceNode = tableNodesDict[onetomanyData.referenced_table_uid];
|
||||
let targetNode = tableNodesDict[onetomanyData.local_table_uid];
|
||||
|
||||
fkColumn.local_column = _.find(targetNode.getColumns(), (col)=>col.attnum==onetomanyData.local_column_attnum).name;
|
||||
fkColumn.referenced = _.find(sourceNode.getColumns(), (col)=>col.attnum==onetomanyData.referenced_column_attnum).name;
|
||||
fkColumn.references = onetomanyData.referenced_table_uid;
|
||||
fkColumn.references_table_name = sourceNode.getData().name;
|
||||
|
||||
let tableData = targetNode.getData();
|
||||
tableData.foreign_key = tableData.foreign_key || [];
|
||||
|
||||
let col = newFk.fkColumnSchema.getNewData(fkColumn);
|
||||
tableData.foreign_key.push(
|
||||
newFk.getNewData({
|
||||
columns: [col],
|
||||
})
|
||||
);
|
||||
targetNode.setData(tableData);
|
||||
let newLink = this.addLink(onetomanyData, 'onetomany');
|
||||
this.clearSelection();
|
||||
newLink.setSelected(true);
|
||||
this.repaint();
|
||||
}
|
||||
|
||||
removeOneToManyLink(link) {
|
||||
let linkData = link.getData();
|
||||
let tableNode = this.getModel().getNodesDict()[linkData.local_table_uid];
|
||||
let tableData = tableNode.getData();
|
||||
|
||||
let newForeingKeys = [];
|
||||
tableData.foreign_key?.forEach((theFkRow)=>{
|
||||
let theFk = theFkRow.columns[0];
|
||||
let attnum = _.find(tableNode.getColumns(), (col)=>col.name==theFk.local_column).attnum;
|
||||
/* Skip all those whose attnum matches to the link */
|
||||
if(linkData.local_column_attnum != attnum) {
|
||||
newForeingKeys.push(theFkRow);
|
||||
}
|
||||
});
|
||||
tableData.foreign_key = newForeingKeys;
|
||||
tableNode.setData(tableData);
|
||||
link.getTargetPort().remove();
|
||||
link.getSourcePort().remove();
|
||||
link.setSelected(false);
|
||||
link.remove();
|
||||
}
|
||||
|
||||
addManyToManyLink(manytomanyData) {
|
||||
let nodes = this.getModel().getNodesDict();
|
||||
let leftNode = nodes[manytomanyData.left_table_uid];
|
||||
let rightNode = nodes[manytomanyData.right_table_uid];
|
||||
|
||||
let tableObj = new TableSchema({}, {}, {
|
||||
constraints:()=>{},
|
||||
columns:()=>new ColumnSchema(()=>{}, {}, {}, {}),
|
||||
vacuum_settings:()=>{},
|
||||
}, ()=>{}, ()=>{}, ()=>{}, ()=>{});
|
||||
|
||||
let tableData = tableObj.getNewData({
|
||||
name: `${leftNode.getData().name}_${rightNode.getData().name}`,
|
||||
schema: leftNode.getData().schema,
|
||||
columns: [tableObj.columnsSchema.getNewData({
|
||||
...leftNode.getColumnAt(manytomanyData.left_table_column_attnum),
|
||||
'name': `${leftNode.getData().name}_${leftNode.getColumnAt(manytomanyData.left_table_column_attnum).name}`,
|
||||
'attnum': 0,
|
||||
'is_primary_key': false,
|
||||
}),tableObj.columnsSchema.getNewData({
|
||||
...rightNode.getColumnAt(manytomanyData.right_table_column_attnum),
|
||||
'name': `${rightNode.getData().name}_${rightNode.getColumnAt(manytomanyData.right_table_column_attnum).name}`,
|
||||
'attnum': 1,
|
||||
'is_primary_key': false,
|
||||
})],
|
||||
});
|
||||
|
||||
// let tableData = {
|
||||
// name: `${leftNode.getData().name}_${rightNode.getData().name}`,
|
||||
// schema: leftNode.getData().schema,
|
||||
// columns: [{
|
||||
// ...leftNode.getColumnAt(manytomanyData.left_table_column_attnum),
|
||||
// 'name': `${leftNode.getData().name}_${leftNode.getColumnAt(manytomanyData.left_table_column_attnum).name}`,
|
||||
// 'is_primary_key': false,
|
||||
// 'attnum': 0,
|
||||
// },{
|
||||
// ...rightNode.getColumnAt(manytomanyData.right_table_column_attnum),
|
||||
// 'name': `${rightNode.getData().name}_${rightNode.getColumnAt(manytomanyData.right_table_column_attnum).name}`,
|
||||
// 'is_primary_key': false,
|
||||
// 'attnum': 1,
|
||||
// }],
|
||||
// };
|
||||
let newNode = this.addNode(tableData);
|
||||
this.clearSelection();
|
||||
newNode.setSelected(true);
|
||||
|
||||
let linkData = {
|
||||
local_table_uid: newNode.getID(),
|
||||
local_column_attnum: newNode.getColumns()[0].attnum,
|
||||
referenced_table_uid: manytomanyData.left_table_uid,
|
||||
referenced_column_attnum : manytomanyData.left_table_column_attnum,
|
||||
};
|
||||
this.addOneToManyLink(linkData);
|
||||
|
||||
linkData = {
|
||||
local_table_uid: newNode.getID(),
|
||||
local_column_attnum: newNode.getColumns()[1].attnum,
|
||||
referenced_table_uid: manytomanyData.right_table_uid,
|
||||
referenced_column_attnum : manytomanyData.right_table_column_attnum,
|
||||
};
|
||||
this.addOneToManyLink(linkData);
|
||||
|
||||
this.repaint();
|
||||
}
|
||||
|
||||
serialize(version) {
|
||||
return {
|
||||
version: version||0,
|
||||
@@ -246,65 +433,53 @@ export default class ERDCore {
|
||||
}
|
||||
|
||||
serializeData() {
|
||||
let nodes = {}, links = {};
|
||||
let nodes = {};
|
||||
let nodesDict = this.getModel().getNodesDict();
|
||||
|
||||
Object.keys(nodesDict).forEach((id)=>{
|
||||
nodes[id] = nodesDict[id].serializeData();
|
||||
});
|
||||
|
||||
this.getModel().getLinks().map((link)=>{
|
||||
links[link.getID()] = link.serializeData(nodesDict);
|
||||
});
|
||||
|
||||
/* Separate the links from nodes so that we don't have any dependancy issues */
|
||||
return {
|
||||
'nodes': nodes,
|
||||
'links': links,
|
||||
};
|
||||
}
|
||||
|
||||
deserializeData(data){
|
||||
let oidUidMap = {};
|
||||
let uidFks = [];
|
||||
data.forEach((node)=>{
|
||||
let newData = {
|
||||
name: node.name,
|
||||
schema: node.schema,
|
||||
description: node.description,
|
||||
columns: node.columns,
|
||||
primary_key: node.primary_key,
|
||||
};
|
||||
let newNode = this.addNode(newData);
|
||||
oidUidMap[node.oid] = newNode.getID();
|
||||
if(node.foreign_key) {
|
||||
node.foreign_key.forEach((a_fk)=>{
|
||||
uidFks.push({
|
||||
uid: newNode.getID(),
|
||||
data: a_fk.columns[0],
|
||||
});
|
||||
|
||||
/* Add the nodes */
|
||||
data.forEach((nodeData)=>{
|
||||
let newNode = this.addNode(TableSchema.getErdSupportedData(nodeData));
|
||||
oidUidMap[nodeData.oid] = newNode.getID();
|
||||
});
|
||||
|
||||
/* Lets use the oidUidMap for creating the links */
|
||||
let tableNodesDict = this.getModel().getNodesDict();
|
||||
_.forIn(tableNodesDict, (node, uid)=>{
|
||||
let nodeData = node.getData();
|
||||
if(nodeData.foreign_key) {
|
||||
nodeData.foreign_key.forEach((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];
|
||||
|
||||
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;
|
||||
|
||||
this.addLink(newData, 'onetomany');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/* Lets use the oidUidMap for creating the links */
|
||||
uidFks.forEach((fkData)=>{
|
||||
let tableNodesDict = this.getModel().getNodesDict();
|
||||
let newData = {
|
||||
local_table_uid: fkData.uid,
|
||||
local_column_attnum: undefined,
|
||||
referenced_table_uid: oidUidMap[fkData.data.references],
|
||||
referenced_column_attnum: undefined,
|
||||
};
|
||||
|
||||
let sourceNode = tableNodesDict[newData.referenced_table_uid];
|
||||
let targetNode = tableNodesDict[newData.local_table_uid];
|
||||
|
||||
newData.local_column_attnum = _.find(targetNode.getColumns(), (col)=>col.name==fkData.data.local_column).attnum;
|
||||
newData.referenced_column_attnum = _.find(sourceNode.getColumns(), (col)=>col.name==fkData.data.referenced).attnum;
|
||||
|
||||
this.addLink(newData, 'onetomany');
|
||||
});
|
||||
setTimeout(this.dagreDistributeNodes.bind(this), 250);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,152 +7,98 @@
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import gettext from 'sources/gettext';
|
||||
import * as commonUtils from 'sources/utils';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import SchemaView from '../../../../../../static/js/SchemaView';
|
||||
|
||||
export default class DialogWrapper {
|
||||
constructor(dialogContainerSelector, dialogTitle, typeOfDialog,
|
||||
jquery, pgBrowser, alertify, backform, backgrid) {
|
||||
|
||||
constructor(dialogContainerSelector, dialogTitle, typeOfDialog, alertify, serverInfo) {
|
||||
this.dialogContainerSelector = dialogContainerSelector;
|
||||
this.dialogTitle = dialogTitle;
|
||||
this.jquery = jquery;
|
||||
this.pgBrowser = pgBrowser;
|
||||
this.alertify = alertify;
|
||||
this.backform = backform;
|
||||
this.backgrid = backgrid;
|
||||
this.typeOfDialog = typeOfDialog;
|
||||
this.serverInfo = serverInfo;
|
||||
|
||||
let self = this;
|
||||
this.hooks = {
|
||||
onshow: ()=>{
|
||||
self.createDialog(self.elements.content);
|
||||
},
|
||||
onclose: ()=>{
|
||||
self.cleanupDialog(self.elements.content);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
main(title, dialogModel, okCallback) {
|
||||
main(title, dialogSchema, okCallback) {
|
||||
this.set('title', title);
|
||||
this.dialogModel = dialogModel;
|
||||
this.dialogSchema = dialogSchema;
|
||||
this.okCallback = okCallback;
|
||||
}
|
||||
|
||||
build() {
|
||||
this.alertify.pgDialogBuild.apply(this);
|
||||
this.elements.dialog.classList.add('erd-dialog');
|
||||
}
|
||||
|
||||
disableOKButton() {
|
||||
this.__internal.buttons[1].element.disabled = true;
|
||||
}
|
||||
|
||||
enableOKButton() {
|
||||
this.__internal.buttons[1].element.disabled = false;
|
||||
}
|
||||
|
||||
focusOnDialog(alertifyDialog) {
|
||||
let backform_tab = this.jquery(alertifyDialog.elements.body).find('.backform-tab');
|
||||
backform_tab.attr('tabindex', -1);
|
||||
this.pgBrowser.keyboardNavigation.getDialogTabNavigator(this.jquery(alertifyDialog.elements.dialog));
|
||||
let container = backform_tab.find('.tab-content:first > .tab-pane.active:first');
|
||||
|
||||
if(container.length === 0 && alertifyDialog.elements.content.innerHTML) {
|
||||
container = this.jquery(alertifyDialog.elements.content);
|
||||
}
|
||||
commonUtils.findAndSetFocus(container);
|
||||
prepare() {
|
||||
/* If tooltip is mounted after alertify in dom and button is click,
|
||||
alertify re-positions itself on DOM to come in focus. This makes it lose
|
||||
the button click events. Making it modal along with following fixes things. */
|
||||
this.elements.modal.style.maxHeight=0;
|
||||
this.elements.modal.style.maxWidth='none';
|
||||
this.elements.modal.style.overflow='visible';
|
||||
this.elements.dimmer.style.display='none';
|
||||
}
|
||||
|
||||
setup() {
|
||||
return {
|
||||
buttons: [{
|
||||
text: gettext('Cancel'),
|
||||
key: 27,
|
||||
className: 'btn btn-secondary fa fa-lg fa-times pg-alertify-button',
|
||||
'data-btn-name': 'cancel',
|
||||
}, {
|
||||
text: gettext('OK'),
|
||||
key: 13,
|
||||
className: 'btn btn-primary fa fa-lg fa-save pg-alertify-button',
|
||||
'data-btn-name': 'ok',
|
||||
}],
|
||||
buttons: [],
|
||||
// Set options for dialog
|
||||
options: {
|
||||
title: this.dialogTitle,
|
||||
//disable both padding and overflow control.
|
||||
padding: !1,
|
||||
overflow: !1,
|
||||
model: 0,
|
||||
resizable: true,
|
||||
maximizable: true,
|
||||
pinnable: false,
|
||||
closableByDimmer: false,
|
||||
modal: false,
|
||||
modal: true,
|
||||
autoReset: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
prepare() {
|
||||
const $container = this.jquery(this.dialogContainerSelector);
|
||||
const dialog = this.createDialog($container);
|
||||
dialog.render();
|
||||
this.elements.content.innerHTML = '';
|
||||
this.elements.content.appendChild($container.get(0));
|
||||
this.jquery(this.elements.body.childNodes[0]).addClass(
|
||||
'alertify_tools_dialog_properties obj_properties'
|
||||
);
|
||||
const statusBar = this.jquery(
|
||||
'<div class=\'pg-prop-status-bar pg-prop-status-bar-absolute pg-el-xs-12 d-none\'>' +
|
||||
' <div class="error-in-footer"> ' +
|
||||
' <div class="d-flex px-2 py-1"> ' +
|
||||
' <div class="pr-2"> ' +
|
||||
' <i class="fa fa-exclamation-triangle text-danger" aria-hidden="true"></i> ' +
|
||||
' </div> ' +
|
||||
' <div class="alert-text" role="alert"></div> ' +
|
||||
' <div class="ml-auto close-error-bar"> ' +
|
||||
' <a aria-label="' + gettext('Close error bar') + '" class="close-error fa fa-times text-danger"></a> ' +
|
||||
' </div> ' +
|
||||
' </div> ' +
|
||||
' </div> ' +
|
||||
'</div>').appendTo($container);
|
||||
|
||||
statusBar.find('.close-error').on('click', ()=>{
|
||||
statusBar.addClass('d-none');
|
||||
onSaveClick(isNew, data) {
|
||||
return new Promise((resolve)=>{
|
||||
this.okCallback(data);
|
||||
this.close();
|
||||
resolve();
|
||||
});
|
||||
|
||||
var onSessionInvalid = (msg) => {
|
||||
statusBar.find('.alert-text').text(msg);
|
||||
statusBar.removeClass('d-none');
|
||||
this.disableOKButton();
|
||||
return true;
|
||||
};
|
||||
|
||||
var onSessionValidated = () => {
|
||||
statusBar.find('.alert-text').text('');
|
||||
statusBar.addClass('d-none');
|
||||
this.enableOKButton();
|
||||
return true;
|
||||
};
|
||||
|
||||
this.dialogModel.on('pgadmin-session:valid', onSessionValidated);
|
||||
this.dialogModel.on('pgadmin-session:invalid', onSessionInvalid);
|
||||
this.dialogModel.startNewSession();
|
||||
this.disableOKButton();
|
||||
this.focusOnDialog(this);
|
||||
}
|
||||
|
||||
callback(event) {
|
||||
if (this.wasOkButtonPressed(event)) {
|
||||
this.okCallback(this.view.model.toJSON(true));
|
||||
}
|
||||
createDialog(container) {
|
||||
let self = this;
|
||||
ReactDOM.render(
|
||||
<SchemaView
|
||||
formType={'dialog'}
|
||||
getInitData={()=>Promise.resolve({})}
|
||||
schema={this.dialogSchema}
|
||||
viewHelperProps={{
|
||||
mode: 'create',
|
||||
keepCid: true,
|
||||
serverInfo: this.serverInfo,
|
||||
}}
|
||||
onSave={this.onSaveClick.bind(this)}
|
||||
onClose={()=>self.close()}
|
||||
onDataChange={()=>{}}
|
||||
hasSQL={false}
|
||||
disableSqlHelp={true}
|
||||
disableDialogHelp={true}
|
||||
/>, container);
|
||||
}
|
||||
|
||||
createDialog($container) {
|
||||
let fields = this.backform.generateViewSchema(
|
||||
null, this.dialogModel, 'create', null, null, true, null
|
||||
);
|
||||
|
||||
this.view = new this.backform.Dialog({
|
||||
el: $container,
|
||||
model: this.dialogModel,
|
||||
schema: fields,
|
||||
});
|
||||
|
||||
return this.view;
|
||||
}
|
||||
|
||||
wasOkButtonPressed(event) {
|
||||
return event.button['data-btn-name'] === 'ok';
|
||||
cleanupDialog(container) {
|
||||
ReactDOM.unmountComponentAtNode(container);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,13 +8,48 @@
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import gettext from 'sources/gettext';
|
||||
import Backform from 'sources/backform.pgadmin';
|
||||
import Alertify from 'pgadmin.alertifyjs';
|
||||
import $ from 'jquery';
|
||||
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
|
||||
|
||||
import DialogWrapper from './DialogWrapper';
|
||||
import _ from 'lodash';
|
||||
|
||||
class ManyToManySchema extends BaseUISchema {
|
||||
constructor(fieldOptions={}, initValues={}) {
|
||||
super({
|
||||
left_table_uid: undefined,
|
||||
left_table_column_attnum: undefined,
|
||||
right_table_uid: undefined,
|
||||
right_table_column_attnum: undefined,
|
||||
...initValues,
|
||||
});
|
||||
this.fieldOptions = fieldOptions;
|
||||
}
|
||||
get baseFields() {
|
||||
return [{
|
||||
id: 'left_table_uid', label: gettext('Local Table'),
|
||||
type: 'select', readonly: true, controlProps: {allowClear: false},
|
||||
options: this.fieldOptions.left_table_uid,
|
||||
}, {
|
||||
id: 'left_table_column_attnum', label: gettext('Local Column'),
|
||||
type: 'select', options: this.fieldOptions.left_table_column_attnum,
|
||||
controlProps: {allowClear: false}, noEmpty: true,
|
||||
},{
|
||||
id: 'right_table_uid', label: gettext('Referenced Table'),
|
||||
type: 'select', options: this.fieldOptions.right_table_uid,
|
||||
controlProps: {allowClear: false}, noEmpty: true,
|
||||
},{
|
||||
id: 'right_table_column_attnum', label: gettext('Referenced Column'),
|
||||
controlProps: {allowClear: false}, deps: ['right_table_uid'],
|
||||
type: (state)=>({
|
||||
type: 'select',
|
||||
options: state.right_table_uid ? ()=>this.fieldOptions.getRefColumns(state.right_table_uid) : [],
|
||||
optionsReloadBasis: state.right_table_uid,
|
||||
}),
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
export default class ManyToManyDialog {
|
||||
constructor(pgBrowser) {
|
||||
this.pgBrowser = pgBrowser;
|
||||
@@ -24,93 +59,29 @@ export default class ManyToManyDialog {
|
||||
return 'manytomany_dialog';
|
||||
}
|
||||
|
||||
getDataModel(attributes, tableNodesDict) {
|
||||
const parseColumns = (columns)=>{
|
||||
return columns.map((col)=>{
|
||||
return {
|
||||
value: col.attnum, label: col.name,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
let dialogModel = this.pgBrowser.DataModel.extend({
|
||||
defaults: {
|
||||
left_table_uid: undefined,
|
||||
left_table_column_attnum: undefined,
|
||||
right_table_uid: undefined,
|
||||
right_table_column_attnum: undefined,
|
||||
},
|
||||
schema: [{
|
||||
id: 'left_table_uid', label: gettext('Left Table'),
|
||||
type: 'select2', readonly: true,
|
||||
options: ()=>{
|
||||
let retOpts = [];
|
||||
_.forEach(tableNodesDict, (node, uid)=>{
|
||||
let [schema, name] = node.getSchemaTableName();
|
||||
retOpts.push({value: uid, label: `(${schema}) ${name}`});
|
||||
});
|
||||
return retOpts;
|
||||
},
|
||||
}, {
|
||||
id: 'left_table_column_attnum', label: gettext('Left table Column'),
|
||||
type: 'select2', disabled: false, first_empty: false,
|
||||
editable: true, options: (view)=>{
|
||||
return parseColumns(tableNodesDict[view.model.get('left_table_uid')].getColumns());
|
||||
},
|
||||
},{
|
||||
id: 'right_table_uid', label: gettext('Right Table'),
|
||||
type: 'select2', disabled: false,
|
||||
editable: true, options: (view)=>{
|
||||
let retOpts = [];
|
||||
_.forEach(tableNodesDict, (node, uid)=>{
|
||||
if(uid === view.model.get('left_table_uid')) {
|
||||
return;
|
||||
}
|
||||
let [schema, name] = node.getSchemaTableName();
|
||||
retOpts.push({value: uid, label: `(${schema}) ${name}`});
|
||||
});
|
||||
return retOpts;
|
||||
},
|
||||
},{
|
||||
id: 'right_table_column_attnum', label: gettext('Right table Column'),
|
||||
type: 'select2', disabled: false, deps: ['right_table_uid'],
|
||||
editable: true, options: (view)=>{
|
||||
if(view.model.get('right_table_uid')) {
|
||||
return parseColumns(tableNodesDict[view.model.get('right_table_uid')].getColumns());
|
||||
}
|
||||
return [];
|
||||
},
|
||||
}],
|
||||
validate: function(keys) {
|
||||
var msg = undefined;
|
||||
|
||||
// Nothing to validate
|
||||
if (keys && keys.length == 0) {
|
||||
this.errorModel.clear();
|
||||
return null;
|
||||
} else {
|
||||
this.errorModel.clear();
|
||||
}
|
||||
|
||||
if (_.isUndefined(this.get('left_table_column_attnum')) || this.get('left_table_column_attnum') == '') {
|
||||
msg = gettext('Select the left table column.');
|
||||
this.errorModel.set('left_table_column_attnum', msg);
|
||||
return msg;
|
||||
}
|
||||
if (_.isUndefined(this.get('right_table_uid')) || this.get('right_table_uid') == '') {
|
||||
msg = gettext('Select the right table.');
|
||||
this.errorModel.set('right_table_uid', msg);
|
||||
return msg;
|
||||
}
|
||||
if (_.isUndefined(this.get('right_table_column_attnum')) || this.get('right_table_column_attnum') == '') {
|
||||
msg = gettext('Select the right table column.');
|
||||
this.errorModel.set('right_table_column_attnum', msg);
|
||||
return msg;
|
||||
}
|
||||
},
|
||||
getUISchema(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 dialogModel(attributes);
|
||||
return new ManyToManySchema({
|
||||
left_table_uid: tablesData,
|
||||
left_table_column_attnum: tableNodesDict[attributes.left_table_uid].getColumns().map((col)=>{
|
||||
return {
|
||||
value: col.attnum, label: col.name, 'image': 'icon-column',
|
||||
};
|
||||
}),
|
||||
right_table_uid: tablesData,
|
||||
getRefColumns: (uid)=>{
|
||||
return tableNodesDict[uid].getColumns().map((col)=>{
|
||||
return {
|
||||
value: col.attnum, label: col.name, 'image': 'icon-column',
|
||||
};
|
||||
});
|
||||
},
|
||||
}, attributes);
|
||||
}
|
||||
|
||||
createOrGetDialog(title) {
|
||||
@@ -122,19 +93,16 @@ export default class ManyToManyDialog {
|
||||
`<div class="${dialogName}"></div>`,
|
||||
title,
|
||||
null,
|
||||
$,
|
||||
this.pgBrowser,
|
||||
Alertify,
|
||||
Backform
|
||||
);
|
||||
});
|
||||
}
|
||||
return Alertify[dialogName];
|
||||
}
|
||||
|
||||
show(title, attributes, tablesData, sVersion, callback) {
|
||||
show(title, attributes, tablesData, serverInfo, callback) {
|
||||
let dialogTitle = title || gettext('Unknown');
|
||||
const dialog = this.createOrGetDialog('manytomany_dialog');
|
||||
dialog(dialogTitle, this.getDataModel(attributes, tablesData), callback).resizeTo(this.pgBrowser.stdW.sm, this.pgBrowser.stdH.md);
|
||||
const dialog = this.createOrGetDialog('manytomany_dialog', serverInfo);
|
||||
dialog(dialogTitle, this.getUISchema(attributes, tablesData), callback).resizeTo(this.pgBrowser.stdW.sm, this.pgBrowser.stdH.md);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,13 +8,48 @@
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import gettext from 'sources/gettext';
|
||||
import Backform from 'sources/backform.pgadmin';
|
||||
import Alertify from 'pgadmin.alertifyjs';
|
||||
import $ from 'jquery';
|
||||
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
|
||||
|
||||
import DialogWrapper from './DialogWrapper';
|
||||
import _ from 'lodash';
|
||||
|
||||
class OneToManySchema extends BaseUISchema {
|
||||
constructor(fieldOptions={}, initValues={}) {
|
||||
super({
|
||||
local_table_uid: undefined,
|
||||
local_column_attnum: undefined,
|
||||
referenced_table_uid: undefined,
|
||||
referenced_column_attnum: undefined,
|
||||
...initValues,
|
||||
});
|
||||
this.fieldOptions = fieldOptions;
|
||||
}
|
||||
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: '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,
|
||||
}),
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
export default class OneToManyDialog {
|
||||
constructor(pgBrowser) {
|
||||
this.pgBrowser = pgBrowser;
|
||||
@@ -24,93 +59,32 @@ export default class OneToManyDialog {
|
||||
return 'onetomany_dialog';
|
||||
}
|
||||
|
||||
getDataModel(attributes, tableNodesDict) {
|
||||
const parseColumns = (columns)=>{
|
||||
return columns.map((col)=>{
|
||||
return {
|
||||
value: col.attnum, label: col.name,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
let dialogModel = this.pgBrowser.DataModel.extend({
|
||||
defaults: {
|
||||
local_table_uid: undefined,
|
||||
local_column_attnum: undefined,
|
||||
referenced_table_uid: undefined,
|
||||
referenced_column_attnum: undefined,
|
||||
},
|
||||
schema: [{
|
||||
id: 'local_table_uid', label: gettext('Local Table'),
|
||||
type: 'select2', readonly: true,
|
||||
options: ()=>{
|
||||
let retOpts = [];
|
||||
_.forEach(tableNodesDict, (node, uid)=>{
|
||||
let [schema, name] = node.getSchemaTableName();
|
||||
retOpts.push({value: uid, label: `(${schema}) ${name}`});
|
||||
});
|
||||
return retOpts;
|
||||
},
|
||||
}, {
|
||||
id: 'local_column_attnum', label: gettext('Local Column'),
|
||||
type: 'select2', disabled: false, first_empty: false,
|
||||
editable: true, options: (view)=>{
|
||||
return parseColumns(tableNodesDict[view.model.get('local_table_uid')].getColumns());
|
||||
},
|
||||
},{
|
||||
id: 'referenced_table_uid', label: gettext('Referenced Table'),
|
||||
type: 'select2', disabled: false,
|
||||
editable: true, options: ()=>{
|
||||
let retOpts = [];
|
||||
_.forEach(tableNodesDict, (node, uid)=>{
|
||||
let [schema, name] = node.getSchemaTableName();
|
||||
retOpts.push({value: uid, label: `(${schema}) ${name}`});
|
||||
});
|
||||
return retOpts;
|
||||
},
|
||||
},{
|
||||
id: 'referenced_column_attnum', label: gettext('Referenced Column'),
|
||||
type: 'select2', disabled: false, deps: ['referenced_table_uid'],
|
||||
editable: true, options: (view)=>{
|
||||
if(view.model.get('referenced_table_uid')) {
|
||||
return parseColumns(tableNodesDict[view.model.get('referenced_table_uid')].getColumns());
|
||||
}
|
||||
return [];
|
||||
},
|
||||
}],
|
||||
validate: function(keys) {
|
||||
var msg = undefined;
|
||||
|
||||
// Nothing to validate
|
||||
if (keys && keys.length == 0) {
|
||||
this.errorModel.clear();
|
||||
return null;
|
||||
} else {
|
||||
this.errorModel.clear();
|
||||
}
|
||||
|
||||
if (_.isUndefined(this.get('local_column_attnum')) || this.get('local_column_attnum') == '') {
|
||||
msg = gettext('Select the local column.');
|
||||
this.errorModel.set('local_column_attnum', msg);
|
||||
return msg;
|
||||
}
|
||||
if (_.isUndefined(this.get('referenced_table_uid')) || this.get('referenced_table_uid') == '') {
|
||||
msg = gettext('Select the referenced table.');
|
||||
this.errorModel.set('referenced_table_uid', msg);
|
||||
return msg;
|
||||
}
|
||||
if (_.isUndefined(this.get('referenced_column_attnum')) || this.get('referenced_column_attnum') == '') {
|
||||
msg = gettext('Select the referenced table column.');
|
||||
this.errorModel.set('referenced_column_attnum', msg);
|
||||
return msg;
|
||||
}
|
||||
},
|
||||
getUISchema(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 dialogModel(attributes);
|
||||
return new OneToManySchema({
|
||||
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);
|
||||
}
|
||||
|
||||
createOrGetDialog(title) {
|
||||
createOrGetDialog(title, sVersion) {
|
||||
const dialogName = this.dialogName();
|
||||
|
||||
if (!Alertify[dialogName]) {
|
||||
@@ -119,19 +93,17 @@ export default class OneToManyDialog {
|
||||
`<div class="${dialogName}"></div>`,
|
||||
title,
|
||||
null,
|
||||
$,
|
||||
this.pgBrowser,
|
||||
Alertify,
|
||||
Backform
|
||||
sVersion
|
||||
);
|
||||
});
|
||||
}
|
||||
return Alertify[dialogName];
|
||||
}
|
||||
|
||||
show(title, attributes, tablesData, sVersion, callback) {
|
||||
show(title, attributes, tablesData, serverInfo, callback) {
|
||||
let dialogTitle = title || gettext('Unknown');
|
||||
const dialog = this.createOrGetDialog('onetomany_dialog');
|
||||
dialog(dialogTitle, this.getDataModel(attributes, tablesData), callback).resizeTo(this.pgBrowser.stdW.sm, this.pgBrowser.stdH.md);
|
||||
const dialog = this.createOrGetDialog('onetomany_dialog', serverInfo);
|
||||
dialog(dialogTitle, this.getUISchema(attributes, tablesData), callback).resizeTo(this.pgBrowser.stdW.sm, this.pgBrowser.stdH.md);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,37 +8,22 @@
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import gettext from 'sources/gettext';
|
||||
import Backgrid from 'sources/backgrid.pgadmin';
|
||||
import Backform from 'sources/backform.pgadmin';
|
||||
import Alertify from 'pgadmin.alertifyjs';
|
||||
import $ from 'jquery';
|
||||
import _ from 'lodash';
|
||||
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
|
||||
|
||||
import DialogWrapper from './DialogWrapper';
|
||||
import TableSchema, { ConstraintsSchema } 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 ForeignKeySchema from '../../../../../../browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/static/js/foreign_key.ui';
|
||||
|
||||
export function transformToSupported(data) {
|
||||
/* Table fields */
|
||||
data = _.pick(data, ['oid', 'name', 'schema', 'description', 'columns', 'primary_key', 'foreign_key']);
|
||||
class EmptySchema extends BaseUISchema {
|
||||
get baseFields() {
|
||||
return [];
|
||||
}
|
||||
changeColumnOptions() {
|
||||
|
||||
/* Columns */
|
||||
data['columns'] = data['columns'].map((column)=>{
|
||||
return _.pick(column,[
|
||||
'name','description','attowner','attnum','cltype','min_val_attlen','min_val_attprecision','max_val_attlen',
|
||||
'max_val_attprecision', 'is_primary_key','attnotnull','attlen','attprecision','attidentity','colconstype',
|
||||
'seqincrement','seqstart','seqmin','seqmax','seqcache','seqcycle',
|
||||
]);
|
||||
});
|
||||
|
||||
/* Primary key */
|
||||
data['primary_key'] = data['primary_key'].map((primary_key)=>{
|
||||
primary_key = _.pick(primary_key, ['columns']);
|
||||
primary_key['columns'] = primary_key['columns'].map((column)=>{
|
||||
return _.pick(column, ['column']);
|
||||
});
|
||||
return primary_key;
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
export default class TableDialog {
|
||||
@@ -47,682 +32,81 @@ export default class TableDialog {
|
||||
}
|
||||
|
||||
dialogName() {
|
||||
return 'entity_dialog';
|
||||
return 'table_dialog';
|
||||
}
|
||||
|
||||
getDataModel(attributes, isNew, allTables, colTypes, schemas, sVersion) {
|
||||
let dialogObj = this;
|
||||
let columnsModel = this.pgBrowser.DataModel.extend({
|
||||
idAttribute: 'attnum',
|
||||
defaults: {
|
||||
name: undefined,
|
||||
description: undefined,
|
||||
attowner: undefined,
|
||||
attnum: undefined,
|
||||
cltype: undefined,
|
||||
min_val_attlen: undefined,
|
||||
min_val_attprecision: undefined,
|
||||
max_val_attlen: undefined,
|
||||
max_val_attprecision: undefined,
|
||||
is_primary_key: false,
|
||||
attnotnull: false,
|
||||
attlen: null,
|
||||
attprecision: null,
|
||||
attidentity: 'a',
|
||||
colconstype: 'n',
|
||||
seqincrement: undefined,
|
||||
seqstart: undefined,
|
||||
seqmin: undefined,
|
||||
seqmax: undefined,
|
||||
seqcache: undefined,
|
||||
seqcycle: undefined,
|
||||
},
|
||||
initialize: function(attrs) {
|
||||
if (_.size(attrs) !== 0) {
|
||||
this.set({
|
||||
'old_attidentity': this.get('attidentity'),
|
||||
}, {silent: true});
|
||||
}
|
||||
dialogObj.pgBrowser.DataModel.prototype.initialize.apply(this, arguments);
|
||||
getUISchema(attributes, isNew, tableNodesDict, colTypes, schemas) {
|
||||
let treeNodeInfo = undefined;
|
||||
|
||||
if(!this.get('cltype') && colTypes.length > 0) {
|
||||
this.set({
|
||||
'cltype': colTypes[0]['value'],
|
||||
}, {silent: true});
|
||||
}
|
||||
},
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), cell: 'string',
|
||||
type: 'text', disabled: false,
|
||||
cellHeaderClasses: 'width_percent_30',
|
||||
editable: true,
|
||||
}, {
|
||||
// Need to show this field only when creating new table
|
||||
// [in SubNode control]
|
||||
id: 'is_primary_key', label: gettext('Primary key?'),
|
||||
cell: Backgrid.Extension.TableChildSwitchCell, type: 'switch',
|
||||
deps: ['name'], cellHeaderClasses: 'width_percent_5',
|
||||
options: {
|
||||
onText: gettext('Yes'), offText: gettext('No'),
|
||||
onColor: 'success', offColor: 'ternary',
|
||||
},
|
||||
visible: function () {
|
||||
return true;
|
||||
},
|
||||
disabled: false,
|
||||
editable: true,
|
||||
}, {
|
||||
id: 'description', label: gettext('Comment'), cell: 'string', type: 'multiline',
|
||||
}, {
|
||||
id: 'cltype', label: gettext('Data type'),
|
||||
cell: 'select2',
|
||||
type: 'select2', disabled: false,
|
||||
control: 'select2',
|
||||
cellHeaderClasses: 'width_percent_30',
|
||||
select2: { allowClear: false, first_empty: false }, group: gettext('Definition'),
|
||||
options: function () {
|
||||
return colTypes;
|
||||
},
|
||||
}, {
|
||||
id: 'attlen', label: gettext('Length/Precision'), cell: Backgrid.Extension.IntegerDepCell,
|
||||
deps: ['cltype'], type: 'int', group: gettext('Definition'), cellHeaderClasses: 'width_percent_20',
|
||||
disabled: function (m) {
|
||||
var of_type = m.get('cltype'),
|
||||
flag = true;
|
||||
_.each(colTypes, function (o) {
|
||||
if (of_type == o.value) {
|
||||
if (o.length) {
|
||||
m.set('min_val_attlen', o.min_val, { silent: true });
|
||||
m.set('max_val_attlen', o.max_val, { silent: true });
|
||||
flag = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
let columnSchema = new ColumnSchema(
|
||||
()=>{},
|
||||
treeNodeInfo,
|
||||
()=>colTypes,
|
||||
()=>[],
|
||||
()=>[],
|
||||
true,
|
||||
);
|
||||
|
||||
flag && setTimeout(function () {
|
||||
if (m.get('attlen')) {
|
||||
m.set('attlen', null);
|
||||
}
|
||||
}, 10);
|
||||
|
||||
return flag;
|
||||
},
|
||||
editable: function (m) {
|
||||
var of_type = m.get('cltype'),
|
||||
flag = false;
|
||||
_.each(colTypes, function (o) {
|
||||
if (of_type == o.value) {
|
||||
if (o.length) {
|
||||
m.set('min_val_attlen', o.min_val, { silent: true });
|
||||
m.set('max_val_attlen', o.max_val, { silent: true });
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
!flag && setTimeout(function () {
|
||||
if (m.get('attlen')) {
|
||||
m.set('attlen', null);
|
||||
}
|
||||
}, 10);
|
||||
|
||||
return flag;
|
||||
},
|
||||
}, {
|
||||
id: 'attprecision', label: gettext('Scale'), cell: Backgrid.Extension.IntegerDepCell,
|
||||
deps: ['cltype'], type: 'int', group: gettext('Definition'), cellHeaderClasses: 'width_percent_20',
|
||||
disabled: function (m) {
|
||||
var of_type = m.get('cltype'),
|
||||
flag = true;
|
||||
_.each(colTypes, function (o) {
|
||||
if (of_type == o.value) {
|
||||
if (o.precision) {
|
||||
m.set('min_val_attprecision', 0, { silent: true });
|
||||
m.set('max_val_attprecision', o.max_val, { silent: true });
|
||||
flag = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
flag && setTimeout(function () {
|
||||
if (m.get('attprecision')) {
|
||||
m.set('attprecision', null);
|
||||
}
|
||||
}, 10);
|
||||
return flag;
|
||||
},
|
||||
editable: function (m) {
|
||||
if (!colTypes) {
|
||||
// datatypes not loaded yet, may be this call is from CallByNeed from backgrid cell initialize.
|
||||
return true;
|
||||
}
|
||||
|
||||
var of_type = m.get('cltype'),
|
||||
flag = false;
|
||||
_.each(colTypes, function (o) {
|
||||
if (of_type == o.value) {
|
||||
if (o.precision) {
|
||||
m.set('min_val_attprecision', 0, { silent: true });
|
||||
m.set('max_val_attprecision', o.max_val, { silent: true });
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
!flag && setTimeout(function () {
|
||||
if (m.get('attprecision')) {
|
||||
m.set('attprecision', null);
|
||||
}
|
||||
}, 10);
|
||||
|
||||
return flag;
|
||||
},
|
||||
}, {
|
||||
id: 'attnotnull', label: gettext('Not NULL?'), cell: 'switch',
|
||||
type: 'switch', cellHeaderClasses: 'width_percent_20',
|
||||
group: gettext('Constraints'),
|
||||
options: { onText: gettext('Yes'), offText: gettext('No'), onColor: 'success', offColor: 'ternary' },
|
||||
disabled: function(m) {
|
||||
if (m.get('colconstype') == 'i') {
|
||||
setTimeout(function () {
|
||||
m.set('attnotnull', true);
|
||||
}, 10);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
}, {
|
||||
id: 'colconstype',
|
||||
label: gettext('Type'),
|
||||
cell: 'string',
|
||||
type: 'radioModern',
|
||||
controlsClassName: 'pgadmin-controls col-12 col-sm-9',
|
||||
controlLabelClassName: 'control-label col-sm-3 col-12',
|
||||
group: gettext('Constraints'),
|
||||
options: function() {
|
||||
var opt_array = [
|
||||
{'label': gettext('NONE'), 'value': 'n'},
|
||||
{'label': gettext('IDENTITY'), 'value': 'i'},
|
||||
];
|
||||
|
||||
if (sVersion >= 120000) {
|
||||
opt_array.push({
|
||||
'label': gettext('GENERATED'),
|
||||
'value': 'g',
|
||||
});
|
||||
}
|
||||
|
||||
return opt_array;
|
||||
},
|
||||
disabled: false,
|
||||
visible: function() {
|
||||
if (sVersion >= 100000) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
}, {
|
||||
id: 'attidentity', label: gettext('Identity'), control: 'select2',
|
||||
cell: 'select2',
|
||||
select2: {placeholder: 'Select identity', allowClear: false, width: '100%'},
|
||||
group: gettext('Constraints'),
|
||||
'options': [
|
||||
{label: gettext('ALWAYS'), value: 'a'},
|
||||
{label: gettext('BY DEFAULT'), value: 'd'},
|
||||
],
|
||||
deps: ['colconstype'],
|
||||
visible: function(m) {
|
||||
if (sVersion >= 100000 && m.isTypeIdentity(m)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
disabled: function() {
|
||||
return false;
|
||||
},
|
||||
}, {
|
||||
id: 'seqincrement', label: gettext('Increment'), type: 'int',
|
||||
mode: ['properties', 'create', 'edit'], group: gettext('Constraints'),
|
||||
min: 1, deps: ['attidentity', 'colconstype'], disabled: 'isIdentityColumn',
|
||||
visible: 'isTypeIdentity',
|
||||
},{
|
||||
id: 'seqstart', label: gettext('Start'), type: 'int',
|
||||
mode: ['properties', 'create', 'edit'], group: gettext('Constraints'),
|
||||
disabled: function(m) {
|
||||
let isIdentity = m.get('attidentity');
|
||||
if(!_.isUndefined(isIdentity) && !_.isNull(isIdentity) && !_.isEmpty(isIdentity))
|
||||
return false;
|
||||
return true;
|
||||
}, deps: ['attidentity', 'colconstype'],
|
||||
visible: 'isTypeIdentity',
|
||||
},{
|
||||
id: 'seqmin', label: gettext('Minimum'), type: 'int',
|
||||
mode: ['properties', 'create', 'edit'], group: gettext('Constraints'),
|
||||
deps: ['attidentity', 'colconstype'], disabled: 'isIdentityColumn',
|
||||
visible: 'isTypeIdentity',
|
||||
},{
|
||||
id: 'seqmax', label: gettext('Maximum'), type: 'int',
|
||||
mode: ['properties', 'create', 'edit'], group: gettext('Constraints'),
|
||||
deps: ['attidentity', 'colconstype'], disabled: 'isIdentityColumn',
|
||||
visible: 'isTypeIdentity',
|
||||
},{
|
||||
id: 'seqcache', label: gettext('Cache'), type: 'int',
|
||||
mode: ['properties', 'create', 'edit'], group: gettext('Constraints'),
|
||||
min: 1, deps: ['attidentity', 'colconstype'], disabled: 'isIdentityColumn',
|
||||
visible: 'isTypeIdentity',
|
||||
},{
|
||||
id: 'seqcycle', label: gettext('Cycled'), type: 'switch',
|
||||
mode: ['properties', 'create', 'edit'], group: gettext('Constraints'),
|
||||
deps: ['attidentity', 'colconstype'], disabled: 'isIdentityColumn',
|
||||
visible: 'isTypeIdentity',
|
||||
}],
|
||||
validate: function(keys) {
|
||||
var msg = undefined;
|
||||
|
||||
// Nothing to validate
|
||||
if (keys && keys.length == 0) {
|
||||
this.errorModel.clear();
|
||||
return null;
|
||||
} else {
|
||||
this.errorModel.clear();
|
||||
}
|
||||
|
||||
if (_.isUndefined(this.get('name'))
|
||||
|| String(this.get('name')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('Column name cannot be empty.');
|
||||
this.errorModel.set('name', msg);
|
||||
return msg;
|
||||
}
|
||||
|
||||
if (_.isUndefined(this.get('cltype'))
|
||||
|| String(this.get('cltype')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('Column type cannot be empty.');
|
||||
this.errorModel.set('cltype', msg);
|
||||
return msg;
|
||||
}
|
||||
|
||||
if (!_.isUndefined(this.get('cltype'))
|
||||
&& !_.isUndefined(this.get('attlen'))
|
||||
&& !_.isNull(this.get('attlen'))
|
||||
&& this.get('attlen') !== '') {
|
||||
// Validation for Length field
|
||||
if (this.get('attlen') < this.get('min_val_attlen'))
|
||||
msg = gettext('Length/Precision should not be less than: ') + this.get('min_val_attlen');
|
||||
if (this.get('attlen') > this.get('max_val_attlen'))
|
||||
msg = gettext('Length/Precision should not be greater than: ') + this.get('max_val_attlen');
|
||||
// If we have any error set then throw it to user
|
||||
if(msg) {
|
||||
this.errorModel.set('attlen', msg);
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_.isUndefined(this.get('cltype'))
|
||||
&& !_.isUndefined(this.get('attprecision'))
|
||||
&& !_.isNull(this.get('attprecision'))
|
||||
&& this.get('attprecision') !== '') {
|
||||
// Validation for precision field
|
||||
if (this.get('attprecision') < this.get('min_val_attprecision'))
|
||||
msg = gettext('Scale should not be less than: ') + this.get('min_val_attprecision');
|
||||
if (this.get('attprecision') > this.get('max_val_attprecision'))
|
||||
msg = gettext('Scale should not be greater than: ') + this.get('max_val_attprecision');
|
||||
// If we have any error set then throw it to user
|
||||
if(msg) {
|
||||
this.errorModel.set('attprecision', msg);
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
|
||||
var minimum = this.get('seqmin'),
|
||||
maximum = this.get('seqmax'),
|
||||
start = this.get('seqstart');
|
||||
|
||||
if (!this.isNew() && this.get('colconstype') == 'i' &&
|
||||
(this.get('old_attidentity') == 'a' || this.get('old_attidentity') == 'd') &&
|
||||
(this.get('attidentity') == 'a' || this.get('attidentity') == 'd')) {
|
||||
if (_.isUndefined(this.get('seqincrement'))
|
||||
|| String(this.get('seqincrement')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('Increment value cannot be empty.');
|
||||
this.errorModel.set('seqincrement', msg);
|
||||
return msg;
|
||||
} else {
|
||||
this.errorModel.unset('seqincrement');
|
||||
}
|
||||
|
||||
if (_.isUndefined(this.get('seqmin'))
|
||||
|| String(this.get('seqmin')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('Minimum value cannot be empty.');
|
||||
this.errorModel.set('seqmin', msg);
|
||||
return msg;
|
||||
} else {
|
||||
this.errorModel.unset('seqmin');
|
||||
}
|
||||
|
||||
if (_.isUndefined(this.get('seqmax'))
|
||||
|| String(this.get('seqmax')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('Maximum value cannot be empty.');
|
||||
this.errorModel.set('seqmax', msg);
|
||||
return msg;
|
||||
} else {
|
||||
this.errorModel.unset('seqmax');
|
||||
}
|
||||
|
||||
if (_.isUndefined(this.get('seqcache'))
|
||||
|| String(this.get('seqcache')).replace(/^\s+|\s+$/g, '') == '') {
|
||||
msg = gettext('Cache value cannot be empty.');
|
||||
this.errorModel.set('seqcache', msg);
|
||||
return msg;
|
||||
} else {
|
||||
this.errorModel.unset('seqcache');
|
||||
}
|
||||
}
|
||||
var min_lt = gettext('Minimum value must be less than maximum value.'),
|
||||
start_lt = gettext('Start value cannot be less than minimum value.'),
|
||||
start_gt = gettext('Start value cannot be greater than maximum value.');
|
||||
|
||||
if (_.isEmpty(minimum) || _.isEmpty(maximum))
|
||||
return null;
|
||||
|
||||
if ((minimum == 0 && maximum == 0) ||
|
||||
(parseInt(minimum, 10) >= parseInt(maximum, 10))) {
|
||||
this.errorModel.set('seqmin', min_lt);
|
||||
return min_lt;
|
||||
} else {
|
||||
this.errorModel.unset('seqmin');
|
||||
}
|
||||
|
||||
if (start && minimum && parseInt(start) < parseInt(minimum)) {
|
||||
this.errorModel.set('seqstart', start_lt);
|
||||
return start_lt;
|
||||
} else {
|
||||
this.errorModel.unset('seqstart');
|
||||
}
|
||||
|
||||
if (start && maximum && parseInt(start) > parseInt(maximum)) {
|
||||
this.errorModel.set('seqstart', start_gt);
|
||||
return start_gt;
|
||||
} else {
|
||||
this.errorModel.unset('seqstart');
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
// Check whether the column is identity column or not
|
||||
isIdentityColumn: function(m) {
|
||||
let isIdentity = m.get('attidentity');
|
||||
if(!_.isUndefined(isIdentity) && !_.isNull(isIdentity) && !_.isEmpty(isIdentity))
|
||||
return false;
|
||||
return true;
|
||||
},
|
||||
// Check whether the column is a identity column
|
||||
isTypeIdentity: function(m) {
|
||||
let colconstype = m.get('colconstype');
|
||||
if (!_.isUndefined(colconstype) && !_.isNull(colconstype) && colconstype == 'i') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
// Check whether the column is a generated column
|
||||
isTypeGenerated: function(m) {
|
||||
let colconstype = m.get('colconstype');
|
||||
if (!_.isUndefined(colconstype) && !_.isNull(colconstype) && colconstype == 'g') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
||||
const formatSchemaItem = function(opt) {
|
||||
if (!opt.id) {
|
||||
return opt.text;
|
||||
}
|
||||
|
||||
var optimage = $(opt.element).data('image');
|
||||
|
||||
if (!optimage) {
|
||||
return opt.text;
|
||||
} else {
|
||||
return $('<span></span>').append(
|
||||
$('<span></span>', {
|
||||
class: 'wcTabIcon ' + optimage,
|
||||
})
|
||||
).append($('<span></span>').text(opt.text));
|
||||
}
|
||||
};
|
||||
|
||||
let dialogModel = this.pgBrowser.DataModel.extend({
|
||||
defaults: {
|
||||
name: undefined,
|
||||
schema: undefined,
|
||||
description: undefined,
|
||||
columns: [],
|
||||
primary_key: [],
|
||||
},
|
||||
initialize: function() {
|
||||
dialogObj.pgBrowser.DataModel.prototype.initialize.apply(this, arguments);
|
||||
|
||||
if(!this.get('schema') && schemas.length > 0) {
|
||||
this.set({
|
||||
'schema': schemas[0]['name'],
|
||||
}, {silent: true});
|
||||
}
|
||||
},
|
||||
schema: [{
|
||||
id: 'name', label: gettext('Name'), type: 'text', disabled: false,
|
||||
},{
|
||||
id: 'schema', label: gettext('Schema'), type: 'text',
|
||||
control: 'select2', select2: {
|
||||
allowClear: false, first_empty: false,
|
||||
templateResult: formatSchemaItem,
|
||||
templateSelection: formatSchemaItem,
|
||||
},
|
||||
options: function () {
|
||||
return schemas.map((schema)=>{
|
||||
return {
|
||||
'value': schema['name'],
|
||||
'image': 'icon-schema',
|
||||
'label': schema['name'],
|
||||
};
|
||||
});
|
||||
},
|
||||
filter: function(d) {
|
||||
// If schema name start with pg_* then we need to exclude them
|
||||
if(d && d.label.match(/^pg_/))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},{
|
||||
id: 'description', label: gettext('Comment'), type: 'multiline',
|
||||
},{
|
||||
id: 'columns', label: gettext('Columns'), type: 'collection', mode: ['create'],
|
||||
group: gettext('Columns'),
|
||||
model: columnsModel,
|
||||
subnode: columnsModel,
|
||||
disabled: false,
|
||||
uniqueCol : ['name'],
|
||||
columns : ['name' , 'cltype', 'attlen', 'attprecision', 'attnotnull', 'is_primary_key'],
|
||||
control: Backform.UniqueColCollectionControl.extend({
|
||||
initialize: function() {
|
||||
|
||||
Backform.UniqueColCollectionControl.prototype.initialize.apply(this, arguments);
|
||||
var self = this,
|
||||
collection = self.model.get(self.field.get('name'));
|
||||
|
||||
if(collection.isEmpty()) {
|
||||
self.last_attnum = -1;
|
||||
} else {
|
||||
var lastCol = collection.max(function(col) {
|
||||
return col.get('attnum');
|
||||
});
|
||||
self.last_attnum = lastCol.get('attnum');
|
||||
}
|
||||
|
||||
collection.on('change:is_primary_key', function(m) {
|
||||
var primary_key_coll = self.model.get('primary_key'),
|
||||
column_name = m.get('name'),
|
||||
primary_key, primary_key_column_coll;
|
||||
|
||||
if(m.get('is_primary_key')) {
|
||||
// Add column to primary key.
|
||||
if (primary_key_coll.length < 1) {
|
||||
primary_key = new (primary_key_coll.model)({}, {
|
||||
top: self.model,
|
||||
collection: primary_key_coll,
|
||||
handler: primary_key_coll,
|
||||
});
|
||||
primary_key_coll.add(primary_key);
|
||||
} else {
|
||||
primary_key = primary_key_coll.first();
|
||||
}
|
||||
|
||||
primary_key_column_coll = primary_key.get('columns');
|
||||
var primary_key_column_exist = primary_key_column_coll.where({column:column_name});
|
||||
|
||||
if (primary_key_column_exist.length == 0) {
|
||||
var primary_key_column = new (
|
||||
primary_key_column_coll.model
|
||||
)({column: column_name}, {
|
||||
silent: true,
|
||||
top: self.model,
|
||||
collection: primary_key_coll,
|
||||
handler: primary_key_coll,
|
||||
});
|
||||
|
||||
primary_key_column_coll.add(primary_key_column);
|
||||
}
|
||||
|
||||
primary_key_column_coll.trigger(
|
||||
'pgadmin:multicolumn:updated', primary_key_column_coll
|
||||
);
|
||||
} else {
|
||||
// remove column from primary key.
|
||||
if (primary_key_coll.length > 0) {
|
||||
primary_key = primary_key_coll.first();
|
||||
// Do not alter existing primary key columns.
|
||||
if (!_.isUndefined(primary_key.get('oid'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
primary_key_column_coll = primary_key.get('columns');
|
||||
var removedCols = primary_key_column_coll.where({column:column_name});
|
||||
if (removedCols.length > 0) {
|
||||
primary_key_column_coll.remove(removedCols);
|
||||
_.each(removedCols, function(local_model) {
|
||||
local_model.destroy();
|
||||
});
|
||||
if (primary_key_column_coll.length == 0) {
|
||||
/* Ideally above line of code should be "primary_key_coll.reset()".
|
||||
* But our custom DataCollection (extended from Backbone collection in datamodel.js)
|
||||
* does not respond to reset event, it only supports add, remove, change events.
|
||||
* And hence no custom event listeners/validators get called for reset event.
|
||||
*/
|
||||
primary_key_coll.remove(primary_key_coll.first());
|
||||
}
|
||||
}
|
||||
primary_key_column_coll.trigger('pgadmin:multicolumn:updated', primary_key_column_coll);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
collection.on('change:name', function(m) {
|
||||
let primary_key = self.model.get('primary_key').first();
|
||||
if(primary_key) {
|
||||
let updatedCols = primary_key.get('columns').where(
|
||||
{column: m.previous('name')}
|
||||
);
|
||||
if (updatedCols.length > 0) {
|
||||
/*
|
||||
* Table column name has changed so update
|
||||
* column name in primary key as well.
|
||||
*/
|
||||
updatedCols[0].set(
|
||||
{'column': m.get('name')},
|
||||
{silent: true});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
collection.on('remove', function(m) {
|
||||
let primary_key = self.model.get('primary_key').first();
|
||||
if(primary_key) {
|
||||
let removedCols = primary_key.get('columns').where(
|
||||
{column: m.get('name')}
|
||||
);
|
||||
|
||||
primary_key.get('columns').remove(removedCols);
|
||||
}
|
||||
});
|
||||
},
|
||||
return new TableSchema(
|
||||
{
|
||||
relowner: [],
|
||||
schema: schemas.map((schema)=>{
|
||||
return {
|
||||
'value': schema['name'],
|
||||
'image': 'icon-schema',
|
||||
'label': schema['name'],
|
||||
};
|
||||
}),
|
||||
canAdd: true,
|
||||
canEdit: true, canDelete: true,
|
||||
// For each row edit/delete button enable/disable
|
||||
canEditRow: true,
|
||||
canDeleteRow: true,
|
||||
allowMultipleEmptyRow: false,
|
||||
beforeAdd: function(newModel) {
|
||||
this.last_attnum++;
|
||||
newModel.set('attnum', this.last_attnum);
|
||||
return newModel;
|
||||
},
|
||||
},{
|
||||
// Here we will create tab control for constraints
|
||||
// We will hide the tab for ERD
|
||||
type: 'nested', control: 'tab', group: gettext('Constraints'), mode: ['properties'],
|
||||
schema: [{
|
||||
id: 'primary_key', label: '',
|
||||
model: this.pgBrowser.Nodes['primary_key'].model.extend({
|
||||
validate: ()=>{},
|
||||
}),
|
||||
subnode: this.pgBrowser.Nodes['primary_key'].model.extend({
|
||||
validate: ()=>{},
|
||||
}),
|
||||
editable: false, type: 'collection',
|
||||
},
|
||||
],
|
||||
}],
|
||||
validate: function() {
|
||||
var msg,
|
||||
name = this.get('name'),
|
||||
schema = this.get('schema');
|
||||
|
||||
if (
|
||||
_.isUndefined(name) || _.isNull(name) ||
|
||||
String(name).replace(/^\s+|\s+$/g, '') == ''
|
||||
) {
|
||||
msg = gettext('Table name cannot be empty.');
|
||||
this.errorModel.set('name', msg);
|
||||
return msg;
|
||||
}
|
||||
|
||||
/* Check existing table names */
|
||||
let sameNameCount = _.filter(allTables, (table)=>table[0]==schema&&table[1]==name).length;
|
||||
if(isNew && this.sessAttrs['name'] && sameNameCount > 0 || isNew && sameNameCount > 0) {
|
||||
msg = gettext('Table name already exists.');
|
||||
this.errorModel.set('name', msg);
|
||||
return msg;
|
||||
}
|
||||
this.errorModel.unset('name');
|
||||
if (
|
||||
_.isUndefined(schema) || _.isNull(schema) ||
|
||||
String(schema).replace(/^\s+|\s+$/g, '') == ''
|
||||
) {
|
||||
msg = gettext('Table schema cannot be empty.');
|
||||
this.errorModel.set('schema', msg);
|
||||
return msg;
|
||||
}
|
||||
this.errorModel.unset('schema');
|
||||
|
||||
|
||||
return null;
|
||||
spcname: [],
|
||||
coll_inherits: [],
|
||||
typname: [],
|
||||
like_relation: [],
|
||||
},
|
||||
});
|
||||
|
||||
return new dialogModel(attributes);
|
||||
treeNodeInfo,
|
||||
{
|
||||
columns: ()=>columnSchema,
|
||||
vacuum_settings: ()=>new EmptySchema(),
|
||||
constraints: ()=>new ConstraintsSchema(
|
||||
treeNodeInfo,
|
||||
()=>new ForeignKeySchema({
|
||||
local_column: [],
|
||||
references: ()=>{
|
||||
let retOpts = [];
|
||||
_.forEach(tableNodesDict, (node, uid)=>{
|
||||
let [schema, name] = node.getSchemaTableName();
|
||||
retOpts.push({value: uid, label: `(${schema}) ${name}`});
|
||||
});
|
||||
return retOpts;
|
||||
}
|
||||
},
|
||||
treeNodeInfo,
|
||||
(params)=>{
|
||||
if(params.tid) {
|
||||
return tableNodesDict[params.tid].getColumns().map((col)=>{
|
||||
return {
|
||||
value: col.name, label: col.name, 'image': 'icon-column',
|
||||
};
|
||||
});
|
||||
}
|
||||
}, {autoindex: false}, true),
|
||||
()=>new EmptySchema(),
|
||||
{spcname: []},
|
||||
true
|
||||
),
|
||||
},
|
||||
()=>new EmptySchema(),
|
||||
()=>[],
|
||||
()=>[],
|
||||
()=>[],
|
||||
()=>[],
|
||||
isNew ? {
|
||||
schema: schemas[0]?.name,
|
||||
} : attributes,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
createOrGetDialog(type) {
|
||||
createOrGetDialog(type, sVersion) {
|
||||
const dialogName = this.dialogName();
|
||||
|
||||
if (!Alertify[dialogName]) {
|
||||
@@ -731,19 +115,17 @@ export default class TableDialog {
|
||||
`<div class="${dialogName}"></div>`,
|
||||
null,
|
||||
type,
|
||||
$,
|
||||
this.pgBrowser,
|
||||
Alertify,
|
||||
Backform
|
||||
sVersion
|
||||
);
|
||||
});
|
||||
}
|
||||
return Alertify[dialogName];
|
||||
}
|
||||
|
||||
show(title, attributes, isNew, allTables, colTypes, schemas, sVersion, callback) {
|
||||
show(title, attributes, isNew, tableNodesDict, colTypes, schemas, serverInfo, callback) {
|
||||
let dialogTitle = title || gettext('Unknown');
|
||||
const dialog = this.createOrGetDialog('table_dialog');
|
||||
dialog(dialogTitle, this.getDataModel(attributes, isNew, allTables, colTypes, schemas, sVersion), callback).resizeTo(this.pgBrowser.stdW.md, this.pgBrowser.stdH.md);
|
||||
const dialog = this.createOrGetDialog('table_dialog', serverInfo);
|
||||
dialog(dialogTitle, this.getUISchema(attributes, isNew, tableNodesDict, colTypes, schemas, serverInfo), callback).resizeTo(this.pgBrowser.stdW.lg, this.pgBrowser.stdH.md);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import TableDialog, {transformToSupported as transformToSupportedTable} from './TableDialog';
|
||||
import TableDialog from './TableDialog';
|
||||
import OneToManyDialog from './OneToManyDialog';
|
||||
import ManyToManyDialog from './ManyToManyDialog';
|
||||
import pgBrowser from 'top/browser/static/js/browser';
|
||||
@@ -15,7 +15,7 @@ import 'sources/backgrid.pgadmin';
|
||||
import 'sources/backform.pgadmin';
|
||||
|
||||
export default function getDialog(dialogName) {
|
||||
if(dialogName === 'entity_dialog') {
|
||||
if(dialogName === 'table_dialog') {
|
||||
return new TableDialog(pgBrowser);
|
||||
} else if(dialogName === 'onetomany_dialog') {
|
||||
return new OneToManyDialog(pgBrowser);
|
||||
@@ -23,10 +23,3 @@ export default function getDialog(dialogName) {
|
||||
return new ManyToManyDialog(pgBrowser);
|
||||
}
|
||||
}
|
||||
|
||||
export function transformToSupported(type, data) {
|
||||
if(type == 'table') {
|
||||
return transformToSupportedTable(data);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import ReactDOM from 'react-dom';
|
||||
import _ from 'lodash';
|
||||
|
||||
import BodyWidget from './ui_components/BodyWidget';
|
||||
import getDialog, {transformToSupported} from './dialogs';
|
||||
import getDialog from './dialogs';
|
||||
import Alertify from 'pgadmin.alertifyjs';
|
||||
import pgWindow from 'sources/window';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
@@ -40,7 +40,6 @@ export default class ERDTool {
|
||||
<BodyWidget
|
||||
params={this.params}
|
||||
getDialog={getDialog}
|
||||
transformToSupported={transformToSupported}
|
||||
pgWindow={pgWindow}
|
||||
pgAdmin={pgAdmin}
|
||||
panel={panel}
|
||||
|
||||
@@ -188,7 +188,7 @@ export class TableNodeWidget extends React.Component {
|
||||
render() {
|
||||
let node_data = this.props.node.getData();
|
||||
return (
|
||||
<div className={'table-node ' + (this.props.node.isSelected() ? 'selected': '') } onDoubleClick={()=>{this.props.node.fireEvent({}, 'editNode');}}>
|
||||
<div className={'table-node ' + (this.props.node.isSelected() ? 'selected': '') } onDoubleClick={()=>{this.props.node.fireEvent({}, 'editTable');}}>
|
||||
<div className="table-toolbar">
|
||||
<DetailsToggleButton className='btn-xs' showDetails={this.state.show_details} onClick={this.toggleShowDetails} onDoubleClick={(e)=>{e.stopPropagation();}} />
|
||||
{this.props.node.getNote() &&
|
||||
|
||||
@@ -25,6 +25,7 @@ import gettext from 'sources/gettext';
|
||||
import url_for from 'sources/url_for';
|
||||
import {showERDSqlTool} from 'tools/datagrid/static/js/show_query_tool';
|
||||
import 'wcdocker';
|
||||
import Theme from '../../../../../../static/js/Theme';
|
||||
|
||||
/* Custom react-diagram action for keyboard events */
|
||||
export class KeyboardShortcutAction extends Action {
|
||||
@@ -76,6 +77,9 @@ export default class BodyWidget extends React.Component {
|
||||
show_details: true,
|
||||
is_new_tab: false,
|
||||
preferences: {},
|
||||
table_dialog_open: true,
|
||||
oto_dialog_open: true,
|
||||
otm_dialog_open: true,
|
||||
};
|
||||
this.diagram = new ERDCore();
|
||||
/* Flag for checking if user has opted for save before close */
|
||||
@@ -88,7 +92,7 @@ export default class BodyWidget extends React.Component {
|
||||
this.keyboardActionObj = null;
|
||||
|
||||
_.bindAll(this, ['onLoadDiagram', 'onSaveDiagram', 'onSaveAsDiagram', 'onSQLClick',
|
||||
'onImageClick', 'onAddNewNode', 'onEditNode', 'onCloneNode', 'onDeleteNode', 'onNoteClick',
|
||||
'onImageClick', 'onAddNewNode', 'onEditTable', 'onCloneNode', 'onDeleteNode', 'onNoteClick',
|
||||
'onNoteClose', 'onOneToManyClick', 'onManyToManyClick', 'onAutoDistribute', 'onDetailsToggle',
|
||||
'onDetailsToggle', 'onHelpClick'
|
||||
]);
|
||||
@@ -130,8 +134,8 @@ export default class BodyWidget extends React.Component {
|
||||
'showNote': (event)=>{
|
||||
this.showNote(event.node);
|
||||
},
|
||||
'editNode': (event) => {
|
||||
this.addEditNode(event.node);
|
||||
'editTable': (event) => {
|
||||
this.addEditTable(event.node);
|
||||
},
|
||||
};
|
||||
Object.keys(diagramEvents).forEach(eventName => {
|
||||
@@ -150,7 +154,7 @@ export default class BodyWidget extends React.Component {
|
||||
[this.state.preferences.generate_sql, this.onSQLClick],
|
||||
[this.state.preferences.download_image, this.onImageClick],
|
||||
[this.state.preferences.add_table, this.onAddNewNode],
|
||||
[this.state.preferences.edit_table, this.onEditNode],
|
||||
[this.state.preferences.edit_table, this.onEditTable],
|
||||
[this.state.preferences.clone_table, this.onCloneNode],
|
||||
[this.state.preferences.drop_table, this.onDeleteNode],
|
||||
[this.state.preferences.add_edit_note, this.onNoteClick],
|
||||
@@ -297,19 +301,20 @@ export default class BodyWidget extends React.Component {
|
||||
}
|
||||
|
||||
getDialog(dialogName) {
|
||||
if(dialogName === 'entity_dialog') {
|
||||
let allTables = this.diagram.getModel().getNodes().map((node)=>{
|
||||
return node.getSchemaTableName();
|
||||
});
|
||||
let serverInfo = {
|
||||
type: this.props.params.server_type,
|
||||
version: this.state.server_version,
|
||||
};
|
||||
if(dialogName === 'table_dialog') {
|
||||
return (title, attributes, isNew, callback)=>{
|
||||
this.props.getDialog(dialogName).show(
|
||||
title, attributes, isNew, allTables, this.diagram.getCache('colTypes'), this.diagram.getCache('schemas'), this.state.server_version, callback
|
||||
title, attributes, isNew, this.diagram.getModel().getNodesDict(), this.diagram.getCache('colTypes'), this.diagram.getCache('schemas'), serverInfo, callback
|
||||
);
|
||||
};
|
||||
} else if(dialogName === 'onetomany_dialog' || dialogName === 'manytomany_dialog') {
|
||||
return (title, attributes, callback)=>{
|
||||
this.props.getDialog(dialogName).show(
|
||||
title, attributes, this.diagram.getModel().getNodesDict(), this.state.server_version, callback
|
||||
title, attributes, this.diagram.getModel().getNodesDict(), serverInfo, callback
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -328,17 +333,20 @@ export default class BodyWidget extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
addEditNode(node) {
|
||||
let dialog = this.getDialog('entity_dialog');
|
||||
addEditTable(node) {
|
||||
let dialog = this.getDialog('table_dialog');
|
||||
if(node) {
|
||||
let [schema, table] = node.getSchemaTableName();
|
||||
dialog(gettext('Table: %s (%s)', _.escape(table),_.escape(schema)), node.getData(), false, (newData)=>{
|
||||
let oldData = node.getData();
|
||||
node.setData(newData);
|
||||
this.diagram.syncTableLinks(node, oldData);
|
||||
this.diagram.repaint();
|
||||
});
|
||||
} else {
|
||||
dialog(gettext('New table'), {name: this.diagram.getNextTableName()}, true, (newData)=>{
|
||||
dialog(gettext('New table'), {}, true, (newData)=>{
|
||||
let newNode = this.diagram.addNode(newData);
|
||||
this.diagram.syncTableLinks(newNode);
|
||||
newNode.setSelected(true);
|
||||
});
|
||||
}
|
||||
@@ -353,15 +361,15 @@ export default class BodyWidget extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
onEditNode() {
|
||||
onEditTable() {
|
||||
const selected = this.diagram.getSelectedNodes();
|
||||
if(selected.length == 1) {
|
||||
this.addEditNode(selected[0]);
|
||||
this.addEditTable(selected[0]);
|
||||
}
|
||||
}
|
||||
|
||||
onAddNewNode() {
|
||||
this.addEditNode();
|
||||
this.addEditTable();
|
||||
}
|
||||
|
||||
onCloneNode() {
|
||||
@@ -385,10 +393,7 @@ export default class BodyWidget extends React.Component {
|
||||
node.remove();
|
||||
});
|
||||
this.diagram.getSelectedLinks().forEach((link)=>{
|
||||
link.getTargetPort().remove();
|
||||
link.getSourcePort().remove();
|
||||
link.setSelected(false);
|
||||
link.remove();
|
||||
this.diagram.removeOneToManyLink(link);
|
||||
});
|
||||
this.diagram.repaint();
|
||||
},
|
||||
@@ -656,10 +661,7 @@ export default class BodyWidget extends React.Component {
|
||||
let dialog = this.getDialog('onetomany_dialog');
|
||||
let initData = {local_table_uid: this.diagram.getSelectedNodes()[0].getID()};
|
||||
dialog(gettext('One to many relation'), initData, (newData)=>{
|
||||
let newLink = this.diagram.addLink(newData, 'onetomany');
|
||||
this.diagram.clearSelection();
|
||||
newLink.setSelected(true);
|
||||
this.diagram.repaint();
|
||||
this.diagram.addOneToManyLink(newData);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -667,46 +669,7 @@ export default class BodyWidget extends React.Component {
|
||||
let dialog = this.getDialog('manytomany_dialog');
|
||||
let initData = {left_table_uid: this.diagram.getSelectedNodes()[0].getID()};
|
||||
dialog(gettext('Many to many relation'), initData, (newData)=>{
|
||||
let nodes = this.diagram.getModel().getNodesDict();
|
||||
let left_table = nodes[newData.left_table_uid];
|
||||
let right_table = nodes[newData.right_table_uid];
|
||||
let tableData = {
|
||||
name: `${left_table.getData().name}_${right_table.getData().name}`,
|
||||
schema: left_table.getData().schema,
|
||||
columns: [{
|
||||
...left_table.getColumnAt(newData.left_table_column_attnum),
|
||||
'name': `${left_table.getData().name}_${left_table.getColumnAt(newData.left_table_column_attnum).name}`,
|
||||
'is_primary_key': false,
|
||||
'attnum': 0,
|
||||
},{
|
||||
...right_table.getColumnAt(newData.right_table_column_attnum),
|
||||
'name': `${right_table.getData().name}_${right_table.getColumnAt(newData.right_table_column_attnum).name}`,
|
||||
'is_primary_key': false,
|
||||
'attnum': 1,
|
||||
}],
|
||||
};
|
||||
let newNode = this.diagram.addNode(tableData);
|
||||
this.diagram.clearSelection();
|
||||
newNode.setSelected(true);
|
||||
|
||||
let linkData = {
|
||||
local_table_uid: newNode.getID(),
|
||||
local_column_attnum: newNode.getColumns()[0].attnum,
|
||||
referenced_table_uid: newData.left_table_uid,
|
||||
referenced_column_attnum : newData.left_table_column_attnum,
|
||||
};
|
||||
this.diagram.addLink(linkData, 'onetomany');
|
||||
|
||||
linkData = {
|
||||
local_table_uid: newNode.getID(),
|
||||
local_column_attnum: newNode.getColumns()[1].attnum,
|
||||
referenced_table_uid: newData.right_table_uid,
|
||||
referenced_column_attnum : newData.right_table_column_attnum,
|
||||
};
|
||||
|
||||
this.diagram.addLink(linkData, 'onetomany');
|
||||
|
||||
this.diagram.repaint();
|
||||
this.diagram.addManyToManyLink(newData);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -794,10 +757,7 @@ export default class BodyWidget extends React.Component {
|
||||
|
||||
try {
|
||||
let response = await axios.get(url);
|
||||
let tables = response.data.data.map((table)=>{
|
||||
return this.props.transformToSupported('table', table);
|
||||
});
|
||||
this.diagram.deserializeData(tables);
|
||||
this.diagram.deserializeData(response.data.data);
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.handleAxiosCatch(error);
|
||||
@@ -809,7 +769,7 @@ export default class BodyWidget extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<Theme>
|
||||
<ToolBar id="btn-toolbar">
|
||||
<ButtonGroup>
|
||||
<IconButton id="open-file" icon="fa fa-folder-open" onClick={this.onLoadDiagram} title={gettext('Load from file')}
|
||||
@@ -828,7 +788,7 @@ export default class BodyWidget extends React.Component {
|
||||
<ButtonGroup>
|
||||
<IconButton id="add-node" icon="fa fa-plus-square" onClick={this.onAddNewNode} title={gettext('Add table')}
|
||||
shortcut={this.state.preferences.add_table}/>
|
||||
<IconButton id="edit-node" icon="fa fa-pencil-alt" onClick={this.onEditNode} title={gettext('Edit table')}
|
||||
<IconButton id="edit-node" icon="fa fa-pencil-alt" onClick={this.onEditTable} title={gettext('Edit table')}
|
||||
shortcut={this.state.preferences.edit_table} disabled={!this.state.single_node_selected || this.state.single_link_selected}/>
|
||||
<IconButton id="clone-node" icon="fa fa-clone" onClick={this.onCloneNode} title={gettext('Clone table')}
|
||||
shortcut={this.state.preferences.clone_table} disabled={!this.state.single_node_selected || this.state.single_link_selected}/>
|
||||
@@ -869,7 +829,7 @@ export default class BodyWidget extends React.Component {
|
||||
<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()} />
|
||||
</div>
|
||||
</>
|
||||
</Theme>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -888,7 +848,6 @@ BodyWidget.propTypes = {
|
||||
gen: PropTypes.bool.isRequired,
|
||||
}),
|
||||
getDialog: PropTypes.func.isRequired,
|
||||
transformToSupported: PropTypes.func.isRequired,
|
||||
pgWindow: PropTypes.object.isRequired,
|
||||
pgAdmin: PropTypes.object.isRequired,
|
||||
alertify: PropTypes.object.isRequired,
|
||||
|
||||
@@ -205,3 +205,14 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.alertify {
|
||||
.erd-dialog {
|
||||
.ajs-body .ajs-content {
|
||||
bottom: 0!important;
|
||||
}
|
||||
.ajs-footer {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user