diff --git a/web/pgadmin/misc/__init__.py b/web/pgadmin/misc/__init__.py index 972245c0ead..e47f1d46948 100644 --- a/web/pgadmin/misc/__init__.py +++ b/web/pgadmin/misc/__init__.py @@ -335,23 +335,19 @@ def validate_binary_path(): if data != '': data = json.loads(data) - version_str = '' - # Do not allow storage dir as utility path if 'utility_path' in data and data['utility_path'] is not None and \ Path(config.STORAGE_DIR) != Path(data['utility_path']) and \ Path(config.STORAGE_DIR) not in Path(data['utility_path']).parents: binary_versions = get_binary_path_versions(data['utility_path']) - for utility, version in binary_versions.items(): - if version is None: - version_str += "" + utility + ": " + \ - "not found on the specified binary path.
" - else: - version_str += "" + utility + ": " + version + "
" + utilities = [ + {'utility': utility, 'version': version} + for utility, version in binary_versions.items() + ] else: return precondition_required(gettext('Invalid binary path.')) - return make_json_response(data=gettext(version_str), status=200) + return make_json_response(data=utilities, status=200) @blueprint.route("/upgrade_check", endpoint="upgrade_check", diff --git a/web/pgadmin/preferences/static/js/components/binary_path.ui.js b/web/pgadmin/preferences/static/js/components/binary_path.ui.jsx similarity index 84% rename from web/pgadmin/preferences/static/js/components/binary_path.ui.js rename to web/pgadmin/preferences/static/js/components/binary_path.ui.jsx index 6d5f28bc5c5..da4291fda5d 100644 --- a/web/pgadmin/preferences/static/js/components/binary_path.ui.js +++ b/web/pgadmin/preferences/static/js/components/binary_path.ui.jsx @@ -13,6 +13,7 @@ import url_for from 'sources/url_for'; import BaseUISchema from 'sources/SchemaView/base_schema.ui'; import getApiInstance from '../../../../static/js/api_instance'; import pgAdmin from 'sources/pgadmin'; +import { SafeMessage } from '../../../../static/js/components/SafeMessage'; export function getBinaryPathSchema() { @@ -68,7 +69,13 @@ export default class BinaryPathSchema extends BaseUISchema { api.post(url_for('misc.validate_binary_path'), JSON.stringify({ 'utility_path': data })) .then(function (res) { - pgAdmin.Browser.notifier.alertText(gettext('Validate binary path'), gettext(res.data.data)); + const rows = (res.data.data ?? []).map(({utility, version}) => ( +
+ {utility}:{' '} + +
+ )); + pgAdmin.Browser.notifier.alert(gettext('Validate binary path'), <>{rows}); }) .catch(function (error) { pgAdmin.Browser.notifier.pgNotifier('error', error, gettext('Failed to validate binary path.')); diff --git a/web/pgadmin/static/js/helpers/ModalProvider.jsx b/web/pgadmin/static/js/helpers/ModalProvider.jsx index 3b7406da6b5..e9fba9d18b0 100644 --- a/web/pgadmin/static/js/helpers/ModalProvider.jsx +++ b/web/pgadmin/static/js/helpers/ModalProvider.jsx @@ -68,7 +68,7 @@ export function AlertContent({ text, confirm, okLabel = gettext('OK'), cancelLab ); } AlertContent.propTypes = { - text: PropTypes.string, + text: PropTypes.node, confirm: PropTypes.bool, onOkClick: PropTypes.func, onCancelClick: PropTypes.func, diff --git a/web/regression/javascript/schema_ui_files/binary_path.ui.spec.js b/web/regression/javascript/schema_ui_files/binary_path.ui.spec.js index dee2cb3d288..dadf2f218cf 100644 --- a/web/regression/javascript/schema_ui_files/binary_path.ui.spec.js +++ b/web/regression/javascript/schema_ui_files/binary_path.ui.spec.js @@ -8,10 +8,14 @@ ////////////////////////////////////////////////////////////// +import {render} from '@testing-library/react'; import {genericBeforeEach, getEditView} from '../genericFunctions'; import pgAdmin from '../fake_pgadmin'; import { getBinaryPathSchema } from '../../../pgadmin/preferences/static/js/components/binary_path.ui'; +let mockPost = jest.fn(); +jest.mock('sources/api_instance', () => () => ({ post: mockPost })); + describe('BinaryPathschema', ()=>{ let schemaObj = getBinaryPathSchema(); @@ -25,16 +29,51 @@ describe('BinaryPathschema', ()=>{ beforeEach(()=>{ genericBeforeEach(); + mockPost.mockReset(); + pgAdmin.Browser.notifier.alert.mockClear(); }); it('edit', async ()=>{ await getEditView(schemaObj, getInitData); }); - it('validate path', ()=>{ + it('validate path - empty path', ()=>{ let validate = _.find(schemaObj.fields, (f)=>f.id=='binaryPath').validate; let status = validate(''); expect(status).toBe(true); }); + it('validate path - renders bold labels and line breaks, not raw markup', async ()=>{ + mockPost.mockResolvedValue({ + data: { + data: [ + {utility: 'pg_dump', version: null}, + {utility: 'psql', version: 'psql 17.2'}, + ], + }, + }); + + let validate = _.find(schemaObj.fields, (f)=>f.id=='binaryPath').validate; + let status = validate('/some/path'); + expect(status).toBe(true); + + // Let the post().then() microtask run. + await Promise.resolve(); + await Promise.resolve(); + + expect(pgAdmin.Browser.notifier.alert).toHaveBeenCalledTimes(1); + const [title, node] = pgAdmin.Browser.notifier.alert.mock.calls[0]; + expect(title).toBe('Validate binary path'); + + const ctrl = render(node); + expect(ctrl.container.querySelectorAll('b')).toHaveLength(2); + expect(ctrl.container.textContent).toContain('pg_dump:'); + expect(ctrl.container.textContent).toContain('not found on the specified binary path.'); + expect(ctrl.container.textContent).toContain('psql:'); + expect(ctrl.container.textContent).toContain('psql 17.2'); + // No literal markup should ever appear in the rendered text. + expect(ctrl.container.textContent).not.toContain(''); + expect(ctrl.container.textContent).not.toContain('
'); + }); + });