Continue fixing multiple UI and SonarQube issues found when testing wcDocker changes. #6479

This commit is contained in:
Aditya Toshniwal 2023-10-27 15:51:45 +05:30
parent 1bfd8d7f3c
commit b11b2a2f50
26 changed files with 171 additions and 111 deletions

View File

@ -207,8 +207,7 @@ Front End
pgAdmin uses javascript extensively for the front-end implementation. It uses
require.js to allow the lazy loading (or, say load only when required),
bootstrap and MaterialUI for UI look and feel, React for generating
properties/create dialog for selected node. We have
ReactJS with CSS and MaterialUI for UI look and feel. We have
divided each module in small chunks as much as possible. Not all javascript
modules are required to be loaded (i.e. loading a javascript module for
database will make sense only when a server node is loaded completely.) Please

View File

@ -22,14 +22,12 @@ purpose or how it works if not obvious from a quick review of the code itself.
CSS 3
*****
CSS3 is used for styling and layout throughout the application. Extensive use is
made of the Bootstrap Framework to aid in that process, however additional
styles must still be created from time to time.
CSS3 is used for styling and layout throughout the application.
Most custom styling comes from individual modules which may advertise static
stylesheets to be included in the module that is loading them via hooks.
Styling overrides (for example, to alter the Bootstrap look and feel) will
Styling overrides (for example, to alter the look and feel) will
typically be found in the **overrides.css** file in the main static file
directory for the application.

View File

@ -5,3 +5,5 @@ logFilters:
level: discard
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-3.6.4.cjs

View File

@ -230,8 +230,8 @@ PROXY_X_PREFIX_COUNT = 0
# COMPRESSION
COMPRESS_MIMETYPES = [
'text/html', 'text/css', 'text/xml', 'application/json',
'application/javascript'
'text/html', 'text/css', 'text/xml', 'text/javascript',
'application/json', 'application/javascript'
]
COMPRESS_LEVEL = 9
COMPRESS_MIN_SIZE = 500

View File

