Show object breadcrumbs path along with its comment on object hover. #2078

pull/6190/head
Aditya Toshniwal 2023-04-26 11:18:16 +05:30 committed by GitHub
parent 60265b306f
commit 1e7517dc98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
99 changed files with 736 additions and 149 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 KiB

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 217 KiB

View File

@ -88,6 +88,30 @@ displayed in the *Browser* tree control:
catalogs, you can reduce the number of object types displayed to increase
speed.
Use the fields on the *Object Breadcrumbs* panel to change object breadcrumbs
related settings:
.. image:: images/preferences_browser_breadcrumbs.png
:alt: Preferences dialog object breadcrumbs section
:align: center
* Use *Enable object breadcrumbs?* to enable or disable object breadcrumbs
displayed on on object mouse hover.
* Use *Show comment with object breadcrumbs?* to enable or disable the
comment visibility which comes displayed with object breadcrumbs.
Use the fields on the *Processes* panel to change processes tab
related settings:
.. image:: images/preferences_browser_processes.png
:alt: Preferences dialog processes section
:align: center
* Change *Process details/logs retention days* to the number of days,
the process info and logs will be automatically cleared.
Use fields on the *Properties* panel to specify browser properties:
.. image:: images/preferences_browser_properties.png

View File

@ -10,7 +10,8 @@ import sys
from flask_babel import gettext
from pgadmin.utils.constants import PREF_LABEL_DISPLAY,\
PREF_LABEL_KEYBOARD_SHORTCUTS, PREF_LABEL_TABS_SETTINGS, \
PREF_LABEL_OPTIONS, QT_DEFAULT_PLACEHOLDER, VW_EDT_DEFAULT_PLACEHOLDER
PREF_LABEL_OPTIONS, QT_DEFAULT_PLACEHOLDER, VW_EDT_DEFAULT_PLACEHOLDER, \
PREF_LABEL_BREADCRUMBS
from flask import current_app
import config
@ -557,3 +558,25 @@ def register_browser_preferences(self):
' back to the default title with placeholders.'
)
)
self.preference.register(
'breadcrumbs', 'breadcrumbs_enable',
gettext("Enable object breadcrumbs?"),
'boolean',
True, category_label=PREF_LABEL_BREADCRUMBS,
help_str=gettext(
'Enable breadcrumbs to show the complete path of an object in the '
'object explorer. The breadcrumbs are displayed on object mouse '
'hover.'
)
)
self.preference.register(
'breadcrumbs', 'breadcrumbs_show_comment',
gettext("Show comment with object breadcrumbs?"),
'boolean',
True, category_label=PREF_LABEL_BREADCRUMBS,
help_str=gettext(
'Show object comment along with breadcrumbs.'
)
)

View File

@ -271,7 +271,8 @@ class ServerModule(sg.ServerGroupPluginModule):
shared=server.shared,
is_kerberos_conn=bool(server.kerberos_conn),
gss_authenticated=manager.gss_authenticated,
cloud_status=server.cloud_status
cloud_status=server.cloud_status,
description=server.comment
)
@property
@ -595,7 +596,8 @@ class ServerNode(PGChildNodeView):
username=server.username,
shared=server.shared,
is_kerberos_conn=bool(server.kerberos_conn),
gss_authenticated=manager.gss_authenticated
gss_authenticated=manager.gss_authenticated,
description=server.comment
)
)
@ -846,7 +848,8 @@ class ServerNode(PGChildNodeView):
server_type='pg', # default server type
username=server.username,
role=server.role,
is_password_saved=bool(server.save_password)
is_password_saved=bool(server.save_password),
description=server.comment
)
)

View File

@ -386,7 +386,8 @@ class DatabaseView(PGChildNodeView):
canDisconn=can_dis_conn,
canDrop=can_drop,
isTemplate=row['is_template'],
inode=True if row['datallowconn'] else False
inode=True if row['datallowconn'] else False,
description=row['description']
)
)

View File

@ -282,7 +282,8 @@ class CastView(PGChildNodeView, SchemaDiffObjectCompare):
row['oid'],
did,
row['name'],
icon="icon-cast"
icon="icon-cast",
description=row['description']
))
return make_json_response(
@ -449,12 +450,17 @@ class CastView(PGChildNodeView, SchemaDiffObjectCompare):
if not status:
return internal_server_error(errormsg=res)
other_node_info = {}
if 'description' in data:
other_node_info['description'] = data['description']
return jsonify(
node=self.blueprint.generate_browser_node(
cid,
did,
name,
"icon-{0}".format(self.node_type)
"icon-{0}".format(self.node_type),
**other_node_info
)
)

View File

@ -1,6 +1,7 @@
SELECT
ca.oid,
pg_catalog.concat(pg_catalog.format_type(st.oid,NULL),'->',pg_catalog.format_type(tt.oid,tt.typtypmod)) as name
pg_catalog.concat(pg_catalog.format_type(st.oid,NULL),'->',pg_catalog.format_type(tt.oid,tt.typtypmod)) as name,
des.description
FROM pg_catalog.pg_cast ca
JOIN pg_catalog.pg_type st ON st.oid=castsource
JOIN pg_catalog.pg_namespace ns ON ns.oid=st.typnamespace

View File

@ -264,7 +264,8 @@ class EventTriggerView(PGChildNodeView, SchemaDiffObjectCompare):
row['oid'],
did,
row['name'],
self.node_icon
self.node_icon,
description=row['comment']
))
return make_json_response(
@ -481,12 +482,17 @@ class EventTriggerView(PGChildNodeView, SchemaDiffObjectCompare):
)
status, etid = self.conn.execute_scalar(sql)
other_node_info = {}
if 'comment' in data:
other_node_info['description'] = data['comment']
return jsonify(
node=self.blueprint.generate_browser_node(
etid,
did,
data['name'],
self.node_icon
self.node_icon,
**other_node_info
)
)
else:

View File

@ -1,4 +1,5 @@
SELECT e.oid, e.evtname AS name
SELECT e.oid, e.evtname AS name,
pg_catalog.obj_description(e.oid, 'pg_event_trigger') AS comment
FROM pg_catalog.pg_event_trigger e
{% if etid %}
WHERE e.oid={{etid}}::oid

View File

@ -188,7 +188,8 @@ class ExtensionView(PGChildNodeView, SchemaDiffObjectCompare):
row['oid'],
did,
row['name'],
'icon-extension'
'icon-extension',
description=row['comment']
))
return make_json_response(
@ -327,12 +328,17 @@ class ExtensionView(PGChildNodeView, SchemaDiffObjectCompare):
if not status:
return internal_server_error(errormsg=res)
other_node_info = {}
if 'comment' in data:
other_node_info['description'] = data['comment']
return jsonify(
node=self.blueprint.generate_browser_node(
eid,
did,
name,
icon="icon-%s" % self.node_type
icon="icon-%s" % self.node_type,
**other_node_info
)
)
except Exception as e:

View File

@ -299,7 +299,8 @@ class ForeignDataWrapperView(PGChildNodeView, SchemaDiffObjectCompare):
row['oid'],
did,
row['name'],
icon="icon-foreign_data_wrapper"
icon="icon-foreign_data_wrapper",
description=row['description']
))
return make_json_response(
@ -505,12 +506,17 @@ class ForeignDataWrapperView(PGChildNodeView, SchemaDiffObjectCompare):
if not status:
return internal_server_error(errormsg=res)
other_node_info = {}
if 'description' in data:
other_node_info['description'] = data['description']
return jsonify(
node=self.blueprint.generate_browser_node(
fid,
did,
name,
icon="icon-%s" % self.node_type
icon="icon-%s" % self.node_type,
**other_node_info
)
)
except Exception as e:

View File

@ -290,7 +290,8 @@ class ForeignServerView(PGChildNodeView, SchemaDiffObjectCompare):
row['oid'],
fid,
row['name'],
icon="icon-foreign_server"
icon="icon-foreign_server",
description=row['description']
))
return make_json_response(
@ -503,12 +504,17 @@ class ForeignServerView(PGChildNodeView, SchemaDiffObjectCompare):
if not status:
return internal_server_error(errormsg=res)
other_node_info = {}
if 'description' in data:
other_node_info['description'] = data['description']
return jsonify(
node=self.blueprint.generate_browser_node(
fsid,
fid,
name,
icon="icon-%s" % self.node_type
icon="icon-%s" % self.node_type,
**other_node_info
)
)

View File

@ -296,7 +296,8 @@ class LanguageView(PGChildNodeView, SchemaDiffObjectCompare):
row['oid'],
did,
row['name'],
icon="icon-language"
icon="icon-language",
description=row['description']
))
return make_json_response(
@ -434,12 +435,17 @@ class LanguageView(PGChildNodeView, SchemaDiffObjectCompare):
if not status:
return internal_server_error(errormsg=res)
other_node_info = {}
if 'description' in data:
other_node_info['description'] = data['description']
return jsonify(
node=self.blueprint.generate_browser_node(
lid,
did,
name,
icon="icon-%s" % self.node_type
icon="icon-%s" % self.node_type,
**other_node_info
)
)
except Exception as e:

View File

@ -540,7 +540,8 @@ class SchemaView(PGChildNodeView):
row['name'],
icon=self.node_icon,
can_create=row['can_create'],
has_usage=row['has_usage']
has_usage=row['has_usage'],
description=row['description']
),
status=200
)
@ -553,7 +554,8 @@ class SchemaView(PGChildNodeView):
row['name'],
icon=self.node_icon,
can_create=row['can_create'],
has_usage=row['has_usage']
has_usage=row['has_usage'],
description=row['description']
)
)
@ -750,12 +752,17 @@ It may have been removed by another user.
if not status:
return internal_server_error(errormsg=res)
other_node_info = {}
if 'description' in data:
other_node_info['description'] = data['description']
return jsonify(
node=self.blueprint.generate_browser_node(
scid,
did,
name,
icon=self.node_icon
icon=self.node_icon,
**other_node_info
)
)
except Exception as e:

