mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2026-10-27 15:10:05 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a144815201 | ||
|
|
c6220bd8e2 | ||
|
|
bff425f49e | ||
|
|
843925e5bc | ||
|
|
18ee965da6 | ||
|
|
ca534aaabc | ||
|
|
f7acbad684 | ||
|
|
d0c53e1621 | ||
|
|
c75f72e6e2 | ||
|
|
6fa5d1f061 | ||
|
|
12c44cd0fa | ||
|
|
623204d062 | ||
|
|
004e777032 | ||
|
|
0e9d59fe51 | ||
|
|
919d973ec0 | ||
|
|
e86363bbc0 | ||
|
|
feaa47497c | ||
|
|
a4451fead8 | ||
|
|
5e8d99fbbd | ||
|
|
45b7abd267 | ||
|
|
724fa361ea | ||
|
|
eb68176ee1 | ||
|
|
65ede27a38 | ||
|
|
92c9d757b2 | ||
|
|
465106206b | ||
|
|
81dc2161cb | ||
|
|
ae1eca5b3f | ||
|
|
c599edb342 | ||
|
|
4d03fbd34f | ||
|
|
b2b52b2f66 | ||
|
|
23c3a27201 | ||
|
|
c8356f2bfd | ||
|
|
b5e7e7848c | ||
|
|
f58dbf36bb | ||
|
|
57431ca754 | ||
|
|
ab62af0d55 |
No files matched your search
+3
-4
@@ -61,8 +61,8 @@ requests 2.25.1
|
||||
pyotp 2.8.0 MIT License https://github.com/pyotp/pyotp
|
||||
qrcode 7.4.2 BSD https://github.com/lincolnloop/python-qrcode
|
||||
Pillow 9.4.0 HPND https://python-pillow.org
|
||||
boto3 1.26.64 Apache License 2.0 https://github.com/boto/boto3
|
||||
botocore 1.29.64 Apache License 2.0 https://github.com/boto/botocore
|
||||
boto3 1.26.71 Apache License 2.0 https://github.com/boto/boto3
|
||||
botocore 1.29.71 Apache License 2.0 https://github.com/boto/botocore
|
||||
urllib3 1.26.14 MIT https://urllib3.readthedocs.io/
|
||||
Werkzeug 2.1.2 BSD-3-Clause https://palletsprojects.com/p/werkzeug/
|
||||
azure-mgmt-rdbms 10.1.0 MIT License https://github.com/Azure/azure-sdk-for-python
|
||||
@@ -315,7 +315,6 @@ json-schema-traverse 1.0.0
|
||||
json-schema-traverse 0.4.1 MIT git+https://github.com/epoberezkin/json-schema-traverse.git
|
||||
json-source-map 0.6.1 MIT git+https://github.com/epoberezkin/json-source-map.git
|
||||
json5 2.2.3 MIT git+https://github.com/json5/json5.git
|
||||
jsoneditor-react 3.1.2 MIT git+https://github.com/vankop/jsoneditor-react.git
|
||||
jss 10.9.2 MIT https://github.com/cssinjs/jss.git
|
||||
jss-plugin-camel-case 10.9.2 MIT https://github.com/cssinjs/jss.git
|
||||
jss-plugin-default-unit 10.9.2 MIT https://github.com/cssinjs/jss.git
|
||||
@@ -573,5 +572,5 @@ tslib 2.3.1
|
||||
type-fest 1.4.0 (MIT OR CC0-1.0) https://github.com/sindresorhus/type-fest.git
|
||||
webcabin-docker 2.2.5 MIT* Unknown
|
||||
|
||||
492 dependencies listed.
|
||||
491 dependencies listed.
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2018, The pgAdmin Development Team
|
||||
# Copyright (C) 2013 - 2023, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
#########################################################################
|
||||
|
||||
@@ -274,10 +274,16 @@ REM Main build sequence Ends
|
||||
REM YARN END
|
||||
|
||||
REM WGET
|
||||
FOR /f "tokens=2 delims='" %%i IN ('yarn info nw ^| findstr "latest: "') DO SET "NW_VERSION=%%i"
|
||||
:GET_NW
|
||||
wget https://dl.nwjs.io/v%NW_VERSION%/nwjs-v%NW_VERSION%-win-x64.zip -O "%TMPDIR%\nwjs-v%NW_VERSION%-win-x64.zip"
|
||||
IF %ERRORLEVEL% NEQ 0 GOTO GET_NW
|
||||
REM Comment out the below for loop as the latest version having
|
||||
REM some problem to launch on CentOS 7, so for
|
||||
REM the time being hardcoded the version to 0.72.0
|
||||
REM FOR /f "tokens=2 delims='" %%i IN ('yarn info nw ^| findstr "latest: "') DO SET "NW_VERSION=%%i"
|
||||
REM :GET_NW
|
||||
REM wget https://dl.nwjs.io/v%NW_VERSION%/nwjs-v%NW_VERSION%-win-x64.zip -O "%TMPDIR%\nwjs-v%NW_VERSION%-win-x64.zip"
|
||||
REM IF %ERRORLEVEL% NEQ 0 GOTO GET_NW
|
||||
|
||||
SET "NW_VERSION=0.72.0"
|
||||
wget https://dl.nwjs.io/v%NW_VERSION%/nwjs-v%NW_VERSION%-win-x64.zip -O "%TMPDIR%\nwjs-v%NW_VERSION%-win-x64.zip"
|
||||
|
||||
tar -C "%TMPDIR%" -xvf "%TMPDIR%\nwjs-v%NW_VERSION%-win-x64.zip" || EXIT /B 1
|
||||
REM WGET END
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2018, The pgAdmin Development Team
|
||||
# Copyright (C) 2013 - 2023, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
#########################################################################
|
||||
|
||||
+15
-12
@@ -135,18 +135,21 @@ Utility Options
|
||||
:class: longtable
|
||||
:widths: 1 4 1
|
||||
|
||||
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
|
||||
| Icon | Behavior | Shortcut |
|
||||
+======================+===================================================================================================+================+
|
||||
| *Add/Edit note* | Click this button to make notes on tables nodes while designing the database. | Option/Alt + |
|
||||
| | | Ctrl + N |
|
||||
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
|
||||
| *Auto align* | Click this button to auto align all tables and links to make it look more cleaner. | Option/Alt + |
|
||||
| | | Ctrl + L |
|
||||
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
|
||||
| *Show details* | Click this button to toggle the column details visibility. It allows you to show few or more | Option/Alt + |
|
||||
| | column details. | Shift + D |
|
||||
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
|
||||
+-------------------------+------------------------------------------------------------------------------------------------+----------------+
|
||||
| Icon | Behavior | Shortcut |
|
||||
+=========================+================================================================================================+================+
|
||||
| *Add/Edit note* | Click this button to make notes on tables nodes while designing the database. | Option/Alt + |
|
||||
| | | Ctrl + N |
|
||||
+-------------------------+------------------------------------------------------------------------------------------------+----------------+
|
||||
| *Auto align* | Click this button to auto align all tables and links to make it look more cleaner. | Option/Alt + |
|
||||
| | | Ctrl + L |
|
||||
+-------------------------+------------------------------------------------------------------------------------------------+----------------+
|
||||
| *Show details* | Click this button to toggle the column details visibility. It allows you to show few or more | Option/Alt + |
|
||||
| | column details. | Shift + D |
|
||||
+-------------------------+------------------------------------------------------------------------------------------------+----------------+
|
||||
| *Cardinality Notation* | Change the cardinality notation format used to present relationship links. Options available | |
|
||||
| | are - Crow's Foot Notation and Chen Notation. | |
|
||||
+-------------------------+------------------------------------------------------------------------------------------------+----------------+
|
||||
|
||||
Zoom Options
|
||||
************
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 185 KiB After Width: | Height: | Size: 340 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 22 KiB |
@@ -11,6 +11,7 @@ notes for it.
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
release_notes_6_21
|
||||
release_notes_6_20
|
||||
release_notes_6_19
|
||||
release_notes_6_18
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
************
|
||||
Version 6.21
|
||||
************
|
||||
|
||||
Release date: 2023-03-09
|
||||
|
||||
This release contains a number of bug fixes and new features since the release of pgAdmin 4 v6.20.
|
||||
|
||||
Supported Database Servers
|
||||
**************************
|
||||
**PostgreSQL**: 10, 11, 12, 13, 14 and 15
|
||||
|
||||
**EDB Advanced Server**: 10, 11, 12, 13, 14 and 15
|
||||
|
||||
Bundled PostgreSQL Utilities
|
||||
****************************
|
||||
**psql**, **pg_dump**, **pg_dumpall**, **pg_restore**: 15.1
|
||||
|
||||
|
||||
New features
|
||||
************
|
||||
|
||||
| `Issue #5832 <https://github.com/pgadmin-org/pgadmin4/issues/5832>`_ - Allow changing cardinality notation in ERD to use Chen notation.
|
||||
| `Issue #5842 <https://github.com/pgadmin-org/pgadmin4/issues/5842>`_ - Add additional logging for successful logins and user creation.
|
||||
|
||||
Housekeeping
|
||||
************
|
||||
|
||||
|
||||
Bug fixes
|
||||
*********
|
||||
|
||||
| `Issue #5269 <https://github.com/pgadmin-org/pgadmin4/issues/5269>`_ - Ensure that the schema diff tool should pick up the change in the column grants.
|
||||
| `Issue #5685 <https://github.com/pgadmin-org/pgadmin4/issues/5685>`_ - Ensure that Grant column permission to a view is visible in the SQL tab.
|
||||
| `Issue #5747 <https://github.com/pgadmin-org/pgadmin4/issues/5747>`_ - Ensure that content on the DDL comparison panel should get refreshed on selecting the object using the up and down arrow keys.
|
||||
| `Issue #5756 <https://github.com/pgadmin-org/pgadmin4/issues/5756>`_ - Fix for query tool prompting for unsaved changes even if no changes have been made.
|
||||
| `Issue #5758 <https://github.com/pgadmin-org/pgadmin4/issues/5758>`_ - Fixed an issue where lock layout menu was not in sync with preferences.
|
||||
| `Issue #5760 <https://github.com/pgadmin-org/pgadmin4/issues/5760>`_ - Fixed an issue where query was not pasted to editor after trojan source warning.
|
||||
| `Issue #5764 <https://github.com/pgadmin-org/pgadmin4/issues/5764>`_ - Fix an issue where the maintenance dialog for Materialized View gives an error.
|
||||
| `Issue #5773 <https://github.com/pgadmin-org/pgadmin4/issues/5773>`_ - Fixed an issue where Clear Saved Password should be disabled if the password is already cleared.
|
||||
| `Issue #5790 <https://github.com/pgadmin-org/pgadmin4/issues/5790>`_ - Fixed an issue where the user can't create trigger AFTER UPDATE OF.
|
||||
| `Issue #5803 <https://github.com/pgadmin-org/pgadmin4/issues/5803>`_ - Fix an issue where query tool is stripping spaces in grid cell.
|
||||
| `Issue #5810 <https://github.com/pgadmin-org/pgadmin4/issues/5810>`_ - Fix an issue where sequence owner is remove on sequence edit.
|
||||
| `Issue #5822 <https://github.com/pgadmin-org/pgadmin4/issues/5822>`_ - Do not allow to save invalid JSON in query tool JSON editor.
|
||||
| `Issue #5847 <https://github.com/pgadmin-org/pgadmin4/issues/5847>`_ - Fixed an issue where pgAdmin failed to connect when the Postgres password included special characters.
|
||||
| `Issue #5870 <https://github.com/pgadmin-org/pgadmin4/issues/5870>`_ - Ensure that the database migration does not fail with a NoSuchTableError exception.
|
||||
| `Issue #5872 <https://github.com/pgadmin-org/pgadmin4/issues/5872>`_ - Handle MERGE operation in query tool explain introduced in PostgreSQL 15.
|
||||
| `Issue #5889 <https://github.com/pgadmin-org/pgadmin4/issues/5889>`_ - Fixed an issue where the database server is not connected using a service file.
|
||||
| `Issue #5909 <https://github.com/pgadmin-org/pgadmin4/issues/5909>`_ - Fixed an issue where the file name in the query tool tab was not changing if another file is opened.
|
||||
@@ -130,7 +130,11 @@ _build_runtime() {
|
||||
# YARN END
|
||||
|
||||
# WGET:
|
||||
NW_VERSION=$(yarn info nw | grep latest | awk -F "'" '{ print $2}')
|
||||
# Comment out the below line as the latest version (0.73.0) having some
|
||||
# problem in launching the pgAdmin 4 in CentOS 7
|
||||
# hardcoded the version to 0.72.0
|
||||
# NW_VERSION=$(yarn info nw | grep latest | awk -F "'" '{ print $2}')
|
||||
NW_VERSION="0.72.0"
|
||||
|
||||
pushd "${BUILDROOT}" > /dev/null || exit
|
||||
while true;do
|
||||
|
||||
@@ -34,7 +34,11 @@ _build_runtime() {
|
||||
# YARN END
|
||||
|
||||
# WGET:
|
||||
NW_VERSION=$(yarn info nw | grep latest | awk -F "'" '{ print $2}')
|
||||
# Comment out the below line as the latest version (0.73.0) having some
|
||||
# problem in launching the pgAdmin 4 in CentOS 7
|
||||
# hardcoded the version to 0.72.0
|
||||
# NW_VERSION=$(yarn info nw | grep latest | awk -F "'" '{ print $2}')
|
||||
NW_VERSION="0.72.0"
|
||||
|
||||
pushd "${BUILD_ROOT}" > /dev/null || exit
|
||||
while true;do
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^6.3.0",
|
||||
"nw": "^0.70.0"
|
||||
"nw": "0.72.0"
|
||||
},
|
||||
"scripts": {
|
||||
"linter": "yarn eslint --no-eslintrc -c .eslintrc.js --ext .js ."
|
||||
|
||||
+4
-4
@@ -1008,10 +1008,10 @@ number-is-nan@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
|
||||
integrity sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==
|
||||
|
||||
nw@^0.70.0:
|
||||
version "0.70.1"
|
||||
resolved "https://registry.yarnpkg.com/nw/-/nw-0.70.1.tgz#592bd715097970090440183974d79ae5005ad377"
|
||||
integrity sha512-q0zyS04L+d8vzxC5jCnfouu1G0NR3PTAuTT9x4GY25i14TFk+7H8wQmrZCV9MUvEWK7m/NkjcfxRmHkFqog7zw==
|
||||
nw@0.72.0:
|
||||
version "0.72.0"
|
||||
resolved "https://registry.yarnpkg.com/nw/-/nw-0.72.0.tgz#b8d340c165756b2f07320e999f5aaa486131e38f"
|
||||
integrity sha512-uMLsvXe3cDJ+YY8Jcd+YoLg7ggqJAbpTskNx0X21Z6gn9oWWnte9eJsWNC/9J5itg3TzzqyvIyUfXsMGekqPmw==
|
||||
dependencies:
|
||||
chalk "~1.1.3"
|
||||
decompress "^4.2.0"
|
||||
|
||||
@@ -20,7 +20,7 @@ import codecs
|
||||
ALLOWED_FILE_EXTENSIONS = (
|
||||
".py", ".js", ".sql", ".cpp", ".h", ".rc", ".am", ".wsgi", ".pro",
|
||||
".plist", ".rst", ".sh", ".in", ".mako", ".ini", ".jsx", ".rtf", ".rst",
|
||||
"LICENSE", ".ts", ".tsx"
|
||||
"LICENSE", "Dockerfile", "Makefile", ".ts", ".tsx"
|
||||
)
|
||||
|
||||
EXCLUDE_DIR = ("node_modules")
|
||||
|
||||
+2
-2
@@ -43,7 +43,7 @@ APP_ICON = 'pg-icon'
|
||||
|
||||
# Application version number components
|
||||
APP_RELEASE = 6
|
||||
APP_REVISION = 20
|
||||
APP_REVISION = 21
|
||||
|
||||
# Application version suffix, e.g. 'beta1', 'dev'. Usually an empty string
|
||||
# for GA releases.
|
||||
@@ -52,7 +52,7 @@ APP_SUFFIX = ''
|
||||
# Numeric application version for upgrade checks. Should be in the format:
|
||||
# [X]XYYZZ, where X is the release version, Y is the revision, with a leading
|
||||
# zero if needed, and Z represents the suffix, with a leading zero if needed
|
||||
APP_VERSION_INT = 62000
|
||||
APP_VERSION_INT = 62100
|
||||
|
||||
# DO NOT CHANGE!
|
||||
# The application version string, constructed from the components
|
||||
|
||||
@@ -14,7 +14,7 @@ Create Date: 2023-01-02 14:52:48.109290
|
||||
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
from alembic import op, context
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
@@ -32,6 +32,25 @@ def migrate_connection_params(table_name):
|
||||
op.add_column(table_name,
|
||||
sa.Column('connection_params', sa.JSON()))
|
||||
|
||||
# Fixed the NoSuchTableError exception when migrating from v6.2 and below
|
||||
# to directly v6.16 and above. Below statements has been removed from
|
||||
# migration file 3ce25f562f3b_.py as a part of using the alembic code
|
||||
# instead of SQL queries directly.
|
||||
if context.get_impl().bind.dialect.name == "sqlite":
|
||||
try:
|
||||
# Rename user table to user_old and again user_old to user to
|
||||
# change the foreign key reference of user_old table which is not
|
||||
# exists
|
||||
op.execute("ALTER TABLE \"user\" RENAME TO user_old")
|
||||
op.execute("ALTER TABLE user_old RENAME TO \"user\"")
|
||||
# Rename user table to server_old and again server_old to server to
|
||||
# change the foreign key reference of user_old table which is not
|
||||
# exists.
|
||||
op.execute("ALTER TABLE server RENAME TO server_old")
|
||||
op.execute("ALTER TABLE server_old RENAME TO server")
|
||||
except Exception as _:
|
||||
pass
|
||||
|
||||
# define table representation
|
||||
meta = sa.MetaData(bind=op.get_bind())
|
||||
meta.reflect(only=(table_name,))
|
||||
|
||||
@@ -124,7 +124,6 @@
|
||||
"jquery": "^3.6.0",
|
||||
"json-bignumber": "^1.0.1",
|
||||
"jsoneditor": "^9.5.4",
|
||||
"jsoneditor-react": "^3.1.1",
|
||||
"karma-coverage": "^2.0.3",
|
||||
"leaflet": "^1.5.1",
|
||||
"lodash": "4.*",
|
||||
|
||||
@@ -78,6 +78,8 @@ class BaseAuthentication(metaclass=AuthSourceRegistry):
|
||||
if not status:
|
||||
current_app.logger.exception(self.messages('LOGIN_FAILED'))
|
||||
return False, self.messages('LOGIN_FAILED')
|
||||
current_app.logger.info(
|
||||
"Internal user {0} logged in.".format(username))
|
||||
return True, None
|
||||
|
||||
def messages(self, msg_key):
|
||||
|
||||
@@ -273,6 +273,10 @@ class KerberosAuthentication(BaseAuthentication):
|
||||
user = User.query.filter_by(
|
||||
username=username, auth_source=KERBEROS).first()
|
||||
if user is None:
|
||||
create_msg = ("Creating user {0} with email {1} "
|
||||
"from auth source KERBEROS.")
|
||||
current_app.logger.info(create_msg.format(username,
|
||||
username))
|
||||
return create_user({
|
||||
'username': username,
|
||||
'email': username,
|
||||
|
||||
@@ -175,6 +175,8 @@ class LDAPAuthentication(BaseAuthentication):
|
||||
if not status:
|
||||
current_app.logger.exception(self.messages('LOGIN_FAILED'))
|
||||
return False, self.messages('LOGIN_FAILED')
|
||||
current_app.logger.info(
|
||||
"LDAP user {0} logged in.".format(user))
|
||||
return True, None
|
||||
|
||||
def __auto_create_user(self, user_email):
|
||||
@@ -188,6 +190,10 @@ class LDAPAuthentication(BaseAuthentication):
|
||||
self.username)).first()
|
||||
|
||||
if user is None:
|
||||
create_msg = ("Creating user {0} with email {1} "
|
||||
"from auth source LDAP.")
|
||||
current_app.logger.info(create_msg.format(self.username,
|
||||
user_email))
|
||||
return create_user({
|
||||
'username': self.username,
|
||||
'email': user_email,
|
||||
|
||||
@@ -157,6 +157,8 @@ class OAuth2Authentication(BaseAuthentication):
|
||||
username=username, auth_source=OAUTH2).first()
|
||||
current_app.login_manager.logout_view = \
|
||||
OAuth2Authentication.LOGOUT_VIEW
|
||||
current_app.logger.info(
|
||||
"OAUTH2 user {0} logged in.".format(username))
|
||||
return login_user(user), None
|
||||
return False, msg
|
||||
|
||||
@@ -189,6 +191,10 @@ class OAuth2Authentication(BaseAuthentication):
|
||||
user = User.query.filter_by(username=username,
|
||||
auth_source=OAUTH2).first()
|
||||
if not user:
|
||||
create_msg = ("Creating user {0} with email {1} "
|
||||
"from auth source OAUTH2.")
|
||||
current_app.logger.info(create_msg.format(username,
|
||||
email))
|
||||
return create_user({
|
||||
'username': username,
|
||||
'email': email,
|
||||
|
||||
@@ -104,6 +104,8 @@ class WebserverAuthentication(BaseAuthentication):
|
||||
if not status:
|
||||
current_app.logger.exception(self.messages('LOGIN_FAILED'))
|
||||
return False, self.messages('LOGIN_FAILED')
|
||||
current_app.logger.info(
|
||||
"Webserver user {0} logged in.".format(username))
|
||||
return True, None
|
||||
return False, self.messages('LOGIN_FAILED')
|
||||
|
||||
@@ -112,6 +114,10 @@ class WebserverAuthentication(BaseAuthentication):
|
||||
if config.WEBSERVER_AUTO_CREATE_USER:
|
||||
user = User.query.filter_by(username=username).first()
|
||||
if not user:
|
||||
create_msg = ("Creating user {0} with email {1} "
|
||||
"from auth source Webserver.")
|
||||
current_app.logger.info(create_msg.format(username,
|
||||
useremail))
|
||||
return create_user({
|
||||
'username': username,
|
||||
'email': useremail,
|
||||
|
||||
@@ -566,6 +566,8 @@ def utils():
|
||||
# This will be opposite of use_space option
|
||||
editor_indent_with_tabs = False if editor_use_spaces else True
|
||||
|
||||
prefs = Preferences.module('browser')
|
||||
current_ui_lock = prefs.preference('lock_layout').get()
|
||||
# Try to fetch current libpq version from the driver
|
||||
try:
|
||||
from config import PG_DEFAULT_DRIVER
|
||||
@@ -628,7 +630,8 @@ def utils():
|
||||
username=current_user.username,
|
||||
auth_source=auth_source,
|
||||
heartbeat_timeout=config.SERVER_HEARTBEAT_TIMEOUT,
|
||||
password_length_min=config.PASSWORD_LENGTH_MIN
|
||||
password_length_min=config.PASSWORD_LENGTH_MIN,
|
||||
current_ui_lock=current_ui_lock
|
||||
),
|
||||
200, {'Content-Type': MIMETYPE_APP_JS})
|
||||
|
||||
|
||||
+7
@@ -51,6 +51,13 @@ export class OwnedBySchema extends BaseUISchema {
|
||||
optionsReloadBasis: state.owned_table,
|
||||
};
|
||||
},
|
||||
depChange: (state)=>{
|
||||
if(!state.owned_table) {
|
||||
return {
|
||||
owned_column: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ ALTER SEQUENCE IF EXISTS {{ conn|qtIdent(o_data.schema, data.name) }}
|
||||
OWNER TO {{ conn|qtIdent(data.seqowner) }};
|
||||
|
||||
{% endif %}
|
||||
{% if (data.owned_table == None or data.owned_table is not defined) and (o_data.owned_table is defined and o_data.owned_table != None) and (data.owned_column == None or data.owned_column is not defined) %}
|
||||
{% if (data.owned_table == None) and (data.owned_column == None) %}
|
||||
ALTER SEQUENCE IF EXISTS {{ conn|qtIdent(o_data.schema, data.name) }}
|
||||
OWNED BY NONE;
|
||||
{% elif (data.owned_table is defined or data.owned_column is defined) and (data.owned_table != o_data.owned_table or data.owned_column != o_data.owned_column) %}
|
||||
|
||||
+2
-1
@@ -7,7 +7,8 @@ CREATE SEQUENCE IF NOT EXISTS public."Seq1_$%{}[]()&*^!@""'`\/#"
|
||||
START 5
|
||||
MINVALUE 5
|
||||
MAXVALUE 900
|
||||
CACHE 1;
|
||||
CACHE 1
|
||||
OWNED BY tableforownedby.col2;
|
||||
|
||||
ALTER SEQUENCE public."Seq1_$%{}[]()&*^!@""'`\/#"
|
||||
OWNER TO postgres;
|
||||
-2
@@ -1,4 +1,2 @@
|
||||
ALTER SEQUENCE IF EXISTS public."Seq1_$%{}[]()&*^!@""'`\/#"
|
||||
OWNED BY NONE;
|
||||
ALTER SEQUENCE IF EXISTS public."Seq1_$%{}[]()&*^!@""'`\/#"
|
||||
MAXVALUE 900;
|
||||
+2
-1
@@ -7,7 +7,8 @@ CREATE SEQUENCE IF NOT EXISTS public."Seq1_$%{}[]()&*^!@""'`\/#"
|
||||
START 5
|
||||
MINVALUE 5
|
||||
MAXVALUE 900
|
||||
CACHE 1;
|
||||
CACHE 1
|
||||
OWNED BY tableforownedby.col2;
|
||||
|
||||
ALTER SEQUENCE public."Seq1_$%{}[]()&*^!@""'`\/#"
|
||||
OWNER TO enterprisedb;
|
||||
-2
@@ -1,4 +1,2 @@
|
||||
ALTER SEQUENCE IF EXISTS public."Seq1_$%{}[]()&*^!@""'`\/#"
|
||||
OWNED BY NONE;
|
||||
ALTER SEQUENCE IF EXISTS public."Seq1_$%{}[]()&*^!@""'`\/#"
|
||||
MAXVALUE 900;
|
||||
+14
@@ -176,6 +176,20 @@ class SchemaDiffTableCompare(SchemaDiffObjectCompare):
|
||||
source['attnum'] = tmp['attnum']
|
||||
|
||||
if tmp and source != tmp:
|
||||
# check column level grants
|
||||
acl_dict = dict()
|
||||
if 'attacl' in source and 'attacl' in tmp and \
|
||||
source['attacl'] != tmp['attacl']:
|
||||
if len(source['attacl']) > 0 and len(tmp['attacl']) < 1:
|
||||
acl_dict['added'] = source['attacl'].copy()
|
||||
source['attacl'] = acl_dict
|
||||
elif len(source['attacl']) < 1 and len(tmp['attacl']) > 0:
|
||||
acl_dict['deleted'] = tmp['attacl'].copy()
|
||||
source['attacl'] = acl_dict
|
||||
else:
|
||||
acl_dict['changed'] = source['attacl'].copy()
|
||||
source['attacl'] = acl_dict
|
||||
|
||||
updated.append(source)
|
||||
target_cols.remove(tmp)
|
||||
elif tmp and source == tmp:
|
||||
|
||||
-4
@@ -181,10 +181,6 @@ export default class TriggerSchema extends BaseUISchema {
|
||||
flag = (state.evnt_update && (_.size(state.columns) >= 1 && state.columns[0] != ''));
|
||||
}
|
||||
|
||||
if(flag && state.name) {
|
||||
state.name = null;
|
||||
}
|
||||
|
||||
return flag;
|
||||
}
|
||||
|
||||
|
||||
@@ -252,6 +252,14 @@ def check_precondition(f):
|
||||
self.column_template_path = 'columns/sql/#{0}#'.format(
|
||||
self.manager.version)
|
||||
|
||||
try:
|
||||
self.allowed_acls = render_template(
|
||||
"/".join([self.template_path, self._ALLOWED_PRIVS_JSON])
|
||||
)
|
||||
self.allowed_acls = json.loads(self.allowed_acls, encoding='utf-8')
|
||||
except Exception as e:
|
||||
current_app.logger.exception(e)
|
||||
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return wrap
|
||||
@@ -370,6 +378,7 @@ class ViewNode(PGChildNodeView, VacuumSettings, SchemaDiffObjectCompare):
|
||||
self.conn = None
|
||||
self.template_path = None
|
||||
self.template_initial = 'views'
|
||||
self.allowed_acls = []
|
||||
|
||||
@staticmethod
|
||||
def ppas_template_path(ver):
|
||||
@@ -793,22 +802,15 @@ class ViewNode(PGChildNodeView, VacuumSettings, SchemaDiffObjectCompare):
|
||||
|
||||
ViewNode._get_info_from_data(data, res)
|
||||
|
||||
try:
|
||||
acls = render_template(
|
||||
"/".join([self.template_path, self._ALLOWED_PRIVS_JSON])
|
||||
)
|
||||
acls = json.loads(acls, encoding='utf-8')
|
||||
except Exception as e:
|
||||
current_app.logger.exception(e)
|
||||
|
||||
# Privileges
|
||||
ViewNode._parse_privilege_data(acls, data)
|
||||
ViewNode._parse_privilege_data(self.allowed_acls, data)
|
||||
|
||||
data['del_sql'] = False
|
||||
old_data['acl_sql'] = ''
|
||||
|
||||
is_error, errmsg = self._get_definition_data(vid, data, old_data,
|
||||
res, acls)
|
||||
res,
|
||||
self.allowed_acls)
|
||||
if is_error:
|
||||
return None, errmsg
|
||||
|
||||
@@ -866,17 +868,8 @@ class ViewNode(PGChildNodeView, VacuumSettings, SchemaDiffObjectCompare):
|
||||
if 'schema' in data and isinstance(data['schema'], int):
|
||||
data['schema'] = self._get_schema(data['schema'])
|
||||
|
||||
acls = []
|
||||
try:
|
||||
acls = render_template(
|
||||
"/".join([self.template_path, self._ALLOWED_PRIVS_JSON])
|
||||
)
|
||||
acls = json.loads(acls, encoding='utf-8')
|
||||
except Exception as e:
|
||||
current_app.logger.exception(e)
|
||||
|
||||
# Privileges
|
||||
ViewNode._parse_priv_data(acls, data)
|
||||
ViewNode._parse_priv_data(self.allowed_acls, data)
|
||||
|
||||
sql = render_template("/".join(
|
||||
[self.template_path, self._SQL_PREFIX + self._CREATE_SQL]),
|
||||
@@ -1361,6 +1354,43 @@ class ViewNode(PGChildNodeView, VacuumSettings, SchemaDiffObjectCompare):
|
||||
self._UPDATE_SQL.format(self.manager.version)]),
|
||||
o_data=o_data, data=res, is_view_only=True)
|
||||
sql_data += SQL
|
||||
|
||||
# Get Column Grant SQL
|
||||
for rows in data['rows']:
|
||||
res = {
|
||||
'name': rows['name'],
|
||||
'table': rows['relname'],
|
||||
'schema': self.view_schema
|
||||
}
|
||||
|
||||
if 'attacl' in rows and rows['attacl']:
|
||||
# We need to parse & convert ACL coming from database to json
|
||||
# format
|
||||
SQL = render_template(
|
||||
"/".join([self.column_template_path, 'acl.sql']),
|
||||
tid=vid, clid=rows['attnum'])
|
||||
status, acl = self.conn.execute_dict(SQL)
|
||||
|
||||
if not status:
|
||||
return internal_server_error(errormsg=acl)
|
||||
|
||||
allowed_acls = []
|
||||
if self.allowed_acls and 'datacl' in self.allowed_acls and \
|
||||
'acl' in self.allowed_acls['datacl']:
|
||||
allowed_acls = self.allowed_acls['datacl']['acl']
|
||||
|
||||
for row in acl['rows']:
|
||||
priv = parse_priv_from_db(row)
|
||||
res.setdefault(row['deftype'], []).append(priv)
|
||||
res[row['deftype']] = \
|
||||
parse_priv_to_db(res[row['deftype']], allowed_acls)
|
||||
|
||||
grant_sql = render_template("/".join(
|
||||
[self.template_path, self._SQL_PREFIX + self._GRANT_SQL]),
|
||||
data=res)
|
||||
|
||||
sql_data += grant_sql
|
||||
|
||||
return sql_data
|
||||
|
||||
@check_precondition
|
||||
@@ -1414,19 +1444,10 @@ class ViewNode(PGChildNodeView, VacuumSettings, SchemaDiffObjectCompare):
|
||||
|
||||
result.update(res['rows'][0])
|
||||
|
||||
acls = []
|
||||
try:
|
||||
acls = render_template(
|
||||
"/".join([self.template_path, self._ALLOWED_PRIVS_JSON])
|
||||
)
|
||||
acls = json.loads(acls, encoding='utf-8')
|
||||
except Exception as e:
|
||||
current_app.logger.exception(e)
|
||||
|
||||
# Privileges
|
||||
for aclcol in acls:
|
||||
for aclcol in self.allowed_acls:
|
||||
if aclcol in result:
|
||||
allowedacl = acls[aclcol]
|
||||
allowedacl = self.allowed_acls[aclcol]
|
||||
result[aclcol] = parse_priv_to_db(
|
||||
result[aclcol], allowedacl['acl']
|
||||
)
|
||||
@@ -1766,6 +1787,7 @@ class MViewNode(ViewNode, VacuumSettings):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.template_initial = 'mviews'
|
||||
self.allowed_acls = []
|
||||
|
||||
@staticmethod
|
||||
def ppas_template_path(ver):
|
||||
@@ -1834,19 +1856,10 @@ class MViewNode(ViewNode, VacuumSettings):
|
||||
# table vacuum toast: separate list of changed and reset data for
|
||||
self.merge_to_vacuum_data(old_data, data, 'vacuum_toast')
|
||||
|
||||
acls = []
|
||||
try:
|
||||
acls = render_template(
|
||||
"/".join([self.template_path, self._ALLOWED_PRIVS_JSON])
|
||||
)
|
||||
acls = json.loads(acls, encoding='utf-8')
|
||||
except Exception as e:
|
||||
current_app.logger.exception(e)
|
||||
|
||||
# Privileges
|
||||
for aclcol in acls:
|
||||
for aclcol in self.allowed_acls:
|
||||
if aclcol in data:
|
||||
allowedacl = acls[aclcol]
|
||||
allowedacl = self.allowed_acls[aclcol]
|
||||
|
||||
for key in ['added', 'changed', 'deleted']:
|
||||
if key in data[aclcol]:
|
||||
@@ -1902,19 +1915,10 @@ class MViewNode(ViewNode, VacuumSettings):
|
||||
if data.get('toast_autovacuum', False):
|
||||
data['vacuum_data'] += vacuum_toast
|
||||
|
||||
acls = []
|
||||
try:
|
||||
acls = render_template(
|
||||
"/".join([self.template_path, self._ALLOWED_PRIVS_JSON])
|
||||
)
|
||||
acls = json.loads(acls, encoding='utf-8')
|
||||
except Exception as e:
|
||||
current_app.logger.exception(e)
|
||||
|
||||
# Privileges
|
||||
for aclcol in acls:
|
||||
for aclcol in self.allowed_acls:
|
||||
if aclcol in data:
|
||||
allowedacl = acls[aclcol]
|
||||
allowedacl = self.allowed_acls[aclcol]
|
||||
data[aclcol] = parse_priv_to_db(
|
||||
data[aclcol], allowedacl['acl']
|
||||
)
|
||||
@@ -1970,19 +1974,10 @@ class MViewNode(ViewNode, VacuumSettings):
|
||||
|
||||
result['vacuum_data'] = vacuum_table + vacuum_toast
|
||||
|
||||
acls = []
|
||||
try:
|
||||
acls = render_template(
|
||||
"/".join([self.template_path, self._ALLOWED_PRIVS_JSON])
|
||||
)
|
||||
acls = json.loads(acls, encoding='utf-8')
|
||||
except Exception as e:
|
||||
current_app.logger.exception(e)
|
||||
|
||||
# Privileges
|
||||
for aclcol in acls:
|
||||
for aclcol in self.allowed_acls:
|
||||
if aclcol in result:
|
||||
allowedacl = acls[aclcol]
|
||||
allowedacl = self.allowed_acls[aclcol]
|
||||
result[aclcol] = parse_priv_to_db(
|
||||
result[aclcol], allowedacl['acl']
|
||||
)
|
||||
|
||||
+3
-1
@@ -1,6 +1,8 @@
|
||||
{# ===== Grant Permissions to User Role on Views/Tables ===== #}
|
||||
{% import 'macros/schemas/security.macros' as SECLABEL %}
|
||||
{% import 'macros/schemas/privilege.macros' as PRIVILEGE %}
|
||||
{% import 'columns/macros/privilege.macros' as COLUMN_PRIVILEGE %}
|
||||
{# ===== We will generate Security Label SQL using macro ===== #}
|
||||
{% if data.seclabels %}{% for r in data.seclabels %}{{ SECLABEL.SET(conn, 'VIEW', data.name, r.provider, r.label, data.schema) }}{{'\r'}}{% endfor %}{{'\r'}}{% endif %}{% if data.datacl %}
|
||||
{% for priv in data.datacl %}{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }}{% endfor %}{% endif %}
|
||||
{% for priv in data.datacl %}{{ PRIVILEGE.SET(conn, 'TABLE', priv.grantee, data.name, priv.without_grant, priv.with_grant, data.schema) }}{% endfor %}{% endif %}{% if data.attacl %}
|
||||
{% for priv in data.attacl %}{{ COLUMN_PRIVILEGE.APPLY(conn, data.schema, data.table, data.name, priv.grantee, priv.without_grant, priv.with_grant) }}{% endfor %}{% endif %}
|
||||
@@ -447,6 +447,10 @@ define('pgadmin.node.server', [
|
||||
if (res.success == 1) {
|
||||
Notify.success(res.info);
|
||||
t.itemData(i).is_password_saved=res.data.is_password_saved;
|
||||
t.deselect(i);
|
||||
setTimeout(function() {
|
||||
t.select(i);
|
||||
});
|
||||
}
|
||||
else {
|
||||
Notify.error(res.info);
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
{% endif %}label: "{{ item.label }}", applies: ["{{ key.lower() }}"],
|
||||
priority: {{ item.priority }},
|
||||
enable: "{{ item.enable }}",
|
||||
{% if item.checked is defined %}checked: {% if item.checked %}true{% else %}false{% endif %},
|
||||
{% if item.checked is defined %}checked: {% if (item.checked or item.name == 'mnu_lock_'+current_ui_lock) %}true{% else %}false{% endif %},
|
||||
{% endif %}
|
||||
{% if item.below is defined %}below: {% if item.below %}true{% else %}false{% endif %},
|
||||
{% endif %}
|
||||
|
||||
@@ -574,6 +574,19 @@ export default function PreferencesComponent({ ...props }) {
|
||||
gettext('Later')
|
||||
);
|
||||
}
|
||||
// Sync the lock layout menu with preferences
|
||||
if (pref.name == 'lock_layout') {
|
||||
let fileMenu = pgAdmin.Browser.MainMenus.find((menu) => menu.name == 'file');
|
||||
let layoutSubMenu = fileMenu['menuItems'].find(menu => menu.name == 'mnu_locklayout');
|
||||
layoutSubMenu['menu_items'].forEach(item => {
|
||||
if (item.name === 'mnu_lock_'+save_data[0]['value']) {
|
||||
item.checked = true;
|
||||
} else {
|
||||
item.checked = false;
|
||||
}
|
||||
});
|
||||
pgAdmin.Browser.Events.trigger('pgadmin:nw-refresh-menu-item', 'lock_layout');
|
||||
}
|
||||
}
|
||||
|
||||
if (requires_refresh) {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
##########################################################################
|
||||
|
||||
import os
|
||||
import sys
|
||||
import getpass
|
||||
from pgadmin.utils.constants import KERBEROS
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ ExplainRow.propTypes = {
|
||||
Plans: PropTypes.array,
|
||||
level: PropTypes.array,
|
||||
_serial: PropTypes.number,
|
||||
parent_node: PropTypes.number,
|
||||
parent_node: PropTypes.string,
|
||||
exclusive: PropTypes.number,
|
||||
exclusive_flag: PropTypes.string,
|
||||
inclusive: PropTypes.number,
|
||||
|
||||
@@ -92,7 +92,7 @@ function Multitext({currentXpos, currentYpos, label, maxWidth}) {
|
||||
wordBreakIndex = Math.round((maxWidth / letterWidth)) - 1;
|
||||
svgElem.remove();
|
||||
|
||||
let words = label.split(' '),
|
||||
let words = label?.split(' ') ?? '',
|
||||
widthSoFar = 0,
|
||||
lines = [],
|
||||
currLine = '',
|
||||
|
||||
@@ -168,6 +168,11 @@ const ImageMapper = {
|
||||
'image': 'ex_delete.svg',
|
||||
'image_text': 'Delete',
|
||||
};
|
||||
case 'Merge':
|
||||
return {
|
||||
'image': 'ex_merge.svg',
|
||||
'image_text': 'Merge',
|
||||
};
|
||||
}
|
||||
},
|
||||
'Named Tuplestore Scan': {
|
||||
|
||||
@@ -73,7 +73,7 @@ function getForQueryParams(data) {
|
||||
let retData = {...data};
|
||||
Object.keys(retData).forEach((key)=>{
|
||||
let value = retData[key];
|
||||
if(_.isObject(value)) {
|
||||
if(_.isObject(value) || _.isNull(value)) {
|
||||
retData[key] = JSON.stringify(value);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -21,6 +21,7 @@ export default function uplotOverride(theme) {
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
color: theme.palette.background.default,
|
||||
backgroundColor: theme.palette.text.primary,
|
||||
zIndex: 9999,
|
||||
|
||||
'& .uplot-tooltip-label': {
|
||||
display: 'flex',
|
||||
|
||||
@@ -368,8 +368,8 @@ function calcFontSize(fontSize) {
|
||||
return '1em';
|
||||
}
|
||||
|
||||
function handlePaste(_editor, e) {
|
||||
let copiedText = e.clipboardData.getData('text');
|
||||
async function handlePaste(_editor, e) {
|
||||
let copiedText = await e.clipboardData.getData('text');
|
||||
checkTrojanSource(copiedText, true);
|
||||
}
|
||||
|
||||
|
||||
@@ -69,6 +69,7 @@ export default function StreamingChart({xRange=75, data, options}) {
|
||||
alpha: 0.3,
|
||||
},
|
||||
cursor: {
|
||||
y: false,
|
||||
drag: {
|
||||
setScale: false,
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ const useStyles = makeStyles((theme)=>({
|
||||
...theme.mixins.panelBorder.right,
|
||||
...theme.mixins.panelBorder.bottom,
|
||||
fontWeight: 'abc',
|
||||
whiteSpace: 'pre',
|
||||
'&[aria-colindex="1"]': {
|
||||
padding: 0,
|
||||
},
|
||||
|
||||
@@ -400,6 +400,31 @@ class ERDModule(PgAdminModule):
|
||||
)
|
||||
)
|
||||
|
||||
self.preference.register(
|
||||
'options', 'cardinality_notation',
|
||||
gettext('Cardinality Notation'), 'radioModern', 'crows',
|
||||
category_label=PREF_LABEL_OPTIONS, options=[
|
||||
{'label': gettext('Crow\'s foot'), 'value': 'crows'},
|
||||
{'label': gettext('Chen'), 'value': 'chen'},
|
||||
],
|
||||
help_str=gettext(
|
||||
'Notation to be used to present cardinality.'
|
||||
)
|
||||
)
|
||||
|
||||
self.preference.register(
|
||||
'options',
|
||||
'sql_with_drop',
|
||||
gettext('SQL With DROP Table'),
|
||||
'boolean',
|
||||
False,
|
||||
category_label=PREF_LABEL_OPTIONS,
|
||||
help_str=gettext(
|
||||
'If enabled, the SQL generated by the ERD Tool will add '
|
||||
'DROP table DDL before each CREATE table DDL.'
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
blueprint = ERDModule(MODULE_NAME, __name__, static_url_path='/static')
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ class ERDTool extends React.Component {
|
||||
_.bindAll(this, ['onLoadDiagram', 'onSaveDiagram', 'onSaveAsDiagram', 'onSQLClick',
|
||||
'onImageClick', 'onAddNewNode', 'onEditTable', 'onCloneNode', 'onDeleteNode', 'onNoteClick',
|
||||
'onNoteClose', 'onOneToManyClick', 'onManyToManyClick', 'onAutoDistribute', 'onDetailsToggle',
|
||||
'onDetailsToggle', 'onChangeColors', 'onHelpClick', 'onDropNode', 'onBeforeUnload',
|
||||
'onChangeColors', 'onHelpClick', 'onDropNode', 'onBeforeUnload', 'onNotationChange',
|
||||
]);
|
||||
|
||||
this.diagram.zoomToFit = this.diagram.zoomToFit.bind(this.diagram);
|
||||
@@ -293,11 +293,13 @@ class ERDTool extends React.Component {
|
||||
this.setLoading(gettext('Preparing...'));
|
||||
this.registerEvents();
|
||||
|
||||
const erdPref = this.props.pgWindow.pgAdmin.Browser.get_preferences_for_module('erd');
|
||||
this.setState({
|
||||
preferences: this.props.pgWindow.pgAdmin.Browser.get_preferences_for_module('erd'),
|
||||
preferences: erdPref,
|
||||
is_new_tab: (this.props.pgWindow.pgAdmin.Browser.get_preferences_for_module('browser').new_browser_tab_open || '')
|
||||
.includes('erd_tool'),
|
||||
is_close_tab_warning: this.props.pgWindow.pgAdmin.Browser.get_preferences_for_module('browser').confirm_on_refresh_close,
|
||||
cardinality_notation: erdPref.cardinality_notation,
|
||||
}, ()=>{
|
||||
this.registerKeyboardShortcuts();
|
||||
this.setTitle(this.state.current_file);
|
||||
@@ -559,6 +561,10 @@ class ERDTool extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
onNotationChange(e) {
|
||||
this.setState({cardinality_notation: e.value});
|
||||
}
|
||||
|
||||
onHelpClick() {
|
||||
let url = url_for('help.static', {'filename': 'erd_tool.html'});
|
||||
if (this.props.pgWindow) {
|
||||
@@ -948,12 +954,17 @@ class ERDTool extends React.Component {
|
||||
fgcolor={this.props.params.fgcolor} title={this.props.params.title}/>
|
||||
<MainToolBar preferences={this.state.preferences} eventBus={this.eventBus}
|
||||
fillColor={this.state.fill_color} textColor={this.state.text_color}
|
||||
notation={this.state.cardinality_notation} onNotationChange={this.onNotationChange}
|
||||
/>
|
||||
<FloatingNote open={this.state.note_open} onClose={this.onNoteClose}
|
||||
anchorEl={this.noteRefEle} noteNode={this.state.note_node} appendTo={this.diagramContainerRef.current} rows={8}/>
|
||||
<div className={this.props.classes.diagramContainer} data-test="diagram-container" ref={this.diagramContainerRef} onDrop={this.onDropNode} onDragOver={e => {e.preventDefault();}}>
|
||||
<Loader message={this.state.loading_msg} autoEllipsis={true}/>
|
||||
<CanvasWidget className={this.props.classes.diagramCanvas} ref={(ele)=>{this.canvasEle = ele?.ref?.current;}} engine={this.diagram.getEngine()} />
|
||||
<ERDCanvasSettings.Provider value={{
|
||||
cardinality_notation: this.state.cardinality_notation
|
||||
}}>
|
||||
<CanvasWidget className={this.props.classes.diagramCanvas} ref={(ele)=>{this.canvasEle = ele?.ref?.current;}} engine={this.diagram.getEngine()} />
|
||||
</ERDCanvasSettings.Provider>
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
@@ -981,3 +992,5 @@ ERDTool.propTypes = {
|
||||
panel: PropTypes.object,
|
||||
classes: PropTypes.object,
|
||||
};
|
||||
|
||||
export const ERDCanvasSettings = React.createContext({});
|
||||
@@ -27,6 +27,7 @@ import VisibilityOffRoundedIcon from '@material-ui/icons/VisibilityOffRounded';
|
||||
import ImageRoundedIcon from '@material-ui/icons/ImageRounded';
|
||||
import FormatColorFillRoundedIcon from '@material-ui/icons/FormatColorFillRounded';
|
||||
import FormatColorTextRoundedIcon from '@material-ui/icons/FormatColorTextRounded';
|
||||
import AccountTreeOutlinedIcon from '@material-ui/icons/AccountTreeOutlined';
|
||||
|
||||
import { PgMenu, PgMenuItem, usePgMenuGroup } from '../../../../../../static/js/components/Menu';
|
||||
import gettext from 'sources/gettext';
|
||||
@@ -69,7 +70,7 @@ const useStyles = makeStyles((theme)=>({
|
||||
}),
|
||||
}));
|
||||
|
||||
export function MainToolBar({preferences, eventBus, fillColor, textColor}) {
|
||||
export function MainToolBar({preferences, eventBus, fillColor, textColor, notation, onNotationChange}) {
|
||||
const classes = useStyles({fillColor,textColor});
|
||||
const theme = useTheme();
|
||||
const [buttonsDisabled, setButtonsDisabled] = useState({
|
||||
@@ -86,6 +87,7 @@ export function MainToolBar({preferences, eventBus, fillColor, textColor}) {
|
||||
const {openMenuName, toggleMenu, onMenuClose} = usePgMenuGroup();
|
||||
const saveAsMenuRef = React.useRef(null);
|
||||
const sqlMenuRef = React.useRef(null);
|
||||
const notationMenuRef = React.useRef(null);
|
||||
const isDirtyRef = React.useRef(null);
|
||||
const [checkedMenuItems, setCheckedMenuItems] = React.useState({});
|
||||
const modal = useModal();
|
||||
@@ -283,6 +285,9 @@ export function MainToolBar({preferences, eventBus, fillColor, textColor}) {
|
||||
eventBus.fireEvent(ERD_EVENTS.TOGGLE_DETAILS);
|
||||
setShowDetails((prev)=>!prev);
|
||||
}} />
|
||||
<PgIconButton title={gettext('Cardinality Notation')} icon={
|
||||
<><AccountTreeOutlinedIcon /><KeyboardArrowDownIcon style={{marginLeft: '-10px'}} /></>}
|
||||
name="menu-notation" ref={notationMenuRef} onClick={toggleMenu} />
|
||||
</PgButtonGroup>
|
||||
<PgButtonGroup size="small">
|
||||
<PgIconButton title={gettext('Zoom In')} icon={<ZoomInIcon />}
|
||||
@@ -323,6 +328,16 @@ export function MainToolBar({preferences, eventBus, fillColor, textColor}) {
|
||||
>
|
||||
<PgMenuItem hasCheck value="sql_with_drop" checked={checkedMenuItems['sql_with_drop']} onClick={checkMenuClick}>{gettext('With DROP Table')}</PgMenuItem>
|
||||
</PgMenu>
|
||||
<PgMenu
|
||||
anchorRef={notationMenuRef}
|
||||
open={openMenuName=='menu-notation'}
|
||||
onClose={onMenuClose}
|
||||
label={gettext('Cardinality Notation')}
|
||||
|
||||
>
|
||||
<PgMenuItem hasCheck closeOnCheck value="crows" checked={notation == 'crows'} onClick={onNotationChange}>{gettext('Crow\'s Foot Notation')}</PgMenuItem>
|
||||
<PgMenuItem hasCheck closeOnCheck value="chen" checked={notation == 'chen'} onClick={onNotationChange}>{gettext('Chen Notation')}</PgMenuItem>
|
||||
</PgMenu>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -332,6 +347,8 @@ MainToolBar.propTypes = {
|
||||
eventBus: PropTypes.object,
|
||||
fillColor: PropTypes.string,
|
||||
textColor: PropTypes.string,
|
||||
notation: PropTypes.string,
|
||||
onNotationChange: PropTypes.func,
|
||||
};
|
||||
|
||||
const ColorButton = withColorPicker(PgIconButton);
|
||||
@@ -7,7 +7,7 @@
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import React, { forwardRef } from 'react';
|
||||
import React, { forwardRef, useContext } from 'react';
|
||||
import {
|
||||
RightAngleLinkModel,
|
||||
RightAngleLinkWidget,
|
||||
@@ -21,6 +21,7 @@ import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import { makeStyles } from '@material-ui/core';
|
||||
import clsx from 'clsx';
|
||||
import { ERDCanvasSettings } from '../components/ERDTool';
|
||||
|
||||
export const POINTER_SIZE = 30;
|
||||
|
||||
@@ -81,6 +82,7 @@ export class OneToManyLinkModel extends RightAngleLinkModel {
|
||||
const useStyles = makeStyles((theme)=>({
|
||||
svgLink: {
|
||||
stroke: theme.palette.text.primary,
|
||||
fontSize: '0.8em',
|
||||
},
|
||||
'@keyframes svgLinkSelected': {
|
||||
'from': { strokeDashoffset: 24},
|
||||
@@ -99,15 +101,37 @@ const useStyles = makeStyles((theme)=>({
|
||||
}
|
||||
}));
|
||||
|
||||
const CustomLinkEndWidget = props => {
|
||||
function ChenNotation({rotation, type}) {
|
||||
const classes = useStyles();
|
||||
const textX = Math.sign(rotation) > 0 ? -14 : 8;
|
||||
const textY = -5;
|
||||
return (
|
||||
<>
|
||||
<text className={classes.svgLink} x={textX} y={textY} transform={'rotate(' + -rotation + ')' }>
|
||||
{type == 'one' ? '1' : 'N'}
|
||||
</text>
|
||||
<line className={classes.svgLink} x1="0" y1="0" x2="0" y2="30"></line>
|
||||
</>
|
||||
);
|
||||
}
|
||||
ChenNotation.propTypes = {
|
||||
rotation: PropTypes.number,
|
||||
type: PropTypes.string,
|
||||
};
|
||||
|
||||
function CustomLinkEndWidget(props) {
|
||||
const { point, rotation, tx, ty, type } = props;
|
||||
const classes = useStyles();
|
||||
const settings = useContext(ERDCanvasSettings);
|
||||
|
||||
const svgForType = (itype) => {
|
||||
if(settings.cardinality_notation == 'chen') {
|
||||
return <ChenNotation rotation={rotation} type={itype} />;
|
||||
}
|
||||
if(itype == 'many') {
|
||||
return (
|
||||
<>
|
||||
<circle className={clsx(classes.svgLink, classes.svgLinkCircle)} cx="0" cy="16" r={props.width*1.75} strokeWidth={props.width} />
|
||||
<circle className={clsx(classes.svgLink, classes.svgLinkCircle)} cx="0" cy="16" r={props.width*2.5} strokeWidth={props.width} />
|
||||
<polyline className={classes.svgLink} points="-8,0 0,15 0,0 0,30 0,15 8,0" fill="none" strokeWidth={props.width} />
|
||||
</>
|
||||
);
|
||||
@@ -127,7 +151,7 @@ const CustomLinkEndWidget = props => {
|
||||
</g>
|
||||
</g>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
CustomLinkEndWidget.propTypes = {
|
||||
point: PropTypes.instanceOf(PointModel).isRequired,
|
||||
|
||||
@@ -202,6 +202,17 @@ const styles = (theme)=>({
|
||||
padding: '0.125rem 0.25rem',
|
||||
display: 'flex',
|
||||
},
|
||||
columnSection: {
|
||||
display:'flex',
|
||||
width: '100%' ,
|
||||
...theme.mixins.panelBorder.bottom,
|
||||
},
|
||||
columnName: {
|
||||
display:'flex',
|
||||
width: '100%' ,
|
||||
padding: '0.125rem 0.25rem',
|
||||
wordBreak: 'break-all',
|
||||
},
|
||||
tableToolbar: {
|
||||
background: theme.otherVars.editorToolbarBg,
|
||||
borderTopLeftRadius: 'inherit',
|
||||
@@ -269,11 +280,11 @@ class TableNodeWidgetRaw extends React.Component {
|
||||
|
||||
const {classes} = this.props;
|
||||
return (
|
||||
<div className={classes.tableSection} key={col.attnum} data-test="column-row">
|
||||
<Box className={classes.columnSection} key={col.attnum} data-test="column-row">
|
||||
<Box marginRight="auto" padding="0" minHeight="0" display="flex" alignItems="center">
|
||||
{this.generatePort(leftPort)}
|
||||
</Box>
|
||||
<Box display="flex" width="100%" style={{wordBreak: 'break-all'}}>
|
||||
<Box className={classes.columnName}>
|
||||
<RowIcon icon={icon} />
|
||||
<Box margin="auto 0">
|
||||
<span data-test="column-name">{col.name}</span>
|
||||
@@ -284,7 +295,7 @@ class TableNodeWidgetRaw extends React.Component {
|
||||
<Box marginLeft="auto" padding="0" minHeight="0" display="flex" alignItems="center">
|
||||
{this.generatePort(rightPort)}
|
||||
</Box>
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -99,8 +99,13 @@ define([
|
||||
extraData['database'] = treeInfo.database._label;
|
||||
if(treeInfo?.schema) {
|
||||
extraData['schema'] = treeInfo?.schema._label;
|
||||
}
|
||||
if(treeInfo?.table) {
|
||||
extraData['table'] = treeInfo?.table._label;
|
||||
}
|
||||
if(treeInfo?.mview) {
|
||||
extraData['table'] = treeInfo?.mview._label;
|
||||
}
|
||||
extraData['save_btn_icon'] = 'done';
|
||||
return extraData;
|
||||
},
|
||||
|
||||
@@ -115,10 +115,12 @@ export default class MaintenanceSchema extends BaseUISchema {
|
||||
{
|
||||
'label': gettext('REINDEX'),
|
||||
value: 'REINDEX',
|
||||
disabled: obj.nodeInfo?.mview?true:false
|
||||
},
|
||||
{
|
||||
'label': gettext('CLUSTER'),
|
||||
value: 'CLUSTER',
|
||||
disabled: obj.nodeInfo?.mview?true:false
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -111,7 +111,6 @@ const useStyles = makeStyles((theme) => ({
|
||||
blockSize: '100%',
|
||||
|
||||
'& span': {
|
||||
display: 'table-cell',
|
||||
verticalAlign: 'middle',
|
||||
cursor: 'pointer',
|
||||
}
|
||||
@@ -661,38 +660,49 @@ export function ResultGridComponent({ gridData, allRowIds, filterParams, selecte
|
||||
}, [filterParams]);
|
||||
|
||||
const eventBus = useContext(SchemaDiffEventsContext);
|
||||
const rowSelectionTimeoutRef = useRef();
|
||||
|
||||
const rowSelection = (row) => {
|
||||
|
||||
if (row.ddlData != undefined && row.status != FILTER_NAME.IDENTICAL) {
|
||||
eventBus.fireEvent(SCHEMA_DIFF_EVENT.TRIGGER_CHANGE_RESULT_SQL, row.ddlData);
|
||||
} else if (row.status == FILTER_NAME.IDENTICAL) {
|
||||
let url_params = {
|
||||
'trans_id': transId,
|
||||
'source_sid': sourceData.sid,
|
||||
'source_did': sourceData.did,
|
||||
'source_scid': row.source_scid,
|
||||
'target_sid': targetData.sid,
|
||||
'target_did': targetData.did,
|
||||
'target_scid': row.target_scid,
|
||||
'comp_status': row.status,
|
||||
'source_oid': row.source_oid,
|
||||
'target_oid': row.target_oid,
|
||||
'node_type': row.itemType,
|
||||
};
|
||||
|
||||
let baseUrl = url_for('schema_diff.ddl_compare', url_params);
|
||||
schemaDiffToolContext.api.get(baseUrl).then((res) => {
|
||||
row.ddlData = {
|
||||
'SQLdiff': res.data.diff_ddl,
|
||||
'sourceSQL': res.data.source_ddl,
|
||||
'targetSQL': res.data.target_ddl
|
||||
};
|
||||
eventBus.fireEvent(SCHEMA_DIFF_EVENT.TRIGGER_CHANGE_RESULT_SQL, row.ddlData);
|
||||
}).catch((err) => {
|
||||
Notifier.alert(err.message);
|
||||
});
|
||||
const rowSelection = (rowIdx) => {
|
||||
if (rowSelectionTimeoutRef.current) {
|
||||
clearTimeout(rowSelectionTimeoutRef.current);
|
||||
rowSelectionTimeoutRef.current = null;
|
||||
}
|
||||
|
||||
rowSelectionTimeoutRef.current = setTimeout(()=> {
|
||||
rowSelectionTimeoutRef.current = null;
|
||||
const row = rows[rowIdx];
|
||||
if (row.ddlData != undefined && row.status != FILTER_NAME.IDENTICAL) {
|
||||
eventBus.fireEvent(SCHEMA_DIFF_EVENT.TRIGGER_CHANGE_RESULT_SQL, row.ddlData);
|
||||
} else if (row.status == FILTER_NAME.IDENTICAL) {
|
||||
let url_params = {
|
||||
'trans_id': transId,
|
||||
'source_sid': sourceData.sid,
|
||||
'source_did': sourceData.did,
|
||||
'source_scid': row.source_scid,
|
||||
'target_sid': targetData.sid,
|
||||
'target_did': targetData.did,
|
||||
'target_scid': row.target_scid,
|
||||
'comp_status': row.status,
|
||||
'source_oid': row.source_oid,
|
||||
'target_oid': row.target_oid,
|
||||
'node_type': row.itemType,
|
||||
};
|
||||
|
||||
let baseUrl = url_for('schema_diff.ddl_compare', url_params);
|
||||
schemaDiffToolContext.api.get(baseUrl).then((res) => {
|
||||
row.ddlData = {
|
||||
'SQLdiff': res.data.diff_ddl,
|
||||
'sourceSQL': res.data.source_ddl,
|
||||
'targetSQL': res.data.target_ddl
|
||||
};
|
||||
eventBus.fireEvent(SCHEMA_DIFF_EVENT.TRIGGER_CHANGE_RESULT_SQL, row.ddlData);
|
||||
}).catch((err) => {
|
||||
Notifier.alert(err.message);
|
||||
});
|
||||
} else {
|
||||
eventBus.fireEvent(SCHEMA_DIFF_EVENT.TRIGGER_CHANGE_RESULT_SQL, {});
|
||||
}
|
||||
}, 250);
|
||||
};
|
||||
|
||||
function rowKeyGetter(row) {
|
||||
@@ -714,7 +724,7 @@ export function ResultGridComponent({ gridData, allRowIds, filterParams, selecte
|
||||
}}
|
||||
headerRowHeight={28}
|
||||
rowHeight={28}
|
||||
onRowClick={rowSelection}
|
||||
onItemSelect={rowSelection}
|
||||
enableCellSelect={false}
|
||||
rowKeyGetter={rowKeyGetter}
|
||||
direction={'vertical-lr'}
|
||||
|
||||
@@ -349,6 +349,7 @@ export function JsonTextEditor({row, column, onRowChange, onClose}) {
|
||||
return newVal;
|
||||
});
|
||||
const [localVal, setLocalVal] = React.useState(value);
|
||||
const [hasError, setHasError] = React.useState(false);
|
||||
|
||||
const onChange = React.useCallback((newVal)=>{
|
||||
setLocalVal(newVal);
|
||||
@@ -358,6 +359,10 @@ export function JsonTextEditor({row, column, onRowChange, onClose}) {
|
||||
onClose(false);
|
||||
return;
|
||||
}
|
||||
if(hasError) {
|
||||
Notifier.error(gettext('Invalid JSON input'));
|
||||
return;
|
||||
}
|
||||
onRowChange({ ...row, [column.key]: localVal}, true);
|
||||
onClose();
|
||||
};
|
||||
@@ -370,7 +375,7 @@ export function JsonTextEditor({row, column, onRowChange, onClose}) {
|
||||
value={localVal}
|
||||
options={{
|
||||
onChange: onChange,
|
||||
onError: (error)=>Notifier.error('Invalid Json: ' + error.message.split(':')[0]),
|
||||
onValidationError: (errors)=>{setHasError(Boolean(errors.length));}
|
||||
}}
|
||||
className={'jsoneditor-div'}
|
||||
/>
|
||||
|
||||
@@ -251,6 +251,9 @@ export function MainToolBar({containerRef, onFilterClick, onManageMacros}) {
|
||||
eventBus.registerListener(QUERY_TOOL_EVENTS.QUERY_CHANGED, (isDirty)=>{
|
||||
setDisableButton('save', !isDirty);
|
||||
});
|
||||
eventBus.registerListener(QUERY_TOOL_EVENTS.LOAD_FILE_DONE, ()=>{
|
||||
setDisableButton('save', true);
|
||||
});
|
||||
eventBus.registerListener(QUERY_TOOL_EVENTS.DATAGRID_CHANGED, (isDirty)=>{
|
||||
setDisableButton('save-data', !isDirty);
|
||||
});
|
||||
|
||||
@@ -29,6 +29,7 @@ from pgadmin.utils.exception import ConnectionLost, SSHTunnelConnectionLost,\
|
||||
from pgadmin.utils.master_password import get_crypt_key
|
||||
from pgadmin.utils.exception import ObjectGone
|
||||
from pgadmin.utils.passexec import PasswordExec
|
||||
from psycopg2.extensions import make_dsn
|
||||
|
||||
if config.SUPPORT_SSH_TUNNEL:
|
||||
from sshtunnel import SSHTunnelForwarder, BaseSSHTunnelForwarderError
|
||||
@@ -633,22 +634,23 @@ WHERE db.oid = {0}""".format(did))
|
||||
This function is used to create connection string based on the
|
||||
parameters.
|
||||
"""
|
||||
full_connection_string = \
|
||||
'host=\'{0}\' port=\'{1}\' dbname=\'{2}\' user=\'{3}\''.format(
|
||||
self.local_bind_host if self.use_ssh_tunnel else self.host,
|
||||
self.local_bind_port if self.use_ssh_tunnel else self.port,
|
||||
database, user)
|
||||
|
||||
self.display_connection_string = full_connection_string
|
||||
dsn_args = dict()
|
||||
dsn_args['host'] = \
|
||||
self.local_bind_host if self.use_ssh_tunnel else self.host
|
||||
dsn_args['port'] = \
|
||||
self.local_bind_port if self.use_ssh_tunnel else self.port
|
||||
dsn_args['dbname'] = database
|
||||
dsn_args['user'] = user
|
||||
if self.service is not None:
|
||||
dsn_args['service'] = self.service
|
||||
|
||||
# Make a copy to display the connection string on GUI.
|
||||
display_dsn_args = dsn_args.copy()
|
||||
# Password should not be visible into the connection string, so
|
||||
# setting the value with password to 'xxxxxxx'.
|
||||
if password:
|
||||
self.display_connection_string = "{0} password='xxxxxxx'".format(
|
||||
self.display_connection_string)
|
||||
|
||||
full_connection_string = "{0} password='{1}'".format(
|
||||
full_connection_string, password)
|
||||
display_dsn_args['password'] = 'xxxxxxx'
|
||||
dsn_args['password'] = password
|
||||
|
||||
# Loop through all the connection parameters set in the server dialog.
|
||||
if self.connection_params and isinstance(self.connection_params, dict):
|
||||
@@ -657,7 +659,7 @@ WHERE db.oid = {0}""".format(did))
|
||||
orig_value = value
|
||||
# Getting complete file path if the key is one of the below.
|
||||
if key in ['passfile', 'sslcert', 'sslkey', 'sslrootcert',
|
||||
'sslcrl', 'service', 'sslcrldir']:
|
||||
'sslcrl', 'sslcrldir']:
|
||||
with_complete_path = True
|
||||
value = get_complete_file_path(value)
|
||||
|
||||
@@ -666,11 +668,10 @@ WHERE db.oid = {0}""".format(did))
|
||||
value = self.local_bind_host if self.use_ssh_tunnel else \
|
||||
value
|
||||
|
||||
self.display_connection_string = "{0} {1}='{2}'".format(
|
||||
self.display_connection_string, key,
|
||||
orig_value if with_complete_path else value)
|
||||
dsn_args[key] = value
|
||||
display_dsn_args[key] = orig_value if with_complete_path else \
|
||||
value
|
||||
|
||||
full_connection_string = \
|
||||
"{0} {1}='{2}'".format(full_connection_string, key, value)
|
||||
self.display_connection_string = make_dsn(**display_dsn_args)
|
||||
|
||||
return full_connection_string
|
||||
return make_dsn(**dsn_args)
|
||||
@@ -61,9 +61,12 @@ class BrowserToolBarFeatureTest(BaseFeatureTest):
|
||||
self.test_table_name)
|
||||
|
||||
def test_query_tool_button(self):
|
||||
self.page.expand_database_node("Servers", self.server['name'],
|
||||
self.server['db_password'],
|
||||
self.test_db)
|
||||
self.assertTrue(self.page.expand_database_node("Servers",
|
||||
self.server['name'],
|
||||
self.server[
|
||||
'db_password'],
|
||||
self.test_db),
|
||||
'Tree is not expanded to database node')
|
||||
self.assertTrue(self.page.retry_click(
|
||||
(By.CSS_SELECTOR,
|
||||
BrowserToolBarLocators.open_query_tool_button_css),
|
||||
@@ -72,9 +75,12 @@ class BrowserToolBarFeatureTest(BaseFeatureTest):
|
||||
self.page.close_query_tool(prompt=False)
|
||||
|
||||
def test_view_data_tool_button(self):
|
||||
self.page.expand_tables_node("Servers", self.server['name'],
|
||||
self.server['db_password'], self.test_db,
|
||||
"public")
|
||||
self.assertTrue(
|
||||
self.page.expand_tables_node("Servers", self.server['name'],
|
||||
self.server['db_password'],
|
||||
self.test_db,
|
||||
"public"),
|
||||
'Tree is not expanded to table node')
|
||||
|
||||
table_node = self.page.check_if_element_exists_with_scroll(
|
||||
TreeAreaLocators.table_node(self.test_table_name))
|
||||
|
||||
@@ -40,6 +40,7 @@ class QueryToolJourneyTest(BaseFeatureTest):
|
||||
query_history_tab_id = "id-history"
|
||||
query_editor_tab_name = "Query Editor"
|
||||
query_editor_tab_id = "id-query"
|
||||
query_tool_opened = False
|
||||
|
||||
def before(self):
|
||||
self.test_table_name = "test_table" + str(
|
||||
@@ -421,10 +422,12 @@ class QueryToolJourneyTest(BaseFeatureTest):
|
||||
self.page.click_modal('Yes')
|
||||
|
||||
def _navigate_to_query_tool(self):
|
||||
self.page.expand_database_node("Server", self.server['name'],
|
||||
self.server['db_password'],
|
||||
self.test_db)
|
||||
self.page.open_query_tool()
|
||||
self.assertTrue(
|
||||
self.page.expand_database_node("Server", self.server['name'],
|
||||
self.server['db_password'],
|
||||
self.test_db),
|
||||
'Tree is not expanded to database node')
|
||||
self.query_tool_opened = self.page.open_query_tool()
|
||||
self.page.wait_for_spinner_to_disappear()
|
||||
|
||||
def _explain_query(self, query):
|
||||
@@ -498,7 +501,8 @@ class QueryToolJourneyTest(BaseFeatureTest):
|
||||
QueryToolLocators.new_row_xpath)
|
||||
|
||||
def after(self):
|
||||
self.page.close_query_tool()
|
||||
if self.query_tool_opened:
|
||||
self.page.close_query_tool()
|
||||
test_utils.delete_table(
|
||||
self.server, self.test_db, self.test_table_name)
|
||||
test_utils.delete_table(
|
||||
|
||||
@@ -291,13 +291,6 @@ describe('ERDCore', ()=>{
|
||||
]));
|
||||
});
|
||||
|
||||
it('dagreDistributeNodes', ()=>{
|
||||
spyOn(erdCoreObj.dagre_engine, 'redistribute');
|
||||
erdCoreObj.dagreDistributeNodes();
|
||||
expect(erdEngine.getLinkFactories().getFactory().calculateRoutingMatrix).toHaveBeenCalled();
|
||||
expect(erdCoreObj.dagre_engine.redistribute).toHaveBeenCalledWith(erdEngine.getModel());
|
||||
});
|
||||
|
||||
it('zoomIn', ()=>{
|
||||
spyOn(erdEngine.getModel(), 'getZoomLevel').and.returnValue(100);
|
||||
spyOn(erdCoreObj, 'repaint');
|
||||
|
||||
@@ -6235,13 +6235,6 @@ json5@^2.1.2, json5@^2.2.1:
|
||||
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
|
||||
integrity "sha1-eM1vGhm9wStz21rQxh79ZsHikoM= sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="
|
||||
|
||||
jsoneditor-react@^3.1.1:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/jsoneditor-react/-/jsoneditor-react-3.1.2.tgz#bc36356ac5ecb0d8f88d49a5ccbeecdd7f996164"
|
||||
integrity sha512-XqU8BMdIhrlS5HUnn7rGhgZw315bdJGQrf6NG5UH40FSw2xNirQrxnM05aeAplHkp8FNkzN2WX0tfvEWdl2UUA==
|
||||
dependencies:
|
||||
prop-types "^15.7.2"
|
||||
|
||||
jsoneditor@^9.5.4:
|
||||
version "9.9.2"
|
||||
resolved "https://registry.yarnpkg.com/jsoneditor/-/jsoneditor-9.9.2.tgz#176112f71acbf37e977aae8656a17bc33a7a4269"
|
||||
|
||||
Reference in new issue
Block a user