diff --git a/web/package.json b/web/package.json index 046126c66..d7bb3d505 100644 --- a/web/package.json +++ b/web/package.json @@ -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", diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/static/js/column.ui.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/static/js/column.ui.js index ee6edcd1b..f57d540f0 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/static/js/column.ui.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/columns/static/js/column.ui.js @@ -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'], diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/static/js/foreign_key.ui.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/static/js/foreign_key.ui.js index 5903f5394..2dffa5f02 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/static/js/foreign_key.ui.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/static/js/foreign_key.ui.js @@ -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: ''}; } diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.ui.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.ui.js index 94622e2b3..1290e6ce3 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.ui.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.ui.js @@ -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 { '', ].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'], diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/utils.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/utils.py index e0099396e..9f370a951 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/utils.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/utils.py @@ -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 += ')' diff --git a/web/pgadmin/static/js/SchemaView/FormView.jsx b/web/pgadmin/static/js/SchemaView/FormView.jsx index 17371202e..74fa2bb2a 100644 --- a/web/pgadmin/static/js/SchemaView/FormView.jsx +++ b/web/pgadmin/static/js/SchemaView/FormView.jsx @@ -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(); } - } 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); diff --git a/web/pgadmin/static/js/SchemaView/index.jsx b/web/pgadmin/static/js/SchemaView/index.jsx index 0f3073e9e..d78ba7a7b 100644 --- a/web/pgadmin/static/js/SchemaView/index.jsx +++ b/web/pgadmin/static/js/SchemaView/index.jsx @@ -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), diff --git a/web/pgadmin/static/js/Theme/index.jsx b/web/pgadmin/static/js/Theme/index.jsx index b27692875..3a0118802 100644 --- a/web/pgadmin/static/js/Theme/index.jsx +++ b/web/pgadmin/static/js/Theme/index.jsx @@ -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', diff --git a/web/pgadmin/static/scss/_alertify.overrides.scss b/web/pgadmin/static/scss/_alertify.overrides.scss index 40f6ac383..ddaaee18d 100644 --- a/web/pgadmin/static/scss/_alertify.overrides.scss +++ b/web/pgadmin/static/scss/_alertify.overrides.scss @@ -42,7 +42,7 @@ } & .ajs-handle { - z-index: 5; + z-index: 1020; } } diff --git a/web/pgadmin/tools/erd/__init__.py b/web/pgadmin/tools/erd/__init__.py index 6d16cbd79..6978d5db1 100644 --- a/web/pgadmin/tools/erd/__init__.py +++ b/web/pgadmin/tools/erd/__init__.py @@ -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////', 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, diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/ERDCore.js b/web/pgadmin/tools/erd/static/js/erd_tool/ERDCore.js index 23b592149..b63b1786a 100644 --- a/web/pgadmin/tools/erd/static/js/erd_tool/ERDCore.js +++ b/web/pgadmin/tools/erd/static/js/erd_tool/ERDCore.js @@ -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 */ - 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'); + 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'); + }); + } }); + setTimeout(this.dagreDistributeNodes.bind(this), 250); } diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/DialogWrapper.js b/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/DialogWrapper.js index cb0b1ff52..0ed761629 100644 --- a/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/DialogWrapper.js +++ b/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/DialogWrapper.js @@ -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); - } - - disableOKButton() { - this.__internal.buttons[1].element.disabled = true; + this.elements.dialog.classList.add('erd-dialog'); } - 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( - '
' + - ' ' + - '
').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 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; + createDialog(container) { + let self = this; + ReactDOM.render( + 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); } - wasOkButtonPressed(event) { - return event.button['data-btn-name'] === 'ok'; + cleanupDialog(container) { + ReactDOM.unmountComponentAtNode(container); } } diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/ManyToManyDialog.js b/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/ManyToManyDialog.js index 5a35243ab..dc2421b7e 100644 --- a/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/ManyToManyDialog.js +++ b/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/ManyToManyDialog.js @@ -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)=>{ + 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 ManyToManySchema({ + left_table_uid: tablesData, + left_table_column_attnum: tableNodesDict[attributes.left_table_uid].getColumns().map((col)=>{ return { - value: col.attnum, label: col.name, + value: col.attnum, label: col.name, 'image': 'icon-column', }; - }); - }; - - 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, + }), + right_table_uid: tablesData, + getRefColumns: (uid)=>{ + return tableNodesDict[uid].getColumns().map((col)=>{ + return { + value: col.attnum, label: col.name, 'image': 'icon-column', + }; + }); }, - 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; - } - }, - }); - - return new dialogModel(attributes); + }, attributes); } createOrGetDialog(title) { @@ -122,19 +93,16 @@ export default class ManyToManyDialog { `
`, 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); } } diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/OneToManyDialog.js b/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/OneToManyDialog.js index efcc5e63b..7cb45810d 100644 --- a/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/OneToManyDialog.js +++ b/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/OneToManyDialog.js @@ -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)=>{ + 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 OneToManySchema({ + local_table_uid: tablesData, + local_column_attnum: tableNodesDict[attributes.local_table_uid].getColumns().map((col)=>{ return { - value: col.attnum, label: col.name, + value: col.attnum, label: col.name, 'image': 'icon-column', }; - }); - }; - - let dialogModel = this.pgBrowser.DataModel.extend({ - defaults: { - local_table_uid: undefined, - local_column_attnum: undefined, - referenced_table_uid: undefined, - referenced_column_attnum: undefined, + }), + referenced_table_uid: tablesData, + getRefColumns: (uid)=>{ + return tableNodesDict[uid].getColumns().map((col)=>{ + return { + value: col.attnum, label: col.name, 'image': 'icon-column', + }; + }); }, - 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; - } - }, - }); - - return new dialogModel(attributes); + }, attributes); } - createOrGetDialog(title) { + createOrGetDialog(title, sVersion) { const dialogName = this.dialogName(); if (!Alertify[dialogName]) { @@ -119,19 +93,17 @@ export default class OneToManyDialog { `
`, 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); } } diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/TableDialog.js b/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/TableDialog.js index 3375fdc6f..eafc881e7 100644 --- a/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/TableDialog.js +++ b/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/TableDialog.js @@ -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']); - - /* 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; - }); +class EmptySchema extends BaseUISchema { + get baseFields() { + return []; + } + changeColumnOptions() { - 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); - - 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; - } - } - }); - - 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 $('').append( - $('', { - class: 'wcTabIcon ' + optimage, - }) - ).append($('').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}); - } + getUISchema(attributes, isNew, tableNodesDict, colTypes, schemas) { + let treeNodeInfo = undefined; + + let columnSchema = new ColumnSchema( + ()=>{}, + treeNodeInfo, + ()=>colTypes, + ()=>[], + ()=>[], + true, + ); + + return new TableSchema( + { + relowner: [], + schema: schemas.map((schema)=>{ + return { + 'value': schema['name'], + 'image': 'icon-schema', + 'label': schema['name'], + }; + }), + spcname: [], + coll_inherits: [], + typname: [], + like_relation: [], }, - 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'); + 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}`}); }); - self.last_attnum = lastCol.get('attnum'); + return retOpts; } - - 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); - } - }); }, - }), - 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; + 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 + ), }, - }); - - return new dialogModel(attributes); + ()=>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 { `
`, 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); } } diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/index.js b/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/index.js index d58d3f9a6..359085cd2 100644 --- a/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/index.js +++ b/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/index.js @@ -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; -} diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/index.js b/web/pgadmin/tools/erd/static/js/erd_tool/index.js index e865c29bd..2b1906f14 100644 --- a/web/pgadmin/tools/erd/static/js/erd_tool/index.js +++ b/web/pgadmin/tools/erd/static/js/erd_tool/index.js @@ -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 { {this.props.node.fireEvent({}, 'editNode');}}> +
{this.props.node.fireEvent({}, 'editTable');}}>
{e.stopPropagation();}} /> {this.props.node.getNote() && diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/ui_components/BodyWidget.jsx b/web/pgadmin/tools/erd/static/js/erd_tool/ui_components/BodyWidget.jsx index 0e69c0b9a..4ff8825c1 100644 --- a/web/pgadmin/tools/erd/static/js/erd_tool/ui_components/BodyWidget.jsx +++ b/web/pgadmin/tools/erd/static/js/erd_tool/ui_components/BodyWidget.jsx @@ -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 ( - <> + - @@ -869,7 +829,7 @@ export default class BodyWidget extends React.Component { {this.canvasEle = ele?.ref?.current;}} engine={this.diagram.getEngine()} />
- + ); } } @@ -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, diff --git a/web/pgadmin/tools/erd/static/scss/_erd.scss b/web/pgadmin/tools/erd/static/scss/_erd.scss index f48d65b4c..150ef6778 100644 --- a/web/pgadmin/tools/erd/static/scss/_erd.scss +++ b/web/pgadmin/tools/erd/static/scss/_erd.scss @@ -205,3 +205,14 @@ } } } + +.alertify { + .erd-dialog { + .ajs-body .ajs-content { + bottom: 0!important; + } + .ajs-footer { + display: none; + } + } +} diff --git a/web/regression/javascript/erd/erd_core_spec.js b/web/regression/javascript/erd/erd_core_spec.js index b26a5fc05..635d488d4 100644 --- a/web/regression/javascript/erd/erd_core_spec.js +++ b/web/regression/javascript/erd/erd_core_spec.js @@ -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'}, diff --git a/web/regression/javascript/erd/fake_item.js b/web/regression/javascript/erd/fake_item.js new file mode 100644 index 000000000..498ef1db1 --- /dev/null +++ b/web/regression/javascript/erd/fake_item.js @@ -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() {} +} diff --git a/web/regression/javascript/erd/ui_components/body_widget_spec.js b/web/regression/javascript/erd/ui_components/body_widget_spec.js index 2095ecd90..ca30f63ff 100644 --- a/web/regression/javascript/erd/ui_components/body_widget_spec.js +++ b/web/regression/javascript/erd/ui_components/body_widget_spec.js @@ -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({}} alertify={alertify}/>); + body = mount(); 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', diff --git a/web/regression/javascript/schema_ui_files/column.ui.spec.js b/web/regression/javascript/schema_ui_files/column.ui.spec.js index a6528ab16..bde3aff27 100644 --- a/web/regression/javascript/schema_ui_files/column.ui.spec.js +++ b/web/regression/javascript/schema_ui_files/column.ui.spec.js @@ -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', ()=>{ diff --git a/web/yarn.lock b/web/yarn.lock index b92c49a2d..254a37a9d 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -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/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== - dependencies: - "@projectstorm/geometry" "^6.5.2" - -"@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== - dependencies: - "@projectstorm/geometry" "^6.5.2" - "@projectstorm/react-canvas-core" "^6.5.2" - -"@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== - dependencies: - "@projectstorm/react-diagrams-core" "^6.5.2" - -"@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== - dependencies: - "@projectstorm/geometry" "^6.5.2" - "@projectstorm/react-diagrams-core" "^6.5.2" - "@projectstorm/react-diagrams-defaults" "^6.5.2" - -"@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== - dependencies: - "@projectstorm/react-diagrams-core" "^6.5.2" - "@projectstorm/react-diagrams-defaults" "^6.5.2" - "@projectstorm/react-diagrams-routing" "^6.5.2" +"@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.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.6.1" + +"@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.6.1" + "@projectstorm/react-canvas-core" "^6.6.1" + +"@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.6.1" + +"@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.6.1" + "@projectstorm/react-diagrams-core" "^6.6.1" + "@projectstorm/react-diagrams-defaults" "^6.6.1" + +"@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.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"