@ -314,7 +314,6 @@ define('pgadmin.browser', [
'pgadmin:server:disconnect', stop_heartbeat.bind(obj)
);
obj.set_master_password('');
obj.check_corrupted_db_file();
obj.Events.on('pgadmin:browser:tree:add', obj.onAddTreeNode.bind(obj));
obj.Events.on('pgadmin:browser:tree:update', obj.onUpdateTreeNode.bind(obj));
@ -328,6 +327,7 @@ define('pgadmin.browser', [
obj.start_inactivity_timeout_daemon();
},
uiloaded: function() {
this.set_master_password('');
this.check_version_update();
},
check_corrupted_db_file: function() {

View File

@ -13,7 +13,7 @@ import gettext from 'sources/gettext';
import PropTypes from 'prop-types';
import { makeStyles } from '@material-ui/core/styles';
import { BgProcessManagerEvents, BgProcessManagerProcessState } from './BgProcessConstants';
import { PgIconButton } from '../../../../static/js/components/Buttons';
import { PgButtonGroup, PgIconButton } from '../../../../static/js/components/Buttons';
import CancelIcon from '@material-ui/icons/Cancel';
import DescriptionOutlinedIcon from '@material-ui/icons/DescriptionOutlined';
import DeleteIcon from '@material-ui/icons/Delete';
@ -282,27 +282,27 @@ export default function Processes() {
CustomHeader={()=>{
return (
<Box>
<PgIconButton
className={classes.dropButton}
icon={<DeleteIcon/>}
aria-label="Acknowledge and Remove"
title={gettext('Acknowledge and Remove')}
onClick={() => {
pgAdmin.Browser.notifier.confirm(gettext('Remove Processes'), gettext('Are you sure you want to remove the selected processes?'), ()=>{
pgAdmin.Browser.BgProcessManager.acknowledge(selectedRows.map((p)=>p.original.id));
});
}}
disabled={selectedRows.length <= 0}
></PgIconButton>
<PgIconButton
icon={<HelpIcon/>}
aria-label="Help"
title={gettext('Help')}
style={{marginLeft: '8px'}}
onClick={() => {
window.open(url_for('help.static', {'filename': 'processes.html'}));
}}
></PgIconButton>
<PgButtonGroup>
<PgIconButton
icon={<DeleteIcon style={{height: '1.4rem'}}/>}
aria-label="Acknowledge and Remove"
title={gettext('Acknowledge and Remove')}
onClick={() => {
pgAdmin.Browser.notifier.confirm(gettext('Remove Processes'), gettext('Are you sure you want to remove the selected processes?'), ()=>{
pgAdmin.Browser.BgProcessManager.acknowledge(selectedRows.map((p)=>p.original.id));
});
}}
disabled={selectedRows.length <= 0}
></PgIconButton>
<PgIconButton
icon={<HelpIcon style={{height: '1.4rem'}}/>}
aria-label="Help"
title={gettext('Help')}
onClick={() => {
window.open(url_for('help.static', {'filename': 'processes.html'}));
}}
></PgIconButton>
</PgButtonGroup>
</Box>
);
}}

View File

@ -154,6 +154,7 @@ function Dependencies({ nodeData, nodeItem, node, treeNodeInfo, isActive, isStal
if (message != '') {
setMsg(message);
setLoaderText('');
setTableData([]);
}
setIsStale(false);
}, [isActive, isStale]);

View File

@ -152,6 +152,7 @@ function Dependents({ nodeData, nodeItem, node, treeNodeInfo, isActive, isStale,
}
if (message != '') {
setLoaderText('');
setTableData([]);
setMsg(message);
}

View File

@ -15,7 +15,7 @@ import gettext from 'sources/gettext';
import PgTable from 'sources/components/PgTable';
import Theme from 'sources/Theme';
import PropTypes from 'prop-types';
import { PgIconButton } from '../../static/js/components/Buttons';
import { PgButtonGroup, PgIconButton } from '../../static/js/components/Buttons';
import DeleteIcon from '@material-ui/icons/Delete';
import DeleteSweepIcon from '@material-ui/icons/DeleteSweep';
import DeleteForeverIcon from '@material-ui/icons/DeleteForever';
@ -64,9 +64,6 @@ const useStyles = makeStyles((theme) => ({
overflow: 'hidden !important',
overflowX: 'auto !important'
},
dropButton: {
marginRight: '8px !important'
},
readOnlySwitch: {
opacity: 0.75,
'& .MuiSwitch-track': {
@ -291,48 +288,47 @@ export default function CollectionNodeProperties({
const canDropForce = evalFunc(node, node.canDropForce, nodeData, nodeItem, treeNodeInfo);
return (
<Box >
<PgIconButton
className={classes.dropButton}
icon={<DeleteIcon/>}
aria-label="Delete"
title={gettext('Delete')}
onClick={() => {
onDrop('drop');
}}
disabled={
(selectedObject.length > 0)
? !canDrop
: true
}
></PgIconButton>
{node.type !== 'coll-database' ? <PgIconButton
className={classes.dropButton}
icon={<DeleteSweepIcon />}
aria-label="Delete Cascade"
title={gettext('Delete (Cascade)')}
onClick={() => {
onDrop('dropCascade');
}}
disabled={
(selectedObject.length > 0)
? !canDropCascade
: true
}
></PgIconButton> :
<PgButtonGroup size="small">
<PgIconButton
className={classes.dropButton}
icon={<DeleteForeverIcon />}
aria-label="Delete Force"
title={gettext('Delete (Force)')}
icon={<DeleteIcon style={{height: '1.35rem'}}/>}
aria-label="Delete"
title={gettext('Delete')}
onClick={() => {
onDrop('dropForce');
onDrop('drop');
}}
disabled={
(selectedObject.length > 0)
? !canDropForce
? !canDrop
: true
}
></PgIconButton>}
></PgIconButton>
{node.type !== 'coll-database' ? <PgIconButton
icon={<DeleteSweepIcon style={{height: '1.5rem'}} />}
aria-label="Delete Cascade"
title={gettext('Delete (Cascade)')}
onClick={() => {
onDrop('dropCascade');
}}
disabled={
(selectedObject.length > 0)
? !canDropCascade
: true
}
></PgIconButton> :
<PgIconButton
icon={<DeleteForeverIcon style={{height: '1.4rem'}} />}
aria-label="Delete Force"
title={gettext('Delete (Force)')}
onClick={() => {
onDrop('dropForce');
}}
disabled={
(selectedObject.length > 0)
? !canDropForce
: true
}
></PgIconButton>}
</PgButtonGroup>
</Box>);
};
@ -356,9 +352,7 @@ export default function CollectionNodeProperties({
:
(
<div className={classes.emptyPanel}>
{loaderText ? (<Loader message={loaderText}/>) :
<EmptyPanelMessage text={gettext(infoMsg)}/>
}
<EmptyPanelMessage text={gettext(infoMsg)}/>
</div>
)
}

View File

@ -90,7 +90,7 @@ function SQL({nodeData, node, treeNodeInfo, isActive, isStale, setIsStale}) {
setNodeSQL(sql);
}
setIsStale(false);
}, [isStale, isActive]);
}, [isStale, isActive, nodeData?.id]);
return (
<>

View File

@ -237,10 +237,11 @@ function Statistics({ nodeData, nodeItem, node, treeNodeInfo, isActive, isStale,
}
}
if (message != '') {
setTableData([]);
setMsg(message);
}
setIsStale(false);
}, [isStale, isActive]);
}, [isStale, isActive, nodeData?.id]);
return (
<>

View File

@ -1,4 +1,4 @@
import React, {useLayoutEffect, useMemo } from 'react';
import React, {useEffect, useMemo, useState } from 'react';
import AppMenuBar from './AppMenuBar';
import ObjectBreadcrumbs from './components/ObjectBreadcrumbs';
import Layout, { LayoutDocker, getDefaultGroup } from './helpers/Layout';
@ -35,27 +35,27 @@ const mainPanelGroup = {
};
export const processesPanelData = {
id: BROWSER_PANELS.PROCESSES, title: gettext('Processes'), content: <Processes />, closable: true, group: 'main'
id: BROWSER_PANELS.PROCESSES, title: gettext('Processes'), content: <Processes />, closable: true, group: 'playground'
};
export const defaultTabsData = [
{
id: BROWSER_PANELS.DASHBOARD, title: gettext('Dashboard'), content: <Dashboard />, closable: true, group: 'main'
id: BROWSER_PANELS.DASHBOARD, title: gettext('Dashboard'), content: <Dashboard />, closable: true, group: 'playground'
},
{
id: BROWSER_PANELS.PROPERTIES, title: gettext('Properties'), content: <Properties />, closable: true, group: 'main'
id: BROWSER_PANELS.PROPERTIES, title: gettext('Properties'), content: <Properties />, closable: true, group: 'playground'
},
{
id: BROWSER_PANELS.SQL, title: gettext('SQL'), content: <SQL />, closable: true, group: 'main'
id: BROWSER_PANELS.SQL, title: gettext('SQL'), content: <SQL />, closable: true, group: 'playground'
},
{
id: BROWSER_PANELS.STATISTICS, title: gettext('Statistics'), content: <Statistics />, closable: true, group: 'main'
id: BROWSER_PANELS.STATISTICS, title: gettext('Statistics'), content: <Statistics />, closable: true, group: 'playground'
},
{
id: BROWSER_PANELS.DEPENDENCIES, title: gettext('Dependencies'), content: <Dependencies />, closable: true, group: 'main'
id: BROWSER_PANELS.DEPENDENCIES, title: gettext('Dependencies'), content: <Dependencies />, closable: true, group: 'playground'
},
{
id: BROWSER_PANELS.DEPENDENTS, title: gettext('Dependents'), content: <Dependents />, closable: true, group: 'main'
id: BROWSER_PANELS.DEPENDENTS, title: gettext('Dependents'), content: <Dependents />, closable: true, group: 'playground'
},
processesPanelData,
];
@ -81,9 +81,9 @@ export default function BrowserComponent({pgAdmin}) {
{
size: 80,
id: BROWSER_PANELS.MAIN,
group: 'main',
group: 'playground',
tabs: defaultTabsData.map((t)=>LayoutDocker.getPanel(t)),
panelLock: {panelStyle: 'main'},
panelLock: {panelStyle: 'playground'},
}
]
},
@ -92,10 +92,13 @@ export default function BrowserComponent({pgAdmin}) {
};
const {isLoading, failed} = usePreferences();
let { name: browser } = useMemo(()=>getBrowser(), []);
const [uiReady, setUiReady] = useState(false);
useLayoutEffect(()=>{
pgAdmin?.Browser?.uiloaded?.();
}, []);
useEffect(()=>{
if(uiReady) {
pgAdmin?.Browser?.uiloaded?.();
}
}, [uiReady]);
if(isLoading) {
return <></>;
@ -107,9 +110,9 @@ export default function BrowserComponent({pgAdmin}) {
return (
<PgAdminContext.Provider value={pgAdmin}>
<ModalProvider>
<NotifierProvider pgAdmin={pgAdmin} />
<NotifierProvider pgAdmin={pgAdmin} onReady={()=>setUiReady(true)}/>
{browser != 'Nwjs' && <AppMenuBar />}
<div style={{height: 'calc(100% - 32px)'}}>
<div style={{height: 'calc(100% - 30px)'}}>
<Layout
getLayoutInstance={(obj)=>{
pgAdmin.Browser.docker = obj;
@ -119,7 +122,7 @@ export default function BrowserComponent({pgAdmin}) {
savedLayout={pgAdmin.Browser.utils.layout}
groups={{
'object-explorer': objectExplorerGroup,
'main': mainPanelGroup,
'playground': mainPanelGroup,
}}
noContextGroups={['object-explorer']}
resetToTabPanel={BROWSER_PANELS.MAIN}

View File

@ -39,6 +39,7 @@ import DataGridView from './DataGridView';
import { useIsMounted } from '../custom_hooks';
import ErrorBoundary from '../helpers/ErrorBoundary';
import { usePgAdmin } from '../BrowserComponent';
import { PgButtonGroup } from '../components/Buttons';
const useDialogStyles = makeStyles((theme)=>({
root: {
@ -866,7 +867,7 @@ const usePropsStyles = makeStyles((theme)=>({
flexGrow: 1,
},
toolbar: {
padding: theme.spacing(0.5, 1),
padding: theme.spacing(1),
background: theme.palette.background.default,
...theme.mixins.panelBorder.bottom,
},
@ -1000,11 +1001,13 @@ function SchemaPropertiesView({
<Box className={classes.root}>
<Loader message={loaderText}/>
<Box className={classes.toolbar}>
<PgIconButton
data-test="help" onClick={()=>props.onHelp(true, false)} icon={<InfoIcon />} disabled={props.disableSqlHelp}
title="SQL help for this object type." className={classes.buttonMargin} />
<PgIconButton data-test="edit"
onClick={props.onEdit} icon={<EditIcon />} title={gettext('Edit object...')} />
<PgButtonGroup size="small">
<PgIconButton
data-test="help" onClick={()=>props.onHelp(true, false)} icon={<InfoIcon />} disabled={props.disableSqlHelp}
title="SQL help for this object type." />
<PgIconButton data-test="edit"
onClick={props.onEdit} icon={<EditIcon />} title={gettext('Edit object...')} />
</PgButtonGroup>
</Box>
<Box className={clsx(classes.form, classes.formProperties)}>
<Box>

View File

@ -413,6 +413,9 @@ function getFinalTheme(baseTheme) {
MuiAccordion: {
root: {
...mixins.panelBorder,
'&.Mui-expanded': {
marginBottom: '8px',
},
}
},
MuiAccordionSummary: {

View File

@ -41,10 +41,14 @@ export default function rcdockOverride(theme) {
},
'& .dock-tab-active': {
color: theme.palette.text.primary,
cursor: 'move',
'&::hover': {
color: theme.palette.text.primary,
}
},
'& .dock-tab-btn': {
pointerEvents: 'none',
}
},
'&.dock-style-dialogs': {
borderRadius: theme.shape.borderRadius,
@ -159,7 +163,30 @@ export default function rcdockOverride(theme) {
},
'& .drag-accept-reject::after': {
content: '',
},
'& .dock-nav-more': {
color: theme.custom.icon.contrastText
}
}
},
'.dock-dropdown': {
zIndex: 1004,
'& .dock-dropdown-menu': {
padding: '4px 0px',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
border: `1px solid ${theme.otherVars.borderColor}`,
},
'& .dock-dropdown-menu-item': {
display: 'flex',
padding: '3px 12px',
color: theme.palette.text.primary,
transition: 'none',
'&.dock-dropdown-menu-item-active, &:hover': {
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
}
}
},
};
}

View File

@ -69,7 +69,7 @@ export default function ToolView() {
manualClose: true,
...tabParams,
cache: false,
group: 'main'
group: 'playground'
}, BROWSER_PANELS.MAIN, 'middle', true);
}
});

View File

@ -81,11 +81,11 @@ const useStyles = makeStyles((theme)=>({
},
noBorder: {
border: 0,
color: 'inherit',
backgroundColor: 'transparent',
color: theme.custom.icon.contrastText,
'&:hover': {
border: 0,
color: 'inherit',
color: theme.custom.icon.contrastText,
backgroundColor: 'inherit',
filter: 'brightness(85%)',
},

View File

@ -92,7 +92,7 @@ const useStyles = makeStyles((theme) => ({
pgTableHeader: {
display: 'flex',
background: theme.palette.background.default,
padding: '4px 8px',
padding: '8px 8px 4px',
},
tableRowContent:{
display: 'flex',

View File

@ -118,7 +118,6 @@ export class FileTreeX extends React.Component<IFileTreeXProps> {
setPseudoActiveFile: this.setPseudoActiveFile,
toggleDirectory: this.toggleDirectory,
closeDir: this.closeDir,
remove: this.removeDir,
newFile: async (dirOrPath: Directory | string) => this.supervisePrompt(await handle.promptNewFile(dirOrPath as string)),
newFolder: async (dirOrPath: Directory | string) => this.supervisePrompt(await handle.promptNewDirectory(dirOrPath as string)),
onBlur: (callback) => this.events.add(FileTreeXEvent.OnBlur, callback),

View File

@ -335,7 +335,7 @@ export function getDefaultGroup() {
closable: false,
maximizable: false,
floatable: false,
moreIcon: <ExpandMoreIcon style={{height: '0.9em'}} />,
moreIcon: <ExpandMoreIcon style={{height: '0.9em', marginTop: '4px'}} />,
panelExtra: (panelData, context) => {
let icon = <ExpandDialogIcon style={{width: '0.7em'}}/>;
let title = gettext('Maximise');

View File

@ -183,13 +183,14 @@ class Notifier {
}
}
export function NotifierProvider({ pgAdmin, pgWindow, getInstance, children }) {
export function NotifierProvider({ pgAdmin, pgWindow, getInstance, children, onReady }) {
const modal = useModal();
useEffect(()=>{
// if open in an iframe then use top pgAdmin
if(window.self != window.top) {
pgAdmin.Browser.notifier = new Notifier(modal, pgWindow.pgAdmin.Browser.notifier.snackbar);
onReady?.();
getInstance?.(pgAdmin.Browser.notifier);
}
}, []);
@ -203,6 +204,7 @@ export function NotifierProvider({ pgAdmin, pgWindow, getInstance, children }) {
ref={(obj)=>{
pgAdmin.Browser.notifier = new Notifier(modal, new SnackbarNotifier(obj));
getInstance?.(pgAdmin.Browser.notifier);
onReady?.();
}}
>
{children}
@ -221,6 +223,7 @@ NotifierProvider.propTypes = {
pgWindow: PropTypes.object,
getInstance: PropTypes.func,
children: CustomPropTypes.children,
onReady: PropTypes.func,
};
export default Notifier;

View File

@ -960,6 +960,7 @@ table.table-empty-rows{
.dialog-node-icon {
margin-right: 2px !important;
padding: 0px 10px;
background-position: 50%;
}
textarea {

View File

@ -75,10 +75,10 @@
});
</script>
<script type="application/javascript" src="{{ url_for('static', filename='js/generated/vendor.main.js') }}" ></script>
<!-- View specified scripts -->
<script type="application/javascript" src="{{ url_for('static', filename='js/generated/vendor.react.js') }}" ></script>
<script type="application/javascript" src="{{ url_for('static', filename='js/generated/vendor.main.js') }}" ></script>
<script type="application/javascript" src="{{ url_for('static', filename='js/generated/vendor.others.js') }}" ></script>
<script type="application/javascript" src="{{ url_for('static', filename='js/generated/vendor.sqleditor.js') }}"></script>
<script type="application/javascript" src="{{ url_for('static', filename='js/generated/pgadmin_commons.js') }}" ></script>
</head>

View File

@ -824,6 +824,7 @@ export default function DebuggerArgumentComponent({ debuggerInfo, restartDebug,
schema={debuggerArgsSchema.current}
showFooter={false}
isTabView={false}
Notifier={pgAdmin.Browser.notifier}
onDataChange={(isChanged, changedData) => {
let isValid = false;
let skipStep = false;

View File

@ -393,19 +393,19 @@ export function MainToolBar({containerRef, onFilterClick, onManageMacros}) {
{
shortcut: queryToolPref.execute_query,
options: {
callback: ()=>{!buttonsDisabled['execute']?executeQuery():null;}
callback: ()=>{!buttonsDisabled['execute']&&executeQuery();}
}
},
{
shortcut: queryToolPref.explain_query,
options: {
callback: (e)=>{e.preventDefault(); !buttonsDisabled['explain']?explain():null;}
callback: (e)=>{e.preventDefault(); !buttonsDisabled['explain']&&explain();}
}
},
{
shortcut: queryToolPref.explain_analyze_query,
options: {
callback: ()=>{!buttonsDisabled['explain_analyse']?explainAnalyse():null;}
callback: ()=>{!buttonsDisabled['explain_analyse']&&explainAnalyse();}
}
},
{

View File

@ -580,6 +580,30 @@ module.exports = [{
] : [],
splitChunks: {
cacheGroups: {
vendor_sqleditor: {
name: 'vendor_sqleditor',
filename: 'vendor.sqleditor.js',
chunks: 'all',
reuseExistingChunk: true,
priority: 9,
minChunks: 2,
enforce: true,
test(module) {
return webpackShimConfig.matchModules(module, ['jsoneditor', 'leaflet']);
},
},
vendor_react: {
name: 'vendor_react',
filename: 'vendor.react.js',
chunks: 'all',
reuseExistingChunk: true,
priority: 8,
minChunks: 2,
enforce: true,
test(module) {
return webpackShimConfig.matchModules(module, ['react', 'react-dom']);
},
},
vendor_main: {
name: 'vendor_main',
filename: 'vendor.main.js',
@ -589,7 +613,7 @@ module.exports = [{
minChunks: 2,
enforce: true,
test(module) {
return webpackShimConfig.matchModules(module, ['react', 'react-dom', 'bootstrap', 'popper']);
return webpackShimConfig.matchModules(module, ['codemirror', 'rc-', '@material-ui']);
},
},
vendor_others: {