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
@@ -9,6 +9,8 @@ This release contains a number of bug fixes and new features since the release o
|
||||
New features
|
||||
************
|
||||
|
||||
| `Issue #6081 <https://redmine.postgresql.org/issues/6081>`_ - Added support for advanced table fields like the foreign key, primary key in the ERD tool.
|
||||
| `Issue #6529 <https://redmine.postgresql.org/issues/6529>`_ - Added index creation when generating SQL in the ERD tool.
|
||||
|
||||
Housekeeping
|
||||
************
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
"@material-ui/icons": "^4.11.2",
|
||||
"@material-ui/lab": "4.0.0-alpha.58",
|
||||
"@material-ui/pickers": "^3.2.10",
|
||||
"@projectstorm/react-diagrams": "^6.4.2",
|
||||
"@projectstorm/react-diagrams": "^6.6.1",
|
||||
"@simonwep/pickr": "^1.5.1",
|
||||
"@tippyjs/react": "^4.2.0",
|
||||
"@types/classnames": "^2.2.6",
|
||||
|
||||
@@ -19,7 +19,7 @@ export function getNodeColumnSchema(treeNodeInfo, itemNodeData, pgBrowser) {
|
||||
}
|
||||
|
||||
export default class ColumnSchema extends BaseUISchema {
|
||||
constructor(getPrivilegeRoleSchema, nodeInfo, cltypeOptions, collspcnameOptions) {
|
||||
constructor(getPrivilegeRoleSchema, nodeInfo, cltypeOptions, collspcnameOptions, inErd=false) {
|
||||
super({
|
||||
name: undefined,
|
||||
attowner: undefined,
|
||||
@@ -57,6 +57,7 @@ export default class ColumnSchema extends BaseUISchema {
|
||||
this.nodeInfo = nodeInfo;
|
||||
this.cltypeOptions = cltypeOptions;
|
||||
this.collspcnameOptions = collspcnameOptions;
|
||||
this.inErd = inErd;
|
||||
|
||||
this.datatypes = [];
|
||||
}
|
||||
@@ -71,8 +72,7 @@ export default class ColumnSchema extends BaseUISchema {
|
||||
return true;
|
||||
}
|
||||
|
||||
if('schema' in this.nodeInfo)
|
||||
{
|
||||
if(this.nodeInfo && ('schema' in this.nodeInfo)) {
|
||||
// We will disable control if it's system columns
|
||||
// inheritedfrom check is useful when we use this schema in table node
|
||||
// inheritedfrom has value then we should disable it
|
||||
@@ -86,7 +86,7 @@ export default class ColumnSchema extends BaseUISchema {
|
||||
// ie: it's position is less than 1
|
||||
return !(!_.isUndefined(state.attnum) && state.attnum > 0);
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
editableCheckForTable(state) {
|
||||
@@ -179,7 +179,7 @@ export default class ColumnSchema extends BaseUISchema {
|
||||
) || (
|
||||
'is_partitioned' in obj.top.origData
|
||||
&& obj.top.origData['is_partitioned']
|
||||
&& obj.nodeInfo.server && obj.nodeInfo.server.version < 11000
|
||||
&& obj.getServerVersion() < 11000
|
||||
))
|
||||
) {
|
||||
return true;
|
||||
@@ -239,7 +239,7 @@ export default class ColumnSchema extends BaseUISchema {
|
||||
filter: (options)=>{
|
||||
let result = options;
|
||||
let edit_types = state?.edit_types || [];
|
||||
if(!obj.isNew(state)) {
|
||||
if(!obj.isNew(state) && !this.inErd) {
|
||||
result = _.filter(options, (o)=>edit_types.indexOf(o.value) > -1);
|
||||
}
|
||||
return result;
|
||||
@@ -256,7 +256,7 @@ export default class ColumnSchema extends BaseUISchema {
|
||||
filter: (options)=>{
|
||||
let result = options;
|
||||
let edit_types = row?.edit_types || [];
|
||||
if(!obj.isNew(row)) {
|
||||
if(!obj.isNew(row) && !this.inErd) {
|
||||
result = _.filter(options, (o)=>edit_types.indexOf(o.value) > -1);
|
||||
}
|
||||
return result;
|
||||
@@ -272,7 +272,10 @@ export default class ColumnSchema extends BaseUISchema {
|
||||
id: 'inheritedfrom', label: gettext('Inherited from table'),
|
||||
type: 'text', readonly: true, editable: false,
|
||||
visible: function() {
|
||||
return _.isUndefined(this.nodeInfo['table'] || this.nodeInfo['view'] || this.nodeInfo['mview']);
|
||||
if(this.nodeInfo) {
|
||||
return _.isUndefined(this.nodeInfo['table'] || this.nodeInfo['view'] || this.nodeInfo['mview']);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
},{
|
||||
id: 'attlen', label: gettext('Length/Precision'),
|
||||
@@ -417,8 +420,7 @@ export default class ColumnSchema extends BaseUISchema {
|
||||
{'label': gettext('IDENTITY'), 'value': 'i'},
|
||||
];
|
||||
|
||||
if (this.nodeInfo && this.nodeInfo.server &&
|
||||
this.nodeInfo.server.version >= 120000) {
|
||||
if (this.getServerVersion() >= 120000) {
|
||||
// You can't change the existing column to Generated column.
|
||||
if (this.isNew(state)) {
|
||||
options.push({
|
||||
@@ -529,15 +531,18 @@ export default class ColumnSchema extends BaseUISchema {
|
||||
], null, null, ['name', 'value']),
|
||||
uniqueCol : ['name'], mode: ['edit', 'create'],
|
||||
canAdd: true, canEdit: false, canDelete: true,
|
||||
}, {
|
||||
},{
|
||||
id: 'security', label: gettext('Security'), type: 'group',
|
||||
visible: !this.inErd,
|
||||
},{
|
||||
id: 'attacl', label: gettext('Privileges'), type: 'collection',
|
||||
group: gettext('Security'),
|
||||
group: 'security',
|
||||
schema: this.getPrivilegeRoleSchema(['a','r','w','x']),
|
||||
mode: ['edit'], canAdd: true, canDelete: true,
|
||||
uniqueCol : ['grantee'],
|
||||
},{
|
||||
id: 'seclabels', label: gettext('Security labels'), canAdd: true,
|
||||
schema: new SecLabelSchema(), group: gettext('Security'),
|
||||
schema: new SecLabelSchema(), group: 'security',
|
||||
mode: ['edit', 'create'], editable: false, type: 'collection',
|
||||
min_version: 90100, canEdit: false, canDelete: true,
|
||||
uniqueCol : ['provider'],
|
||||
|
||||
@@ -86,7 +86,7 @@ class ForeignKeyHeaderSchema extends BaseUISchema {
|
||||
}
|
||||
}
|
||||
|
||||
class ForeignKeyColumnSchema extends BaseUISchema {
|
||||
export class ForeignKeyColumnSchema extends BaseUISchema {
|
||||
constructor() {
|
||||
super({
|
||||
local_column: undefined,
|
||||
@@ -111,7 +111,7 @@ class ForeignKeyColumnSchema extends BaseUISchema {
|
||||
}
|
||||
|
||||
export default class ForeignKeySchema extends BaseUISchema {
|
||||
constructor(fieldOptions={}, nodeInfo, getColumns, initData={}) {
|
||||
constructor(fieldOptions={}, nodeInfo, getColumns, initValues={}, inErd=false) {
|
||||
super({
|
||||
name: undefined,
|
||||
reftab: undefined,
|
||||
@@ -128,7 +128,7 @@ export default class ForeignKeySchema extends BaseUISchema {
|
||||
autoindex: true,
|
||||
coveringindex: undefined,
|
||||
hasindex:undefined,
|
||||
...initData,
|
||||
...initValues,
|
||||
});
|
||||
|
||||
this.nodeInfo = nodeInfo;
|
||||
@@ -136,7 +136,7 @@ export default class ForeignKeySchema extends BaseUISchema {
|
||||
this.fkHeaderSchema = new ForeignKeyHeaderSchema(fieldOptions, getColumns);
|
||||
this.fkHeaderSchema.fkObj = this;
|
||||
this.fkColumnSchema = new ForeignKeyColumnSchema();
|
||||
|
||||
this.inErd = inErd;
|
||||
}
|
||||
|
||||
get idAttribute() {
|
||||
@@ -240,9 +240,7 @@ export default class ForeignKeySchema extends BaseUISchema {
|
||||
return true;
|
||||
}
|
||||
// If we are in table edit mode then
|
||||
if(obj.inTable) {
|
||||
return true;
|
||||
} else if(state.hasindex) {
|
||||
if(state.hasindex) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -252,7 +250,7 @@ export default class ForeignKeySchema extends BaseUISchema {
|
||||
return {};
|
||||
}
|
||||
// If we are in table edit mode
|
||||
if(obj.inTable) {
|
||||
if(obj.inTable && !this.inErd) {
|
||||
if(obj.isNew(state) && obj.top.isNew()) {
|
||||
return {autoindex: false, coveringindex: ''};
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ export function getNodeTableSchema(treeNodeInfo, itemNodeData, pgBrowser) {
|
||||
}
|
||||
|
||||
export class ConstraintsSchema extends BaseUISchema {
|
||||
constructor(nodeInfo, getFkObj, getExConsObj, otherOptions) {
|
||||
constructor(nodeInfo, getFkObj, getExConsObj, otherOptions, inErd=false) {
|
||||
super();
|
||||
this.nodeInfo = nodeInfo;
|
||||
this.primaryKeyObj = new PrimaryKeySchema({
|
||||
@@ -81,13 +81,16 @@ export class ConstraintsSchema extends BaseUISchema {
|
||||
spcname: otherOptions.spcname,
|
||||
}, nodeInfo);
|
||||
this.exConsObj = getExConsObj();
|
||||
this.inErd = inErd;
|
||||
}
|
||||
|
||||
changeColumnOptions(colOptions) {
|
||||
this.primaryKeyObj.changeColumnOptions(colOptions);
|
||||
this.fkObj.changeColumnOptions(colOptions);
|
||||
this.uniqueConsObj.changeColumnOptions(colOptions);
|
||||
this.exConsObj.changeColumnOptions(colOptions);
|
||||
if(!this.inErd) {
|
||||
this.uniqueConsObj.changeColumnOptions(colOptions);
|
||||
this.exConsObj.changeColumnOptions(colOptions);
|
||||
}
|
||||
}
|
||||
|
||||
anyColumnAdded(state) {
|
||||
@@ -147,20 +150,24 @@ export class ConstraintsSchema extends BaseUISchema {
|
||||
return {foreign_key: []};
|
||||
}
|
||||
}
|
||||
},{
|
||||
id: 'check_group', type: 'group', label: gettext('Check'), visible: !this.inErd,
|
||||
},{
|
||||
id: 'check_constraint', label: '',
|
||||
schema: new CheckConstraintSchema(),
|
||||
editable: false, type: 'collection',
|
||||
group: gettext('Check'), mode: ['edit', 'create'],
|
||||
group: 'check_group', mode: ['edit', 'create'],
|
||||
canEdit: true, canDelete: true, deps:['is_partitioned'],
|
||||
canAdd: true,
|
||||
columns : ['name', 'consrc'],
|
||||
disabled: this.inCatalog,
|
||||
},{
|
||||
id: 'unique_group', type: 'group', label: gettext('Unique'), visible: !this.inErd,
|
||||
},{
|
||||
id: 'unique_constraint', label: '',
|
||||
schema: this.uniqueConsObj,
|
||||
editable: false, type: 'collection',
|
||||
group: gettext('Unique'), mode: ['edit', 'create'],
|
||||
group: 'unique_group', mode: ['edit', 'create'],
|
||||
canEdit: true, canDelete: true, deps:['is_partitioned', 'typname'],
|
||||
columns : ['name', 'columns'],
|
||||
disabled: this.inCatalog,
|
||||
@@ -176,11 +183,13 @@ export class ConstraintsSchema extends BaseUISchema {
|
||||
return {unique_constraint: []};
|
||||
}
|
||||
}
|
||||
},{
|
||||
id: 'exclude_group', type: 'group', label: gettext('Exclude'), visible: !this.inErd,
|
||||
},{
|
||||
id: 'exclude_constraint', label: '',
|
||||
schema: this.exConsObj,
|
||||
editable: false, type: 'collection',
|
||||
group: gettext('Exclude'), mode: ['edit', 'create'],
|
||||
group: 'exclude_group', mode: ['edit', 'create'],
|
||||
canEdit: true, canDelete: true, deps:['is_partitioned'],
|
||||
columns : ['name', 'columns', 'constraint'],
|
||||
disabled: this.inCatalog,
|
||||
@@ -276,7 +285,7 @@ export class LikeSchema extends BaseUISchema {
|
||||
|
||||
export default class TableSchema extends BaseUISchema {
|
||||
constructor(fieldOptions={}, nodeInfo, schemas={}, getPrivilegeRoleSchema=()=>{}, getColumns=()=>[],
|
||||
getCollations=()=>[], getOperatorClass=()=>[], getAttachTables=()=>[], initValues={}) {
|
||||
getCollations=()=>[], getOperatorClass=()=>[], getAttachTables=()=>[], initValues={}, inErd=false) {
|
||||
super({
|
||||
name: undefined,
|
||||
oid: undefined,
|
||||
@@ -307,6 +316,7 @@ export default class TableSchema extends BaseUISchema {
|
||||
autovacuum_enabled: 'x',
|
||||
primary_key: [],
|
||||
foreign_key: [],
|
||||
partition_keys: [],
|
||||
partitions: [],
|
||||
partition_type: 'range',
|
||||
is_partitioned: false,
|
||||
@@ -325,6 +335,24 @@ export default class TableSchema extends BaseUISchema {
|
||||
this.columnsSchema = this.schemas.columns && this.schemas.columns() || {};
|
||||
this.vacuumSettingsSchema = this.schemas.vacuum_settings && this.schemas.vacuum_settings() || {};
|
||||
this.partitionKeysObj = new PartitionKeysSchema([], getCollations, getOperatorClass);
|
||||
this.inErd = inErd;
|
||||
}
|
||||
|
||||
static getErdSupportedData(data) {
|
||||
let newData = {...data};
|
||||
const SUPPORTED_KEYS = [
|
||||
'name', 'schema', 'description', 'rlspolicy', 'forcerlspolicy', 'fillfactor',
|
||||
'toast_tuple_target', 'parallel_workers', 'relhasoids', 'relpersistence',
|
||||
'columns', 'primary_key', 'foreign_key',
|
||||
];
|
||||
newData = _.pick(newData, SUPPORTED_KEYS);
|
||||
|
||||
/* Remove inherited references */
|
||||
newData.columns = newData.columns.map((c)=>{
|
||||
delete c.inheritedfromtable;
|
||||
return c;
|
||||
});
|
||||
return newData;
|
||||
}
|
||||
|
||||
get idAttribute() {
|
||||
@@ -397,9 +425,9 @@ export default class TableSchema extends BaseUISchema {
|
||||
id: 'oid', label: gettext('OID'), type: 'text', mode: ['properties'],
|
||||
},{
|
||||
id: 'relowner', label: gettext('Owner'), type: 'select',
|
||||
options: this.fieldOptions.relowner, noEmpty: true,
|
||||
options: this.fieldOptions.relowner, noEmpty: this.inErd ? false : true,
|
||||
mode: ['properties', 'create', 'edit'], controlProps: {allowClear: false},
|
||||
readonly: this.inCatalog,
|
||||
readonly: this.inCatalog, visible: !this.inErd,
|
||||
},{
|
||||
id: 'schema', label: gettext('Schema'), type: 'select',
|
||||
options: this.fieldOptions.schema, noEmpty: true,
|
||||
@@ -407,7 +435,7 @@ export default class TableSchema extends BaseUISchema {
|
||||
readonly: this.inCatalog,
|
||||
},{
|
||||
id: 'spcname', label: gettext('Tablespace'),
|
||||
|
||||
visible: !this.inErd,
|
||||
mode: ['properties', 'create', 'edit'], deps: ['is_partitioned'],
|
||||
readonly: this.inCatalog, type: (state)=>{
|
||||
return {
|
||||
@@ -421,6 +449,9 @@ export default class TableSchema extends BaseUISchema {
|
||||
id: 'partition', type: 'group', label: gettext('Partitions'),
|
||||
mode: ['edit', 'create'], min_version: 100000,
|
||||
visible: function(state) {
|
||||
if(this.inErd) {
|
||||
return false;
|
||||
}
|
||||
// Always show in case of create mode
|
||||
if (obj.isNew(state) || state.is_partitioned)
|
||||
return true;
|
||||
@@ -429,7 +460,7 @@ export default class TableSchema extends BaseUISchema {
|
||||
},{
|
||||
id: 'is_partitioned', label:gettext('Partitioned table?'), cell: 'switch',
|
||||
type: 'switch', mode: ['properties', 'create', 'edit'],
|
||||
min_version: 100000,
|
||||
min_version: 100000, visible: !this.inErd,
|
||||
readonly: function(state) {
|
||||
if (!obj.isNew(state))
|
||||
return true;
|
||||
@@ -447,7 +478,7 @@ export default class TableSchema extends BaseUISchema {
|
||||
type: 'select', group: gettext('Columns'),
|
||||
deps: ['typname', 'is_partitioned'], mode: ['create', 'edit'],
|
||||
controlProps: { multiple: true, allowClear: false, placeholder: gettext('Select to inherit from...')},
|
||||
options: this.fieldOptions.coll_inherits,
|
||||
options: this.fieldOptions.coll_inherits, visible: !this.inErd,
|
||||
optionsLoaded: (res)=>obj.inheritedTableList=res,
|
||||
disabled: (state)=>{
|
||||
if(state.adding_inherit_cols || state.is_partitioned){
|
||||
@@ -581,7 +612,18 @@ export default class TableSchema extends BaseUISchema {
|
||||
deps: ['typname', 'is_partitioned'],
|
||||
depChange: (state, source, topState, actionObj)=>{
|
||||
if(source[0] === 'columns') {
|
||||
obj.changeColumnOptions(state.columns);
|
||||
/* In ERD, attnum is an imp var for setting the links
|
||||
Here, attnum is set to max avail value.
|
||||
*/
|
||||
let columns = state.columns;
|
||||
if(actionObj.type === SCHEMA_STATE_ACTIONS.ADD_ROW && this.inErd) {
|
||||
let lastAttnum = _.maxBy(columns, (c)=>c.attnum)?.attnum;
|
||||
if(_.isUndefined(lastAttnum) || _.isNull(lastAttnum)) {
|
||||
lastAttnum = -1;
|
||||
}
|
||||
columns[columns.length-1].attnum = lastAttnum + 1;
|
||||
}
|
||||
obj.changeColumnOptions(columns);
|
||||
/* If primary key switch changes, primary key collection need to change */
|
||||
if(actionObj.path.indexOf('is_primary_key') > -1) {
|
||||
let tabColPath = _.slice(actionObj.path, 0, -1);
|
||||
@@ -631,6 +673,7 @@ export default class TableSchema extends BaseUISchema {
|
||||
},{
|
||||
id: 'typname', label: gettext('Of type'), type: 'select',
|
||||
mode: ['properties', 'create', 'edit'], group: 'advanced', deps: ['coll_inherits'],
|
||||
visible: !this.inErd,
|
||||
disabled: (state)=>{
|
||||
if(!obj.inSchemaWithModelCheck(state) && isEmptyString(state.coll_inherits)) {
|
||||
return false;
|
||||
@@ -743,6 +786,7 @@ export default class TableSchema extends BaseUISchema {
|
||||
type: 'nested-fieldset', label: gettext('Like'),
|
||||
group: 'advanced', mode: ['create'],
|
||||
schema: new LikeSchema(this.fieldOptions.like_relation),
|
||||
visible: !this.inErd,
|
||||
},{
|
||||
id: 'partition_type', label:gettext('Partition Type'),
|
||||
editable: false, type: 'select', controlProps: {allowClear: false},
|
||||
@@ -774,6 +818,7 @@ export default class TableSchema extends BaseUISchema {
|
||||
id: 'partition_keys', label:gettext('Partition Keys'),
|
||||
schema: obj.partitionKeysObj,
|
||||
editable: true, type: 'collection',
|
||||
columns: ['key_type', 'pt_column', 'expression'].concat(!this.inErd ? ['collationame', 'op_class'] : []),
|
||||
group: 'partition', mode: ['create'],
|
||||
deps: ['is_partitioned', 'partition_type', 'typname'],
|
||||
canEdit: false, canDelete: true,
|
||||
@@ -885,28 +930,32 @@ export default class TableSchema extends BaseUISchema {
|
||||
'</li></ul>',
|
||||
].join(''),
|
||||
min_version: 100000,
|
||||
},
|
||||
{
|
||||
},{
|
||||
type: 'group', id: 'parameters', label: gettext('Parameters'),
|
||||
visible: !this.inErd,
|
||||
},{
|
||||
// Here - we will create tab control for storage parameters
|
||||
// (auto vacuum).
|
||||
type: 'nested-tab', group: gettext('Parameters'),
|
||||
type: 'nested-tab', group: 'parameters',
|
||||
mode: ['edit', 'create'], deps: ['is_partitioned'],
|
||||
schema: this.vacuumSettingsSchema,
|
||||
schema: this.vacuumSettingsSchema, visible: !this.inErd,
|
||||
},{
|
||||
id: 'security_group', type: 'group', label: gettext('Security'), visible: !this.inErd,
|
||||
},
|
||||
{
|
||||
id: 'relacl_str', label: gettext('Privileges'), disabled: this.inCatalog,
|
||||
type: 'text', mode: ['properties'], group: gettext('Security'),
|
||||
type: 'text', mode: ['properties'], group: 'security_group',
|
||||
},
|
||||
{
|
||||
id: 'relacl', label: gettext('Privileges'), type: 'collection',
|
||||
group: gettext('Security'), schema: this.getPrivilegeRoleSchema(['a','r','w','d','D','x','t']),
|
||||
group: 'security_group', schema: this.getPrivilegeRoleSchema(['a','r','w','d','D','x','t']),
|
||||
mode: ['edit', 'create'], canAdd: true, canDelete: true,
|
||||
uniqueCol : ['grantee'],
|
||||
},{
|
||||
id: 'seclabels', label: gettext('Security labels'), canEdit: false,
|
||||
schema: new SecLabelSchema(), editable: false, canAdd: true,
|
||||
type: 'collection', min_version: 90100, mode: ['edit', 'create'],
|
||||
group: gettext('Security'), canDelete: true, control: 'unique-col-collection',
|
||||
group: 'security_group', canDelete: true, control: 'unique-col-collection',
|
||||
},{
|
||||
id: 'vacuum_settings_str', label: gettext('Storage settings'),
|
||||
type: 'multiline', group: 'advanced', mode: ['properties'],
|
||||
|
||||
@@ -1125,7 +1125,7 @@ class BaseTableView(PGChildNodeView, BasePartitionTable, VacuumSettings):
|
||||
elif part_type in data and data[part_type] == 'hash':
|
||||
partition_scheme = 'HASH ('
|
||||
|
||||
for row in data[part_keys]:
|
||||
for row in data.get(part_keys, []):
|
||||
if row['key_type'] == 'column':
|
||||
partition_scheme += self.qtIdent(
|
||||
self.conn, row['pt_column'])
|
||||
@@ -1141,7 +1141,7 @@ class BaseTableView(PGChildNodeView, BasePartitionTable, VacuumSettings):
|
||||
partition_scheme += row['expression'] + ', '
|
||||
|
||||
# Remove extra space and comma
|
||||
if len(data[part_keys]) > 0:
|
||||
if len(data.get(part_keys, [])) > 0:
|
||||
partition_scheme = partition_scheme[:-2]
|
||||
partition_scheme += ')'
|
||||
|
||||
|
||||
@@ -205,6 +205,13 @@ export default function FormView({
|
||||
|
||||
if(modeSupported) {
|
||||
let {group, CustomControl} = field;
|
||||
if(field.type === 'group') {
|
||||
groupLabels[field.id] = field.label;
|
||||
if(!visible) {
|
||||
schemaRef.current.filterGroups.push(field.label);
|
||||
}
|
||||
return;
|
||||
}
|
||||
group = groupLabels[group] || group || defaultTab;
|
||||
|
||||
if(!tabs[group]) tabs[group] = [];
|
||||
@@ -261,11 +268,6 @@ export default function FormView({
|
||||
} else {
|
||||
tabs[group].push(<DataGridView {...props}/>);
|
||||
}
|
||||
} else if(field.type === 'group') {
|
||||
groupLabels[field.id] = field.label;
|
||||
if(!visible) {
|
||||
schemaRef.current.filterGroups.push(field.label);
|
||||
}
|
||||
} else {
|
||||
/* Its a form control */
|
||||
const hasError = _.isEqual(accessPath.concat(field.id), stateUtils.formErr.name);
|
||||
|
||||
@@ -89,7 +89,8 @@ function isValueEqual(val1, val2) {
|
||||
/* If the orig value and new value are of different datatype but of same value(numeric) "no change" */
|
||||
/* If the orig value is undefined or null and new value is boolean false "no change" */
|
||||
if ((_.isEqual(val1, val2)
|
||||
|| ((val1 === null || _.isUndefined(val1)) && !val2)
|
||||
|| ((val1 === null || _.isUndefined(val1)) && val2 === '')
|
||||
|| ((val1 === null || _.isUndefined(val1)) && typeof(val2) === 'boolean' && !val2)
|
||||
|| (attrDefined ? _.isEqual(val1.toString(), val2.toString()) : false
|
||||
))) {
|
||||
return true;
|
||||
@@ -98,22 +99,6 @@ function isValueEqual(val1, val2) {
|
||||
}
|
||||
}
|
||||
|
||||
function objectComparator(obj1, obj2) {
|
||||
for(const key of _.union(Object.keys(obj1), Object.keys(obj2))) {
|
||||
let equal = isValueEqual(obj1[key], obj2[key]);
|
||||
if(equal) {
|
||||
continue;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const diffArrayOptions = {
|
||||
compareFunction: objectComparator,
|
||||
};
|
||||
|
||||
function getChangedData(topSchema, viewHelperProps, sessData, stringify=false, includeSkipChange=true) {
|
||||
let changedData = {};
|
||||
let isEdit = viewHelperProps.mode === 'edit';
|
||||
@@ -173,13 +158,13 @@ function getChangedData(topSchema, viewHelperProps, sessData, stringify=false, i
|
||||
);
|
||||
change = {};
|
||||
if(changeDiff.added.length > 0) {
|
||||
change['added'] = cleanCid(changeDiff.added);
|
||||
change['added'] = cleanCid(changeDiff.added, viewHelperProps.keepCid);
|
||||
}
|
||||
if(changeDiff.removed.length > 0) {
|
||||
change['deleted'] = cleanCid(changeDiff.removed.map((row)=>{
|
||||
/* Deleted records should be original, not the changed */
|
||||
return _.find(_.get(origVal, field.id), ['cid', row.cid]);
|
||||
}));
|
||||
}), viewHelperProps.keepCid);
|
||||
}
|
||||
if(changeDiff.updated.length > 0) {
|
||||
/* There is change in collection. Parse further to go deep */
|
||||
@@ -204,7 +189,7 @@ function getChangedData(topSchema, viewHelperProps, sessData, stringify=false, i
|
||||
});
|
||||
}
|
||||
}
|
||||
change['changed'] = cleanCid(change['changed']);
|
||||
change['changed'] = cleanCid(change['changed'], viewHelperProps.keepCid);
|
||||
}
|
||||
if(Object.keys(change).length > 0) {
|
||||
attrChanged(field.id, change, true);
|
||||
@@ -214,21 +199,18 @@ function getChangedData(topSchema, viewHelperProps, sessData, stringify=false, i
|
||||
}
|
||||
} else if(!isEdit) {
|
||||
if(field.type === 'collection') {
|
||||
/* For fixed rows, check the updated changes */
|
||||
if(!_.isUndefined(field.fixedRows)) {
|
||||
const changeDiff = diffArray(
|
||||
_.get(origVal, field.id) || [],
|
||||
_.get(sessVal, field.id) || [],
|
||||
'cid',
|
||||
diffArrayOptions
|
||||
);
|
||||
if(changeDiff.updated.length > 0) {
|
||||
let change = cleanCid(_.get(sessVal, field.id));
|
||||
attrChanged(field.id, change, true);
|
||||
}
|
||||
} else {
|
||||
let change = cleanCid(_.get(sessVal, field.id));
|
||||
attrChanged(field.id, change);
|
||||
const changeDiff = diffArray(
|
||||
_.get(origVal, field.id) || [],
|
||||
_.get(sessVal, field.id) || [],
|
||||
'cid',
|
||||
);
|
||||
/* For fixed rows, check only the updated changes */
|
||||
if((!_.isUndefined(field.fixedRows) && changeDiff.updated.length > 0)
|
||||
|| (_.isUndefined(field.fixedRows) && (
|
||||
changeDiff.added.length > 0 || changeDiff.removed.length > 0 || changeDiff.updated.length > 0
|
||||
))) {
|
||||
let change = cleanCid(_.get(sessVal, field.id), viewHelperProps.keepCid);
|
||||
attrChanged(field.id, change, true);
|
||||
}
|
||||
} else {
|
||||
attrChanged(field.id);
|
||||
@@ -409,18 +391,18 @@ const sessDataReducer = (state, action)=>{
|
||||
};
|
||||
|
||||
/* Remove cid key added by prepareData */
|
||||
function cleanCid(coll) {
|
||||
if(!coll) {
|
||||
function cleanCid(coll, keepCid=false) {
|
||||
if(!coll || keepCid) {
|
||||
return coll;
|
||||
}
|
||||
return coll.map((o)=>_.pickBy(o, (v, k)=>k!='cid'));
|
||||
}
|
||||
|
||||
function prepareData(val) {
|
||||
function prepareData(val, createMode=false) {
|
||||
if(_.isPlainObject(val)) {
|
||||
_.forIn(val, function (el) {
|
||||
if (_.isObject(el)) {
|
||||
prepareData(el);
|
||||
prepareData(el, createMode);
|
||||
}
|
||||
});
|
||||
} else if(_.isArray(val)) {
|
||||
@@ -432,8 +414,8 @@ function prepareData(val) {
|
||||
So to decide whether row is new or not set, the cid starts with
|
||||
nn (not new) for existing rows. Newly added will start with 'c' (created)
|
||||
*/
|
||||
el['cid'] = _.uniqueId('nn');
|
||||
prepareData(el);
|
||||
el['cid'] = createMode ? _.uniqueId('c') : _.uniqueId('nn');
|
||||
prepareData(el, createMode);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -532,7 +514,7 @@ function SchemaDialogView({
|
||||
});
|
||||
} else {
|
||||
/* Use the defaults as the initital data */
|
||||
schema.origData = prepareData(schema.defaults);
|
||||
schema.origData = prepareData(schema.defaults, true);
|
||||
schema.initialise(schema.origData);
|
||||
sessDispatch({
|
||||
type: SCHEMA_STATE_ACTIONS.INIT,
|
||||
@@ -624,6 +606,7 @@ function SchemaDialogView({
|
||||
);
|
||||
}
|
||||
}).catch((err)=>{
|
||||
console.error(err);
|
||||
setFormErr({
|
||||
name: 'apierror',
|
||||
message: parseApiError(err),
|
||||
|
||||
@@ -106,23 +106,6 @@ basicSettings = createMuiTheme(basicSettings, {
|
||||
paddingRight: basicSettings.spacing(1.5),
|
||||
}
|
||||
},
|
||||
MuiToggleButton: {
|
||||
root: {
|
||||
textTransform: 'none,',
|
||||
padding: basicSettings.spacing(0.5, 2.5, 0.5, 0.5),
|
||||
color: 'abc',
|
||||
'&:hover':{
|
||||
backgroundColor: 'abc',
|
||||
},
|
||||
'&$selected': {
|
||||
color: 'abc',
|
||||
backgroundColor: 'abc',
|
||||
'&:hover':{
|
||||
backgroundColor: 'abc',
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiAccordion: {
|
||||
root: {
|
||||
boxShadow: 'none',
|
||||
@@ -172,6 +155,17 @@ basicSettings = createMuiTheme(basicSettings, {
|
||||
body1: {
|
||||
fontSize: '1em',
|
||||
}
|
||||
},
|
||||
MuiDialog: {
|
||||
paper: {
|
||||
margin: 0,
|
||||
}
|
||||
},
|
||||
MuiTooltip: {
|
||||
popper: {
|
||||
top: 0,
|
||||
zIndex: 9999,
|
||||
}
|
||||
}
|
||||
},
|
||||
transitions: {
|
||||
@@ -374,6 +368,7 @@ function getFinalTheme(baseTheme) {
|
||||
},
|
||||
MuiToggleButton: {
|
||||
root: {
|
||||
padding: 'abc',
|
||||
paddingRight: baseTheme.spacing(2.5),
|
||||
paddingLeft: baseTheme.spacing(0.5),
|
||||
color: 'abc',
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
}
|
||||
|
||||
& .ajs-handle {
|
||||
z-index: 5;
|
||||
z-index: 1020;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import ERDCore from 'pgadmin.tools.erd/erd_tool/ERDCore';
|
||||
import * as createEngineLib from '@projectstorm/react-diagrams';
|
||||
import TEST_TABLES_DATA from './test_tables';
|
||||
import { FakeLink, FakeNode } from './fake_item';
|
||||
|
||||
describe('ERDCore', ()=>{
|
||||
let eleFactory = jasmine.createSpyObj('nodeFactories', {
|
||||
@@ -132,7 +133,8 @@ describe('ERDCore', ()=>{
|
||||
});
|
||||
|
||||
it('addNode', ()=>{
|
||||
let newNode = jasmine.createSpyObj('newNode', ['setPosition']);
|
||||
let newNode = new FakeNode({});
|
||||
spyOn(newNode, 'setPosition');
|
||||
spyOn(erdCoreObj, 'getNewNode').and.returnValue(newNode);
|
||||
spyOn(erdCoreObj, 'clearSelection');
|
||||
|
||||
@@ -151,33 +153,17 @@ describe('ERDCore', ()=>{
|
||||
|
||||
|
||||
it('addLink', ()=>{
|
||||
let node1 = new FakeNode({'name': 'table1'}, 'id1');
|
||||
let node2 = new FakeNode({'name': 'table2'}, 'id2');
|
||||
spyOn(node1, 'addPort').and.callThrough();
|
||||
spyOn(node2, 'addPort').and.callThrough();
|
||||
let nodesDict = {
|
||||
'id1': {
|
||||
serializeData: function(){ return {
|
||||
'name': 'table1',
|
||||
};},
|
||||
getPortName: function(attnum) {
|
||||
return `port-${attnum}`;
|
||||
},
|
||||
getPort: function() {
|
||||
return null;
|
||||
},
|
||||
addPort: jasmine.createSpy('addPort').and.callFake((obj)=>obj),
|
||||
},
|
||||
'id2': {
|
||||
serializeData: function(){ return {
|
||||
'name': 'table2',
|
||||
};},
|
||||
getPortName: function(attnum) {
|
||||
return `port-${attnum}`;
|
||||
},
|
||||
getPort: function() {
|
||||
return null;
|
||||
},
|
||||
addPort: jasmine.createSpy('addPort').and.callFake((obj)=>obj),
|
||||
},
|
||||
'id1': node1,
|
||||
'id2': node2,
|
||||
};
|
||||
let link = jasmine.createSpyObj('link', ['setSourcePort', 'setTargetPort']);
|
||||
let link = new FakeLink();
|
||||
spyOn(link, 'setSourcePort').and.callThrough();
|
||||
spyOn(link, 'setTargetPort').and.callThrough();
|
||||
spyOn(erdEngine.getModel(), 'getNodesDict').and.returnValue(nodesDict);
|
||||
spyOn(erdCoreObj, 'getNewLink').and.callFake(function() {
|
||||
return link;
|
||||
@@ -199,7 +185,6 @@ describe('ERDCore', ()=>{
|
||||
expect(nodesDict['id2'].addPort).toHaveBeenCalledWith({name: 'port-3'});
|
||||
expect(link.setSourcePort).toHaveBeenCalledWith({name: 'port-1'});
|
||||
expect(link.setTargetPort).toHaveBeenCalledWith({name: 'port-3'});
|
||||
|
||||
});
|
||||
|
||||
it('serialize', ()=>{
|
||||
@@ -222,41 +207,26 @@ describe('ERDCore', ()=>{
|
||||
});
|
||||
|
||||
it('serializeData', ()=>{
|
||||
spyOn(erdEngine.getModel(), 'getNodesDict').and.returnValue({
|
||||
'id1': {
|
||||
serializeData: function(){ return {
|
||||
'name': 'table1',
|
||||
};},
|
||||
},
|
||||
'id2': {
|
||||
serializeData: function(){ return {
|
||||
'name': 'table2',
|
||||
};},
|
||||
},
|
||||
});
|
||||
let node1 = new FakeNode({'name': 'table1'}, 'id1');
|
||||
let node2 = new FakeNode({'name': 'table2'}, 'id2');
|
||||
let nodesDict = {
|
||||
'id1': node1,
|
||||
'id2': node2,
|
||||
};
|
||||
spyOn(erdEngine.getModel(), 'getNodesDict').and.returnValue(nodesDict);
|
||||
spyOn(erdEngine.getModel(), 'getLinks').and.returnValue([
|
||||
{
|
||||
serializeData: function(){ return {
|
||||
'name': 'link1',
|
||||
};},
|
||||
getID: function(){ return 'lid1'; },
|
||||
},
|
||||
{
|
||||
serializeData: function(){ return {
|
||||
'name': 'link2',
|
||||
};},
|
||||
getID: function(){ return 'lid2'; },
|
||||
},
|
||||
new FakeLink({
|
||||
'name': 'link1',
|
||||
}, 'lid1'),
|
||||
new FakeLink({
|
||||
'name': 'link2',
|
||||
}, 'lid2'),
|
||||
]);
|
||||
expect(JSON.stringify(erdCoreObj.serializeData())).toEqual(JSON.stringify({
|
||||
nodes: {
|
||||
'id1': {'name': 'table1'},
|
||||
'id2': {'name': 'table2'},
|
||||
},
|
||||
links: {
|
||||
'lid1': {'name': 'link1'},
|
||||
'lid2': {'name': 'link2'},
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -276,6 +246,9 @@ describe('ERDCore', ()=>{
|
||||
addPort: function() {
|
||||
|
||||
},
|
||||
getData: function() {
|
||||
return table;
|
||||
}
|
||||
};
|
||||
});
|
||||
spyOn(erdEngine.getModel(), 'getNodesDict').and.returnValue(nodesDict);
|
||||
@@ -288,11 +261,7 @@ describe('ERDCore', ()=>{
|
||||
});
|
||||
spyOn(erdCoreObj, 'getNewPort').and.returnValue({id: 'id'});
|
||||
spyOn(erdCoreObj, 'addNode').and.callFake(function(data) {
|
||||
return {
|
||||
getID: function() {
|
||||
return `id-${data.name}`;
|
||||
},
|
||||
};
|
||||
return new FakeNode({}, `id-${data.name}`);
|
||||
});
|
||||
spyOn(erdCoreObj, 'addLink');
|
||||
spyOn(erdCoreObj, 'dagreDistributeNodes');
|
||||
@@ -319,8 +288,8 @@ describe('ERDCore', ()=>{
|
||||
|
||||
it('getNodesData', ()=>{
|
||||
spyOn(erdEngine.getModel(), 'getNodes').and.returnValue([
|
||||
{getData: function () {return {name:'node1'};}},
|
||||
{getData: function () {return {name:'node2'};}},
|
||||
new FakeNode({name:'node1'}),
|
||||
new FakeNode({name:'node2'}),
|
||||
]);
|
||||
expect(JSON.stringify(erdCoreObj.getNodesData())).toEqual(JSON.stringify([
|
||||
{name:'node1'}, {name:'node2'},
|
||||
|
||||
42
web/regression/javascript/erd/fake_item.js
Normal file
42
web/regression/javascript/erd/fake_item.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
export class FakeNode {
|
||||
constructor(data, id='nid1') {
|
||||
this.data = data || {};
|
||||
this.id = id;
|
||||
}
|
||||
setSelected() {}
|
||||
getColumns() {return this.data.columns;}
|
||||
getID() {return this.id;}
|
||||
setData(data) {this.data = data;}
|
||||
getData() {return this.data;}
|
||||
getPosition() {return {x: 30, y: 30};}
|
||||
setPosition() {}
|
||||
serializeData() {return this.getData();}
|
||||
getPortName(attnum) {return `port-${attnum}`;}
|
||||
getPort() {return null;}
|
||||
addPort(obj) {return obj;}
|
||||
getColumnAt(pos) {return _.find(this.getColumns()||[], (c)=>c.attnum==pos);}
|
||||
remove() {}
|
||||
getSchemaTableName() {return [this.data.schema, this.data.name];}
|
||||
cloneData(tabName) {
|
||||
let retVal = {...this.data};
|
||||
retVal.name = tabName;
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
|
||||
export class FakeLink {
|
||||
constructor(data, id='lid1') {
|
||||
this.data = data;
|
||||
this.id = id;
|
||||
}
|
||||
setSelected() {}
|
||||
getID() {return this.id;}
|
||||
getData() {return this.data;}
|
||||
getSourcePort() {return {remove: ()=>{}};}
|
||||
setSourcePort() {}
|
||||
getTargetPort() {return {remove: ()=>{}};}
|
||||
setTargetPort() {}
|
||||
remove() {}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import * as erdModule from 'pgadmin.tools.erd/erd_module';
|
||||
import erdPref from './erd_preferences';
|
||||
import BodyWidget from 'pgadmin.tools.erd/erd_tool/ui_components/BodyWidget';
|
||||
import * as ERDSqlTool from 'tools/datagrid/static/js/show_query_tool';
|
||||
import { FakeLink, FakeNode } from '../fake_item';
|
||||
|
||||
let pgAdmin = {
|
||||
Browser: {
|
||||
@@ -60,7 +61,7 @@ let mtmDialog = jasmine.createSpyObj('mtmDialog', ['show']);
|
||||
|
||||
let getDialog = (dialogName)=>{
|
||||
switch(dialogName) {
|
||||
case 'entity_dialog': return tableDialog;
|
||||
case 'table_dialog': return tableDialog;
|
||||
case 'onetomany_dialog': return otmDialog;
|
||||
case 'manytomany_dialog': return mtmDialog;
|
||||
}
|
||||
@@ -93,19 +94,16 @@ describe('ERD BodyWidget', ()=>{
|
||||
title: 'postgres/postgres@PostgreSQL 12',
|
||||
trans_id: 110008,
|
||||
};
|
||||
let newNode = new FakeNode({
|
||||
columns: [{attnum: 0}, {attnum: 1}],
|
||||
}, 'newid1');
|
||||
|
||||
beforeAll(()=>{
|
||||
spyOn(erdModule, 'setPanelTitle');
|
||||
spyOn(ERDCore.prototype, 'repaint');
|
||||
spyOn(ERDCore.prototype, 'deserializeData');
|
||||
spyOn(ERDCore.prototype, 'addNode').and.returnValue({
|
||||
setSelected: ()=>{},
|
||||
getColumns: ()=>([{attnum: 0}, {attnum: 1}]),
|
||||
getID: ()=>'newid1',
|
||||
});
|
||||
spyOn(ERDCore.prototype, 'addLink').and.returnValue({
|
||||
setSelected: ()=>{},
|
||||
});
|
||||
spyOn(ERDCore.prototype, 'addNode').and.returnValue(newNode);
|
||||
spyOn(ERDCore.prototype, 'addLink').and.returnValue(new FakeLink());
|
||||
spyOn(alertify, 'confirm').and.callFake((arg1, arg2, okCallback)=>{
|
||||
okCallback();
|
||||
});
|
||||
@@ -128,7 +126,7 @@ describe('ERD BodyWidget', ()=>{
|
||||
|
||||
beforeEach(()=>{
|
||||
jasmineEnzyme();
|
||||
body = mount(<BodyWidget params={params} pgAdmin={pgAdmin} pgWindow={pgWindow} getDialog={getDialog} transformToSupported={()=>{}} alertify={alertify}/>);
|
||||
body = mount(<BodyWidget params={params} pgAdmin={pgAdmin} pgWindow={pgWindow} getDialog={getDialog} alertify={alertify}/>);
|
||||
bodyInstance = body.instance();
|
||||
});
|
||||
|
||||
@@ -225,18 +223,18 @@ describe('ERD BodyWidget', ()=>{
|
||||
});
|
||||
});
|
||||
|
||||
it('event editNode', (done)=>{
|
||||
it('event editTable', (done)=>{
|
||||
let node = {key: 'value', getNote: ()=>'a note'};
|
||||
spyOn(bodyInstance, 'addEditNode');
|
||||
bodyInstance.diagram.fireEvent({node: node}, 'editNode', true);
|
||||
spyOn(bodyInstance, 'addEditTable');
|
||||
bodyInstance.diagram.fireEvent({node: node}, 'editTable', true);
|
||||
setTimeout(()=>{
|
||||
expect(bodyInstance.addEditNode).toHaveBeenCalledWith(node);
|
||||
expect(bodyInstance.addEditTable).toHaveBeenCalledWith(node);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('getDialog', ()=>{
|
||||
bodyInstance.getDialog('entity_dialog')();
|
||||
bodyInstance.getDialog('table_dialog')();
|
||||
expect(tableDialog.show).toHaveBeenCalled();
|
||||
|
||||
bodyInstance.getDialog('onetomany_dialog')();
|
||||
@@ -246,10 +244,20 @@ describe('ERD BodyWidget', ()=>{
|
||||
expect(mtmDialog.show).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('addEditNode', ()=>{
|
||||
it('addEditTable', ()=>{
|
||||
let node1 = new FakeNode({'name': 'table1', schema: 'erd1', columns: [{name: 'col1', type: 'type1', attnum: 1}]}, 'id1');
|
||||
let node2 = new FakeNode({'name': 'table2', schema: 'erd2', columns: [{name: 'col2', type: 'type2', attnum: 2}]}, 'id2');
|
||||
let nodesDict = {
|
||||
'id1': node1,
|
||||
'id2': node2,
|
||||
};
|
||||
spyOn(bodyInstance.diagram, 'getModel').and.returnValue({
|
||||
'getNodesDict': ()=>nodesDict,
|
||||
});
|
||||
spyOn(bodyInstance.diagram, 'addLink');
|
||||
/* New */
|
||||
tableDialog.show.calls.reset();
|
||||
bodyInstance.addEditNode();
|
||||
bodyInstance.addEditTable();
|
||||
expect(tableDialog.show).toHaveBeenCalled();
|
||||
|
||||
let saveCallback = tableDialog.show.calls.mostRecent().args[7];
|
||||
@@ -259,12 +267,9 @@ describe('ERD BodyWidget', ()=>{
|
||||
|
||||
/* Existing */
|
||||
tableDialog.show.calls.reset();
|
||||
let node = jasmine.createSpyObj('node',{
|
||||
getSchemaTableName: ['erd1', 'table1'],
|
||||
setData: null,
|
||||
getData: null,
|
||||
});
|
||||
bodyInstance.addEditNode(node);
|
||||
let node = new FakeNode({name: 'table1', schema: 'erd1'});
|
||||
spyOn(node, 'setData');
|
||||
bodyInstance.addEditTable(node);
|
||||
expect(tableDialog.show).toHaveBeenCalled();
|
||||
|
||||
saveCallback = tableDialog.show.calls.mostRecent().args[7];
|
||||
@@ -273,49 +278,44 @@ describe('ERD BodyWidget', ()=>{
|
||||
expect(node.setData).toHaveBeenCalledWith(newData);
|
||||
});
|
||||
|
||||
it('onEditNode', ()=>{
|
||||
it('onEditTable', ()=>{
|
||||
let node = {key: 'value'};
|
||||
spyOn(bodyInstance, 'addEditNode');
|
||||
spyOn(bodyInstance, 'addEditTable');
|
||||
spyOn(bodyInstance.diagram, 'getSelectedNodes').and.returnValue([node]);
|
||||
bodyInstance.onEditNode();
|
||||
expect(bodyInstance.addEditNode).toHaveBeenCalledWith(node);
|
||||
bodyInstance.onEditTable();
|
||||
expect(bodyInstance.addEditTable).toHaveBeenCalledWith(node);
|
||||
});
|
||||
|
||||
it('onAddNewNode', ()=>{
|
||||
spyOn(bodyInstance, 'addEditNode');
|
||||
spyOn(bodyInstance, 'addEditTable');
|
||||
bodyInstance.onAddNewNode();
|
||||
expect(bodyInstance.addEditNode).toHaveBeenCalled();
|
||||
expect(bodyInstance.addEditTable).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('onCloneNode', ()=>{
|
||||
let node = jasmine.createSpyObj('node',{
|
||||
getSchemaTableName: ['erd1', 'table1'],
|
||||
setData: null,
|
||||
getData: null,
|
||||
cloneData: {key: 'value'},
|
||||
getPosition: {x: 30, y: 30},
|
||||
});
|
||||
let node = new FakeNode({name: 'table1', schema: 'erd1'});
|
||||
spyOn(bodyInstance.diagram, 'getSelectedNodes').and.returnValue([node]);
|
||||
spyOn(bodyInstance.diagram, 'getNextTableName').and.returnValue('newtable1');
|
||||
bodyInstance.diagram.addNode.calls.reset();
|
||||
bodyInstance.onCloneNode();
|
||||
expect(bodyInstance.diagram.addNode).toHaveBeenCalledWith({key: 'value'}, [50, 50]);
|
||||
let cloneArgs = bodyInstance.diagram.addNode.calls.argsFor(0);
|
||||
expect(cloneArgs[0]).toEqual(jasmine.objectContaining({
|
||||
name: 'newtable1',
|
||||
schema: 'erd1',
|
||||
}));
|
||||
expect(cloneArgs[1]).toEqual([50, 50]);
|
||||
});
|
||||
|
||||
it('onDeleteNode', (done)=>{
|
||||
let node = jasmine.createSpyObj('node',{
|
||||
getSchemaTableName: ['erd1', 'table1'],
|
||||
setData: null,
|
||||
getData: null,
|
||||
cloneData: {key: 'value'},
|
||||
getPosition: {x: 30, y: 30},
|
||||
remove: null,
|
||||
setSelected: null,
|
||||
});
|
||||
let link = jasmine.createSpyObj('link', {
|
||||
remove: null,
|
||||
setSelected: null,
|
||||
getTargetPort: jasmine.createSpyObj('port', ['remove']),
|
||||
getSourcePort: jasmine.createSpyObj('port', ['remove']),
|
||||
let node = new FakeNode({name: 'table1', schema: 'erd1'});
|
||||
spyOn(node, 'remove');
|
||||
let link = new FakeLink({local_table_uid: 'tid1'});
|
||||
spyOn(link, 'remove');
|
||||
let nodesDict = {
|
||||
'tid1': node
|
||||
};
|
||||
spyOn(bodyInstance.diagram, 'getModel').and.returnValue({
|
||||
'getNodesDict': ()=>nodesDict,
|
||||
});
|
||||
spyOn(bodyInstance.diagram, 'getSelectedNodes').and.returnValue([node]);
|
||||
spyOn(bodyInstance.diagram, 'getSelectedLinks').and.returnValue([link]);
|
||||
@@ -413,9 +413,17 @@ describe('ERD BodyWidget', ()=>{
|
||||
});
|
||||
|
||||
it('onOneToManyClick', ()=>{
|
||||
let node = jasmine.createSpyObj('node',{
|
||||
getID: 'id1',
|
||||
let node = new FakeNode({}, 'id1');
|
||||
let node1 = new FakeNode({'name': 'table1', schema: 'erd1', columns: [{name: 'col1', type: 'type1', attnum: 1}]}, 'id1');
|
||||
let node2 = new FakeNode({'name': 'table2', schema: 'erd2', columns: [{name: 'col2', type: 'type2', attnum: 2}]}, 'id2');
|
||||
let nodesDict = {
|
||||
'id1': node1,
|
||||
'id2': node2,
|
||||
};
|
||||
spyOn(bodyInstance.diagram, 'getModel').and.returnValue({
|
||||
'getNodesDict': ()=>nodesDict,
|
||||
});
|
||||
spyOn(bodyInstance.diagram, 'addLink');
|
||||
spyOn(bodyInstance.diagram, 'getSelectedNodes').and.returnValue([node]);
|
||||
|
||||
otmDialog.show.calls.reset();
|
||||
@@ -423,15 +431,18 @@ describe('ERD BodyWidget', ()=>{
|
||||
expect(otmDialog.show).toHaveBeenCalled();
|
||||
|
||||
let saveCallback = otmDialog.show.calls.mostRecent().args[4];
|
||||
let newData = {key: 'value'};
|
||||
let newData = {
|
||||
local_table_uid: 'id1',
|
||||
local_column_attnum: 1,
|
||||
referenced_table_uid: 'id2',
|
||||
referenced_column_attnum: 2,
|
||||
};
|
||||
saveCallback(newData);
|
||||
expect(bodyInstance.diagram.addLink).toHaveBeenCalledWith(newData, 'onetomany');
|
||||
});
|
||||
|
||||
it('onManyToManyClick', ()=>{
|
||||
let node = jasmine.createSpyObj('node',{
|
||||
getID: 'id1',
|
||||
});
|
||||
let node = new FakeNode({}, 'id1');
|
||||
spyOn(bodyInstance.diagram, 'getSelectedNodes').and.returnValue([node]);
|
||||
|
||||
mtmDialog.show.calls.reset();
|
||||
@@ -439,19 +450,12 @@ describe('ERD BodyWidget', ()=>{
|
||||
expect(mtmDialog.show).toHaveBeenCalled();
|
||||
|
||||
/* onSave */
|
||||
let node1 = new FakeNode({'name': 'table1', schema: 'erd1', columns: [{name: 'col1', type: 'type1', attnum: 1}]}, 'id1');
|
||||
let node2 = new FakeNode({'name': 'table2', schema: 'erd2', columns: [{name: 'col2', type: 'type2', attnum: 2}]}, 'id2');
|
||||
let nodesDict = {
|
||||
'id1': {
|
||||
getID: ()=>'id1',
|
||||
getData: ()=>({name: 'table1', schema: 'erd1'}),
|
||||
getColumnAt: ()=>({name: 'col1', type: 'type1', attnum: 0}),
|
||||
addPort: jasmine.createSpy('addPort').and.callFake((obj)=>obj),
|
||||
},
|
||||
'id2': {
|
||||
getID: ()=>'id2',
|
||||
getData: ()=>({name: 'table2', schema: 'erd2'}),
|
||||
getColumnAt: ()=>({name: 'col2', type: 'type2', attnum: 1}),
|
||||
addPort: jasmine.createSpy('addPort').and.callFake((obj)=>obj),
|
||||
},
|
||||
'id1': node1,
|
||||
'id2': node2,
|
||||
'newid1': newNode,
|
||||
};
|
||||
spyOn(bodyInstance.diagram, 'getModel').and.returnValue({
|
||||
'getNodesDict': ()=>nodesDict,
|
||||
@@ -468,24 +472,21 @@ describe('ERD BodyWidget', ()=>{
|
||||
bodyInstance.diagram.addNode.calls.reset();
|
||||
bodyInstance.diagram.addLink.calls.reset();
|
||||
saveCallback(newData);
|
||||
expect(bodyInstance.diagram.addNode).toHaveBeenCalledWith({
|
||||
let tableData = bodyInstance.diagram.addNode.calls.argsFor(0)[0];
|
||||
expect(tableData).toEqual(jasmine.objectContaining({
|
||||
name: 'table1_table2',
|
||||
schema: 'erd1',
|
||||
columns: [
|
||||
{
|
||||
type: 'type1',
|
||||
name: 'table1_col1',
|
||||
is_primary_key: false,
|
||||
attnum: 0,
|
||||
},
|
||||
{
|
||||
type: 'type2',
|
||||
name: 'table2_col2',
|
||||
is_primary_key: false,
|
||||
attnum: 1,
|
||||
},
|
||||
],
|
||||
});
|
||||
}));
|
||||
expect(tableData.columns[0]).toEqual(jasmine.objectContaining({
|
||||
type: 'type1',
|
||||
name: 'table1_col1',
|
||||
attnum: 0,
|
||||
}));
|
||||
expect(tableData.columns[1]).toEqual(jasmine.objectContaining({
|
||||
type: 'type2',
|
||||
name: 'table2_col2',
|
||||
attnum: 1,
|
||||
}));
|
||||
|
||||
let linkData = {
|
||||
local_table_uid: 'newid1',
|
||||
|
||||
@@ -202,13 +202,13 @@ describe('ColumnSchema', ()=>{
|
||||
expect(schemaObj.inSchemaWithColumnCheck(state)).toBe(true);
|
||||
|
||||
schemaObj.nodeInfo = {};
|
||||
expect(schemaObj.inSchemaWithColumnCheck(state)).toBe(true);
|
||||
expect(schemaObj.inSchemaWithColumnCheck(state)).toBe(false);
|
||||
});
|
||||
|
||||
it('editableCheckForTable', ()=>{
|
||||
let state = {};
|
||||
schemaObj.nodeInfo = {};
|
||||
expect(schemaObj.editableCheckForTable(state)).toBe(false);
|
||||
expect(schemaObj.editableCheckForTable(state)).toBe(true);
|
||||
});
|
||||
|
||||
it('depChange', ()=>{
|
||||
|
||||
@@ -1464,50 +1464,50 @@
|
||||
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.9.0.tgz#32e63212293dd3efbb521cd35a5020ab66eaa546"
|
||||
integrity sha512-wjtKehFAIARq2OxK8j3JrggNlEslJfNuSm2ArteIbKyRMts2g0a7KzTxfRVNUM+O0gnBJ2hNV8nWPOYBgI1sew==
|
||||
|
||||
"@projectstorm/geometry@^6.5.2":
|
||||
version "6.5.2"
|
||||
resolved "https://registry.yarnpkg.com/@projectstorm/geometry/-/geometry-6.5.2.tgz#76ccc7280a49c64953036aa96287f408981e5913"
|
||||
integrity sha512-PGrcMXr6CkdH1DvcY+MDYzg6suWnOXehP8S69DhiEeWUFmQhKJSCAPEovdQOpohL6urlKaSNbDM8auUypFbTfw==
|
||||
"@projectstorm/geometry@^6.6.1":
|
||||
version "6.6.1"
|
||||
resolved "https://registry.yarnpkg.com/@projectstorm/geometry/-/geometry-6.6.1.tgz#4a42f5c8fdfcc3d951e73f5db7fe9546514acc3d"
|
||||
integrity sha512-gWRkv+fm+VIpoffHzDHPmGYlEqx8xWGfE/JR7TXAZweNdjEIxyhT++hVlCJiFJS+/cGqgN3z+eP7PNjwZUPGRg==
|
||||
|
||||
"@projectstorm/react-canvas-core@^6.5.2":
|
||||
version "6.5.2"
|
||||
resolved "https://registry.yarnpkg.com/@projectstorm/react-canvas-core/-/react-canvas-core-6.5.2.tgz#65726df39c15487d9c6b23616cfa6b182d82fcbe"
|
||||
integrity sha512-tFfWpaPZ71lMDFRL69jRFTjmYit6FKNpkAiI9sqYeH2I4Y7OjuxmCqYomlzh8ZSLVwKHtcI6Vd7hhoatEBupEQ==
|
||||
"@projectstorm/react-canvas-core@^6.6.1":
|
||||
version "6.6.1"
|
||||
resolved "https://registry.yarnpkg.com/@projectstorm/react-canvas-core/-/react-canvas-core-6.6.1.tgz#93cc6e70e986fe620b6fad6b597d3aa038075244"
|
||||
integrity sha512-wAxEh4Wja2Au0QAuLqxJNaWpVxYIffTFUZhjH8wtW8MKCWS6W9RnP6upeC8QVQi29NS59UIX4wXNzb6e6ht5ww==
|
||||
dependencies:
|
||||
"@projectstorm/geometry" "^6.5.2"
|
||||
"@projectstorm/geometry" "^6.6.1"
|
||||
|
||||
"@projectstorm/react-diagrams-core@^6.5.2":
|
||||
version "6.5.2"
|
||||
resolved "https://registry.yarnpkg.com/@projectstorm/react-diagrams-core/-/react-diagrams-core-6.5.2.tgz#4c8c92ed288b2934de11c1a52fda72249b151932"
|
||||
integrity sha512-7ganJGs7lcRB1sTvUs+MQ9RiSCGD+IHgISo45aS81+XFllO8u7uZRaPdIyObhz+khwZvMk2pWmXYPNJwGThOZQ==
|
||||
"@projectstorm/react-diagrams-core@^6.6.1":
|
||||
version "6.6.1"
|
||||
resolved "https://registry.yarnpkg.com/@projectstorm/react-diagrams-core/-/react-diagrams-core-6.6.1.tgz#f626c6253dd6f4b04c3256976b55377ffac2b1bb"
|
||||
integrity sha512-TiDwpcH+t2b2tG/UHDQvhlyx3gOQRJXxTyNDo7p+430ikTrvz1f8uNe5Rt3SrzyqxeUazLFXYBgbGpEanqOykQ==
|
||||
dependencies:
|
||||
"@projectstorm/geometry" "^6.5.2"
|
||||
"@projectstorm/react-canvas-core" "^6.5.2"
|
||||
"@projectstorm/geometry" "^6.6.1"
|
||||
"@projectstorm/react-canvas-core" "^6.6.1"
|
||||
|
||||
"@projectstorm/react-diagrams-defaults@^6.5.2":
|
||||
version "6.5.2"
|
||||
resolved "https://registry.yarnpkg.com/@projectstorm/react-diagrams-defaults/-/react-diagrams-defaults-6.5.2.tgz#084138e8b2ac2643bcfa115fb543f69c0eaf88f3"
|
||||
integrity sha512-8+hAe88Lj7uBsugAT7f5SsjqxmjYfeBYwViaFpnIilmuYF/uXN0w/ns8Mcq2FE49wGOXtBoxFusoWmDWwgE+fQ==
|
||||
"@projectstorm/react-diagrams-defaults@^6.6.1":
|
||||
version "6.6.1"
|
||||
resolved "https://registry.yarnpkg.com/@projectstorm/react-diagrams-defaults/-/react-diagrams-defaults-6.6.1.tgz#a97cdda56d7336b84775f76a925b90286ea2833d"
|
||||
integrity sha512-FJu8BNBjvANVZ8N99WXS/f6Mu5/yMC4Pi55kTG3vq7o14tsVMcghosmxst5eoeL251O4I+ulNvQ/yCc1Mc5org==
|
||||
dependencies:
|
||||
"@projectstorm/react-diagrams-core" "^6.5.2"
|
||||
"@projectstorm/react-diagrams-core" "^6.6.1"
|
||||
|
||||
"@projectstorm/react-diagrams-routing@^6.5.2":
|
||||
version "6.5.2"
|
||||
resolved "https://registry.yarnpkg.com/@projectstorm/react-diagrams-routing/-/react-diagrams-routing-6.5.2.tgz#824dadbcd8c14c647829bdd4761afef69dec5622"
|
||||
integrity sha512-pWsDmk/4hbx6Ii2Z3Uvd9dZ/xB9F7u66mGVGMWb3txxTT68Y6mwdVJRQFu/frm/Sk3sZv7gNf1EqadsilIh7vg==
|
||||
"@projectstorm/react-diagrams-routing@^6.6.1":
|
||||
version "6.6.1"
|
||||
resolved "https://registry.yarnpkg.com/@projectstorm/react-diagrams-routing/-/react-diagrams-routing-6.6.1.tgz#06b77ab1f94f4567391099ffe5e1d91db34408ff"
|
||||
integrity sha512-m8akJynhanxmpc/A2U7bcgFxIMxsjb3zmYBRGFltVJve87mir8ACaH2gmiHYcAfgEHxdh+x7mCuUlfNP242Ytw==
|
||||
dependencies:
|
||||
"@projectstorm/geometry" "^6.5.2"
|
||||
"@projectstorm/react-diagrams-core" "^6.5.2"
|
||||
"@projectstorm/react-diagrams-defaults" "^6.5.2"
|
||||
"@projectstorm/geometry" "^6.6.1"
|
||||
"@projectstorm/react-diagrams-core" "^6.6.1"
|
||||
"@projectstorm/react-diagrams-defaults" "^6.6.1"
|
||||
|
||||
"@projectstorm/react-diagrams@^6.4.2":
|
||||
version "6.5.2"
|
||||
resolved "https://registry.yarnpkg.com/@projectstorm/react-diagrams/-/react-diagrams-6.5.2.tgz#3f65a903b1853e51d68109a513d91d190d278864"
|
||||
integrity sha512-1xlz1jVffWq5R7Oqj/BEY06Cf+2uLV5eEY6pGxuGFfp5SXeTNCSIS/khkbFou707Gor3D8MOqdBpZvF2NfoLmA==
|
||||
"@projectstorm/react-diagrams@^6.6.1":
|
||||
version "6.6.1"
|
||||
resolved "https://registry.yarnpkg.com/@projectstorm/react-diagrams/-/react-diagrams-6.6.1.tgz#3af4007613f487ba58801cdcded955683b3f01c5"
|
||||
integrity sha512-tLSXfEf/dGFUN8JCCRMrYyIBhhn+eVw24xQodmtcReJxQpKa31EWh9CmJ6UEg7xUnabMG9f2plOPyJqyFssGTA==
|
||||
dependencies:
|
||||
"@projectstorm/react-diagrams-core" "^6.5.2"
|
||||
"@projectstorm/react-diagrams-defaults" "^6.5.2"
|
||||
"@projectstorm/react-diagrams-routing" "^6.5.2"
|
||||
"@projectstorm/react-diagrams-core" "^6.6.1"
|
||||
"@projectstorm/react-diagrams-defaults" "^6.6.1"
|
||||
"@projectstorm/react-diagrams-routing" "^6.6.1"
|
||||
|
||||
"@simonwep/pickr@^1.5.1":
|
||||
version "1.8.1"
|
||||
|
||||
Reference in New Issue
Block a user