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

This commit is contained in:
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 catalogs, you can reduce the number of object types displayed to increase
speed. 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: Use fields on the *Properties* panel to specify browser properties:
.. image:: images/preferences_browser_properties.png .. image:: images/preferences_browser_properties.png

View File

@ -10,7 +10,8 @@ import sys
from flask_babel import gettext from flask_babel import gettext
from pgadmin.utils.constants import PREF_LABEL_DISPLAY,\ from pgadmin.utils.constants import PREF_LABEL_DISPLAY,\
PREF_LABEL_KEYBOARD_SHORTCUTS, PREF_LABEL_TABS_SETTINGS, \ 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 from flask import current_app
import config import config
@ -557,3 +558,25 @@ def register_browser_preferences(self):
' back to the default title with placeholders.' ' 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, shared=server.shared,
is_kerberos_conn=bool(server.kerberos_conn), is_kerberos_conn=bool(server.kerberos_conn),
gss_authenticated=manager.gss_authenticated, gss_authenticated=manager.gss_authenticated,
cloud_status=server.cloud_status cloud_status=server.cloud_status,
description=server.comment
) )
@property @property
@ -595,7 +596,8 @@ class ServerNode(PGChildNodeView):
username=server.username, username=server.username,
shared=server.shared, shared=server.shared,
is_kerberos_conn=bool(server.kerberos_conn), 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 server_type='pg', # default server type
username=server.username, username=server.username,
role=server.role, 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, canDisconn=can_dis_conn,
canDrop=can_drop, canDrop=can_drop,
isTemplate=row['is_template'], 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'], row['oid'],
did, did,
row['name'], row['name'],
icon="icon-cast" icon="icon-cast",
description=row['description']
)) ))
return make_json_response( return make_json_response(
@ -449,12 +450,17 @@ class CastView(PGChildNodeView, SchemaDiffObjectCompare):
if not status: if not status:
return internal_server_error(errormsg=res) return internal_server_error(errormsg=res)
other_node_info = {}
if 'description' in data:
other_node_info['description'] = data['description']
return jsonify( return jsonify(
node=self.blueprint.generate_browser_node( node=self.blueprint.generate_browser_node(
cid, cid,
did, did,
name, name,
"icon-{0}".format(self.node_type) "icon-{0}".format(self.node_type),
**other_node_info
) )
) )

View File

@ -1,6 +1,7 @@
SELECT SELECT
ca.oid, 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 FROM pg_catalog.pg_cast ca
JOIN pg_catalog.pg_type st ON st.oid=castsource JOIN pg_catalog.pg_type st ON st.oid=castsource
JOIN pg_catalog.pg_namespace ns ON ns.oid=st.typnamespace JOIN pg_catalog.pg_namespace ns ON ns.oid=st.typnamespace

View File

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

View File

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

View File

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

View File

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

View File

@ -540,7 +540,8 @@ class SchemaView(PGChildNodeView):
row['name'], row['name'],
icon=self.node_icon, icon=self.node_icon,
can_create=row['can_create'], can_create=row['can_create'],
has_usage=row['has_usage'] has_usage=row['has_usage'],
description=row['description']
), ),
status=200 status=200
) )
@ -553,7 +554,8 @@ class SchemaView(PGChildNodeView):
row['name'], row['name'],
icon=self.node_icon, icon=self.node_icon,
can_create=row['can_create'], 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: if not status:
return internal_server_error(errormsg=res) return internal_server_error(errormsg=res)
other_node_info = {}
if 'description' in data:
other_node_info['description'] = data['description']
return jsonify( return jsonify(
node=self.blueprint.generate_browser_node( node=self.blueprint.generate_browser_node(
scid, scid,
did, did,
name, name,
icon=self.node_icon icon=self.node_icon,
**other_node_info
) )
) )
except Exception as e: except Exception as e:

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
SELECT SELECT
attnum, attname attnum, attname, des.description
FROM pg_catalog.pg_attribute att 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 WHERE att.attrelid = {{coid}}::oid
AND att.attnum > 0 AND att.attnum > 0
AND att.attisdropped IS FALSE AND att.attisdropped IS FALSE

View File

@ -1,7 +1,9 @@
SELECT SELECT
c.oid, c.relname as name c.oid, c.relname as name, description
FROM FROM
pg_catalog.pg_class c 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 %} {% if scid %}
WHERE relnamespace = {{scid}}::oid WHERE relnamespace = {{scid}}::oid
{% elif coid %} {% elif coid %}

