diff --git a/web/pgadmin/browser/server_groups/servers/pgagent/schedules/static/js/pga_schedule.ui.js b/web/pgadmin/browser/server_groups/servers/pgagent/schedules/static/js/pga_schedule.ui.js index eed8d4e1a..49796e201 100644 --- a/web/pgadmin/browser/server_groups/servers/pgagent/schedules/static/js/pga_schedule.ui.js +++ b/web/pgadmin/browser/server_groups/servers/pgagent/schedules/static/js/pga_schedule.ui.js @@ -11,9 +11,8 @@ import gettext from 'sources/gettext'; import BaseUISchema from 'sources/SchemaView/base_schema.ui'; import { isEmptyString } from 'sources/validators'; import moment from 'moment'; -import { WEEKDAYS, MONTHDAYS, MONTHS, HOURS, MINUTES } from '../../../../../../static/js/constants'; - -const PGAGENT_MONTHDAYS = [...MONTHDAYS].concat([{label: gettext('Last day'), value: 'Last Day'}]); +import { WEEKDAYS, MONTHS, HOURS, MINUTES, PGAGENT_MONTHDAYS } from '../../../../../../static/js/constants'; +import { DaysSchema, TimesSchema } from './repeat.ui'; export class ExceptionsSchema extends BaseUISchema { constructor(fieldOptions={}, initValues={}) { @@ -63,119 +62,6 @@ export class ExceptionsSchema extends BaseUISchema { } } -const BooleanArrayFormatter = { - fromRaw: (originalValue, options) => { - if (!_.isNull(originalValue) && !_.isUndefined(originalValue) && Array.isArray(originalValue)) { - let retValue = [], - index = 0; - originalValue.forEach( function (value) { - if (value) { - retValue.push(options[index]); - } - index = index + 1; - }); - - return retValue; - } - - return originalValue; - }, - - toRaw: (selectedVal, options)=> { - if (!_.isNull(options) && !_.isUndefined(options) && Array.isArray(options)) { - let retValue = []; - options.forEach( function (option) { - let elementFound = _.find(selectedVal, (selVal)=>_.isEqual(selVal.label, option.label)); - if(_.isUndefined(elementFound)) { - retValue.push(false); - } else { - retValue.push(true); - } - }); - - return retValue; - } - - return selectedVal; - } -}; - -export class DaysSchema extends BaseUISchema { - constructor(fieldOptions={}, initValues={}) { - super({ - ...initValues, - }); - - this.fieldOptions = { - ...fieldOptions, - }; - } - - get baseFields() { - return [ - { - id: 'jscweekdays', label: gettext('Week Days'), type: 'select', - group: gettext('Days'), - controlProps: { allowClear: true, multiple: true, allowSelectAll: true, - placeholder: gettext('Select the weekdays...'), - formatter: BooleanArrayFormatter, - }, - options: WEEKDAYS, - }, { - id: 'jscmonthdays', label: gettext('Month Days'), type: 'select', - group: gettext('Days'), - controlProps: { allowClear: true, multiple: true, allowSelectAll: true, - placeholder: gettext('Select the month days...'), - formatter: BooleanArrayFormatter, - }, - options: PGAGENT_MONTHDAYS, - }, { - id: 'jscmonths', label: gettext('Months'), type: 'select', - group: gettext('Days'), - controlProps: { allowClear: true, multiple: true, allowSelectAll: true, - placeholder: gettext('Select the months...'), - formatter: BooleanArrayFormatter, - }, - options: MONTHS, - } - ]; - } -} - -export class TimesSchema extends BaseUISchema { - constructor(fieldOptions={}, initValues={}) { - super({ - ...initValues, - }); - - this.fieldOptions = { - ...fieldOptions, - }; - } - - get baseFields() { - return [ - { - id: 'jschours', label: gettext('Hours'), type: 'select', - group: gettext('Times'), - controlProps: { allowClear: true, multiple: true, allowSelectAll: true, - placeholder: gettext('Select the hours...'), - formatter: BooleanArrayFormatter, - }, - options: HOURS, - }, { - id: 'jscminutes', label: gettext('Minutes'), type: 'select', - group: gettext('Times'), - controlProps: { allowClear: true, multiple: true, allowSelectAll: true, - placeholder: gettext('Select the minutes...'), - formatter: BooleanArrayFormatter, - }, - options: MINUTES, - } - ]; - } -} - export default class PgaJobScheduleSchema extends BaseUISchema { constructor(fieldOptions={}, initValues={}) { super({ diff --git a/web/pgadmin/browser/server_groups/servers/pgagent/schedules/static/js/repeat.ui.js b/web/pgadmin/browser/server_groups/servers/pgagent/schedules/static/js/repeat.ui.js new file mode 100644 index 000000000..0297c9857 --- /dev/null +++ b/web/pgadmin/browser/server_groups/servers/pgagent/schedules/static/js/repeat.ui.js @@ -0,0 +1,117 @@ +import gettext from 'sources/gettext'; +import { BaseUISchema } from '../../../../../../../static/js/SchemaView'; +import { WEEKDAYS, PGAGENT_MONTHDAYS, MONTHS, HOURS, MINUTES } from '../../../../../../static/js/constants'; + + +const BooleanArrayFormatter = { + fromRaw: (originalValue, options) => { + if (!_.isNull(originalValue) && !_.isUndefined(originalValue) && Array.isArray(originalValue)) { + let retValue = [], + index = 0; + originalValue.forEach( function (value) { + if (value) { + retValue.push(options[index]); + } + index = index + 1; + }); + + return retValue; + } + + return originalValue; + }, + + toRaw: (selectedVal, options)=> { + if (!_.isNull(options) && !_.isUndefined(options) && Array.isArray(options)) { + let retValue = []; + options.forEach( function (option) { + let elementFound = _.find(selectedVal, (selVal)=>_.isEqual(selVal.label, option.label)); + if(_.isUndefined(elementFound)) { + retValue.push(false); + } else { + retValue.push(true); + } + }); + + return retValue; + } + + return selectedVal; + } +}; + +export class TimesSchema extends BaseUISchema { + constructor(initValues={}, schemaConfig={ + hours: {}, + minutes: {} + }) { + super({ + ...initValues, + }); + this.schemaConfig = schemaConfig; + } + + get baseFields() { + return [ + { + id: 'jschours', label: gettext('Hours'), type: 'select', + group: gettext('Times'), + controlProps: { allowClear: true, multiple: true, allowSelectAll: true, + placeholder: gettext('Select the hours...'), + formatter: BooleanArrayFormatter, + }, + options: HOURS, ...(this.schemaConfig.hours??{}) + }, { + id: 'jscminutes', label: gettext('Minutes'), type: 'select', + group: gettext('Times'), + controlProps: { allowClear: true, multiple: true, allowSelectAll: true, + placeholder: gettext('Select the minutes...'), + formatter: BooleanArrayFormatter, + }, + options: MINUTES, ...(this.schemaConfig.hours??{}) + } + ]; + } +} + +export class DaysSchema extends BaseUISchema { + constructor(initValues={}, schemaConfig={ + weekdays: {}, monthdays: {}, months: {} + }) { + super({ + ...initValues, + }); + + this.schemaConfig = schemaConfig; + } + + get baseFields() { + return [ + { + id: 'jscweekdays', label: gettext('Week Days'), type: 'select', + group: gettext('Days'), + controlProps: { allowClear: true, multiple: true, allowSelectAll: true, + placeholder: gettext('Select the weekdays...'), + formatter: BooleanArrayFormatter, + }, + options: WEEKDAYS, ...(this.schemaConfig.weekdays??{}) + }, { + id: 'jscmonthdays', label: gettext('Month Days'), type: 'select', + group: gettext('Days'), + controlProps: { allowClear: true, multiple: true, allowSelectAll: true, + placeholder: gettext('Select the month days...'), + formatter: BooleanArrayFormatter, + }, + options: PGAGENT_MONTHDAYS, ...(this.schemaConfig.monthdays??{}) + }, { + id: 'jscmonths', label: gettext('Months'), type: 'select', + group: gettext('Days'), + controlProps: { allowClear: true, multiple: true, allowSelectAll: true, + placeholder: gettext('Select the months...'), + formatter: BooleanArrayFormatter, + }, + options: MONTHS, ...(this.schemaConfig.months??{}) + } + ]; + } +} diff --git a/web/pgadmin/browser/static/js/constants.js b/web/pgadmin/browser/static/js/constants.js index 46abc3a45..d7bc7221a 100644 --- a/web/pgadmin/browser/static/js/constants.js +++ b/web/pgadmin/browser/static/js/constants.js @@ -104,3 +104,5 @@ export const WEEKDAYS = [ {label: gettext('52'), value: '52'}, {label: gettext('53'), value: '53'}, {label: gettext('54'), value: '54'}, {label: gettext('55'), value: '55'}, {label: gettext('56'), value: '56'}, {label: gettext('57'), value: '57'}, {label: gettext('58'), value: '58'}, {label: gettext('59'), value: '59'}, ]; + +export const PGAGENT_MONTHDAYS = [...MONTHDAYS].concat([{label: gettext('Last day'), value: 'Last Day'}]); diff --git a/web/pgadmin/dashboard/static/js/components/SectionContainer.jsx b/web/pgadmin/dashboard/static/js/components/SectionContainer.jsx index 4454b3e5d..d23db7eb6 100644 --- a/web/pgadmin/dashboard/static/js/components/SectionContainer.jsx +++ b/web/pgadmin/dashboard/static/js/components/SectionContainer.jsx @@ -35,7 +35,7 @@ const StyledBox = styled(Box)(({theme}) => ({ export default function SectionContainer({title, titleExtras, children, style}) { return ( - <StyledBox style={style}> + <StyledBox className='SectionContainer-root' style={style}> <Box className='SectionContainer-cardHeader' title={title}> <div className='SectionContainer-cardTitle'>{title}</div> <div style={{marginLeft: 'auto'}}> diff --git a/web/pgadmin/static/js/BrowserComponent.jsx b/web/pgadmin/static/js/BrowserComponent.jsx index 9f962ce2f..9ef4c2ce1 100644 --- a/web/pgadmin/static/js/BrowserComponent.jsx +++ b/web/pgadmin/static/js/BrowserComponent.jsx @@ -10,10 +10,8 @@ import Statistics from '../../misc/statistics/static/js/Statistics'; import { BROWSER_PANELS } from '../../browser/static/js/constants'; import Dependencies from '../../misc/dependencies/static/js/Dependencies'; import Dependents from '../../misc/dependents/static/js/Dependents'; -import UtilityView from './UtilityView'; import ModalProvider from './helpers/ModalProvider'; import { NotifierProvider } from './helpers/Notifier'; -import ToolView from './ToolView'; import ObjectExplorerToolbar from './helpers/ObjectExplorerToolbar'; import MainMoreToolbar from './helpers/MainMoreToolbar'; import Dashboard from '../../dashboard/static/js/Dashboard'; @@ -144,8 +142,6 @@ export default function BrowserComponent({pgAdmin}) { resetToTabPanel={BROWSER_PANELS.MAIN} /> </div> - <UtilityView /> - <ToolView /> </ModalProvider> <ObjectBreadcrumbs pgAdmin={pgAdmin} /> </PgAdminContext.Provider> diff --git a/web/pgadmin/static/js/Explain/index.jsx b/web/pgadmin/static/js/Explain/index.jsx index be1dd67f3..a69bde8ad 100644 --- a/web/pgadmin/static/js/Explain/index.jsx +++ b/web/pgadmin/static/js/Explain/index.jsx @@ -465,7 +465,9 @@ function parsePlanData(data, ctx) { return retPlan; } -export default function Explain({plans=[]}) { +export default function Explain({plans=[], + emptyMessage=gettext('Use Explain/Explain analyze button to generate the plan for a query. Alternatively, you can also execute "EXPLAIN (FORMAT JSON) [QUERY]".') +}) { const [tabValue, setTabValue] = React.useState(0); @@ -490,7 +492,7 @@ export default function Explain({plans=[]}) { if(_.isEmpty(plans)) { return ( <StyledBox height="100%" display="flex" flexDirection="column"> - <EmptyPanelMessage text={gettext('Use Explain/Explain analyze button to generate the plan for a query. Alternatively, you can also execute "EXPLAIN (FORMAT JSON) [QUERY]".')} /> + {emptyMessage && <EmptyPanelMessage text={emptyMessage} />} </StyledBox> ); } @@ -526,5 +528,6 @@ export default function Explain({plans=[]}) { } Explain.propTypes = { - plans: PropTypes.array, + plans: PropTypes.array.isRequired, + emptyMessage: PropTypes.string, }; diff --git a/web/pgadmin/static/js/SchemaView/SchemaState/SchemaState.js b/web/pgadmin/static/js/SchemaView/SchemaState/SchemaState.js index 9082840c7..192854147 100644 --- a/web/pgadmin/static/js/SchemaView/SchemaState/SchemaState.js +++ b/web/pgadmin/static/js/SchemaView/SchemaState/SchemaState.js @@ -223,7 +223,7 @@ export class SchemaState extends DepListener { state.data = sessData; state._changes = state.changes(); state.updateOptions(); - state.onDataChange && state.onDataChange(state.isDirty, state._changes); + state.onDataChange && state.onDataChange(state.isDirty, state._changes, state.errors); } changes(includeSkipChange=false) { diff --git a/web/pgadmin/static/js/Theme/index.jsx b/web/pgadmin/static/js/Theme/index.jsx index a12cfdf24..c82b213f3 100644 --- a/web/pgadmin/static/js/Theme/index.jsx +++ b/web/pgadmin/static/js/Theme/index.jsx @@ -173,7 +173,7 @@ basicSettings = createTheme(basicSettings, { MuiTabs: { styleOverrides: { root: { - minHeight: 0, + minHeight: '30px', } } }, @@ -242,6 +242,7 @@ basicSettings = createTheme(basicSettings, { marginBottom: 0, marginLeft: 0, marginRight: 0, + gap: '4px' } } }, diff --git a/web/pgadmin/static/js/UtilityView.jsx b/web/pgadmin/static/js/UtilityView.jsx index 6ef0ae339..3d1f617a4 100644 --- a/web/pgadmin/static/js/UtilityView.jsx +++ b/web/pgadmin/static/js/UtilityView.jsx @@ -7,7 +7,7 @@ // ////////////////////////////////////////////////////////////// -import React, { useEffect } from 'react'; +import React, { useEffect, useRef } from 'react'; import getApiInstance from 'sources/api_instance'; import {getHelpUrl, getEPASHelpUrl} from 'pgadmin.help'; import SchemaView from 'sources/SchemaView'; @@ -20,23 +20,32 @@ import usePreferences from '../../preferences/static/js/store'; import gettext from 'sources/gettext'; import PropTypes from 'prop-types'; -export default function UtilityView() { +export default function UtilityView({dockerObj}) { const pgAdmin = usePgAdmin(); + const docker = useRef(dockerObj ?? pgAdmin.Browser.docker); + + useEffect(()=>{ + docker.current = dockerObj ?? pgAdmin.Browser.docker; + }, [dockerObj]); + useEffect(()=>{ pgAdmin.Browser.Events.on('pgadmin:utility:show', (item, panelTitle, dialogProps, width=pgAdmin.Browser.stdW.default, height=pgAdmin.Browser.stdH.md)=>{ - const treeNodeInfo = pgAdmin.Browser.tree.getTreeNodeHierarchy(item); + const treeNodeInfo = pgAdmin.Browser.tree?.getTreeNodeHierarchy(item); const panelId = _.uniqueId(BROWSER_PANELS.UTILITY_DIALOG); - pgAdmin.Browser.docker.openDialog({ + const onClose = ()=>docker.current.close(panelId); + docker.current.openDialog({ id: panelId, title: panelTitle, content: ( <ErrorBoundary> <UtilityViewContent + docker={docker.current} panelId={panelId} schema={dialogProps.schema} treeNodeInfo={treeNodeInfo} actionType={dialogProps.actionType??'create'} formType='dialog' + onClose={onClose} onSave={dialogProps.onSave ?? ((data)=>{ if(data.errormsg) { pgAdmin.Browser.notifier.alert( @@ -48,7 +57,7 @@ export default function UtilityView() { } else if(data.info) { pgAdmin.Browser.notifier.success(data.info); } - pgAdmin.Browser.docker.close(panelId); + onClose(); })} extraData={dialogProps.extraData??{}} saveBtnName={dialogProps.saveBtnName} @@ -64,8 +73,12 @@ export default function UtilityView() { return <></>; } +UtilityView.propTypes = { + dockerObj: PropTypes.object, +}; + /* The entry point for rendering React based view in properties, called in node.js */ -function UtilityViewContent({panelId, schema, treeNodeInfo, actionType, formType, +function UtilityViewContent({schema, treeNodeInfo, actionType, formType, onClose, onSave, extraData, saveBtnName, urlBase, sqlHelpUrl, helpUrl, isTabView=true}) { const pgAdmin = usePgAdmin(); @@ -83,8 +96,6 @@ function UtilityViewContent({panelId, schema, treeNodeInfo, actionType, formType let nodeObj = extraData.nodeType? pgAdmin.Browser.Nodes[extraData.nodeType]: undefined; let itemNodeData = extraData?.itemNodeData ? itemNodeData: undefined; - const onClose = ()=>pgAdmin.Browser.docker.close(panelId); - /* on save button callback, promise required */ const onSaveClick = (isNew, data)=>new Promise((resolve, reject)=>{ return api({ @@ -94,7 +105,7 @@ function UtilityViewContent({panelId, schema, treeNodeInfo, actionType, formType }).then((res)=>{ /* Don't warn the user before closing dialog */ resolve(res.data); - onSave?.(res.data); + onSave?.(res.data, data); onClose(); }).catch((err)=>{ reject(err instanceof Error ? err : Error(gettext('Something went wrong'))); @@ -212,6 +223,7 @@ UtilityViewContent.propTypes = { actionType: PropTypes.string, formType: PropTypes.string, onSave: PropTypes.func, + onClose: PropTypes.func, extraData: PropTypes.object, saveBtnName: PropTypes.string, urlBase: PropTypes.string, diff --git a/web/pgadmin/static/js/components/Buttons.jsx b/web/pgadmin/static/js/components/Buttons.jsx index ff5a4ceed..2f0ca0bd8 100644 --- a/web/pgadmin/static/js/components/Buttons.jsx +++ b/web/pgadmin/static/js/components/Buttons.jsx @@ -165,7 +165,7 @@ DefaultButton.propTypes = { /* pgAdmin Icon button, takes Icon component as input */ -export const PgIconButton = forwardRef(({icon, title, shortcut, className, splitButton, style, color, accesskey, ...props}, ref)=>{ +export const PgIconButton = forwardRef(({icon, title, shortcut, className, splitButton, style, color, accesskey, isDropdown, ...props}, ref)=>{ let shortcutTitle = null; if(accesskey || shortcut) { shortcutTitle = <ShortcutTitle title={title} accesskey={accesskey} shortcut={shortcut}/>; @@ -192,7 +192,7 @@ export const PgIconButton = forwardRef(({icon, title, shortcut, className, split } } else if(color == 'primary') { return ( - <Tooltip title={shortcutTitle || title || ''} aria-label={title || ''}> + <Tooltip title={shortcutTitle || title || ''} aria-label={title || ''} enterDelay={isDropdown ? 1500 : undefined}> <PrimaryButton ref={ref} style={style} className={['Buttons-iconButton', (splitButton ? 'Buttons-splitButton' : ''), className].join(' ')} accessKey={accesskey} data-label={title || ''} {...props}> @@ -203,7 +203,7 @@ export const PgIconButton = forwardRef(({icon, title, shortcut, className, split ); } else { return ( - <Tooltip title={shortcutTitle || title || ''} aria-label={title || ''}> + <Tooltip title={shortcutTitle || title || ''} aria-label={title || ''} enterDelay={isDropdown ? 1500 : undefined}> <DefaultButton ref={ref} style={style} className={['Buttons-iconButton', 'Buttons-iconButtonDefault',(splitButton ? 'Buttons-splitButton' : ''), className].join(' ')} accessKey={accesskey} data-label={title || ''} {...props}> @@ -224,6 +224,7 @@ PgIconButton.propTypes = { color: PropTypes.oneOf(['primary', 'default', undefined]), disabled: PropTypes.bool, splitButton: PropTypes.bool, + isDropdown: PropTypes.bool, }; export const PgButtonGroup = forwardRef(({children, ...props}, ref)=>{ diff --git a/web/pgadmin/static/js/components/FormComponents.jsx b/web/pgadmin/static/js/components/FormComponents.jsx index 834f3db91..da90961f8 100644 --- a/web/pgadmin/static/js/components/FormComponents.jsx +++ b/web/pgadmin/static/js/components/FormComponents.jsx @@ -261,15 +261,17 @@ export function InputDateTimePicker({ value, onChange, readonly, controlProps, . let placeholder = ''; let regExp = /[a-zA-Z]/; let timeZoneString = ''; + + controlProps = controlProps ?? {}; if (controlProps?.pickerType === 'Date') { - format = controlProps.format || DATE_TIME_FORMAT.DATE; + format = controlProps?.format || DATE_TIME_FORMAT.DATE; placeholder = controlProps.placeholder || 'YYYY-MM-DD'; } else if (controlProps?.pickerType === 'Time') { - format = controlProps.format || (controlProps.ampm ? DATE_TIME_FORMAT.TIME_12 : DATE_TIME_FORMAT.TIME_24); - placeholder = controlProps.placeholder || 'HH:mm:ss'; + format = controlProps?.format || (controlProps?.ampm ? DATE_TIME_FORMAT.TIME_12 : DATE_TIME_FORMAT.TIME_24); + placeholder = controlProps?.placeholder || 'HH:mm:ss'; } else { - format = controlProps.format || (controlProps.ampm ? DATE_TIME_FORMAT.DATE_TIME_12 : DATE_TIME_FORMAT.DATE_TIME_24); - placeholder = controlProps.placeholder || 'YYYY-MM-DD HH:mm:ss Z'; + format = controlProps?.format || (controlProps?.ampm ? DATE_TIME_FORMAT.DATE_TIME_12 : DATE_TIME_FORMAT.DATE_TIME_24); + placeholder = controlProps?.placeholder || 'YYYY-MM-DD HH:mm:ss Z'; } const handleChange = (dateVal) => { @@ -572,7 +574,7 @@ FormInputSwitch.propTypes = { labelTooltip: PropTypes.string }; -export function InputCheckbox({ cid, helpid, value, onChange, controlProps, readonly, labelPlacement, ...props }) { +export function InputCheckbox({ cid, helpid, value, onChange, controlProps, readonly, disabled, labelPlacement, ...props }) { controlProps = controlProps || {}; return ( <FormControlLabel @@ -585,6 +587,7 @@ export function InputCheckbox({ cid, helpid, value, onChange, controlProps, read inputProps={{ 'aria-describedby': helpid, 'title': controlProps.label}} {...props} /> } + disabled={disabled} label={controlProps.label} labelPlacement={labelPlacement} /> @@ -597,6 +600,7 @@ InputCheckbox.propTypes = { controlProps: PropTypes.object, onChange: PropTypes.func, readonly: PropTypes.bool, + disabled: PropTypes.bool, labelPlacement: PropTypes.string }; diff --git a/web/pgadmin/static/js/components/PgReactDataGrid.jsx b/web/pgadmin/static/js/components/PgReactDataGrid.jsx index 6cf8f0425..fb78ce511 100644 --- a/web/pgadmin/static/js/components/PgReactDataGrid.jsx +++ b/web/pgadmin/static/js/components/PgReactDataGrid.jsx @@ -39,6 +39,9 @@ const StyledReactDataGrid = styled(ReactDataGrid)(({theme})=>({ }, '& .rdg-cell-value': { height: '100%', + }, + '&.rdg-cell-copied[aria-selected=false][role="gridcell"]': { + backgroundColor: 'unset', } }, '& .rdg-header-row .rdg-cell': { diff --git a/web/pgadmin/static/js/components/PgTable.jsx b/web/pgadmin/static/js/components/PgTable.jsx index b28bb66ba..eed8af2ba 100644 --- a/web/pgadmin/static/js/components/PgTable.jsx +++ b/web/pgadmin/static/js/components/PgTable.jsx @@ -317,13 +317,13 @@ const StyledPgTableRoot = styled('div')(({theme})=>({ const queryClient = new QueryClient(); -export default function PgTable({ caveTable = true, tableNoBorder = true, ...props }) { +export default function PgTable({ caveTable = true, tableNoBorder = true, tableNoHeader=false, ...props }) { const [searchVal, setSearchVal] = React.useState(''); return ( <QueryClientProvider client={queryClient}> <StyledPgTableRoot className={[tableNoBorder ? '' : 'pgtable-pgrt-border', caveTable ? 'pgtable-pgrt-cave' : ''].join(' ')} data-test={props['data-test']}> - <Box className='pgtable-header'> + {!tableNoHeader && <Box className='pgtable-header'> {props.customHeader && (<Box className={['pgtable-custom-header-section', props['className']].join(' ')}> {props.customHeader }</Box>)} <Box marginLeft="auto"> <InputText @@ -336,7 +336,7 @@ export default function PgTable({ caveTable = true, tableNoBorder = true, ...pro }} /> </Box> - </Box> + </Box>} <div className={'pgtable-body'} > <Table {...props} searchVal={searchVal}/> </div> @@ -349,6 +349,7 @@ PgTable.propTypes = { customHeader: PropTypes.element, caveTable: PropTypes.bool, tableNoBorder: PropTypes.bool, + tableNoHeader: PropTypes.bool, 'data-test': PropTypes.string, 'className': PropTypes.string }; diff --git a/web/pgadmin/static/js/helpers/Layout/index.jsx b/web/pgadmin/static/js/helpers/Layout/index.jsx index cb7410536..5f240d44d 100644 --- a/web/pgadmin/static/js/helpers/Layout/index.jsx +++ b/web/pgadmin/static/js/helpers/Layout/index.jsx @@ -15,6 +15,8 @@ import ContextMenu from '../../components/ContextMenu'; import { showRenameTab } from '../../Dialogs'; import usePreferences from '../../../../preferences/static/js/store'; import _ from 'lodash'; +import UtilityView from '../../UtilityView'; +import ToolView from '../../ToolView'; function TabTitle({id, closable, defaultInternal}) { const layoutDocker = React.useContext(LayoutDockerContext); @@ -482,6 +484,8 @@ export default function Layout({groups, noContextGroups, getLayoutInstance, layo <div id="layout-portal"></div> <ContextMenu menuItems={contextMenuItems} position={contextPos} onClose={()=>setContextPos([null, null, null])} label="Layout Context Menu" /> + <UtilityView dockerObj={layoutDockerObj} /> + <ToolView dockerObj={layoutDockerObj} /> </LayoutDockerContext.Provider> ); } diff --git a/web/pgadmin/static/js/pgadmin.js b/web/pgadmin/static/js/pgadmin.js index c70d27675..80063e404 100644 --- a/web/pgadmin/static/js/pgadmin.js +++ b/web/pgadmin/static/js/pgadmin.js @@ -72,51 +72,5 @@ define([], function() { return 1; }; - /** - * Decimal adjustment of a number. - * - * @param {String} type The type of adjustment. - * @param {Number} value The number. - * @param {Integer} exp The exponent (the 10 logarithm of the adjustment base). - * @returns {Number} The adjusted value. - */ - function decimalAdjust(type, value, exp) { - // If the exp is undefined or zero... - if (typeof exp === 'undefined' || +exp === 0) { - return Math[type](value); - } - value = +value; - exp = +exp; - // If the value is not a number or the exp is not an integer... - if (isNaN(value) || exp % 1 !== 0) { - return NaN; - } - // Shift - value = value.toString().split('e'); - value = Math[type](+(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp))); - // Shift back - value = value.toString().split('e'); - return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp)); - } - - // Decimal round - if (!Math.round10) { - Math.round10 = function(value, exp) { - return decimalAdjust('round', value, exp); - }; - } - // Decimal floor - if (!Math.floor10) { - Math.floor10 = function(value, exp) { - return decimalAdjust('floor', value, exp); - }; - } - // Decimal ceil - if (!Math.ceil10) { - Math.ceil10 = function(value, exp) { - return decimalAdjust('ceil', value, exp); - }; - } - return pgAdmin; }); diff --git a/web/pgadmin/static/js/utils.js b/web/pgadmin/static/js/utils.js index ca1914972..95b5f7deb 100644 --- a/web/pgadmin/static/js/utils.js +++ b/web/pgadmin/static/js/utils.js @@ -415,21 +415,27 @@ export function checkTrojanSource(content, isPasteEvent) { } export function downloadBlob(blob, fileName) { - let urlCreator = window.URL || window.webkitURL, - downloadUrl = urlCreator.createObjectURL(blob), - link = document.createElement('a'); - - document.body.appendChild(link); - if (getBrowser() == 'IE' && window.navigator.msSaveBlob) { - // IE10+ : (has Blob, but not a[download] or URL) + // IE10+ : (has Blob, but not a[download] or URL) window.navigator.msSaveBlob(blob, fileName); } else { + const urlCreator = window.URL || window.webkitURL; + const downloadUrl = urlCreator.createObjectURL(blob); + + const link = document.createElement('a'); link.setAttribute('href', downloadUrl); link.setAttribute('download', fileName); + link.style.setProperty('visibility ', 'hidden'); + + document.body.appendChild(link); link.click(); + document.body.removeChild(link); } - document.body.removeChild(link); +} + +export function downloadFile(textData, fileName, fileType) { + const respBlob = new Blob([textData], {type : fileType}); + downloadBlob(respBlob, fileName); } export function toPrettySize(rawSize, from='B') { @@ -748,4 +754,50 @@ export function getPlatform() { } else { return 'Unknown'; } -} \ No newline at end of file +} + +/** + * Decimal adjustment of a number. + * + * @param {String} type The type of adjustment. + * @param {Number} value The number. + * @param {Integer} exp The exponent (the 10 logarithm of the adjustment base). + * @returns {Number} The adjusted value. + */ +function decimalAdjust(type, value, exp) { + // If the exp is undefined or zero... + if (typeof exp === 'undefined' || +exp === 0) { + return Math[type](value); + } + value = +value; + exp = +exp; + // If the value is not a number or the exp is not an integer... + if (isNaN(value) || exp % 1 !== 0) { + return NaN; + } + // Shift + value = value.toString().split('e'); + value = Math[type](+(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp))); + // Shift back + value = value.toString().split('e'); + return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp)); +} + +// Decimal round +if (!Math.round10) { + Math.round10 = function(value, exp) { + return decimalAdjust('round', value, exp); + }; +} +// Decimal floor +if (!Math.floor10) { + Math.floor10 = function(value, exp) { + return decimalAdjust('floor', value, exp); + }; +} +// Decimal ceil +if (!Math.ceil10) { + Math.ceil10 = function(value, exp) { + return decimalAdjust('ceil', value, exp); + }; +} diff --git a/web/pgadmin/utils/driver/psycopg3/connection.py b/web/pgadmin/utils/driver/psycopg3/connection.py index 739ae26e0..74c63762a 100644 --- a/web/pgadmin/utils/driver/psycopg3/connection.py +++ b/web/pgadmin/utils/driver/psycopg3/connection.py @@ -1302,6 +1302,11 @@ WHERE db.datname = current_database()""") rows = [] self.row_count = cur.rowcount + # If multiple queries are run, make sure to reach + # the last query result + while cur.nextset(): + pass # This loop is empty + if cur.get_rowcount() > 0: rows = cur.fetchall() diff --git a/web/regression/javascript/SchemaView/SchemaDialogView.spec.js b/web/regression/javascript/SchemaView/SchemaDialogView.spec.js index a887eb484..92d23bddc 100644 --- a/web/regression/javascript/SchemaView/SchemaDialogView.spec.js +++ b/web/regression/javascript/SchemaView/SchemaDialogView.spec.js @@ -225,7 +225,9 @@ describe('SchemaView', ()=>{ let onResetAction = (data)=> { expect(ctrl.container.querySelector('[data-test="Reset"]').hasAttribute('disabled')).toBe(true); expect(ctrl.container.querySelector('[data-test="Save"]').hasAttribute('disabled')).toBe(true); - expect(onDataChange).toHaveBeenCalledWith(false, data); + const callArgs = onDataChange.mock.calls[onDataChange.mock.calls.length - 1]; + expect(callArgs[0]).toEqual(false); + expect(callArgs[1]).toEqual(data); }; beforeEach(async ()=>{ @@ -274,7 +276,9 @@ describe('SchemaView', ()=>{ expect(ctrl.container.querySelector('[data-test="Reset"]').hasAttribute('disabled')).toBe(true); expect(ctrl.container.querySelector('[data-test="Save"]').hasAttribute('disabled')).toBe(true); // on reset, orig data will be considered - expect(onDataChange).toHaveBeenCalledWith(false, { id: undefined, field1: null, field2: null, fieldcoll: null }); + const callArgs = onDataChange.mock.calls[onDataChange.mock.calls.length - 1]; + expect(callArgs[0]).toEqual(false); + expect(callArgs[1]).toEqual({ id: undefined, field1: null, field2: null, fieldcoll: null }); }); }); }); diff --git a/web/regression/javascript/SchemaView/SchemaDialogViewEdit.spec.js b/web/regression/javascript/SchemaView/SchemaDialogViewEdit.spec.js index ac394fbc6..6071d2506 100644 --- a/web/regression/javascript/SchemaView/SchemaDialogViewEdit.spec.js +++ b/web/regression/javascript/SchemaView/SchemaDialogViewEdit.spec.js @@ -120,7 +120,9 @@ describe('SchemaView', ()=>{ }); expect(ctrl.container.querySelector('[data-test="Reset"]').hasAttribute('disabled')).toBe(true); expect(ctrl.container.querySelector('[data-test="Save"]').hasAttribute('disabled')).toBe(true); - expect(onDataChange).toHaveBeenCalledWith(false, {}); + const callArgs = onDataChange.mock.calls[onDataChange.mock.calls.length - 1]; + expect(callArgs[0]).toEqual(false); + expect(callArgs[1]).toEqual({}); }); }); }); diff --git a/web/regression/javascript/debugger/debugger_spec.js b/web/regression/javascript/debugger/debugger_spec.js index 615dfcd9e..db1a9e4d1 100644 --- a/web/regression/javascript/debugger/debugger_spec.js +++ b/web/regression/javascript/debugger/debugger_spec.js @@ -22,6 +22,7 @@ import DebuggerComponent from '../../../pgadmin/tools/debugger/static/js/compone import FunctionArguments from '../../../pgadmin/tools/debugger/static/js/debugger_ui'; import Debugger from '../../../pgadmin/tools/debugger/static/js/DebuggerModule'; import Theme from '../../../pgadmin/static/js/Theme'; +import { PgAdminContext } from '../../../pgadmin/static/js/BrowserComponent'; describe('Debugger Component', () => { @@ -58,13 +59,15 @@ describe('Debugger Component', () => { await act(async () => { render( <Theme> - <DebuggerComponent - pgAdmin={pgAdmin} - panel={document.getElementById('debugger-main-container')} - selectedNodeInfo={nodeInfo} - layout={''} - params={params} - /> + <PgAdminContext.Provider value={pgAdmin}> + <DebuggerComponent + pgAdmin={pgAdmin} + panel={document.getElementById('debugger-main-container')} + selectedNodeInfo={nodeInfo} + layout={''} + params={params} + /> + </PgAdminContext.Provider> </Theme> ); }); diff --git a/web/regression/javascript/setup-jest.js b/web/regression/javascript/setup-jest.js index 1cab127e1..736df3fbb 100644 --- a/web/regression/javascript/setup-jest.js +++ b/web/regression/javascript/setup-jest.js @@ -94,6 +94,15 @@ Element.prototype.getBoundingClientRect = jest.fn(function () { }; }); +Object.defineProperty(global.SVGElement.prototype, 'getBBox', { + writable: true, + value: jest.fn().mockReturnValue({ + x: 0, + y: 0, + width: 100, + }), +}); + global.TextEncoder = TextEncoder; global.TextDecoder = TextDecoder; diff --git a/web/regression/javascript/withPgadmin.js b/web/regression/javascript/withPgadmin.js new file mode 100644 index 000000000..fb01cbfec --- /dev/null +++ b/web/regression/javascript/withPgadmin.js @@ -0,0 +1,12 @@ +import React from 'react'; +import { PgAdminContext } from '../../pgadmin/static/js/BrowserComponent'; +import fakePgAdmin from './fake_pgadmin'; + +export default function withPgadmin(WrappedComp) { + /* eslint-disable react/display-name */ + return (props)=>{ + return <PgAdminContext.Provider value={fakePgAdmin}> + <WrappedComp {...props}/> + </PgAdminContext.Provider>; + }; +}