View File

@ -231,7 +231,8 @@ class AggregateView(PGChildNodeView):
row['oid'],
scid,
row['name'],
icon="icon-aggregate"
icon="icon-aggregate",
description=row['description']
))
return make_json_response(

View File

@ -1,6 +1,7 @@
SELECT aggfnoid::oid as oid,
proname || '(' || COALESCE(pg_catalog.pg_get_function_arguments(aggfnoid::oid), '') || ')' AS name,
pg_catalog.pg_get_userbyid(proowner) AS owner
pg_catalog.pg_get_userbyid(proowner) AS owner,
description
FROM pg_aggregate ag
LEFT OUTER JOIN pg_catalog.pg_proc pr ON pr.oid = ag.aggfnoid
LEFT OUTER JOIN pg_catalog.pg_type tt on tt.oid=aggtranstype

View File

@ -235,7 +235,8 @@ class CatalogObjectView(PGChildNodeView):
row['oid'],
scid,
row['name'],
icon="icon-catalog_object"
icon="icon-catalog_object",
description=row['description']
))
return make_json_response(

View File

@ -246,7 +246,8 @@ class CatalogObjectColumnsView(PGChildNodeView):
row['attnum'],
coid,
row['attname'],
icon="icon-catalog_object_column"
icon="icon-catalog_object_column",
description=row['description']
))
return make_json_response(

View File

@ -1,6 +1,7 @@
SELECT
attnum, attname
attnum, attname, des.description
FROM pg_catalog.pg_attribute att
LEFT OUTER JOIN pg_catalog.pg_description des ON (des.objoid=att.attrelid AND des.objsubid=att.attnum AND des.classoid='pg_class'::regclass)
WHERE att.attrelid = {{coid}}::oid
AND att.attnum > 0
AND att.attisdropped IS FALSE

View File

@ -1,7 +1,9 @@
SELECT
c.oid, c.relname as name
c.oid, c.relname as name, description
FROM
pg_catalog.pg_class c
LEFT OUTER JOIN pg_catalog.pg_description d
ON d.objoid=c.oid AND d.classoid='pg_class'::regclass
{% if scid %}
WHERE relnamespace = {{scid}}::oid
{% elif coid %}

View File

@ -268,7 +268,8 @@ class CollationView(PGChildNodeView, SchemaDiffObjectCompare):
row['oid'],
scid,
row['name'],
icon="icon-collation"
icon="icon-collation",
description=row['description']
))
return make_json_response(
@ -604,12 +605,17 @@ class CollationView(PGChildNodeView, SchemaDiffObjectCompare):
scid = res['rows'][0]['scid']
other_node_info = {}
if 'description' in data:
other_node_info['description'] = data['description']
return jsonify(
node=self.blueprint.generate_browser_node(
coid,
scid,
name,
icon="icon-%s" % self.node_type
icon="icon-%s" % self.node_type,
**other_node_info
)
)

View File

@ -1,5 +1,6 @@
SELECT c.oid, c.collname AS name
SELECT c.oid, c.collname AS name, des.description
FROM pg_catalog.pg_collation c
LEFT OUTER JOIN pg_catalog.pg_description des ON (des.objoid=c.oid AND des.classoid='pg_collation'::regclass)
{% if scid %}
WHERE c.collnamespace = {{scid}}::oid
{% elif coid %}

View File

@ -358,7 +358,8 @@ class DomainView(PGChildNodeView, DataTypeReader, SchemaDiffObjectCompare):
row['oid'],
scid,
row['name'],
icon="icon-domain"
icon="icon-domain",
description=row['description']
))
return make_json_response(
@ -713,12 +714,17 @@ AND relkind != 'c'))"""
if not status:
return internal_server_error(errormsg=scid)
other_node_info = {}
if 'description' in self.request:
other_node_info['description'] = self.request['description']
return jsonify(
node=self.blueprint.generate_browser_node(
doid,
scid,
name,
icon="icon-%s" % self.node_type
icon="icon-%s" % self.node_type,
**other_node_info
)
)

View File

@ -333,7 +333,8 @@ class DomainConstraintView(PGChildNodeView):
row['oid'],
doid,
row['name'],
icon=icon
icon=icon,
description=row['description']
))
return make_json_response(
@ -559,12 +560,17 @@ class DomainConstraintView(PGChildNodeView):
else:
icon = ''
other_node_info = {}
if 'description' in data:
other_node_info['description'] = data['description']
return jsonify(
node=self.blueprint.generate_browser_node(
coid,
doid,
name,
icon=icon
icon=icon,
**other_node_info
)
)
else:

View File

@ -1,12 +1,14 @@
SELECT
d.oid, d.typname as name, pg_catalog.pg_get_userbyid(d.typowner) as owner,
bn.nspname as basensp
bn.nspname as basensp, des.description
FROM
pg_catalog.pg_type d
JOIN
pg_catalog.pg_type b ON b.oid = d.typbasetype
JOIN
pg_catalog.pg_namespace bn ON bn.oid=d.typnamespace
LEFT OUTER JOIN
pg_catalog.pg_description des ON (des.objoid=d.oid AND des.classoid='pg_type'::regclass)
{% if scid is defined %}
WHERE
d.typnamespace = {{scid}}::oid

View File

@ -447,7 +447,8 @@ class ForeignTableView(PGChildNodeView, DataTypeReader,
row['oid'],
scid,
row['name'],
icon="icon-foreign_table"
icon="icon-foreign_table",
description=row['description']
))
return make_json_response(
@ -835,12 +836,17 @@ class ForeignTableView(PGChildNodeView, DataTypeReader,
scid = res['rows'][0]['scid']
other_node_info = {}
if 'description' in self.request:
other_node_info['description'] = self.request['description']
return jsonify(
node=self.blueprint.generate_browser_node(
foid,
scid,
name,
icon="icon-%s" % self.node_type
icon="icon-%s" % self.node_type,
**other_node_info
)
)
except Exception as e:

View File

@ -296,7 +296,8 @@ class FtsConfigurationView(PGChildNodeView, SchemaDiffObjectCompare):
row['oid'],
scid,
row['name'],
icon="icon-fts_configuration"
icon="icon-fts_configuration",
description=row['description']
))
return make_json_response(
@ -539,12 +540,17 @@ class FtsConfigurationView(PGChildNodeView, SchemaDiffObjectCompare):
_("Could not find the FTS Configuration node to update.")
)
other_node_info = {}
if 'description' in data:
other_node_info['description'] = data['description']
return jsonify(
node=self.blueprint.generate_browser_node(
cfgid,
data['schema'] if 'schema' in data else scid,
name,
icon="icon-%s" % self.node_type
icon="icon-%s" % self.node_type,
**other_node_info
)
)

View File

@ -1,8 +1,10 @@
{# FETCH FTS CONFIGURATION NAME statement #}
SELECT
oid, cfgname as name
cfg.oid, cfgname as name, des.description
FROM
pg_catalog.pg_ts_config cfg
LEFT OUTER JOIN pg_catalog.pg_description des
ON (des.objoid=cfg.oid AND des.classoid='pg_ts_config'::regclass)
WHERE
{% if scid %}
cfg.cfgnamespace = {{scid}}::OID

View File

@ -308,7 +308,8 @@ class FtsDictionaryView(PGChildNodeView, SchemaDiffObjectCompare):
row['oid'],
scid,
row['name'],
icon="icon-fts_dictionary"
icon="icon-fts_dictionary",
description=row['description']
))
return make_json_response(
@ -530,12 +531,17 @@ class FtsDictionaryView(PGChildNodeView, SchemaDiffObjectCompare):
_("Could not find the FTS Dictionary node to update.")
)
other_node_info = {}
if 'description' in data:
other_node_info['description'] = data['description']
return jsonify(
node=self.blueprint.generate_browser_node(
dcid,
res['rows'][0]['schema'],
name,
icon="icon-%s" % self.node_type
icon="icon-%s" % self.node_type,
**other_node_info
)
)

View File

@ -1,9 +1,11 @@
{# Fetch FTS DICTIONARY name statement #}
SELECT
oid, dictname as name,
dictnamespace as schema
dict.oid, dictname as name,
dictnamespace as schema,
des.description
FROM
pg_catalog.pg_ts_dict dict
LEFT OUTER JOIN pg_catalog.pg_description des ON (des.objoid=dict.oid AND des.classoid='pg_ts_dict'::regclass)
WHERE
{% if scid %}
dict.dictnamespace = {{scid}}::OID

View File

@ -279,7 +279,8 @@ class FtsParserView(PGChildNodeView, SchemaDiffObjectCompare):
row['oid'],
scid,
row['name'],
icon="icon-fts_parser"
icon="icon-fts_parser",
description=row['description']
))
return make_json_response(
@ -477,12 +478,17 @@ class FtsParserView(PGChildNodeView, SchemaDiffObjectCompare):
_("Could not find the FTS Parser node to update.")
)
other_node_info = {}
if 'description' in data:
other_node_info['description'] = data['description']
return jsonify(
node=self.blueprint.generate_browser_node(
pid,
data['schema'] if 'schema' in data else scid,
name,
icon="icon-%s" % self.node_type
icon="icon-%s" % self.node_type,
**other_node_info
)
)

View File

@ -1,8 +1,14 @@
{# FETCH FTS PARSER name statement #}
SELECT
oid, prsname as name, prs.prsnamespace AS schema
prs.oid, prsname as name, prs.prsnamespace AS schema, des.description
FROM
pg_catalog.pg_ts_parser prs
LEFT OUTER JOIN pg_catalog.pg_description des
ON
(
des.objoid=prs.oid
AND des.classoid='pg_ts_parser'::regclass
)
WHERE
{% if scid %}
prs.prsnamespace = {{scid}}::OID

View File

@ -257,7 +257,8 @@ class FtsTemplateView(PGChildNodeView, SchemaDiffObjectCompare):
row['oid'],
scid,
row['name'],
icon="icon-fts_template"
icon="icon-fts_template",
description=row['description']
))
return make_json_response(
@ -445,7 +446,8 @@ class FtsTemplateView(PGChildNodeView, SchemaDiffObjectCompare):
tid,
rset['schema'],
rset['name'],
icon="icon-%s" % self.node_type
icon="icon-%s" % self.node_type,
description=rset['description']
)
)

View File

@ -1,7 +1,13 @@
SELECT
oid, tmplname as name, tmpl.tmplnamespace AS schema
tmpl.oid, tmplname as name, tmpl.tmplnamespace AS schema, des.description
FROM
pg_catalog.pg_ts_template tmpl
LEFT OUTER JOIN pg_catalog.pg_description des
ON
(
des.objoid=tmpl.oid
AND des.classoid='pg_ts_template'::regclass
)
WHERE
{% if scid %}
tmpl.tmplnamespace = {{scid}}::OID

View File

@ -435,7 +435,8 @@ class FunctionView(PGChildNodeView, DataTypeReader, SchemaDiffObjectCompare):
row['name'],
icon="icon-" + self.node_type,
funcowner=row['funcowner'],
language=row['lanname']
language=row['lanname'],
description=row['description']
)
)
@ -447,7 +448,8 @@ class FunctionView(PGChildNodeView, DataTypeReader, SchemaDiffObjectCompare):
row['name'],
icon="icon-" + self.node_type,
funcowner=row['funcowner'],
language=row['lanname']
language=row['lanname'],
description=row['description']
))
return make_json_response(
@ -977,7 +979,8 @@ class FunctionView(PGChildNodeView, DataTypeReader, SchemaDiffObjectCompare):
obj_name,
icon="icon-" + self.node_type,
language=resp_data['lanname'],
funcowner=resp_data['funcowner']
funcowner=resp_data['funcowner'],
description=resp_data['description']
)
)
else:

View File

@ -230,7 +230,8 @@ class OperatorView(PGChildNodeView):
row['oid'],
scid,
row['name'],
icon="icon-operator"
icon="icon-operator",
description=row['description']
))
return make_json_response(

View File

@ -7,7 +7,7 @@ SELECT op.oid, pg_catalog.pg_get_userbyid(op.oprowner) as owner,
op.oprname || ' (' || pg_catalog.format_type(lt.oid, NULL) || ')'
ELSE op.oprname || '()'
END as name,
lt.typname as lefttype, rt.typname as righttype
lt.typname as lefttype, rt.typname as righttype, description
FROM pg_catalog.pg_operator op
LEFT OUTER JOIN pg_catalog.pg_type lt ON lt.oid=op.oprleft
LEFT OUTER JOIN pg_catalog.pg_type rt ON rt.oid=op.oprright

View File

@ -239,7 +239,8 @@ class PackageView(PGChildNodeView, SchemaDiffObjectCompare):
row['oid'],
scid,
row['name'],
icon=self.node_icon
icon=self.node_icon,
description=row['description']
)
)
@ -249,7 +250,8 @@ class PackageView(PGChildNodeView, SchemaDiffObjectCompare):
row['oid'],
scid,
row['name'],
icon=self.node_icon
icon=self.node_icon,
description=row['description']
))
return make_json_response(
@ -527,12 +529,17 @@ class PackageView(PGChildNodeView, SchemaDiffObjectCompare):
if not status:
return internal_server_error(errormsg=res)
other_node_info = {}
if 'description' in data:
other_node_info['description'] = data['description']
return jsonify(
node=self.blueprint.generate_browser_node(
pkgid,
scid,
name,
icon=self.node_icon
icon=self.node_icon,
**other_node_info
)
)

View File

@ -1,7 +1,8 @@
SELECT
nsp.oid, nspname AS name
nsp.oid, nspname AS name, des.description
FROM
pg_catalog.pg_namespace nsp
LEFT OUTER JOIN pg_catalog.pg_description des ON (des.objoid=nsp.oid AND des.classoid='pg_namespace'::regclass)
WHERE nspparent = {{scid}}::oid
{% if pkgid %}
AND nsp.oid = {{pkgid}}::oid

View File

@ -229,7 +229,9 @@ class SequenceView(PGChildNodeView, SchemaDiffObjectCompare):
row['oid'],
scid,
row['name'],
icon=self.node_icon
icon=self.node_icon,
description=row['comment']
),
status=200
)
@ -240,7 +242,8 @@ class SequenceView(PGChildNodeView, SchemaDiffObjectCompare):
row['oid'],
scid,
row['name'],
icon=self.node_icon
icon=self.node_icon,
description=row['description']
))
return make_json_response(
@ -563,7 +566,8 @@ class SequenceView(PGChildNodeView, SchemaDiffObjectCompare):
seid,
row['schema'],
row['name'],
icon=self.node_icon
icon=self.node_icon,
description=row['comment']
)
)

View File

@ -1,5 +1,7 @@
SELECT cl.oid as oid, relname as name, relnamespace as schema
SELECT cl.oid as oid, relname as name, relnamespace as schema, description as comment
FROM pg_catalog.pg_class cl
LEFT OUTER JOIN pg_catalog.pg_description des ON (des.objoid=cl.oid
AND des.classoid='pg_class'::regclass)
{% if show_internal %}
LEFT JOIN pg_catalog.pg_depend d1 ON d1.refobjid = cl.oid AND d1.deptype = 'i'
{% endif %}

View File

@ -438,7 +438,8 @@ class TableView(BaseTableView, DataTypeReader, SchemaDiffTableCompare):
icon=icon,
tigger_count=row['triggercount'],
has_enable_triggers=row['has_enable_triggers'],
is_partitioned=self.is_table_partitioned(row)
is_partitioned=self.is_table_partitioned(row),
description=row['description']
))
return make_json_response(

View File

@ -291,7 +291,8 @@ class ColumnsView(PGChildNodeView, DataTypeReader):
tid,
row['name'],
icon="icon-column",
datatype=row['datatype'] # We need datatype somewhere in
datatype=row['datatype'], # We need datatype somewhere in,
description=row['description']
),
status=200
)
@ -303,7 +304,8 @@ class ColumnsView(PGChildNodeView, DataTypeReader):
tid,
row['name'],
icon="icon-column",
datatype=row['datatype'] # We need datatype somewhere in
datatype=row['datatype'], # We need datatype somewhere in
description=row['description']
)) # exclusion constraint.
return make_json_response(
@ -535,12 +537,17 @@ class ColumnsView(PGChildNodeView, DataTypeReader):
if not status:
return internal_server_error(errormsg=res)
other_node_info = {}
if 'description' in data:
other_node_info['description'] = data['description']
return jsonify(
node=self.blueprint.generate_browser_node(
clid,
tid,
name,
icon="icon-%s" % self.node_type
icon="icon-%s" % self.node_type,
**other_node_info
)
)

View File

@ -398,7 +398,8 @@ class CompoundTriggerView(PGChildNodeView, SchemaDiffObjectCompare):
row['name'],
icon="icon-compound_trigger-bad"
if row['is_enable_trigger'] == 'D'
else "icon-compound_trigger"
else "icon-compound_trigger",
description=row['description']
))
return make_json_response(
@ -671,7 +672,8 @@ class CompoundTriggerView(PGChildNodeView, SchemaDiffObjectCompare):
name,
icon="icon-%s-bad" % self.node_type if
data['is_enable_trigger'] == 'D' else
"icon-%s" % self.node_type
"icon-%s" % self.node_type,
description=data['description']
)
)
except Exception as e:

View File

@ -359,7 +359,8 @@ class CheckConstraintView(PGChildNodeView):
tid,
row['name'],
icon=icon,
valid=valid
valid=valid,
description=row['comment']
))
return make_json_response(
data=res,
@ -699,13 +700,18 @@ class CheckConstraintView(PGChildNodeView):
icon = 'icon-check_constraint'
valid = True
other_node_info = {}
if 'comment' in data:
other_node_info['description'] = data['comment']
return jsonify(
node=self.blueprint.generate_browser_node(
cid,
tid,
name,
icon=icon,
valid=valid
valid=valid,
**other_node_info
)
)
except Exception as e:

View File

@ -419,7 +419,8 @@ class ExclusionConstraintView(PGChildNodeView):
row['oid'],
tid,
row['name'],
icon="icon-exclusion_constraint"
icon="icon-exclusion_constraint",
description=row['comment']
))
return make_json_response(
data=res,
@ -639,12 +640,17 @@ class ExclusionConstraintView(PGChildNodeView):
if not status:
return internal_server_error(errormsg=res)
other_node_info = {}
if 'comment' in data:
other_node_info['description'] = data['comment']
return jsonify(
node=self.blueprint.generate_browser_node(
exid,
tid,
name,
icon="icon-%s" % self.node_type
icon="icon-%s" % self.node_type,
**other_node_info
)
)

View File

@ -442,7 +442,8 @@ class ForeignKeyConstraintView(PGChildNodeView):
tid,
row['name'],
icon=icon,
valid=valid
valid=valid,
description=row['comment']
))
return make_json_response(
data=res,
@ -702,6 +703,10 @@ class ForeignKeyConstraintView(PGChildNodeView):
icon = "icon-foreign_key"
valid = True
other_node_info = {}
if 'comment' in data:
other_node_info['description'] = data['comment']
return jsonify(
node=self.blueprint.generate_browser_node(
fkid,

View File

@ -442,7 +442,8 @@ class IndexConstraintView(PGChildNodeView):
row['oid'],
tid,
row['name'],
icon=self.node_icon
icon=self.node_icon,
description=row['comment']
)
)
return make_json_response(
@ -686,6 +687,10 @@ class IndexConstraintView(PGChildNodeView):
if not status:
return internal_server_error(errormsg=res)
other_node_info = {}
if 'comment' in data:
other_node_info['description'] = data['comment']
return jsonify(
node=self.blueprint.generate_browser_node(
cid,

View File

@ -462,7 +462,8 @@ class IndexesView(PGChildNodeView, SchemaDiffObjectCompare):
row['oid'],
tid,
row['name'],
icon="icon-index"
icon="icon-index",
description=row['description']
))
return make_json_response(
@ -770,12 +771,17 @@ class IndexesView(PGChildNodeView, SchemaDiffObjectCompare):
if not status:
return internal_server_error(errormsg=res)
other_node_info = {}
if 'description' in data:
other_node_info['description'] = data['description']
return jsonify(
node=self.blueprint.generate_browser_node(
idx,
tid,
name,
icon="icon-%s" % self.node_type
icon="icon-%s" % self.node_type,
**other_node_info
)
)
except Exception as e:

View File

@ -306,7 +306,8 @@ class PartitionsView(BaseTableView, DataTypeReader, SchemaDiffObjectCompare):
is_partitioned=row['is_partitioned'],
parent_schema_id=scid,
schema_id=row['schema_id'],
schema_name=row['schema_name']
schema_name=row['schema_name'],
description=row['description']
)
if ptid is not None:

View File

@ -274,7 +274,8 @@ class RuleView(PGChildNodeView, SchemaDiffObjectCompare):
row['name'],
icon="icon-rule-bad"
if 'is_enable_rule' in row and
row['is_enable_rule'] == 'D' else "icon-rule"
row['is_enable_rule'] == 'D' else "icon-rule",
description=row['comment']
))
return make_json_response(
@ -381,6 +382,11 @@ class RuleView(PGChildNodeView, SchemaDiffObjectCompare):
status, res = self.conn.execute_scalar(SQL)
if not status:
return internal_server_error(errormsg=res)
other_node_info = {}
if 'comment' in data:
other_node_info['description'] = data['comment']
return jsonify(
node=self.blueprint.generate_browser_node(
rid,
@ -389,7 +395,8 @@ class RuleView(PGChildNodeView, SchemaDiffObjectCompare):
icon="icon-%s-bad" % self.node_type
if 'is_enable_rule' in data and
data['is_enable_rule'] == 'D'
else "icon-%s" % self.node_type
else "icon-%s" % self.node_type,
**other_node_info
)
)
except Exception as e:

View File

@ -1,6 +1,9 @@
SELECT c.oid, conname as name,
NOT convalidated as convalidated, conislocal
NOT convalidated as convalidated, conislocal, description as comment
FROM pg_catalog.pg_constraint c
LEFT OUTER JOIN
pg_catalog.pg_description des ON (des.objoid=c.oid AND
des.classoid='pg_constraint'::regclass)
WHERE contype = 'c'
AND conrelid = {{ tid }}::oid
{% if cid %}

View File

@ -1,5 +1,5 @@
SELECT DISTINCT att.attname as name, att.attnum as OID, pg_catalog.format_type(ty.oid,NULL) AS datatype,
att.attnotnull as not_null, att.atthasdef as has_default_val
att.attnotnull as not_null, att.atthasdef as has_default_val, des.description
FROM pg_catalog.pg_attribute att
JOIN pg_catalog.pg_type ty ON ty.oid=atttypid
JOIN pg_catalog.pg_namespace tn ON tn.oid=ty.typnamespace
@ -10,6 +10,7 @@ FROM pg_catalog.pg_attribute att
LEFT OUTER JOIN (pg_catalog.pg_depend JOIN pg_catalog.pg_class cs ON classid='pg_class'::regclass AND objid=cs.oid AND cs.relkind='S') ON refobjid=att.attrelid AND refobjsubid=att.attnum
LEFT OUTER JOIN pg_catalog.pg_namespace ns ON ns.oid=cs.relnamespace
LEFT OUTER JOIN pg_catalog.pg_index pi ON pi.indrelid=att.attrelid AND indisprimary
LEFT OUTER JOIN pg_catalog.pg_description des ON (des.objoid=att.attrelid AND des.objsubid=att.attnum AND des.classoid='pg_class'::regclass)
WHERE
att.attrelid = {{ tid|qtLiteral(conn) }}::oid
{% if clid %}

View File

@ -1,6 +1,6 @@
SELECT t.oid, t.tgname as name, t.tgenabled AS is_enable_trigger
SELECT t.oid, t.tgname as name, t.tgenabled AS is_enable_trigger, des.description
FROM pg_catalog.pg_trigger t
LEFT OUTER JOIN pg_catalog.pg_description des ON (des.objoid=t.oid AND des.classoid='pg_trigger'::regclass)
WHERE NOT tgisinternal
AND tgrelid = {{tid}}::OID
AND tgpackageoid != 0

View File

@ -1,7 +1,9 @@
SELECT conindid as oid,
conname as name,
NOT convalidated as convalidated
NOT convalidated as convalidated,
desp.description AS comment
FROM pg_catalog.pg_constraint ct
LEFT OUTER JOIN pg_catalog.pg_description desp ON (desp.objoid=ct.oid AND desp.objsubid = 0 AND desp.classoid='pg_constraint'::regclass)
WHERE contype='x' AND
conrelid = {{tid}}::oid
{% if exid %}

View File

@ -1,7 +1,9 @@
SELECT ct.oid,
conname as name,
NOT convalidated as convalidated
NOT convalidated as convalidated,
description as comment
FROM pg_catalog.pg_constraint ct
LEFT OUTER JOIN pg_catalog.pg_description des ON (des.objoid=ct.oid AND des.classoid='pg_constraint'::regclass)
WHERE contype='f' AND
conrelid = {{tid}}::oid
ORDER BY conname

View File

@ -1,4 +1,10 @@
SELECT cls.oid, cls.relname as name
SELECT cls.oid, cls.relname as name,
CASE contype
WHEN 'p' THEN desp.description
WHEN 'u' THEN desp.description
WHEN 'x' THEN desp.description
ELSE des.description
END AS comment
FROM pg_catalog.pg_index idx
JOIN pg_catalog.pg_class cls ON cls.oid=indexrelid
LEFT JOIN pg_catalog.pg_depend dep ON (dep.classid = cls.tableoid AND
@ -10,6 +16,8 @@ LEFT JOIN pg_catalog.pg_depend dep ON (dep.classid = cls.tableoid AND
dep.deptype='i')
LEFT OUTER JOIN pg_catalog.pg_constraint con ON (con.tableoid = dep.refclassid AND
con.oid = dep.refobjid)
LEFT OUTER JOIN pg_catalog.pg_description des ON (des.objoid=cls.oid AND des.classoid='pg_class'::regclass)
LEFT OUTER JOIN pg_catalog.pg_description desp ON (desp.objoid=con.oid AND desp.objsubid = 0 AND desp.classoid='pg_constraint'::regclass)
WHERE indrelid = {{tid}}::oid
AND contype='{{constraint_type}}'
{% if cid %}

View File

@ -2,10 +2,11 @@ SELECT
rw.oid AS oid,
rw.rulename AS name,
CASE WHEN rw.ev_enabled != 'D' THEN True ELSE False END AS enabled,
rw.ev_enabled AS is_enable_rule
rw.ev_enabled AS is_enable_rule,
description AS comment
FROM
pg_catalog.pg_rewrite rw
LEFT OUTER JOIN pg_catalog.pg_description des ON (des.objoid=rw.oid AND des.classoid='pg_rewrite'::regclass)
WHERE
{% if tid %}
rw.ev_class = {{ tid }}

View File

@ -3,8 +3,10 @@ SELECT rel.oid, rel.relname AS name,
(SELECT count(*) FROM pg_catalog.pg_trigger WHERE tgrelid=rel.oid AND tgisinternal = FALSE AND tgenabled = 'O') AS has_enable_triggers,
(CASE WHEN rel.relkind = 'p' THEN true ELSE false END) AS is_partitioned,
(SELECT count(1) FROM pg_catalog.pg_inherits WHERE inhrelid=rel.oid LIMIT 1) as is_inherits,
(SELECT count(1) FROM pg_catalog.pg_inherits WHERE inhparent=rel.oid LIMIT 1) as is_inherited
(SELECT count(1) FROM pg_catalog.pg_inherits WHERE inhparent=rel.oid LIMIT 1) as is_inherited,
des.description
FROM pg_catalog.pg_class rel
LEFT OUTER JOIN pg_catalog.pg_description des ON (des.objoid=rel.oid AND des.objsubid=0 AND des.classoid='pg_class'::regclass)
WHERE rel.relkind IN ('r','s','t','p') AND rel.relnamespace = {{ scid }}::oid
AND NOT rel.relispartition
{% if tid %} AND rel.oid = {{tid}}::OID {% endif %}

View File

@ -1,6 +1,7 @@
SELECT t.oid, t.tgname as name, t.tgenabled AS is_enable_trigger
SELECT t.oid, t.tgname as name, t.tgenabled AS is_enable_trigger, des.description
FROM pg_catalog.pg_trigger t
WHERE NOT tgisinternal
LEFT OUTER JOIN pg_catalog.pg_description des ON (des.objoid=t.oid AND des.classoid='pg_trigger'::regclass)
WHERE NOT tgisinternal
AND tgrelid = {{tid}}::OID
{% if trid %}
AND t.oid = {{trid}}::OID

View File

@ -1,6 +1,7 @@
SELECT t.oid, t.tgname as name, t.tgenabled AS is_enable_trigger
SELECT t.oid, t.tgname as name, t.tgenabled AS is_enable_trigger, des.description
FROM pg_catalog.pg_trigger t
WHERE NOT tgisinternal
LEFT OUTER JOIN pg_catalog.pg_description des ON (des.objoid=t.oid AND des.classoid='pg_trigger'::regclass)
WHERE NOT tgisinternal
AND tgrelid = {{tid}}::OID
{% if trid %}
AND t.oid = {{trid}}::OID

View File

@ -488,7 +488,8 @@ class TriggerView(PGChildNodeView, SchemaDiffObjectCompare):
tid,
row['name'],
icon="icon-trigger-bad" if row['is_enable_trigger'] == 'D'
else "icon-trigger"
else "icon-trigger",
description=row['description']
))
return make_json_response(
@ -763,7 +764,8 @@ class TriggerView(PGChildNodeView, SchemaDiffObjectCompare):
name,
icon="icon-%s-bad" % self.node_type if
data['is_enable_trigger'] == 'D' else
"icon-%s" % self.node_type
"icon-%s" % self.node_type,
description=data['description']
)
)
except Exception as e:

View File

@ -1628,6 +1628,10 @@ class BaseTableView(PGChildNodeView, BasePartitionTable, VacuumSettings):
else:
icon = self.get_icon_css_class(res['rows'][0])
other_node_info = {}
if 'description' in data:
other_node_info['description'] = data['description']
return jsonify(
node=self.blueprint.generate_browser_node(
tid,
@ -1638,7 +1642,8 @@ class BaseTableView(PGChildNodeView, BasePartitionTable, VacuumSettings):
parent_schema_id=scid,
schema_id=rest['rows'][0]['scid'],
schema_name=rest['rows'][0]['nspname'],
affected_partitions=partitions_oid
affected_partitions=partitions_oid,
**other_node_info
)
)
except Exception as e:

View File

@ -3,9 +3,12 @@ SELECT
nsp.oid,
{{ CATALOGS.LABELS('nsp', _) }},
pg_catalog.has_schema_privilege(nsp.oid, 'CREATE') as can_create,
pg_catalog.has_schema_privilege(nsp.oid, 'USAGE') as has_usage
pg_catalog.has_schema_privilege(nsp.oid, 'USAGE') as has_usage,
des.description
FROM
pg_catalog.pg_namespace nsp
LEFT OUTER JOIN pg_catalog.pg_description des ON
(des.objoid=nsp.oid AND des.classoid='pg_namespace'::regclass)
WHERE
{% if scid %}
nsp.oid={{scid}}::oid AND

View File

@ -3,9 +3,12 @@ SELECT
nsp.oid,
{{ CATALOGS.LABELS('nsp', _) }},
pg_catalog.has_schema_privilege(nsp.oid, 'CREATE') as can_create,
pg_catalog.has_schema_privilege(nsp.oid, 'USAGE') as has_usage
pg_catalog.has_schema_privilege(nsp.oid, 'USAGE') as has_usage,
des.description
FROM
pg_catalog.pg_namespace nsp
LEFT OUTER JOIN pg_catalog.pg_description des ON
(des.objoid=nsp.oid AND des.classoid='pg_namespace'::regclass)
WHERE
{% if scid %}
nsp.oid={{scid}}::oid AND

View File

@ -3,9 +3,12 @@ SELECT
nsp.oid,
nsp.nspname as name,
pg_catalog.has_schema_privilege(nsp.oid, 'CREATE') as can_create,
pg_catalog.has_schema_privilege(nsp.oid, 'USAGE') as has_usage
pg_catalog.has_schema_privilege(nsp.oid, 'USAGE') as has_usage,
des.description
FROM
pg_catalog.pg_namespace nsp
LEFT OUTER JOIN pg_catalog.pg_description des ON
(des.objoid=nsp.oid AND des.classoid='pg_namespace'::regclass)
WHERE
{% if scid %}
nsp.oid={{scid}}::oid AND

View File

@ -3,9 +3,12 @@ SELECT
nsp.oid,
nsp.nspname as name,
pg_catalog.has_schema_privilege(nsp.oid, 'CREATE') as can_create,
pg_catalog.has_schema_privilege(nsp.oid, 'USAGE') as has_usage
pg_catalog.has_schema_privilege(nsp.oid, 'USAGE') as has_usage,
des.description
FROM
pg_catalog.pg_namespace nsp
LEFT OUTER JOIN pg_catalog.pg_description des ON
(des.objoid=nsp.oid AND des.classoid='pg_namespace'::regclass)
WHERE
nsp.nspparent = 0 AND
{% if scid %}

View File

@ -358,7 +358,8 @@ class TypeView(PGChildNodeView, DataTypeReader, SchemaDiffObjectCompare):
row['oid'],
scid,
row['name'],
icon=self.icon_str % self.node_type
icon=self.icon_str % self.node_type,
description=row['description']
))
return make_json_response(
@ -1101,12 +1102,17 @@ class TypeView(PGChildNodeView, DataTypeReader, SchemaDiffObjectCompare):
if not status:
return internal_server_error(errormsg=res)
other_node_info = {}
if 'description' in data:
other_node_info['description'] = data['description']
return jsonify(
node=self.blueprint.generate_browser_node(
tid,
scid,
name,
icon=self.icon_str % self.node_type
icon=self.icon_str % self.node_type,
**other_node_info
)
)
except Exception as e:

View File

@ -1,8 +1,9 @@
SELECT t.oid, t.typname AS name
SELECT t.oid, t.typname AS name, des.description
FROM pg_catalog.pg_type t
LEFT OUTER JOIN pg_catalog.pg_type e ON e.oid=t.typelem
LEFT OUTER JOIN pg_catalog.pg_class ct ON ct.oid=t.typrelid AND ct.relkind <> 'c'
LEFT OUTER JOIN pg_catalog.pg_namespace nsp ON nsp.oid = t.typnamespace
LEFT OUTER JOIN pg_catalog.pg_description des ON (des.objoid=t.oid AND des.classoid='pg_type'::regclass)
WHERE t.typtype != 'd' AND t.typname NOT LIKE E'\\_%' AND t.typnamespace = {{scid}}::oid
{% if tid %}
AND t.oid = {{tid}}::oid

View File

@ -1,8 +1,9 @@
SELECT t.oid, t.typname AS name
SELECT t.oid, t.typname AS name, des.description
FROM pg_catalog.pg_type t
LEFT OUTER JOIN pg_catalog.pg_type e ON e.oid=t.typelem
LEFT OUTER JOIN pg_catalog.pg_class ct ON ct.oid=t.typrelid AND ct.relkind <> 'c'
LEFT OUTER JOIN pg_catalog.pg_namespace nsp ON nsp.oid = t.typnamespace
LEFT OUTER JOIN pg_catalog.pg_description des ON (des.objoid=t.oid AND des.classoid='pg_type'::regclass)
WHERE t.typtype != 'd' AND t.typname NOT LIKE E'\\_%' AND t.typnamespace = {{scid}}::oid
{% if tid %}
AND t.oid = {{tid}}::oid

View File

@ -1,10 +1,14 @@
SELECT
db.oid as did, db.datname as name, ta.spcname as spcname, db.datallowconn,
db.datistemplate AS is_template,
pg_catalog.has_database_privilege(db.oid, 'CREATE') as cancreate, datdba as owner
pg_catalog.has_database_privilege(db.oid, 'CREATE') as cancreate, datdba as owner,
descr.description
FROM
pg_catalog.pg_database db
LEFT OUTER JOIN pg_catalog.pg_tablespace ta ON db.dattablespace = ta.oid
LEFT OUTER JOIN pg_catalog.pg_shdescription descr ON (
db.oid=descr.objoid AND descr.classoid='pg_database'::regclass
)
WHERE {% if did %}
db.oid = {{ did|qtLiteral(conn) }}::OID
{% endif %}

View File

@ -209,7 +209,8 @@ SELECT EXISTS(
sid,
rset['rows'][0]['jobname'],
"icon-pga_job" if rset['rows'][0]['jobenabled'] else
"icon-pga_job-disabled"
"icon-pga_job-disabled",
description=rset['rows'][0]['jobdesc']
),
status=200
)
@ -222,7 +223,8 @@ SELECT EXISTS(
sid,
row['jobname'],
"icon-pga_job" if row['jobenabled'] else
"icon-pga_job-disabled"
"icon-pga_job-disabled",
description=row['jobdesc']
)
)
@ -397,7 +399,8 @@ SELECT EXISTS(
sid,
row['jobname'],
icon="icon-pga_job" if row['jobenabled']
else "icon-pga_job-disabled"
else "icon-pga_job-disabled",
description=row['jobdesc']
)
)

View File

@ -265,7 +265,8 @@ class JobScheduleView(PGChildNodeView):
row['jscname'],
icon="icon-pga_schedule" if row['jscenabled'] else
"icon-pga_schedule-disabled",
enabled=row['jscenabled']
enabled=row['jscenabled'],
description=row['jscdesc']
)
)
@ -277,7 +278,8 @@ class JobScheduleView(PGChildNodeView):
row['jscname'],
icon="icon-pga_schedule" if row['jscenabled'] else
"icon-pga_schedule-disabled",
enabled=row['jscenabled']
enabled=row['jscenabled'],
description=row['jscdesc']
)
)
@ -455,7 +457,8 @@ class JobScheduleView(PGChildNodeView):
row['jscname'],
icon="icon-pga_schedule" if row['jscenabled'] else
"icon-pga_schedule-disabled",
enabled=row['jscenabled']
enabled=row['jscenabled'],
description=row['jscdesc']
)
)

View File

@ -280,7 +280,8 @@ SELECT EXISTS(
icon="icon-pga_jobstep" if row['jstenabled'] else
"icon-pga_jobstep-disabled",
enabled=row['jstenabled'],
kind=row['jstkind']
kind=row['jstkind'],
description=row['jstdesc']
)
)
@ -293,7 +294,8 @@ SELECT EXISTS(
icon="icon-pga_jobstep" if row['jstenabled'] else
"icon-pga_jobstep-disabled",
enabled=row['jstenabled'],
kind=row['jstkind']
kind=row['jstkind'],
description=row['jstdesc']
)
)
@ -478,7 +480,8 @@ SELECT EXISTS(
jid,
row['jstname'],
icon="icon-pga_jobstep" if row['jstenabled']
else "icon-pga_jobstep-disabled"
else "icon-pga_jobstep-disabled",
description=row['jstdesc']
)
)

View File

@ -1,5 +1,5 @@
SELECT
jobid, jobname, jobenabled
jobid, jobname, jobenabled, jobdesc
FROM
pgagent.pga_job
{% if jid %}

View File

@ -1,5 +1,5 @@
SELECT
jstid, jstjobid, jstname, jstenabled, jstkind = 's'::bpchar AS jstkind
jstid, jstjobid, jstname, jstenabled, jstkind = 's'::bpchar AS jstkind, jstdesc
FROM
pgagent.pga_jobstep
WHERE

View File

@ -1,5 +1,5 @@
SELECT
jscid, jscjobid, jscname, jscenabled
jscid, jscjobid, jscname, jscenabled, jscdesc
FROM
pgagent.pga_schedule
WHERE

View File

@ -741,7 +741,8 @@ rolmembership:{
row['rolname'],
'icon-role' if row['rolcanlogin'] else 'icon-group',
can_login=row['rolcanlogin'],
is_superuser=row['rolsuper']
is_superuser=row['rolsuper'],
description=row['description']
)
)
@ -1005,7 +1006,8 @@ rolmembership:{
row['rolname'],
'icon-role' if row['rolcanlogin'] else 'icon-group',
can_login=row['rolcanlogin'],
is_superuser=row['rolsuper']
is_superuser=row['rolsuper'],
description=row['description']
)
)

View File

@ -1,5 +1,6 @@
SELECT
r.oid, r.rolname, r.rolcanlogin, r.rolsuper
r.oid, r.rolname, r.rolcanlogin, r.rolsuper,
pg_catalog.shobj_description(r.oid, 'pg_authid') AS description
FROM
pg_catalog.pg_roles r
{% if rid %}

View File

@ -1384,12 +1384,8 @@ define('pgadmin.browser', [
}
if (this.new._id == _id) {
// Found the current
_.extend(this.d, {
'_id': this.new._id,
'_label': this.new._label,
'label': this.new.label,
});
this.t.update(ctx.i, this.new);
_.extend(this.d, this.new);
this.t.update(ctx.i, this.d);
this.t.setLabel(ctx.i, {label: this.new.label});
this.t.addIcon(ctx.i, {icon: this.new.icon});
this.t.setId(ctx.i, {id: this.new.id});
@ -1632,11 +1628,8 @@ define('pgadmin.browser', [
// If server icon/background changes then also we need to re-create it
if ((
_old._type == 'server' && _new._type == 'server' && (
_old._pid != _new._pid ||
_old._label != _new._label ||
_old.icon != _new.icon
)) || _old._pid != _new._pid || _old._label != _new._label ||
_old._id != _new._id
_old._pid != _new._pid || _old.icon != _new.icon
)) || _old._pid != _new._pid || _old._id != _new._id
) {
ctx.op = 'RECREATE';
traversePath();

View File

@ -833,7 +833,7 @@ define('pgadmin.browser.node', [
background: ${bgcolor} !important;
}
${fgcolor ? `
.${dynamic_class} span.file-name {
.${dynamic_class} span.file-name, .${dynamic_class} span.file-name:hover, .${dynamic_class} span.file-name.pseudo-active {
color: ${fgcolor} !important;
}
`:''}

View File

@ -53,8 +53,8 @@ samp,
border-width: 1px;
font-size: 1.15em;
color: $color-fg !important;
border-color: $border-color !important;
color: $color-fg;
border-color: $border-color;
background-color: $color-secondary;
}

View File

@ -98,6 +98,7 @@ require.onResourceLoad = function (context, map, depMaps) {
{% else %}
<div id="dockerContainer" class="pg-docker pg-docker-native"></div>
{% endif %}
<div id="object-breadcrumbs"></div>
{% include 'browser/messages.html' %}

View File

@ -11,6 +11,7 @@ import React from 'react';
import ReactDOM from 'react-dom';
import MainMenuFactory from '../../browser/static/js/MainMenuFactory';
import AppMenuBar from '../js/AppMenuBar';
import ObjectBreadcrumbs from '../js/components/ObjectBreadcrumbs';
import Theme from '../js/Theme';
define('app', [
@ -52,6 +53,16 @@ define('app', [
const menuContainerEle = document.querySelector('#main-menu-container');
if(menuContainerEle) {
ReactDOM.render(<Theme><AppMenuBar /></Theme>, document.querySelector('#main-menu-container'));
ReactDOM.render(
<Theme>
<AppMenuBar />
</Theme>, menuContainerEle
);
}
ReactDOM.render(
<Theme>
<ObjectBreadcrumbs pgAdmin={pgAdmin} />
</Theme>, document.querySelector('#object-breadcrumbs')
);
});

View File

@ -0,0 +1,120 @@
import { Box, makeStyles } from '@material-ui/core';
import React, { useState, useEffect } from 'react';
import AccountTreeIcon from '@material-ui/icons/AccountTree';
import CommentIcon from '@material-ui/icons/Comment';
import ArrowForwardIosRoundedIcon from '@material-ui/icons/ArrowForwardIosRounded';
import PropTypes from 'prop-types';
import { useIsMounted } from '../custom_hooks';
const useStyles = makeStyles((theme)=>({
root: {
position: 'absolute',
bottom: 0,
width: 'auto',
maxWidth: '99%',
zIndex: 9999,
padding: '0.25rem 0.5rem',
fontSize: '0.95em',
color: theme.palette.background.default,
backgroundColor: theme.palette.text.primary,
borderTopRightRadius: theme.shape.borderRadius,
},
row: {
display: 'flex',
alignItems: 'center'
},
overflow: {
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
overflow: 'hidden',
}
}));
export default function ObjectBreadcrumbs({pgAdmin}) {
const classes = useStyles();
const checkIsMounted = useIsMounted();
const [preferences, setPreferences] = useState({
breadcrumbs_enable: false,
breadcrumbs_show_comment: true,
});
const [objectData, setObjectData] = useState({
path: null,
description: null,
});
const onItemHover = (item, _data)=>{
// if(!checkIsMounted) return;
if(item && !_data?._type.startsWith('coll-')) {
setObjectData({
path: pgAdmin.Browser.tree.getNodeDisplayPath(item, false),
description: item?._metadata?.data.description
});
} else {
setObjectData({
path: null,
description: null
});
}
};
useEffect(()=>{
const setPrefs = ()=>{
if(!checkIsMounted()) return;
let pref = pgAdmin.Browser.get_preferences_for_module('browser');
setPreferences({
breadcrumbs_enable: pref.breadcrumbs_enable,
breadcrumbs_show_comment: pref.breadcrumbs_show_comment,
});
};
let cacheIntervalId = setInterval(function() {
if(pgAdmin.Browser.preference_version() > 0) {
clearInterval(cacheIntervalId);
setPrefs();
}
},0);
pgAdmin.Browser.onPreferencesChange('browser', function() {
setPrefs();
});
}, []);
useEffect(()=>{
if(preferences.breadcrumbs_enable) {
pgAdmin.Browser.Events.on('pgadmin-browser:tree:hovered', onItemHover);
}
return ()=>{
pgAdmin.Browser.Events.off('pgadmin-browser:tree:hovered', onItemHover);
};
}, [preferences.breadcrumbs_enable]);
if(!objectData.path) {
return <></>;
}
return(
<>
<Box className={classes.root}>
<div className={classes.row}>
<AccountTreeIcon style={{height: '1rem', marginRight: '0.125rem'}} />
<div className={classes.overflow}>
{
objectData.path?.reduce((res, item)=>(
res.concat(<span key={item}>{item}</span>, <ArrowForwardIosRoundedIcon key={item+'-arrow'} style={{height: '0.8rem', width: '1.25rem'}} />)
), []).slice(0, -1)
}
</div>
</div>
{preferences.breadcrumbs_show_comment && objectData.description &&
<div className={classes.row}>
<CommentIcon style={{height: '1rem', marginRight: '0.125rem'}} />
<div className={classes.overflow}>{objectData.description}</div>
</div>}
</Box>
</>
);
}
ObjectBreadcrumbs.propTypes = {
pgAdmin: PropTypes.object,
};

View File

@ -18,6 +18,9 @@ interface IItemRendererXProps {
decorations: ClasslistComposite
onClick: (ev: React.MouseEvent, item: FileEntry | Directory, type: ItemType) => void
onContextMenu: (ev: React.MouseEvent, item: FileEntry | Directory) => void
onMouseEnter: (ev: React.MouseEvent, item: FileEntry | Directory) => void
onMouseLeave: (ev: React.MouseEvent, item: FileEntry | Directory) => void
onItemHovered: (ev: React.MouseEvent, item: FileEntry | Directory, type: ItemType) => void
events: Notificar<FileTreeXEvent>
}
@ -81,6 +84,8 @@ export class FileTreeItem extends React.Component<IItemRendererXProps & IItemRen
onClick={this.handleClick}
onDoubleClick={this.handleDoubleClick}
onDragStart={this.handleDragStartItem}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
// required for rendering context menus when opened through context menu button on keyboard
ref={this.handleDivRef}
draggable={true}>
@ -166,6 +171,20 @@ export class FileTreeItem extends React.Component<IItemRendererXProps & IItemRen
}
};
private handleMouseEnter = (ev: React.MouseEvent) => {
const { item, itemType, onMouseEnter } = this.props;
if (itemType === ItemType.File || itemType === ItemType.Directory) {
onMouseEnter?.(ev, item as FileEntry);
}
};
private handleMouseLeave = (ev: React.MouseEvent) => {
const { item, itemType, onMouseLeave } = this.props;
if (itemType === ItemType.File || itemType === ItemType.Directory) {
onMouseLeave?.(ev, item as FileEntry);
}
};
private handleDragStartItem = (e: React.DragEvent) => {
const { item, itemType, events } = this.props;
if (itemType === ItemType.File || itemType === ItemType.Directory) {

View File

@ -29,6 +29,8 @@ export class FileTreeX extends React.Component<IFileTreeXProps> {
private disposables: DisposablesComposite;
private keyboardHotkeys: KeyboardHotkeys;
private fileTreeEvent: IFileTreeXTriggerEvents;
private hoverTimeoutId: React.RefObject<number|null> = React.createRef<number|null>();
private hoverDispatchId: React.RefObject<number|null> = React.createRef<number|null>();
constructor(props: IFileTreeXProps) {
super(props);
this.events = new Notificar();
@ -73,6 +75,8 @@ export class FileTreeX extends React.Component<IFileTreeXProps> {
onClick={this.handleItemClicked}
onDoubleClick={this.handleItemDoubleClicked}
onContextMenu={this.handleItemCtxMenu}
onMouseEnter={this.onItemMouseEnter}
onMouseLeave={this.onItemMouseLeave}
changeDirectoryCount={this.changeDirectoryCount}
events={this.events}/>}
</FileTree>
@ -166,6 +170,22 @@ export class FileTreeX extends React.Component<IFileTreeXProps> {
}
};
private onItemMouseEnter = (ev: React.MouseEvent, item: FileEntry | Directory) => {
clearTimeout(this.hoverDispatchId.current??undefined);
(this.hoverDispatchId as any).current = setTimeout(()=>{
clearTimeout(this.hoverTimeoutId.current??undefined);
this.events.dispatch(FileTreeXEvent.onTreeEvents, ev, 'hovered', item);
}, 500);
};
private onItemMouseLeave = (ev: React.MouseEvent) => {
clearTimeout(this.hoverTimeoutId.current??undefined);
clearTimeout(this.hoverDispatchId.current??undefined);
(this.hoverTimeoutId as any).current = setTimeout(()=>{
this.events.dispatch(FileTreeXEvent.onTreeEvents, ev, 'hovered', null);
}, 100);
};
private setActiveFile = async (fileOrDirOrPath: FileOrDir | string, ensureVisible, align): Promise<void> => {
const fileH = typeof fileOrDirOrPath === 'string'
? await this.fileTreeHandle.getFileHandle(fileOrDirOrPath)

View File

@ -171,7 +171,7 @@
&:hover,
&.pseudo-active {
color: $tree-fg-hover !important;
color: $tree-fg-hover;
}
}
@ -184,14 +184,14 @@
font: inherit;
flex-grow: 1;
user-select: none;
color: $tree-text-fg !important;
color: $tree-text-fg;
margin-left: 3px;
cursor: pointer !important;
white-space: nowrap;
&:hover,
&.pseudo-active {
color: $tree-fg-hover !important;
color: $tree-fg-hover;
}
}
}
@ -324,7 +324,7 @@
flex-grow: 1;
user-select: none;
cursor: default;
color: #c1c1c1;
color: $color-fg;
margin-left: 3px;
& input[type='text'] {

View File

@ -29,6 +29,16 @@ function manageTreeEvents(event, eventName, item) {
console.warn(e.stack || e);
return false;
}
} else if(eventName == 'hovered') {
/* Raise tree events for the nodes */
try {
obj.Events.trigger(
'pgadmin-browser:tree:' + eventName, item, d, node
);
} catch (e) {
console.warn(e.stack || e);
return false;
}
} else {
// Events for browser tree.
if (d && obj.Nodes[d._type]) {
@ -384,6 +394,23 @@ export class Tree {
})(tree.tree.getModel().root);
}
getNodeDisplayPath(item, separator='/', skip_coll=false) {
let retStack = [];
let currItem = item;
while(currItem?.fileName) {
const data = currItem._metadata?.data;
if(data._type.startsWith('coll-') && skip_coll) {
/* Skip collection */
} else {
retStack.push(data._label);
}
currItem = currItem.parent;
}
retStack = retStack.reverse();
if(separator == false) return retStack;
return retStack.join(separator);
}
findNodeByDomElement(domElement) {
const path = domElement.path;
if (!path || !path[0]) {

View File

@ -305,6 +305,9 @@ $card-header-fg: $color-fg !default;
$card-header-border-color: $border-color !default;
$no-border-radius: 0px !important;
$tooltip-color: $color-bg;
$tooltip-bg: $color-fg;
$btn-checkbox-padding: $input-btn-padding-y $input-btn-padding-x;
$scrollbar-base-color: #bac1cd !default;

View File

@ -18,6 +18,7 @@ MIMETYPE_APP_JSON = 'application/json'
# Preference labels
PREF_LABEL_KEYBOARD_SHORTCUTS = gettext('Keyboard shortcuts')
PREF_LABEL_DISPLAY = gettext('Display')
PREF_LABEL_BREADCRUMBS = gettext('Object Breadcrumbs')
PREF_LABEL_OPTIONS = gettext('Options')
PREF_LABEL_EXPLAIN = gettext('Explain')
PREF_LABEL_EDITOR = gettext('Editor')

View File

@ -0,0 +1,105 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2023, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import jasmineEnzyme from 'jasmine-enzyme';
import React from 'react';
import '../helper/enzyme.helper';
import { withTheme } from '../fake_theme';
import { createMount } from '@material-ui/core/test-utils';
import ObjectBreadcrumbs from '../../../pgadmin/static/js/components/ObjectBreadcrumbs';
import EventBus from '../../../pgadmin/static/js/helpers/EventBus';
const pgAdmin = {
Browser: {
Events: new EventBus(),
get_preferences_for_module: function() {
return {
breadcrumbs_enable: true,
breadcrumbs_show_comment: true,
};
},
preference_version: ()=>123,
onPreferencesChange: ()=>{/*This is intentional (SonarQube)*/},
tree: {
getNodeDisplayPath: jasmine.createSpy('getNodeDisplayPath').and.returnValue(['server', 'object']),
}
},
};
describe('ObjectBreadcrumbs', ()=>{
let mount;
/* Use createMount so that material ui components gets the required context */
/* https://material-ui.com/guides/testing/#api */
beforeAll(()=>{
mount = createMount();
});
afterAll(() => {
mount.cleanUp();
});
beforeEach(()=>{
jasmineEnzyme();
});
it('not hovered', (done)=>{
let ThemedObjectBreadcrumbs = withTheme(ObjectBreadcrumbs);
let ctrl = mount(<ThemedObjectBreadcrumbs pgAdmin={pgAdmin} />);
setTimeout(()=>{
ctrl.update();
expect(ctrl.find('ForwardRef(AccountTreeIcon)').length).toBe(0);
done();
}, 0);
});
it('hovered object with comment', (done)=>{
let ThemedObjectBreadcrumbs = withTheme(ObjectBreadcrumbs);
let ctrl = mount(<ThemedObjectBreadcrumbs pgAdmin={pgAdmin} />);
setTimeout(()=>{
ctrl.update();
pgAdmin.Browser.Events.trigger('pgadmin-browser:tree:hovered', {
_metadata: {
data: {
description: 'some description'
}
},
}, {
_type: 'object',
});
setTimeout(()=>{
ctrl.update();
expect(ctrl.find('ForwardRef(AccountTreeIcon)').length).toBe(1);
expect(ctrl.find('ForwardRef(CommentIcon)').length).toBe(1);
done();
}, 500);
}, 500);
});
it('hovered object with no comment', (done)=>{
let ThemedObjectBreadcrumbs = withTheme(ObjectBreadcrumbs);
let ctrl = mount(<ThemedObjectBreadcrumbs pgAdmin={pgAdmin} />);
setTimeout(()=>{
ctrl.update();
pgAdmin.Browser.Events.trigger('pgadmin-browser:tree:hovered', {
_metadata: {
data: {}
},
}, {
_type: 'object',
});
setTimeout(()=>{
ctrl.update();
expect(ctrl.find('ForwardRef(AccountTreeIcon)').length).toBe(1);
expect(ctrl.find('ForwardRef(CommentIcon)').length).toBe(0);
done();
}, 500);
}, 500);
});
});

View File

@ -8368,20 +8368,13 @@ __metadata:
languageName: node
linkType: hard
"jasmine-core@npm:3.10.1":
"jasmine-core@npm:3.10.1, jasmine-core@npm:^3.6.0":
version: 3.10.1
resolution: "jasmine-core@npm:3.10.1"
checksum: 77ee26aaf29576e982a2ebe6586218ff4d7cc4305ad18c400954bbdeb3c7987e9a4a8ac6d6548b65838852f325395fc901d69bf8c24bdccfbd67b263fbf5d4fd
languageName: node
linkType: hard
"jasmine-core@npm:^3.6.0":
version: 3.99.1
resolution: "jasmine-core@npm:3.99.1"
checksum: 4e4a89739d99e471b86c7ccc4c5c244a77cc6d1e17b2b0d87d81266b8415697354d8873f7e764790a10661744f73a753a6e9bcd9b3e48c66a0c9b8a092b071b7
languageName: node
linkType: hard
"jasmine-enzyme@npm:^7.1.2":
version: 7.1.2
resolution: "jasmine-enzyme@npm:7.1.2"