View File

@ -268,7 +268,8 @@ class CollationView(PGChildNodeView, SchemaDiffObjectCompare):
row['oid'], row['oid'],
scid, scid,
row['name'], row['name'],
icon="icon-collation" icon="icon-collation",
description=row['description']
)) ))
return make_json_response( return make_json_response(
@ -604,12 +605,17 @@ class CollationView(PGChildNodeView, SchemaDiffObjectCompare):
scid = res['rows'][0]['scid'] scid = res['rows'][0]['scid']
other_node_info = {}
if 'description' in data:
other_node_info['description'] = data['description']
return jsonify( return jsonify(
node=self.blueprint.generate_browser_node( node=self.blueprint.generate_browser_node(
coid, coid,
scid, scid,
name, 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 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 %} {% if scid %}
WHERE c.collnamespace = {{scid}}::oid WHERE c.collnamespace = {{scid}}::oid
{% elif coid %} {% elif coid %}

View File

@ -358,7 +358,8 @@ class DomainView(PGChildNodeView, DataTypeReader, SchemaDiffObjectCompare):
row['oid'], row['oid'],
scid, scid,
row['name'], row['name'],
icon="icon-domain" icon="icon-domain",
description=row['description']
)) ))
return make_json_response( return make_json_response(
@ -713,12 +714,17 @@ AND relkind != 'c'))"""
if not status: if not status:
return internal_server_error(errormsg=scid) return internal_server_error(errormsg=scid)
other_node_info = {}
if 'description' in self.request:
other_node_info['description'] = self.request['description']
return jsonify( return jsonify(
node=self.blueprint.generate_browser_node( node=self.blueprint.generate_browser_node(
doid, doid,
scid, scid,
name, 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'], row['oid'],
doid, doid,
row['name'], row['name'],
icon=icon icon=icon,
description=row['description']
)) ))
return make_json_response( return make_json_response(
@ -559,12 +560,17 @@ class DomainConstraintView(PGChildNodeView):
else: else:
icon = '' icon = ''
other_node_info = {}
if 'description' in data:
other_node_info['description'] = data['description']
return jsonify( return jsonify(
node=self.blueprint.generate_browser_node( node=self.blueprint.generate_browser_node(
coid, coid,
doid, doid,
name, name,
icon=icon icon=icon,
**other_node_info
) )
) )
else: else:

View File

@ -1,12 +1,14 @@
SELECT SELECT
d.oid, d.typname as name, pg_catalog.pg_get_userbyid(d.typowner) as owner, 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 FROM
pg_catalog.pg_type d pg_catalog.pg_type d
JOIN JOIN
pg_catalog.pg_type b ON b.oid = d.typbasetype pg_catalog.pg_type b ON b.oid = d.typbasetype
JOIN JOIN
pg_catalog.pg_namespace bn ON bn.oid=d.typnamespace 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 %} {% if scid is defined %}
WHERE WHERE
d.typnamespace = {{scid}}::oid d.typnamespace = {{scid}}::oid

View File

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

View File

@ -296,7 +296,8 @@ class FtsConfigurationView(PGChildNodeView, SchemaDiffObjectCompare):
row['oid'], row['oid'],
scid, scid,
row['name'], row['name'],
icon="icon-fts_configuration" icon="icon-fts_configuration",
description=row['description']
)) ))
return make_json_response( return make_json_response(
@ -539,12 +540,17 @@ class FtsConfigurationView(PGChildNodeView, SchemaDiffObjectCompare):
_("Could not find the FTS Configuration node to update.") _("Could not find the FTS Configuration node to update.")
) )
other_node_info = {}
if 'description' in data:
other_node_info['description'] = data['description']
return jsonify( return jsonify(
node=self.blueprint.generate_browser_node( node=self.blueprint.generate_browser_node(
cfgid, cfgid,
data['schema'] if 'schema' in data else scid, data['schema'] if 'schema' in data else scid,
name, 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 #} {# FETCH FTS CONFIGURATION NAME statement #}
SELECT SELECT
oid, cfgname as name cfg.oid, cfgname as name, des.description
FROM FROM
pg_catalog.pg_ts_config cfg 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 WHERE
{% if scid %} {% if scid %}
cfg.cfgnamespace = {{scid}}::OID cfg.cfgnamespace = {{scid}}::OID

View File

@ -308,7 +308,8 @@ class FtsDictionaryView(PGChildNodeView, SchemaDiffObjectCompare):
row['oid'], row['oid'],
scid, scid,
row['name'], row['name'],
icon="icon-fts_dictionary" icon="icon-fts_dictionary",
description=row['description']
)) ))
return make_json_response( return make_json_response(
@ -530,12 +531,17 @@ class FtsDictionaryView(PGChildNodeView, SchemaDiffObjectCompare):
_("Could not find the FTS Dictionary node to update.") _("Could not find the FTS Dictionary node to update.")
) )
other_node_info = {}
if 'description' in data:
other_node_info['description'] = data['description']
return jsonify( return jsonify(
node=self.blueprint.generate_browser_node( node=self.blueprint.generate_browser_node(
dcid, dcid,
res['rows'][0]['schema'], res['rows'][0]['schema'],
name, 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 #} {# Fetch FTS DICTIONARY name statement #}
SELECT SELECT
oid, dictname as name, dict.oid, dictname as name,
dictnamespace as schema dictnamespace as schema,
des.description
FROM FROM
pg_catalog.pg_ts_dict dict 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 WHERE
{% if scid %} {% if scid %}
dict.dictnamespace = {{scid}}::OID dict.dictnamespace = {{scid}}::OID

View File

@ -279,7 +279,8 @@ class FtsParserView(PGChildNodeView, SchemaDiffObjectCompare):
row['oid'], row['oid'],
scid, scid,
row['name'], row['name'],
icon="icon-fts_parser" icon="icon-fts_parser",
description=row['description']
)) ))
return make_json_response( return make_json_response(
@ -477,12 +478,17 @@ class FtsParserView(PGChildNodeView, SchemaDiffObjectCompare):
_("Could not find the FTS Parser node to update.") _("Could not find the FTS Parser node to update.")
) )
other_node_info = {}
if 'description' in data:
other_node_info['description'] = data['description']
return jsonify( return jsonify(
node=self.blueprint.generate_browser_node( node=self.blueprint.generate_browser_node(
pid, pid,
data['schema'] if 'schema' in data else scid, data['schema'] if 'schema' in data else scid,
name, 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 #} {# FETCH FTS PARSER name statement #}
SELECT SELECT
oid, prsname as name, prs.prsnamespace AS schema prs.oid, prsname as name, prs.prsnamespace AS schema, des.description
FROM FROM
pg_catalog.pg_ts_parser prs 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 WHERE
{% if scid %} {% if scid %}
prs.prsnamespace = {{scid}}::OID prs.prsnamespace = {{scid}}::OID

View File

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

View File

@ -1,7 +1,13 @@
SELECT SELECT
oid, tmplname as name, tmpl.tmplnamespace AS schema tmpl.oid, tmplname as name, tmpl.tmplnamespace AS schema, des.description
FROM FROM
pg_catalog.pg_ts_template tmpl 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 WHERE
{% if scid %} {% if scid %}
tmpl.tmplnamespace = {{scid}}::OID tmpl.tmplnamespace = {{scid}}::OID

View File

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

View File

@ -230,7 +230,8 @@ class OperatorView(PGChildNodeView):
row['oid'], row['oid'],
scid, scid,
row['name'], row['name'],
icon="icon-operator" icon="icon-operator",
description=row['description']
)) ))
return make_json_response( 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) || ')' op.oprname || ' (' || pg_catalog.format_type(lt.oid, NULL) || ')'
ELSE op.oprname || '()' ELSE op.oprname || '()'
END as name, 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 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 lt ON lt.oid=op.oprleft
LEFT OUTER JOIN pg_catalog.pg_type rt ON rt.oid=op.oprright 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'], row['oid'],
scid, scid,
row['name'], row['name'],
icon=self.node_icon icon=self.node_icon,
description=row['description']
) )
) )
@ -249,7 +250,8 @@ class PackageView(PGChildNodeView, SchemaDiffObjectCompare):
row['oid'], row['oid'],
scid, scid,
row['name'], row['name'],
icon=self.node_icon icon=self.node_icon,
description=row['description']
)) ))
return make_json_response( return make_json_response(
@ -527,12 +529,17 @@ class PackageView(PGChildNodeView, SchemaDiffObjectCompare):
if not status: if not status:
return internal_server_error(errormsg=res) return internal_server_error(errormsg=res)
other_node_info = {}
if 'description' in data:
other_node_info['description'] = data['description']
return jsonify( return jsonify(
node=self.blueprint.generate_browser_node( node=self.blueprint.generate_browser_node(
pkgid, pkgid,
scid, scid,
name, name,
icon=self.node_icon icon=self.node_icon,
**other_node_info
) )
) )

View File

@ -1,7 +1,8 @@
SELECT SELECT
nsp.oid, nspname AS name nsp.oid, nspname AS name, des.description
FROM FROM
pg_catalog.pg_namespace nsp 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 WHERE nspparent = {{scid}}::oid
{% if pkgid %} {% if pkgid %}
AND nsp.oid = {{pkgid}}::oid AND nsp.oid = {{pkgid}}::oid

View File

@ -229,7 +229,9 @@ class SequenceView(PGChildNodeView, SchemaDiffObjectCompare):
row['oid'], row['oid'],
scid, scid,
row['name'], row['name'],
icon=self.node_icon icon=self.node_icon,
description=row['comment']
), ),
status=200 status=200
) )
@ -240,7 +242,8 @@ class SequenceView(PGChildNodeView, SchemaDiffObjectCompare):
row['oid'], row['oid'],
scid, scid,
row['name'], row['name'],
icon=self.node_icon icon=self.node_icon,
description=row['description']
)) ))
return make_json_response( return make_json_response(
@ -563,7 +566,8 @@ class SequenceView(PGChildNodeView, SchemaDiffObjectCompare):
seid, seid,
row['schema'], row['schema'],
row['name'], 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 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 %} {% if show_internal %}
LEFT JOIN pg_catalog.pg_depend d1 ON d1.refobjid = cl.oid AND d1.deptype = 'i' LEFT JOIN pg_catalog.pg_depend d1 ON d1.refobjid = cl.oid AND d1.deptype = 'i'
{% endif %} {% endif %}

View File

@ -438,7 +438,8 @@ class TableView(BaseTableView, DataTypeReader, SchemaDiffTableCompare):
icon=icon, icon=icon,
tigger_count=row['triggercount'], tigger_count=row['triggercount'],
has_enable_triggers=row['has_enable_triggers'], 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( return make_json_response(

View File

@ -291,7 +291,8 @@ class ColumnsView(PGChildNodeView, DataTypeReader):
tid, tid,
row['name'], row['name'],
icon="icon-column", icon="icon-column",
datatype=row['datatype'] # We need datatype somewhere in datatype=row['datatype'], # We need datatype somewhere in,
description=row['description']
), ),
status=200 status=200
) )
@ -303,7 +304,8 @@ class ColumnsView(PGChildNodeView, DataTypeReader):
tid, tid,
row['name'], row['name'],
icon="icon-column", icon="icon-column",
datatype=row['datatype'] # We need datatype somewhere in datatype=row['datatype'], # We need datatype somewhere in
description=row['description']
)) # exclusion constraint. )) # exclusion constraint.
return make_json_response( return make_json_response(
@ -535,12 +537,17 @@ class ColumnsView(PGChildNodeView, DataTypeReader):
if not status: if not status:
return internal_server_error(errormsg=res) return internal_server_error(errormsg=res)
other_node_info = {}
if 'description' in data:
other_node_info['description'] = data['description']
return jsonify( return jsonify(
node=self.blueprint.generate_browser_node( node=self.blueprint.generate_browser_node(
clid, clid,
tid, tid,
name, 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'], row['name'],
icon="icon-compound_trigger-bad" icon="icon-compound_trigger-bad"
if row['is_enable_trigger'] == 'D' if row['is_enable_trigger'] == 'D'
else "icon-compound_trigger" else "icon-compound_trigger",
description=row['description']
)) ))
return make_json_response( return make_json_response(
@ -671,7 +672,8 @@ class CompoundTriggerView(PGChildNodeView, SchemaDiffObjectCompare):
name, name,
icon="icon-%s-bad" % self.node_type if icon="icon-%s-bad" % self.node_type if
data['is_enable_trigger'] == 'D' else data['is_enable_trigger'] == 'D' else
"icon-%s" % self.node_type "icon-%s" % self.node_type,
description=data['description']
) )
) )
except Exception as e: except Exception as e:

View File

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

View File

@ -419,7 +419,8 @@ class ExclusionConstraintView(PGChildNodeView):
row['oid'], row['oid'],
tid, tid,
row['name'], row['name'],
icon="icon-exclusion_constraint" icon="icon-exclusion_constraint",
description=row['comment']
)) ))
return make_json_response( return make_json_response(
data=res, data=res,
@ -639,12 +640,17 @@ class ExclusionConstraintView(PGChildNodeView):
if not status: if not status:
return internal_server_error(errormsg=res) return internal_server_error(errormsg=res)
other_node_info = {}
if 'comment' in data:
other_node_info['description'] = data['comment']
return jsonify( return jsonify(
node=self.blueprint.generate_browser_node( node=self.blueprint.generate_browser_node(
exid, exid,
tid, tid,
name, 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, tid,
row['name'], row['name'],
icon=icon, icon=icon,
valid=valid valid=valid,
description=row['comment']
)) ))
return make_json_response( return make_json_response(
data=res, data=res,
@ -702,6 +703,10 @@ class ForeignKeyConstraintView(PGChildNodeView):
icon = "icon-foreign_key" icon = "icon-foreign_key"
valid = True valid = True
other_node_info = {}
if 'comment' in data:
other_node_info['description'] = data['comment']
return jsonify( return jsonify(
node=self.blueprint.generate_browser_node( node=self.blueprint.generate_browser_node(
fkid, fkid,

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,9 @@
SELECT c.oid, conname as name, 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 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' WHERE contype = 'c'
AND conrelid = {{ tid }}::oid AND conrelid = {{ tid }}::oid
{% if cid %} {% 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, 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 FROM pg_catalog.pg_attribute att
JOIN pg_catalog.pg_type ty ON ty.oid=atttypid JOIN pg_catalog.pg_type ty ON ty.oid=atttypid
JOIN pg_catalog.pg_namespace tn ON tn.oid=ty.typnamespace 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_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_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_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 WHERE
att.attrelid = {{ tid|qtLiteral(conn) }}::oid att.attrelid = {{ tid|qtLiteral(conn) }}::oid
{% if clid %} {% 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 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 WHERE NOT tgisinternal
AND tgrelid = {{tid}}::OID AND tgrelid = {{tid}}::OID
AND tgpackageoid != 0 AND tgpackageoid != 0

View File

@ -1,7 +1,9 @@
SELECT conindid as oid, SELECT conindid as oid,
conname as name, conname as name,
NOT convalidated as convalidated NOT convalidated as convalidated,
desp.description AS comment
FROM pg_catalog.pg_constraint ct 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 WHERE contype='x' AND
conrelid = {{tid}}::oid conrelid = {{tid}}::oid
{% if exid %} {% if exid %}

View File

@ -1,7 +1,9 @@
SELECT ct.oid, SELECT ct.oid,
conname as name, conname as name,
NOT convalidated as convalidated NOT convalidated as convalidated,
description as comment
FROM pg_catalog.pg_constraint ct 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 WHERE contype='f' AND
conrelid = {{tid}}::oid conrelid = {{tid}}::oid
ORDER BY conname 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 FROM pg_catalog.pg_index idx
JOIN pg_catalog.pg_class cls ON cls.oid=indexrelid JOIN pg_catalog.pg_class cls ON cls.oid=indexrelid
LEFT JOIN pg_catalog.pg_depend dep ON (dep.classid = cls.tableoid AND 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') dep.deptype='i')
LEFT OUTER JOIN pg_catalog.pg_constraint con ON (con.tableoid = dep.refclassid AND LEFT OUTER JOIN pg_catalog.pg_constraint con ON (con.tableoid = dep.refclassid AND
con.oid = dep.refobjid) 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 WHERE indrelid = {{tid}}::oid
AND contype='{{constraint_type}}' AND contype='{{constraint_type}}'
{% if cid %} {% if cid %}

View File

@ -2,10 +2,11 @@ SELECT
rw.oid AS oid, rw.oid AS oid,
rw.rulename AS name, rw.rulename AS name,
CASE WHEN rw.ev_enabled != 'D' THEN True ELSE False END AS enabled, 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 FROM
pg_catalog.pg_rewrite rw 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 WHERE
{% if tid %} {% if tid %}
rw.ev_class = {{ 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, (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, (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 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 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 WHERE rel.relkind IN ('r','s','t','p') AND rel.relnamespace = {{ scid }}::oid
AND NOT rel.relispartition AND NOT rel.relispartition
{% if tid %} AND rel.oid = {{tid}}::OID {% endif %} {% 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 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 AND tgrelid = {{tid}}::OID
{% if trid %} {% if trid %}
AND t.oid = {{trid}}::OID 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 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 AND tgrelid = {{tid}}::OID
{% if trid %} {% if trid %}
AND t.oid = {{trid}}::OID AND t.oid = {{trid}}::OID

View File

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

View File

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

View File

@ -3,9 +3,12 @@ SELECT
nsp.oid, nsp.oid,
{{ CATALOGS.LABELS('nsp', _) }}, {{ CATALOGS.LABELS('nsp', _) }},
pg_catalog.has_schema_privilege(nsp.oid, 'CREATE') as can_create, 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 FROM
pg_catalog.pg_namespace nsp 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 WHERE
{% if scid %} {% if scid %}
nsp.oid={{scid}}::oid AND nsp.oid={{scid}}::oid AND

View File

@ -3,9 +3,12 @@ SELECT
nsp.oid, nsp.oid,
{{ CATALOGS.LABELS('nsp', _) }}, {{ CATALOGS.LABELS('nsp', _) }},
pg_catalog.has_schema_privilege(nsp.oid, 'CREATE') as can_create, 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 FROM
pg_catalog.pg_namespace nsp 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 WHERE
{% if scid %} {% if scid %}
nsp.oid={{scid}}::oid AND nsp.oid={{scid}}::oid AND

View File

@ -3,9 +3,12 @@ SELECT
nsp.oid, nsp.oid,
nsp.nspname as name, nsp.nspname as name,
pg_catalog.has_schema_privilege(nsp.oid, 'CREATE') as can_create, 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 FROM
pg_catalog.pg_namespace nsp 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 WHERE
{% if scid %} {% if scid %}
nsp.oid={{scid}}::oid AND nsp.oid={{scid}}::oid AND

View File

@ -3,9 +3,12 @@ SELECT
nsp.oid, nsp.oid,
nsp.nspname as name, nsp.nspname as name,
pg_catalog.has_schema_privilege(nsp.oid, 'CREATE') as can_create, 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 FROM
pg_catalog.pg_namespace nsp 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 WHERE
nsp.nspparent = 0 AND nsp.nspparent = 0 AND
{% if scid %} {% if scid %}

View File

@ -358,7 +358,8 @@ class TypeView(PGChildNodeView, DataTypeReader, SchemaDiffObjectCompare):
row['oid'], row['oid'],
scid, scid,
row['name'], row['name'],
icon=self.icon_str % self.node_type icon=self.icon_str % self.node_type,
description=row['description']
)) ))
return make_json_response( return make_json_response(
@ -1101,12 +1102,17 @@ class TypeView(PGChildNodeView, DataTypeReader, SchemaDiffObjectCompare):
if not status: if not status:
return internal_server_error(errormsg=res) return internal_server_error(errormsg=res)
other_node_info = {}
if 'description' in data:
other_node_info['description'] = data['description']
return jsonify( return jsonify(
node=self.blueprint.generate_browser_node( node=self.blueprint.generate_browser_node(
tid, tid,
scid, scid,
name, name,
icon=self.icon_str % self.node_type icon=self.icon_str % self.node_type,
**other_node_info
) )
) )
except Exception as e: 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 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_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_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_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 WHERE t.typtype != 'd' AND t.typname NOT LIKE E'\\_%' AND t.typnamespace = {{scid}}::oid
{% if tid %} {% if tid %}
AND t.oid = {{tid}}::oid 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 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_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_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_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 WHERE t.typtype != 'd' AND t.typname NOT LIKE E'\\_%' AND t.typnamespace = {{scid}}::oid
{% if tid %} {% if tid %}
AND t.oid = {{tid}}::oid AND t.oid = {{tid}}::oid

View File

@ -1,10 +1,14 @@
SELECT SELECT
db.oid as did, db.datname as name, ta.spcname as spcname, db.datallowconn, db.oid as did, db.datname as name, ta.spcname as spcname, db.datallowconn,
db.datistemplate AS is_template, 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 FROM
pg_catalog.pg_database db pg_catalog.pg_database db
LEFT OUTER JOIN pg_catalog.pg_tablespace ta ON db.dattablespace = ta.oid 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 %} WHERE {% if did %}
db.oid = {{ did|qtLiteral(conn) }}::OID db.oid = {{ did|qtLiteral(conn) }}::OID
{% endif %} {% endif %}

View File

@ -209,7 +209,8 @@ SELECT EXISTS(
sid, sid,
rset['rows'][0]['jobname'], rset['rows'][0]['jobname'],
"icon-pga_job" if rset['rows'][0]['jobenabled'] else "icon-pga_job" if rset['rows'][0]['jobenabled'] else
"icon-pga_job-disabled" "icon-pga_job-disabled",
description=rset['rows'][0]['jobdesc']
), ),
status=200 status=200
) )
@ -222,7 +223,8 @@ SELECT EXISTS(
sid, sid,
row['jobname'], row['jobname'],
"icon-pga_job" if row['jobenabled'] else "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, sid,
row['jobname'], row['jobname'],
icon="icon-pga_job" if row['jobenabled'] 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'], row['jscname'],
icon="icon-pga_schedule" if row['jscenabled'] else icon="icon-pga_schedule" if row['jscenabled'] else
"icon-pga_schedule-disabled", "icon-pga_schedule-disabled",
enabled=row['jscenabled'] enabled=row['jscenabled'],
description=row['jscdesc']
) )
) )
@ -277,7 +278,8 @@ class JobScheduleView(PGChildNodeView):
row['jscname'], row['jscname'],
icon="icon-pga_schedule" if row['jscenabled'] else icon="icon-pga_schedule" if row['jscenabled'] else
"icon-pga_schedule-disabled", "icon-pga_schedule-disabled",
enabled=row['jscenabled'] enabled=row['jscenabled'],
description=row['jscdesc']
) )
) )
@ -455,7 +457,8 @@ class JobScheduleView(PGChildNodeView):
row['jscname'], row['jscname'],
icon="icon-pga_schedule" if row['jscenabled'] else icon="icon-pga_schedule" if row['jscenabled'] else
"icon-pga_schedule-disabled", "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="icon-pga_jobstep" if row['jstenabled'] else
"icon-pga_jobstep-disabled", "icon-pga_jobstep-disabled",
enabled=row['jstenabled'], 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="icon-pga_jobstep" if row['jstenabled'] else
"icon-pga_jobstep-disabled", "icon-pga_jobstep-disabled",
enabled=row['jstenabled'], enabled=row['jstenabled'],
kind=row['jstkind'] kind=row['jstkind'],
description=row['jstdesc']
) )
) )
@ -478,7 +480,8 @@ SELECT EXISTS(
jid, jid,
row['jstname'], row['jstname'],
icon="icon-pga_jobstep" if row['jstenabled'] 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 SELECT
jobid, jobname, jobenabled jobid, jobname, jobenabled, jobdesc
FROM FROM
pgagent.pga_job pgagent.pga_job
{% if jid %} {% if jid %}

View File

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

View File

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

View File

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

View File

@ -1,5 +1,6 @@
SELECT 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 FROM
pg_catalog.pg_roles r pg_catalog.pg_roles r
{% if rid %} {% if rid %}

View File

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

View File

@ -833,7 +833,7 @@ define('pgadmin.browser.node', [
background: ${bgcolor} !important; background: ${bgcolor} !important;
} }
${fgcolor ? ` ${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; color: ${fgcolor} !important;
} }
`:''} `:''}

View File

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

View File

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

View File

@ -11,6 +11,7 @@ import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import MainMenuFactory from '../../browser/static/js/MainMenuFactory'; import MainMenuFactory from '../../browser/static/js/MainMenuFactory';
import AppMenuBar from '../js/AppMenuBar'; import AppMenuBar from '../js/AppMenuBar';
import ObjectBreadcrumbs from '../js/components/ObjectBreadcrumbs';
import Theme from '../js/Theme'; import Theme from '../js/Theme';
define('app', [ define('app', [
@ -52,6 +53,16 @@ define('app', [
const menuContainerEle = document.querySelector('#main-menu-container'); const menuContainerEle = document.querySelector('#main-menu-container');
if(menuContainerEle) { 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 decorations: ClasslistComposite
onClick: (ev: React.MouseEvent, item: FileEntry | Directory, type: ItemType) => void onClick: (ev: React.MouseEvent, item: FileEntry | Directory, type: ItemType) => void
onContextMenu: (ev: React.MouseEvent, item: FileEntry | Directory) => 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> events: Notificar<FileTreeXEvent>
} }
@ -81,6 +84,8 @@ export class FileTreeItem extends React.Component<IItemRendererXProps & IItemRen
onClick={this.handleClick} onClick={this.handleClick}
onDoubleClick={this.handleDoubleClick} onDoubleClick={this.handleDoubleClick}
onDragStart={this.handleDragStartItem} onDragStart={this.handleDragStartItem}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
// required for rendering context menus when opened through context menu button on keyboard // required for rendering context menus when opened through context menu button on keyboard
ref={this.handleDivRef} ref={this.handleDivRef}
draggable={true}> 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) => { private handleDragStartItem = (e: React.DragEvent) => {
const { item, itemType, events } = this.props; const { item, itemType, events } = this.props;
if (itemType === ItemType.File || itemType === ItemType.Directory) { if (itemType === ItemType.File || itemType === ItemType.Directory) {

View File

@ -29,6 +29,8 @@ export class FileTreeX extends React.Component<IFileTreeXProps> {
private disposables: DisposablesComposite; private disposables: DisposablesComposite;
private keyboardHotkeys: KeyboardHotkeys; private keyboardHotkeys: KeyboardHotkeys;
private fileTreeEvent: IFileTreeXTriggerEvents; 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) { constructor(props: IFileTreeXProps) {
super(props); super(props);
this.events = new Notificar(); this.events = new Notificar();
@ -73,6 +75,8 @@ export class FileTreeX extends React.Component<IFileTreeXProps> {
onClick={this.handleItemClicked} onClick={this.handleItemClicked}
onDoubleClick={this.handleItemDoubleClicked} onDoubleClick={this.handleItemDoubleClicked}
onContextMenu={this.handleItemCtxMenu} onContextMenu={this.handleItemCtxMenu}
onMouseEnter={this.onItemMouseEnter}
onMouseLeave={this.onItemMouseLeave}
changeDirectoryCount={this.changeDirectoryCount} changeDirectoryCount={this.changeDirectoryCount}
events={this.events}/>} events={this.events}/>}
</FileTree> </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> => { private setActiveFile = async (fileOrDirOrPath: FileOrDir | string, ensureVisible, align): Promise<void> => {
const fileH = typeof fileOrDirOrPath === 'string' const fileH = typeof fileOrDirOrPath === 'string'
? await this.fileTreeHandle.getFileHandle(fileOrDirOrPath) ? await this.fileTreeHandle.getFileHandle(fileOrDirOrPath)

View File

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

View File

@ -29,6 +29,16 @@ function manageTreeEvents(event, eventName, item) {
console.warn(e.stack || e); console.warn(e.stack || e);
return false; 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 { } else {
// Events for browser tree. // Events for browser tree.
if (d && obj.Nodes[d._type]) { if (d && obj.Nodes[d._type]) {
@ -384,6 +394,23 @@ export class Tree {
})(tree.tree.getModel().root); })(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) { findNodeByDomElement(domElement) {
const path = domElement.path; const path = domElement.path;
if (!path || !path[0]) { if (!path || !path[0]) {

View File

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

View File

@ -18,6 +18,7 @@ MIMETYPE_APP_JSON = 'application/json'
# Preference labels # Preference labels
PREF_LABEL_KEYBOARD_SHORTCUTS = gettext('Keyboard shortcuts') PREF_LABEL_KEYBOARD_SHORTCUTS = gettext('Keyboard shortcuts')
PREF_LABEL_DISPLAY = gettext('Display') PREF_LABEL_DISPLAY = gettext('Display')
PREF_LABEL_BREADCRUMBS = gettext('Object Breadcrumbs')
PREF_LABEL_OPTIONS = gettext('Options') PREF_LABEL_OPTIONS = gettext('Options')
PREF_LABEL_EXPLAIN = gettext('Explain') PREF_LABEL_EXPLAIN = gettext('Explain')
PREF_LABEL_EDITOR = gettext('Editor') 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 languageName: node
linkType: hard linkType: hard
"jasmine-core@npm:3.10.1": "jasmine-core@npm:3.10.1, jasmine-core@npm:^3.6.0":
version: 3.10.1 version: 3.10.1
resolution: "jasmine-core@npm:3.10.1" resolution: "jasmine-core@npm:3.10.1"
checksum: 77ee26aaf29576e982a2ebe6586218ff4d7cc4305ad18c400954bbdeb3c7987e9a4a8ac6d6548b65838852f325395fc901d69bf8c24bdccfbd67b263fbf5d4fd checksum: 77ee26aaf29576e982a2ebe6586218ff4d7cc4305ad18c400954bbdeb3c7987e9a4a8ac6d6548b65838852f325395fc901d69bf8c24bdccfbd67b263fbf5d4fd
languageName: node languageName: node
linkType: hard 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": "jasmine-enzyme@npm:^7.1.2":
version: 7.1.2 version: 7.1.2
resolution: "jasmine-enzyme@npm:7.1.2" resolution: "jasmine-enzyme@npm:7.1.2"