From 99fe37c9e65698923fe2d77de3ccaeb5b7058c41 Mon Sep 17 00:00:00 2001 From: Aditya Toshniwal Date: Fri, 29 Nov 2024 16:29:34 +0530 Subject: [PATCH] Added support for tags on a server node. #8192 --- docs/en_US/images/server_tags.png | Bin 0 -> 43079 bytes docs/en_US/server_dialog.rst | 15 ++++ web/migrations/versions/f28be870d5ec_.py | 35 +++++++++ .../browser/server_groups/servers/__init__.py | 72 ++++++++++++++++-- .../servers/static/js/server.ui.js | 43 ++++++++++- .../servers/tests/servers_test_data.json | 15 ++++ .../servers/tests/test_add_server.py | 3 + web/pgadmin/model/__init__.py | 3 +- .../static/js/SchemaView/MappedControl.jsx | 3 + .../js/Theme/overrides/reactaspen.override.js | 18 +++-- .../static/js/components/FormComponents.jsx | 8 -- .../components/PgTree/FileTreeItem/index.tsx | 10 ++- .../ReactCodeMirror/components/Editor.jsx | 6 +- .../static/js/helpers/withColorPicker.js | 5 +- web/pgadmin/static/js/utils.js | 4 + .../js/components/QueryToolDataGrid/index.jsx | 12 ++- web/pgadmin/utils/__init__.py | 3 + 17 files changed, 218 insertions(+), 37 deletions(-) create mode 100644 docs/en_US/images/server_tags.png create mode 100644 web/migrations/versions/f28be870d5ec_.py diff --git a/docs/en_US/images/server_tags.png b/docs/en_US/images/server_tags.png new file mode 100644 index 0000000000000000000000000000000000000000..ac849664f740f884475154e1a631d9e7128159b6 GIT binary patch literal 43079 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 0: + old_tags = [ + t for t in old_tags if t['old_text'] not in deleted_ids + ] + + for item in new_tags_info.get('changed', []): + update_tag(old_tags, item) + + for item in new_tags_info.get('added', []): + old_tags.append(item) + + # remove the old_text key + data['tags'] = [ + {k: v for k, v in tag.items() + if k != 'old_text'} for tag in old_tags + ] + @pga_login_required def nodes(self, gid): res = [] @@ -609,7 +649,8 @@ def nodes(self, gid): shared=server.shared, is_kerberos_conn=bool(server.kerberos_conn), gss_authenticated=manager.gss_authenticated, - description=server.comment + description=server.comment, + tags=server.tags ) ) @@ -678,7 +719,8 @@ def node(self, gid, sid): shared=server.shared, username=server.username, is_kerberos_conn=bool(server.kerberos_conn), - gss_authenticated=manager.gss_authenticated + gss_authenticated=manager.gss_authenticated, + tags=server.tags ), ) @@ -783,7 +825,8 @@ def update(self, gid, sid): 'shared_username': 'shared_username', 'kerberos_conn': 'kerberos_conn', 'connection_params': 'connection_params', - 'prepare_threshold': 'prepare_threshold' + 'prepare_threshold': 'prepare_threshold', + 'tags': 'tags' } disp_lbl = { @@ -808,6 +851,7 @@ def update(self, gid, sid): # Update connection parameter if any. self.update_connection_parameter(data, server) + self.update_tags(data, server) if 'connection_params' in data and \ 'hostaddr' in data['connection_params'] and \ @@ -838,6 +882,10 @@ def update(self, gid, sid): errormsg=gettext('No parameters were changed.') ) + # tags is JSON type, sqlalchemy sometimes will not detect change + if 'tags' in data: + flag_modified(server, 'tags') + try: db.session.commit() except Exception as e: @@ -872,7 +920,8 @@ def update(self, gid, sid): username=server.username, role=server.role, is_password_saved=bool(server.save_password), - description=server.comment + description=server.comment, + tags=server.tags ) ) @@ -1022,6 +1071,10 @@ def properties(self, gid, sid): tunnel_authentication = bool(server.tunnel_authentication) tunnel_keep_alive = server.tunnel_keep_alive + tags = None + if server.tags is not None: + tags = [{**tag, 'old_text': tag['text']} + for tag in server.tags] response = { 'id': server.id, 'name': server.name, @@ -1064,7 +1117,8 @@ def properties(self, gid, sid): 'cloud_status': server.cloud_status, 'connection_params': connection_params, 'connection_string': display_connection_str, - 'prepare_threshold': server.prepare_threshold + 'prepare_threshold': server.prepare_threshold, + 'tags': tags, } return ajax_response(response) @@ -1180,7 +1234,8 @@ def create(self, gid): passexec_expiration=data.get('passexec_expiration', None), kerberos_conn=1 if data.get('kerberos_conn', False) else 0, connection_params=connection_params, - prepare_threshold=data.get('prepare_threshold', None) + prepare_threshold=data.get('prepare_threshold', None), + tags=data.get('tags', None) ) db.session.add(server) db.session.commit() @@ -1273,7 +1328,8 @@ def create(self, gid): manager and manager.gss_authenticated else False, is_password_saved=bool(server.save_password), is_tunnel_password_saved=tunnel_password_saved, - user_id=server.user_id + user_id=server.user_id, + tags=data.get('tags', None) ) ) diff --git a/web/pgadmin/browser/server_groups/servers/static/js/server.ui.js b/web/pgadmin/browser/server_groups/servers/static/js/server.ui.js index 8ea3cf18972..06bcdefc693 100644 --- a/web/pgadmin/browser/server_groups/servers/static/js/server.ui.js +++ b/web/pgadmin/browser/server_groups/servers/static/js/server.ui.js @@ -15,6 +15,35 @@ import {default as supportedServers} from 'pgadmin.server.supported_servers'; import current_user from 'pgadmin.user_management.current_user'; import { isEmptyString } from 'sources/validators'; import VariableSchema from './variable.ui'; +import { getRandomColor } from '../../../../../static/js/utils'; + +class TagsSchema extends BaseUISchema { + get idAttribute() { return 'old_text'; } + + get baseFields() { + return [ + { + id: 'text', label: gettext('Text'), cell: 'text', group: null, + mode: ['create', 'edit'], noEmpty: true, controlProps: { + maxLength: 30, + } + }, + { + id: 'color', label: gettext('Color'), cell: 'color', group: null, + mode: ['create', 'edit'], controlProps: { + input: true, + } + }, + ]; + } + + getNewData(data) { + return { + ...data, + color: getRandomColor(), + }; + } +} export default class ServerSchema extends BaseUISchema { constructor(serverGroupOptions=[], userId=0, initValues={}) { @@ -50,11 +79,13 @@ export default class ServerSchema extends BaseUISchema { connection_params: [ {'name': 'sslmode', 'value': 'prefer', 'keyword': 'sslmode'}, {'name': 'connect_timeout', 'value': 10, 'keyword': 'connect_timeout'}], + tags: [], ...initValues, }); this.serverGroupOptions = serverGroupOptions; this.paramSchema = new VariableSchema(this.getConnectionParameters(), null, null, ['name', 'keyword', 'value']); + this.tagsSchema = new TagsSchema(); this.userId = userId; _.bindAll(this, 'isShared'); } @@ -109,8 +140,8 @@ export default class ServerSchema extends BaseUISchema { { id: 'bgcolor', label: gettext('Background'), type: 'color', group: null, mode: ['edit', 'create'], - disabled: obj.isConnected, deps: ['fgcolor'], depChange: (state)=>{ - if(!state.bgcolor && state.fgcolor) { + disabled: obj.isConnected, deps: ['fgcolor'], depChange: (state, source)=>{ + if(source[0] == 'fgcolor' && !state.bgcolor && state.fgcolor) { return {'bgcolor': '#ffffff'}; } } @@ -365,7 +396,13 @@ export default class ServerSchema extends BaseUISchema { mode: ['properties', 'edit', 'create'], helpMessageMode: ['edit', 'create'], helpMessage: gettext('If it is set to 0, every query is prepared the first time it is executed. If it is set to blank, prepared statements are disabled on the connection.') - } + }, + { + id: 'tags', label: '', + type: 'collection', group: gettext('Tags'), + schema: this.tagsSchema, mode: ['edit', 'create'], uniqueCol: ['text'], + canAdd: true, canEdit: false, canDelete: true, + }, ]; } diff --git a/web/pgadmin/browser/server_groups/servers/tests/servers_test_data.json b/web/pgadmin/browser/server_groups/servers/tests/servers_test_data.json index 3de500b8f4a..5cba9062d19 100644 --- a/web/pgadmin/browser/server_groups/servers/tests/servers_test_data.json +++ b/web/pgadmin/browser/server_groups/servers/tests/servers_test_data.json @@ -225,6 +225,21 @@ "expected_data": { "status_code": 200 } + }, + { + "name": "Add server with tags", + "url": "/browser/server/obj/", + "is_positive_test": true, + "test_data": { + "tags": [ + {"text": "tag1", "color": "#000"} + ] + }, + "mocking_required": false, + "mock_data": {}, + "expected_data": { + "status_code": 200 + } } ], "is_password_saved": [ diff --git a/web/pgadmin/browser/server_groups/servers/tests/test_add_server.py b/web/pgadmin/browser/server_groups/servers/tests/test_add_server.py index 1e388c4cad3..686661c3576 100644 --- a/web/pgadmin/browser/server_groups/servers/tests/test_add_server.py +++ b/web/pgadmin/browser/server_groups/servers/tests/test_add_server.py @@ -88,6 +88,9 @@ def runTest(self): if 'bgcolor' in self.test_data: self.server['bgcolor'] = self.test_data['bgcolor'] + if 'tags' in self.test_data: + self.server['tags'] = self.test_data['tags'] + if self.is_positive_test: if hasattr(self, 'with_save'): self.server['save_password'] = self.with_save diff --git a/web/pgadmin/model/__init__.py b/web/pgadmin/model/__init__.py index 06bc3ee84fe..60adc1c09e6 100644 --- a/web/pgadmin/model/__init__.py +++ b/web/pgadmin/model/__init__.py @@ -33,7 +33,7 @@ # ########################################################################## -SCHEMA_VERSION = 40 +SCHEMA_VERSION = 41 ########################################################################## # @@ -209,6 +209,7 @@ class Server(db.Model): cloud_status = db.Column(db.Integer(), nullable=False, default=0) connection_params = db.Column(MutableDict.as_mutable(types.JSON)) prepare_threshold = db.Column(db.Integer(), nullable=True) + tags = db.Column(types.JSON) class ModulePreference(db.Model): diff --git a/web/pgadmin/static/js/SchemaView/MappedControl.jsx b/web/pgadmin/static/js/SchemaView/MappedControl.jsx index e9f9489d271..18aee7c78db 100644 --- a/web/pgadmin/static/js/SchemaView/MappedControl.jsx +++ b/web/pgadmin/static/js/SchemaView/MappedControl.jsx @@ -31,6 +31,7 @@ import { useFieldOptions, useFieldValue, useFieldError, useSchemaStateSubscriber, } from './hooks'; import { listenDepChanges } from './utils'; +import { InputColor } from '../components/FormComponents'; /* Control mapping for form view */ @@ -263,6 +264,8 @@ function MappedCellControlBase({ return ; case 'sql': return ; + case 'color': + return ; case 'file': return ; case 'keyCode': diff --git a/web/pgadmin/static/js/Theme/overrides/reactaspen.override.js b/web/pgadmin/static/js/Theme/overrides/reactaspen.override.js index 26e58bfc863..a46469190d4 100644 --- a/web/pgadmin/static/js/Theme/overrides/reactaspen.override.js +++ b/web/pgadmin/static/js/Theme/overrides/reactaspen.override.js @@ -7,6 +7,7 @@ // ////////////////////////////////////////////////////////////// + export default function reactAspenOverride(theme) { return { '.drag-tree-node': { @@ -47,7 +48,7 @@ export default function reactAspenOverride(theme) { top: '0px' + ' !important', '>div': { - scrollbarGutter: 'stable', + scrollbarGutter: 'auto', overflow: 'overlay' + ' !important', }, }, @@ -136,6 +137,7 @@ export default function reactAspenOverride(theme) { 'span.file-label': { display: 'flex', + gap: '2px', alignItems: 'center', padding: '0 2px 0 2px', border: '1px solid transparent', @@ -153,13 +155,20 @@ export default function reactAspenOverride(theme) { flexGrow: 1, userSelect: 'none', color: theme.otherVars.tree.textFg, - marginLeft: '3px', cursor: 'pointer !important', whiteSpace: 'nowrap', '&:hover, &.pseudo-active': { color: theme.otherVars.tree.fgHover, }, }, + 'div.file-tag': { + color: 'var(--tag-color)', + border: '1px solid color-mix(in srgb, var(--tag-color) 90%, #fff)', + padding: '0px 4px', + borderRadius: theme.shape.borderRadius, + backgroundColor: 'color-mix(in srgb, color-mix(in srgb, var(--tag-color) 10%, #fff) 50%, transparent);', + lineHeight: 1.2 + }, i: { display: 'inline-block', @@ -221,10 +230,5 @@ export default function reactAspenOverride(theme) { }) ), }, - - '.children-count': { - marginLeft: '3px', - }, - }; } diff --git a/web/pgadmin/static/js/components/FormComponents.jsx b/web/pgadmin/static/js/components/FormComponents.jsx index d8b7e590082..019ace41ab0 100644 --- a/web/pgadmin/static/js/components/FormComponents.jsx +++ b/web/pgadmin/static/js/components/FormComponents.jsx @@ -50,14 +50,6 @@ const Root = styled('div')(({theme}) => ({ '& .Form-optionIcon': { ...theme.mixins.nodeIcon, }, - // '& .Form-label': { - // margin: theme.spacing(0.75, 0.75, 0.75, 0.75), - // display: 'flex', - // wordBreak: 'break-word' - // }, - // '& .Form-labelError': { - // color: theme.palette.error.main, - // }, '& .Form-sql': { border: '1px solid ' + theme.otherVars.inputBorderColor, borderRadius: theme.shape.borderRadius, diff --git a/web/pgadmin/static/js/components/PgTree/FileTreeItem/index.tsx b/web/pgadmin/static/js/components/PgTree/FileTreeItem/index.tsx index 27b83de5984..c3119d84b4b 100644 --- a/web/pgadmin/static/js/components/PgTree/FileTreeItem/index.tsx +++ b/web/pgadmin/static/js/components/PgTree/FileTreeItem/index.tsx @@ -72,6 +72,8 @@ export class FileTreeItem extends React.Component 0 && item._metadata.data._type.indexOf('coll-') !== -1 ? '(' + item.children.length + ')' : ''; const extraClasses = item._metadata.data.extraClasses ? item._metadata.data.extraClasses.join(' ') : ''; + const tags = item._metadata.data?.tags ?? []; + return (
{ _.unescape(this.props.item.getMetadata('data')._label)} - {itemChildren} - + {itemChildren} + {tags.map((tag)=>( +
+ {tag.text} +
+ ))}
); } diff --git a/web/pgadmin/static/js/components/ReactCodeMirror/components/Editor.jsx b/web/pgadmin/static/js/components/ReactCodeMirror/components/Editor.jsx index 3d3a38a4b0a..ea497eea9d2 100644 --- a/web/pgadmin/static/js/components/ReactCodeMirror/components/Editor.jsx +++ b/web/pgadmin/static/js/components/ReactCodeMirror/components/Editor.jsx @@ -126,13 +126,13 @@ const defaultExtensions = [ indentOnInput(), syntaxHighlighting, keymap.of([{ + key: 'Tab', + run: acceptCompletion, + },{ key: 'Tab', preventDefault: true, run: insertTabWithUnit, shift: indentLess, - },{ - key: 'Tab', - run: acceptCompletion, },{ key: 'Backspace', preventDefault: true, diff --git a/web/pgadmin/static/js/helpers/withColorPicker.js b/web/pgadmin/static/js/helpers/withColorPicker.js index 2ac97de18e8..fd549b4f617 100644 --- a/web/pgadmin/static/js/helpers/withColorPicker.js +++ b/web/pgadmin/static/js/helpers/withColorPicker.js @@ -15,7 +15,7 @@ import PropTypes from 'prop-types'; import { fullHexColor } from '../utils'; export function withColorPicker(Component) { - + const HOCComponent = ({value, currObj, onChange, onSave, options, ...props})=>{ const pickrOptions = { showPalette: true, @@ -74,10 +74,11 @@ export function withColorPicker(Component) { defaultRepresentation: pickrOptions.colorFormat, disabled: pickrOptions.disabled, save: pickrOptions.allowSave, + input: pickrOptions.input, }, }, }).on('init', instance => { - setColor(value); + setColor(value, true); pickrOptions.disabled && instance.disable(); const { lastColor } = instance.getRoot().preview; diff --git a/web/pgadmin/static/js/utils.js b/web/pgadmin/static/js/utils.js index 8a38c01ba79..3da2c40e949 100644 --- a/web/pgadmin/static/js/utils.js +++ b/web/pgadmin/static/js/utils.js @@ -684,6 +684,10 @@ export function getChartColor(index, theme='light', colorPalette=CHART_THEME_COL return palette[index % palette.length]; } +export function getRandomColor() { + return '#' + ((1 << 24) * Math.random() | 0).toString(16).padStart(6, '0'); +} + // Using this function instead of 'btoa' directly. // https://developer.mozilla.org/en-US/docs/Glossary/Base64#the_unicode_problem function stringToBase64(str) { diff --git a/web/pgadmin/tools/sqleditor/static/js/components/QueryToolDataGrid/index.jsx b/web/pgadmin/tools/sqleditor/static/js/components/QueryToolDataGrid/index.jsx index c7f2955f121..4f545d9aaac 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/QueryToolDataGrid/index.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/QueryToolDataGrid/index.jsx @@ -157,7 +157,7 @@ function SelectAllHeaderRenderer({isCellSelected}) { }, [isRowSelected]); return
; + tabIndex="0" onKeyDown={(e)=>dataGridExtras.handleShortcuts(e, true)}>; } SelectAllHeaderRenderer.propTypes = { onAllRowsSelectionChange: PropTypes.func, @@ -192,7 +192,7 @@ function SelectableHeaderRenderer({column, selectedColumns, onSelectedColumnsCha return ( + onKeyDown={(e)=>dataGridExtras.handleShortcuts(e, true)} data-column-key={column.key}> {(column.column_type_internal == 'geometry' || column.column_type_internal == 'geography') && } size="small" style={{marginRight: '0.25rem'}} onClick={(e)=>{ @@ -385,7 +385,13 @@ export default function QueryToolDataGrid({columns, rows, totalRowCount, dataCha eventBus.fireEvent(QUERY_TOOL_EVENTS.TRIGGER_COPY_DATA); } - function handleShortcuts(e) { + function handleShortcuts(e, withCopy=false) { + // Handle Copy shortcut Cmd/Ctrl + c + if((e.ctrlKey || e.metaKey) && e.key !== 'Control' && e.keyCode == 67 && withCopy) { + e.preventDefault(); + handleCopy(); + } + // Handle Select All Cmd + A(mac) / Ctrl + a (others) if(((isMac() && e.metaKey) || (!isMac() && e.ctrlKey)) && e.key === 'a') { e.preventDefault(); diff --git a/web/pgadmin/utils/__init__.py b/web/pgadmin/utils/__init__.py index e25fbf79b4f..decf1e03017 100644 --- a/web/pgadmin/utils/__init__.py +++ b/web/pgadmin/utils/__init__.py @@ -528,6 +528,7 @@ def dump_database_servers(output_file, selected_servers, server.kerberos_conn), add_value(attr_dict, "ConnectionParameters", server.connection_params) + add_value(attr_dict, "Tags", server.tags) # if desktop mode or server mode with # ENABLE_SERVER_PASS_EXEC_CMD flag is True @@ -766,6 +767,8 @@ def load_database_servers(input_file, selected_servers, new_server.kerberos_conn = obj.get("KerberosAuthentication", None) + new_server.tags = obj.get("Tags", None) + # if desktop mode or server mode with # ENABLE_SERVER_PASS_EXEC_CMD flag is True if not current_app.config['SERVER_MODE'] or \