{
- props.onDataChange(changedData);
- }}
- />
+
+ {
+ props.onDataChange(changedData);
+ }}
+ />
+
);
}
@@ -144,6 +157,7 @@ RightPanel.propTypes = {
export default function PreferencesComponent({ ...props }) {
+ const [refreshKey, setRefreshKey] = React.useState(0);
const [disableSave, setDisableSave] = React.useState(true);
const prefSchema = React.useRef(new PreferencesSchema({}, []));
const prefChangedData = React.useRef({});
@@ -214,12 +228,17 @@ export default function PreferencesComponent({ ...props }) {
setPrefTreeData(preferencesTreeData);
setInitValues(preferencesValues);
// set Preferences schema
- prefSchema.current = new PreferencesSchema(preferencesValues, preferencesData);
+ prefSchema.current = new PreferencesSchema(
+ preferencesValues, preferencesData,
+ );
}).catch((err) => {
pgAdmin.Browser.notifier.alert(err);
});
}, []);
- function setPreferences(node, subNode, nodeData, preferencesValues, preferencesData) {
+
+ function setPreferences(
+ node, subNode, nodeData, preferencesValues, preferencesData
+ ) {
let addBinaryPathNote = false;
subNode.preferences.forEach((element) => {
let note = '';
@@ -335,9 +354,10 @@ export default function PreferencesComponent({ ...props }) {
preferencesData.push(
{
id: _.uniqueId('note') + subNode.id,
- type: 'note', text: note,
+ type: 'note',
+ text: note,
+ 'parentId': nodeData['id'],
visible: false,
- 'parentId': nodeData['id']
},
);
}
@@ -351,28 +371,25 @@ export default function PreferencesComponent({ ...props }) {
}
useEffect(() => {
- let initTreeTimeout = null;
let firstElement = null;
// Listen selected preferences tree node event and show the appropriate components in right panel.
pgAdmin.Browser.Events.on('preferences:tree:selected', (event, item) => {
if (item.type == FileType.File) {
- prefSchema.current.setSelectedCategory(item._metadata.data.name);
prefSchema.current.schemaFields.forEach((field) => {
- field.visible = field.parentId === item._metadata.data.id && !field?.hidden ;
+ field.visible = field.parentId === item._metadata.data.id &&
+ !field?.hidden ;
+
if(field.visible && _.isNull(firstElement)) {
firstElement = field;
}
- field.labelTooltip = item._parent._metadata.data.name.toLowerCase() + ':' + item._metadata.data.name + ':' + field.name;
+
+ field.labelTooltip =
+ item._parent._metadata.data.name.toLowerCase() + ':' +
+ item._metadata.data.name + ':' + field.name;
});
- setLoadTree(crypto.getRandomValues(new Uint16Array(1)));
- initTreeTimeout = setTimeout(() => {
- prefTreeInit.current = true;
- if(firstElement) {
- //set focus on first element on right side panel.
- document.getElementsByName(firstElement.id.toString())[0].focus();
- firstElement = '';
- }
- }, 10);
+ prefSchema.current.categoryUpdated(item._metadata.data.id);
+ setLoadTree(Date.now());
+ setRefreshKey(Date.now());
}
else {
selectChildNode(item, prefTreeInit);
@@ -386,10 +403,6 @@ export default function PreferencesComponent({ ...props }) {
// Listen added preferences tree node event to expand the newly added node on tree load.
pgAdmin.Browser.Events.on('preferences:tree:added', addPrefTreeNode);
- /* Clear the initTreeTimeout timeout if unmounted */
- return () => {
- clearTimeout(initTreeTimeout);
- };
}, []);
function addPrefTreeNode(event, item) {
@@ -655,32 +668,54 @@ export default function PreferencesComponent({ ...props }) {
{
- useMemo(() => (prefTreeData && props.renderTree(prefTreeData)), [prefTreeData])
+ useMemo(
+ () => (prefTreeData && props.renderTree(prefTreeData)),
+ [prefTreeData]
+ )
}
{
prefSchema.current && loadTree > 0 &&
- {
- Object.keys(changedData).length > 0 ? setDisableSave(false) : setDisableSave(true);
- prefChangedData.current = changedData;
- }}>
+ {
+ Object.keys(changedData).length > 0 ?
+ setDisableSave(false) : setDisableSave(true);
+ prefChangedData.current = changedData;
+ }}
+ >
}
- } title={gettext('Help for this dialog.')} />
+ } title={gettext('Help for this dialog.')}
+ />
- }>
+ }>
{gettext('Reset all preferences')}
- { props.closeModal();}} startIcon={ { props.closeModal();}} />}>
+ { props.closeModal();}}
+ startIcon={
+ { props.closeModal();}} />
+ }>
{gettext('Cancel')}
- } disabled={disableSave} onClick={() => { savePreferences(prefChangedData, initValues); }}>
+ }
+ disabled={disableSave}
+ onClick={() => {
+ savePreferences(prefChangedData, initValues);
+ }}>
{gettext('Save')}
diff --git a/web/pgadmin/static/js/SchemaView/DataGridView.jsx b/web/pgadmin/static/js/SchemaView/DataGridView.jsx
deleted file mode 100644
index aab6f7fec5e..00000000000
--- a/web/pgadmin/static/js/SchemaView/DataGridView.jsx
+++ /dev/null
@@ -1,625 +0,0 @@
-/////////////////////////////////////////////////////////////
-//
-// pgAdmin 4 - PostgreSQL Tools
-//
-// Copyright (C) 2013 - 2024, The pgAdmin Development Team
-// This software is released under the PostgreSQL Licence
-//
-//////////////////////////////////////////////////////////////
-
-/* The DataGridView component is based on react-table component */
-
-import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
-import { Box } from '@mui/material';
-import AddIcon from '@mui/icons-material/AddOutlined';
-import {
- useReactTable,
- getCoreRowModel,
- getSortedRowModel,
- getFilteredRowModel,
- getExpandedRowModel,
- flexRender,
-} from '@tanstack/react-table';
-import { useVirtualizer } from '@tanstack/react-virtual';
-import PropTypes from 'prop-types';
-import _ from 'lodash';
-import { DndProvider, useDrag, useDrop } from 'react-dnd';
-import {HTML5Backend} from 'react-dnd-html5-backend';
-
-import { usePgAdmin } from 'sources/BrowserComponent';
-import { PgIconButton } from 'sources/components/Buttons';
-import {
- PgReactTable, PgReactTableBody, PgReactTableCell, PgReactTableHeader,
- PgReactTableRow, PgReactTableRowContent, PgReactTableRowExpandContent,
- getDeleteCell, getEditCell, getReorderCell
-} from 'sources/components/PgReactTableStyled';
-import CustomPropTypes from 'sources/custom_prop_types';
-import { useIsMounted } from 'sources/custom_hooks';
-import { InputText } from 'sources/components/FormComponents';
-import gettext from 'sources/gettext';
-import { evalFunc, requestAnimationAndFocus } from 'sources/utils';
-
-import FormView from './FormView';
-import { MappedCellControl } from './MappedControl';
-import {
- SCHEMA_STATE_ACTIONS, SchemaStateContext, getFieldMetaData,
- isModeSupportedByField
-} from './common';
-import { StyleDataGridBox } from './StyledComponents';
-
-
-function DataTableRow({
- index, row, totalRows, isResizing, isHovered, schema, schemaRef, accessPath,
- moveRow, setHoverIndex, viewHelperProps
-}) {
-
- const [key, setKey] = useState(false);
- const schemaState = useContext(SchemaStateContext);
- const rowRef = useRef(null);
- const dragHandleRef = useRef(null);
-
- /* Memoize the row to avoid unnecessary re-render.
- * If table data changes, then react-table re-renders the complete tables
- * We can avoid re-render by if row data is not changed
- */
- let depsMap = [JSON.stringify(row.original)];
- const externalDeps = useMemo(()=>{
- let retVal = [];
- /* Calculate the fields which depends on the current field
- deps has info on fields which the current field depends on. */
- schema.fields.forEach((field)=>{
- (evalFunc(null, field.deps) || []).forEach((dep)=>{
- let source = accessPath.concat(dep);
- if(_.isArray(dep)) {
- source = dep;
- /* If its an array, then dep is from the top schema and external */
- retVal.push(source);
- }
- });
- });
- return retVal;
- }, []);
-
- useEffect(()=>{
- schemaRef.current.fields.forEach((field)=>{
- /* Self change is also dep change */
- if(field.depChange || field.deferredDepChange) {
- schemaState?.addDepListener(accessPath.concat(field.id), accessPath.concat(field.id), field.depChange, field.deferredDepChange);
- }
- (evalFunc(null, field.deps) || []).forEach((dep)=>{
- let source = accessPath.concat(dep);
- if(_.isArray(dep)) {
- source = dep;
- }
- if(field.depChange) {
- schemaState?.addDepListener(source, accessPath.concat(field.id), field.depChange);
- }
- });
- });
-
- return ()=>{
- /* Cleanup the listeners when unmounting */
- schemaState?.removeDepListener(accessPath);
- };
- }, []);
-
- const [{ handlerId }, drop] = useDrop({
- accept: 'row',
- collect(monitor) {
- return {
- handlerId: monitor.getHandlerId(),
- };
- },
- hover(item, monitor) {
- if (!rowRef.current) {
- return;
- }
- item.hoverIndex = null;
- // Don't replace items with themselves
- if (item.index === index) {
- return;
- }
- // Determine rectangle on screen
- const hoverBoundingRect = rowRef.current?.getBoundingClientRect();
- // Determine mouse position
- const clientOffset = monitor.getClientOffset();
- // Get pixels to the top
- const hoverClientY = clientOffset.y - hoverBoundingRect.top;
- // Only perform the move when the mouse has crossed certain part of the items height
- // Dragging downwards
- if (item.index < index && hoverClientY < (hoverBoundingRect.bottom - hoverBoundingRect.top)/3) {
- return;
- }
- // Dragging upwards
- if (item.index > index && hoverClientY > ((hoverBoundingRect.bottom - hoverBoundingRect.top)*2/3)) {
- return;
- }
- setHoverIndex(index);
- item.hoverIndex = index;
- },
- });
-
- const [, drag] = useDrag({
- type: 'row',
- item: () => {
- return {index};
- },
- end: (item)=>{
- // Time to actually perform the action
- setHoverIndex(null);
- if(item.hoverIndex >= 0) {
- moveRow(item.index, item.hoverIndex);
- }
- }
- });
-
- /* External deps values are from top schema sess data */
- depsMap = depsMap.concat(externalDeps.map((source)=>_.get(schemaRef.current.top?.sessData, source)));
- depsMap = depsMap.concat([totalRows, row.getIsExpanded(), key, isResizing, isHovered]);
-
- drag(dragHandleRef);
- drop(rowRef);
-
- return useMemo(()=>
-
- {row.getVisibleCells().map((cell) => {
- // Let's not render the cell, which are not supported in this mode.
- if (cell.column.field && !isModeSupportedByField(
- cell.column.field, viewHelperProps
- )) return;
-
- const content = flexRender(cell.column.columnDef.cell, {
- key: cell.column.columnDef.cell?.type ?? cell.column.columnDef.id,
- ...cell.getContext(),
- reRenderRow: ()=>{setKey((currKey)=>!currKey);}
- });
-
- return (
-
- {content}
-
- );
- })}
-
- , depsMap);
-}
-
-export function DataGridHeader({label, canAdd, onAddClick, canSearch, onSearchTextChange}) {
- const [searchText, setSearchText] = useState('');
- return (
-
- { label &&
- {label}
- }
- { canSearch &&
-
- {
- onSearchTextChange(value);
- setSearchText(value);
- }}
- placeholder={gettext('Search')}>
-
-
- }
-
- {canAdd && {
- setSearchText('');
- onSearchTextChange('');
- onAddClick();
- }} icon={} className='DataGridView-gridControlsButton' />}
-
-
- );
-}
-DataGridHeader.propTypes = {
- label: PropTypes.string,
- canAdd: PropTypes.bool,
- onAddClick: PropTypes.func,
- canSearch: PropTypes.bool,
- onSearchTextChange: PropTypes.func,
-};
-
-function getMappedCell({
- field,
- schemaRef,
- viewHelperProps,
- accessPath,
- dataDispatch
-}) {
- const Cell = ({row, ...other}) => {
- const value = other.getValue();
- /* Make sure to take the latest field info from schema */
- field = _.find(schemaRef.current.fields, (f)=>f.id==field.id) || field;
-
- let {editable, disabled, modeSupported} = getFieldMetaData(field, schemaRef.current, row.original || {}, viewHelperProps);
-
- if(_.isUndefined(field.cell)) {
- console.error('cell is required ', field);
- }
-
- return modeSupported && {
- if(field.radioType) {
- dataDispatch({
- type: SCHEMA_STATE_ACTIONS.BULK_UPDATE,
- path: accessPath,
- value: changeValue,
- id: field.id
- });
- }
- dataDispatch({
- type: SCHEMA_STATE_ACTIONS.SET_VALUE,
- path: accessPath.concat([row.index, field.id]),
- value: changeValue,
- });
- }}
- reRenderRow={other.reRenderRow}
- />;
- };
-
- Cell.displayName = 'Cell';
- Cell.propTypes = {
- row: PropTypes.object.isRequired,
- value: PropTypes.any,
- onCellChange: PropTypes.func,
- };
-
- return Cell;
-}
-
-export default function DataGridView({
- value, viewHelperProps, schema, accessPath, dataDispatch, containerClassName,
- fixedRows, ...props
-}) {
-
- const schemaState = useContext(SchemaStateContext);
- const checkIsMounted = useIsMounted();
- const [hoverIndex, setHoverIndex] = useState();
- const newRowIndex = useRef();
- const pgAdmin = usePgAdmin();
- const [searchVal, setSearchVal] = useState('');
-
- /* Using ref so that schema variable is not frozen in columns closure */
- const schemaRef = useRef(schema);
- const columns = useMemo(
- ()=>{
- let cols = [];
- if(props.canReorder) {
- let colInfo = {
- header: <> >,
- id: 'btn-reorder',
- accessorFn: ()=>{/*This is intentional (SonarQube)*/},
- enableResizing: false,
- enableSorting: false,
- dataType: 'reorder',
- size: 36,
- maxSize: 26,
- minSize: 26,
- cell: getReorderCell(),
- };
- cols.push(colInfo);
- }
- if(props.canEdit) {
- let colInfo = {
- header: <> >,
- id: 'btn-edit',
- accessorFn: ()=>{/*This is intentional (SonarQube)*/},
- enableResizing: false,
- enableSorting: false,
- dataType: 'edit',
- size: 26,
- maxSize: 26,
- minSize: 26,
- cell: getEditCell({
- isDisabled: (row)=>{
- let canEditRow = true;
- if(props.canEditRow) {
- canEditRow = evalFunc(schemaRef.current, props.canEditRow, row.original || {});
- }
- return !canEditRow;
- },
- title: gettext('Edit row'),
- })
- };
- cols.push(colInfo);
- }
- if(props.canDelete) {
- let colInfo = {
- header: <> >,
- id: 'btn-delete',
- accessorFn: ()=>{/*This is intentional (SonarQube)*/},
- enableResizing: false,
- enableSorting: false,
- dataType: 'delete',
- size: 26,
- maxSize: 26,
- minSize: 26,
- cell: getDeleteCell({
- title: gettext('Delete row'),
- isDisabled: (row)=>{
- let canDeleteRow = true;
- if(props.canDeleteRow) {
- canDeleteRow = evalFunc(schemaRef.current, props.canDeleteRow, row.original || {});
- }
- return !canDeleteRow;
- },
- onClick: (row)=>{
- const deleteRow = ()=> {
- dataDispatch({
- type: SCHEMA_STATE_ACTIONS.DELETE_ROW,
- path: accessPath,
- value: row.index,
- });
- return true;
- };
-
- if (props.onDelete){
- props.onDelete(row.original || {}, deleteRow);
- } else {
- pgAdmin.Browser.notifier.confirm(
- props.customDeleteTitle || gettext('Delete Row'),
- props.customDeleteMsg || gettext('Are you sure you wish to delete this row?'),
- deleteRow,
- function() {
- return true;
- }
- );
- }
- }
- }),
- };
- cols.push(colInfo);
- }
-
- cols = cols.concat(
- schemaRef.current.fields.filter((f) => (
- _.isArray(props.columns) ? props.columns.indexOf(f.id) > -1 : true
- )).sort((firstF, secondF) => (
- _.isArray(props.columns) ? ((
- props.columns.indexOf(firstF.id) <
- props.columns.indexOf(secondF.id)
- ) ? -1 : 1) : 0
- )).map((field) => {
- let widthParms = {};
- if(field.width) {
- widthParms.size = field.width;
- widthParms.minSize = field.width;
- } else {
- widthParms.size = 75;
- widthParms.minSize = 75;
- }
- if(field.minWidth) {
- widthParms.minSize = field.minWidth;
- }
- if(field.maxWidth) {
- widthParms.maxSize = field.maxWidth;
- }
- widthParms.enableResizing =
- _.isUndefined(field.enableResizing) ? true : Boolean(
- field.enableResizing
- );
-
- let colInfo = {
- header: field.label||<> >,
- accessorKey: field.id,
- field: field,
- enableResizing: true,
- enableSorting: false,
- ...widthParms,
- cell: getMappedCell({
- field: field,
- schemaRef: schemaRef,
- viewHelperProps: viewHelperProps,
- accessPath: accessPath,
- dataDispatch: dataDispatch,
- }),
- };
-
- return colInfo;
- })
- );
- return cols;
- },[props.canEdit, props.canDelete, props.canReorder]
- );
-
- const columnVisibility = useMemo(()=>{
- const ret = {};
-
- columns.forEach(column => {
- ret[column.id] = isModeSupportedByField(column.field, viewHelperProps);
- });
-
- return ret;
- }, [columns, viewHelperProps]);
-
- const table = useReactTable({
- columns,
- data: value,
- autoResetAll: false,
- state: {
- globalFilter: searchVal,
- columnVisibility: columnVisibility,
- },
- columnResizeMode: 'onChange',
- getCoreRowModel: getCoreRowModel(),
- getSortedRowModel: getSortedRowModel(),
- getFilteredRowModel: getFilteredRowModel(),
- getExpandedRowModel: getExpandedRowModel(),
- });
-
- const rows = table.getRowModel().rows;
-
- const onAddClick = useCallback(()=>{
- if(!props.canAddRow) {
- return;
- }
- let newRow = schemaRef.current.getNewData();
-
- const current_macros = schemaRef.current?._top?._sessData?.macro || null;
- if (current_macros){
- newRow = schemaRef.current.getNewData(current_macros);
- }
-
- newRowIndex.current = props.addOnTop ? 0 : rows.length;
-
- dataDispatch({
- type: SCHEMA_STATE_ACTIONS.ADD_ROW,
- path: accessPath,
- value: newRow,
- addOnTop: props.addOnTop
- });
- }, [props.canAddRow, rows?.length]);
-
- useEffect(() => {
- let rowsPromise = fixedRows;
-
- // If fixedRows is defined, fetch the details.
- if(typeof rowsPromise === 'function') {
- rowsPromise = rowsPromise();
- }
-
- if(rowsPromise) {
- Promise.resolve(rowsPromise)
- .then((res) => {
- /* If component unmounted, dont update state */
- if(checkIsMounted()) {
- schemaState.setUnpreparedData(accessPath, res);
- }
- });
- }
- }, []);
-
- useEffect(()=>{
- if(newRowIndex.current >= 0) {
- virtualizer.scrollToIndex(newRowIndex.current);
-
- // Try autofocus on newly added row.
- setTimeout(() => {
- const rowInput = tableRef.current?.querySelector(
- `.pgrt-row[data-index="${newRowIndex.current}"] input`
- );
- if(!rowInput) return;
-
- requestAnimationAndFocus(tableRef.current.querySelector(
- `.pgrt-row[data-index="${newRowIndex.current}"] input`
- ));
- props.expandEditOnAdd && props.canEdit &&
- rows[newRowIndex.current]?.toggleExpanded(true);
- newRowIndex.current = undefined;
- }, 50);
- }
- }, [rows?.length]);
-
- const tableRef = useRef();
-
- const moveRow = (dragIndex, hoverIndex) => {
- dataDispatch({
- type: SCHEMA_STATE_ACTIONS.MOVE_ROW,
- path: accessPath,
- oldIndex: dragIndex,
- newIndex: hoverIndex,
- });
- };
-
- const isResizing = _.flatMap(table.getHeaderGroups(), headerGroup => headerGroup.headers.map(header=>header.column.getIsResizing())).includes(true);
-
- const virtualizer = useVirtualizer({
- count: rows.length,
- getScrollElement: () => tableRef.current,
- estimateSize: () => 42,
- measureElement:
- typeof window !== 'undefined' &&
- navigator.userAgent.indexOf('Firefox') === -1
- ? element => element?.getBoundingClientRect().height
- : undefined,
- overscan: viewHelperProps.virtualiseOverscan ?? 10,
- });
-
- if(!props.visible) {
- return <>>;
- }
-
- return (
-
-
- {(props.label || props.canAdd) && {
- setSearchVal(value || undefined);
- }}
- />}
-
-
-
-
- {virtualizer.getVirtualItems().map((virtualRow) => {
- const row = rows[virtualRow.index];
-
- return virtualizer.measureElement(node)}
- style={{
- transform: `translateY(${virtualRow.start}px)`, // this should always be a `style` as it changes on scroll
- }}>
-
- {props.canEdit &&
-
- {
- requestAnimationAndFocus(ele);
- }}/>
-
- }
- ;
- })}
-
-
-
-
-
- );
-}
-
-DataGridView.propTypes = {
- label: PropTypes.string,
- value: PropTypes.array,
- viewHelperProps: PropTypes.object,
- schema: CustomPropTypes.schemaUI,
- accessPath: PropTypes.array.isRequired,
- dataDispatch: PropTypes.func,
- containerClassName: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
- fixedRows: PropTypes.oneOfType([PropTypes.array, PropTypes.instanceOf(Promise), PropTypes.func]),
- columns: PropTypes.array,
- canEdit: PropTypes.bool,
- canAdd: PropTypes.bool,
- canDelete: PropTypes.bool,
- canReorder: PropTypes.bool,
- visible: PropTypes.bool,
- canAddRow: PropTypes.oneOfType([
- PropTypes.bool, PropTypes.func,
- ]),
- canEditRow: PropTypes.oneOfType([
- PropTypes.bool, PropTypes.func,
- ]),
- canDeleteRow: PropTypes.oneOfType([
- PropTypes.bool, PropTypes.func,
- ]),
- expandEditOnAdd: PropTypes.bool,
- customDeleteTitle: PropTypes.string,
- customDeleteMsg: PropTypes.string,
- canSearch: PropTypes.bool,
- onDelete: PropTypes.func,
- addOnTop: PropTypes.bool
-};
diff --git a/web/pgadmin/static/js/SchemaView/DataGridView/SearchBox.jsx b/web/pgadmin/static/js/SchemaView/DataGridView/SearchBox.jsx
new file mode 100644
index 00000000000..a8dab903565
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/DataGridView/SearchBox.jsx
@@ -0,0 +1,47 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import React, { useContext } from 'react';
+
+import {
+ SEARCH_INPUT_ALIGNMENT, SEARCH_INPUT_SIZE, SearchInputText,
+} from 'sources/components/SearchInputText';
+
+import { SchemaStateContext } from '../SchemaState';
+
+import { DataGridContext } from './context';
+import { GRID_STATE } from './utils';
+
+export const SEARCH_STATE_PATH = [GRID_STATE, '__searchText'];
+
+export function SearchBox() {
+ const schemaState = useContext(SchemaStateContext);
+ const {
+ accessPath, field, options: { canSearch }
+ } = useContext(DataGridContext);
+
+ if (!canSearch) return <>>;
+
+ const searchText = schemaState.state(accessPath.concat(SEARCH_STATE_PATH));
+ const searchTextChange = (value) => {
+ schemaState.setState(accessPath.concat(SEARCH_STATE_PATH), value);
+ };
+
+ const searchOptions = field.searchOptions || {
+ size: SEARCH_INPUT_SIZE.HALF,
+ alignment: SEARCH_INPUT_ALIGNMENT.RIGHT,
+ };
+
+ return (
+
+ );
+}
diff --git a/web/pgadmin/static/js/SchemaView/DataGridView/context.js b/web/pgadmin/static/js/SchemaView/DataGridView/context.js
new file mode 100644
index 00000000000..9cf54ec946c
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/DataGridView/context.js
@@ -0,0 +1,14 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import { createContext } from 'react';
+
+export const DataGridContext = createContext();
+export const DataGridRowContext = createContext();
+
diff --git a/web/pgadmin/static/js/SchemaView/DataGridView/features/common.jsx b/web/pgadmin/static/js/SchemaView/DataGridView/features/common.jsx
new file mode 100644
index 00000000000..6c44275bf16
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/DataGridView/features/common.jsx
@@ -0,0 +1,21 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import React from 'react';
+
+export const ACTION_COLUMN = {
+ header: <> >,
+ accessorFn: ()=>{/*This is intentional (SonarQube)*/},
+ enableResizing: false,
+ enableSorting: false,
+ dataType: 'reorder',
+ size: 36,
+ maxSize: 26,
+ minSize: 26,
+};
diff --git a/web/pgadmin/static/js/SchemaView/DataGridView/features/deletable.js b/web/pgadmin/static/js/SchemaView/DataGridView/features/deletable.js
new file mode 100644
index 00000000000..7a18d508371
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/DataGridView/features/deletable.js
@@ -0,0 +1,95 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import React from 'react';
+
+import { getDeleteCell } from 'sources/components/PgReactTableStyled';
+import { SCHEMA_STATE_ACTIONS } from 'sources/SchemaView/SchemaState';
+import gettext from 'sources/gettext';
+
+import {
+ canAddOrDelete, evalIfNotDisabled, registerOptionEvaluator
+} from '../../options';
+
+import { SchemaStateContext } from '../../SchemaState';
+import { useFieldOptions } from '../../hooks';
+import { DataGridRowContext } from '../context';
+import { ACTION_COLUMN } from './common';
+import Feature from './feature';
+
+
+// Register the 'canDelete' options for the collection
+registerOptionEvaluator('canDelete', canAddOrDelete, false, ['collection']);
+
+// Register the 'canDeleteRow' option for the table row
+registerOptionEvaluator('canDeleteRow', evalIfNotDisabled, true, ['row']);
+
+
+export default class DeletableRow extends Feature {
+ // Always add 'edit' column at the start of the columns list
+ // (but - not before the reorder column).
+ static priority = 50;
+
+ constructor() {
+ super();
+ this.canDelete = false;
+ }
+
+ generateColumns({pgAdmin, columns, columnVisibility, options}) {
+ this.canDelete = options.canDelete;
+
+ if (!this.canDelete) return;
+
+ const instance = this;
+ const field = instance.field;
+ const accessPath = instance.accessPath;
+ const dataDispatch = instance.dataDispatch;
+
+ columnVisibility['btn-delete'] = true;
+
+ columns.splice(0, 0, {
+ ...ACTION_COLUMN,
+ id: 'btn-delete',
+ dataType: 'delete',
+ cell: getDeleteCell({
+ isDisabled: () => {
+ const schemaState = React.useContext(SchemaStateContext);
+ const { rowAccessPath } = React.useContext(DataGridRowContext);
+ const options = useFieldOptions(rowAccessPath, schemaState);
+
+ return !options.canDeleteRow;
+ },
+ title: gettext('Delete row'),
+ onClick: (row) => {
+ const deleteRow = () => {
+ dataDispatch({
+ type: SCHEMA_STATE_ACTIONS.DELETE_ROW,
+ path: accessPath,
+ value: row.index,
+ });
+ return true;
+ };
+
+ if (field.onDelete){
+ field.onDelete(row?.original || {}, deleteRow);
+ } else {
+ pgAdmin.Browser.notifier.confirm(
+ field.customDeleteTitle || gettext('Delete Row'),
+ field.customDeleteMsg || gettext(
+ 'Are you sure you wish to delete this row?'
+ ),
+ deleteRow,
+ function() { return true; }
+ );
+ }
+ },
+ }),
+ });
+ }
+}
diff --git a/web/pgadmin/static/js/SchemaView/DataGridView/features/expandabledFormView.jsx b/web/pgadmin/static/js/SchemaView/DataGridView/features/expandabledFormView.jsx
new file mode 100644
index 00000000000..2e954a816c6
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/DataGridView/features/expandabledFormView.jsx
@@ -0,0 +1,88 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import React from 'react';
+import { getExpandedRowModel } from '@tanstack/react-table';
+
+import { getEditCell } from 'sources/components/PgReactTableStyled';
+import gettext from 'sources/gettext';
+import FormView from 'sources/SchemaView/FormView';
+
+import { SchemaStateContext } from '../../SchemaState';
+import { useFieldOptions } from '../../hooks';
+import { DataGridRowContext } from '../context';
+import { ACTION_COLUMN } from './common';
+import Feature from './feature';
+
+
+export default class ExpandedFormView extends Feature {
+ // Always add 'edit' column at the start of the columns list
+ // (but - not before the reorder column).
+ static priority = 70;
+
+ constructor() {
+ super();
+ this.canEdit = false;
+ }
+
+ generateColumns({columns, columnVisibility, options}) {
+ this.canEdit = options.canEdit;
+
+ if (!this.canEdit) return;
+
+ columnVisibility['btn-edit'] = true;
+
+ columns.splice(0, 0, {
+ ...ACTION_COLUMN,
+ id: 'btn-edit',
+ dataType: 'edit',
+ cell: getEditCell({
+ isDisabled: () => {
+ const schemaState = React.useContext(SchemaStateContext);
+ const { rowAccessPath } = React.useContext(DataGridRowContext);
+ const options = useFieldOptions(rowAccessPath, schemaState);
+
+ return !options.canEditRow;
+ },
+ title: gettext('Edit row'),
+ }),
+ });
+ }
+
+ onTable({table}) {
+ table.setOptions(prev => ({
+ ...prev,
+ getExpandedRowModel: getExpandedRowModel(),
+ state: {
+ ...prev.state,
+ }
+ }));
+ }
+
+ onRow({row, expandedRowContents, rowOptions}) {
+ const instance = this;
+
+ if (rowOptions.canEditRow && row?.getIsExpanded()) {
+ expandedRowContents.splice(
+ 0, 0,
+ );
+ }
+ }
+}
diff --git a/web/pgadmin/static/js/SchemaView/DataGridView/features/feature.js b/web/pgadmin/static/js/SchemaView/DataGridView/features/feature.js
new file mode 100644
index 00000000000..888d91e9eea
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/DataGridView/features/feature.js
@@ -0,0 +1,134 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+// Feature class
+
+// Let's not expose the features directory.
+let _featureClasses = [];
+
+export default class Feature {
+ static priority = 1;
+
+ constructor() {
+ this.accessPath = this.field = this.schema = this.table = this.cols =
+ this.viewHelperProps = null;
+ }
+
+ setContext({
+ accessPath, field, schema, viewHelperProps, dataDispatch, schemaState
+ }) {
+ this.accessPath = accessPath;
+ this.field = field;
+ this.schema = schema;
+ this.dataDispatch = dataDispatch;
+ this.viewHelperProps = viewHelperProps;
+ this.schemaState = schemaState;
+ }
+
+ generateColumns(/* { pgAdmin, columns, columnVisibility, options } */) {}
+ onTable(/* { table, options, classList } */) {}
+ onRow(/* {
+ index, row, rowRef, classList, attributes,
+ expandedRowContents, rowOptions, tableOptions
+ } */) {}
+}
+
+function isValidFeatureClass(cls) {
+ // Check if provided class is direct decendent of the Feature class
+ try {
+ if (Reflect.getPrototypeOf(cls) != Feature) {
+ console.error(cls, 'Not a valid Feature class:');
+ console.trace();
+ return false;
+ }
+ } catch(err) {
+ console.trace();
+ console.error('Error while checking type:\n', err);
+ return false;
+ }
+
+ return true;
+}
+
+function addToSortedList(_list, _item, _comparator = (a, b) => (a < b)) {
+ // Insert the given feature class in sorted list based on the priority.
+ let idx = 0;
+
+ for (; idx < _list.length; idx++) {
+ if (_comparator(_item, _list[idx])) {
+ _list.splice(idx, 0, _item);
+ return;
+ }
+ }
+
+ _list.splice(idx, 0, _item);
+}
+
+const featurePriorityCompare = (f1, f2) => (f1.priorty < f2.priority);
+
+export function register(cls) {
+
+ if (!isValidFeatureClass(cls)) return;
+
+ addToSortedList(_featureClasses, cls, featurePriorityCompare);
+}
+
+export class FeatureSet {
+ constructor() {
+ this.id = Date.now();
+ this.features = _featureClasses.map((cls) => new cls());
+ }
+
+ addFeatures(features) {
+ features.forEach((feature) => {
+ if (!(feature instanceof Feature)) {
+ console.error(feature, 'is not a valid feature!\n');
+ console.trace();
+ return;
+ }
+ addToSortedList(
+ this.features, feature, featurePriorityCompare
+ );
+ });
+ }
+
+ setContext({
+ accessPath, field, schema, viewHelperProps, dataDispatch, schemaState
+ }) {
+ this.features.forEach((feature) => {
+ feature.setContext({
+ accessPath, field, schema, viewHelperProps, dataDispatch, schemaState
+ });
+ });
+ }
+
+ generateColumns({pgAdmin, columns, columnVisibility, options}) {
+ this.features.forEach((feature) => {
+ feature.generateColumns({pgAdmin, columns, columnVisibility, options});
+ });
+ }
+
+ onTable({table, options, classList}) {
+ this.features.forEach((feature) => {
+ feature.onTable({table, options, classList});
+ });
+ }
+
+ onRow({
+ index, row, rowRef, classList, attributes, expandedRowContents,
+ rowOptions, tableOptions
+ }) {
+ this.features.forEach((feature) => {
+ feature.onRow({
+ index, row, rowRef, classList, attributes, expandedRowContents,
+ rowOptions, tableOptions
+ });
+ });
+ }
+}
diff --git a/web/pgadmin/static/js/SchemaView/DataGridView/features/fixedRows.jsx b/web/pgadmin/static/js/SchemaView/DataGridView/features/fixedRows.jsx
new file mode 100644
index 00000000000..4916b40edc1
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/DataGridView/features/fixedRows.jsx
@@ -0,0 +1,46 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import { useContext, useEffect } from 'react';
+
+import { useIsMounted } from 'sources/custom_hooks';
+import { SchemaStateContext } from 'sources/SchemaView/SchemaState';
+import Feature from './feature';
+
+
+export default class FixedRows extends Feature {
+
+ onTable() {
+ const instance = this;
+ const schemaState = useContext(SchemaStateContext);
+ const checkIsMounted = useIsMounted();
+
+ useEffect(() => {
+ let rowsPromise = instance.field.fixedRows;
+
+ // Fixed rows is supported only in 'create' mode.
+ if (instance.viewHelperProps.mode !== 'create') return;
+
+ // If fixedRows is defined, fetch the details.
+ if(typeof rowsPromise === 'function') {
+ rowsPromise = rowsPromise();
+ }
+
+ if(rowsPromise) {
+ Promise.resolve(rowsPromise)
+ .then((res) => {
+ // If component is unmounted, don't update state.
+ if(checkIsMounted()) {
+ schemaState.setUnpreparedData(instance.accessPath, res);
+ }
+ });
+ }
+ }, []);
+ }
+}
diff --git a/web/pgadmin/static/js/SchemaView/DataGridView/features/index.jsx b/web/pgadmin/static/js/SchemaView/DataGridView/features/index.jsx
new file mode 100644
index 00000000000..6ab26acaec0
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/DataGridView/features/index.jsx
@@ -0,0 +1,29 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+// The DataGridView component is feature support better extendability.
+
+import { Feature, FeatureSet, register } from './feature';
+import FixedRows from './fixedRows';
+import Reorder from './reorder';
+import ExpandedFormView from './expandabledFormView';
+import DeletableRow from './deletable';
+import GlobalSearch from './search';
+
+register(FixedRows);
+register(DeletableRow);
+register(ExpandedFormView);
+register(GlobalSearch);
+register(Reorder);
+
+export {
+ Feature,
+ FeatureSet,
+ register
+};
diff --git a/web/pgadmin/static/js/SchemaView/DataGridView/features/reorder.jsx b/web/pgadmin/static/js/SchemaView/DataGridView/features/reorder.jsx
new file mode 100644
index 00000000000..991fe96fcd6
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/DataGridView/features/reorder.jsx
@@ -0,0 +1,160 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import React, { useRef } from 'react';
+import { useDrag, useDrop } from 'react-dnd';
+import DragIndicatorRoundedIcon from '@mui/icons-material/DragIndicatorRounded';
+
+import { SCHEMA_STATE_ACTIONS } from 'sources/SchemaView/SchemaState';
+
+import { booleanEvaluator, registerOptionEvaluator } from '../../options';
+
+import { ACTION_COLUMN } from './common';
+import Feature from './feature';
+
+
+// Register the 'canReorder' options for the collection
+registerOptionEvaluator('canReorder', booleanEvaluator, false, ['collection']);
+
+export default class Reorder extends Feature {
+ // Always add reorder column at the start of the columns list.
+ static priority = 100;
+
+ constructor() {
+ super();
+ this.canReorder = false;
+ this.hoverIndex = null;
+ this.moveRow = null;
+ }
+
+ setHoverIndex(index) {
+ this.hoverIndex = index;
+ }
+
+ generateColumns({columns, columnVisibility, options}) {
+ this.canReorder = options.canReorder;
+
+ if (!this.canReorder) return;
+
+ columnVisibility['reorder-cell'] = true;
+
+ const Cell = function({row}) {
+ const dragHandleRef = row?.reorderDragHandleRef;
+ const handlerId = row?.dragHandlerId;
+
+ if (!dragHandleRef) return <>>;
+
+ return (
+
+
+
+ );
+ };
+
+ Cell.displayName = 'ReorderCell';
+
+ columns.splice(0, 0, {
+ ...ACTION_COLUMN,
+ id: 'btn-reorder',
+ dataType: 'reorder',
+ cell: Cell,
+ });
+ }
+
+ onTable() {
+ if (this.canReorder) {
+ this.moveRow = (dragIndex, hoverIndex) => {
+ this.dataDispatch?.({
+ type: SCHEMA_STATE_ACTIONS.MOVE_ROW,
+ path: this.accessPath,
+ oldIndex: dragIndex,
+ newIndex: hoverIndex,
+ });
+ };
+ }
+ }
+
+ onRow({index, row, rowRef, classList}) {
+ const instance = this;
+ const reorderDragHandleRef = useRef(null);
+
+ const [{ handlerId }, drop] = useDrop({
+ accept: 'row',
+ collect(monitor) {
+ return {
+ handlerId: monitor.getHandlerId(),
+ };
+ },
+ hover(item, monitor) {
+ if (!rowRef.current) return;
+
+ item.hoverIndex = null;
+ // Don't replace items with themselves
+ if (item.index === index) return;
+
+ // Determine rectangle on screen
+ const hoverBoundry = rowRef.current?.getBoundingClientRect();
+
+ // Determine mouse position
+ const clientOffset = monitor.getClientOffset();
+
+ // Get pixels to the top
+ const hoverClientY = clientOffset.y - hoverBoundry.top;
+
+ // Only perform the move when the mouse has crossed certain part of the
+ // items height dragging downwards.
+ if (
+ item.index < index &&
+ hoverClientY < (hoverBoundry.bottom - hoverBoundry.top)/3
+ ) return;
+
+ // Dragging upwards
+ if (
+ item.index > index &&
+ hoverClientY > ((hoverBoundry.bottom - hoverBoundry.top) * 2 / 3)
+ ) return;
+
+ instance.setHoverIndex(index);
+ item.hoverIndex = index;
+ },
+ });
+
+ const [, drag, preview] = useDrag({
+ type: 'row',
+ item: () => {
+ return {index};
+ },
+ end: (item) => {
+ // Time to actually perform the action
+ instance.setHoverIndex(null);
+ if(item.hoverIndex >= 0) {
+ instance.moveRow(item.index, item.hoverIndex);
+ }
+ }
+ });
+
+ if (!this.canReorder || !row) return;
+
+ if (row)
+ row.reorderDragHandleRef = reorderDragHandleRef;
+
+ drag(row.reorderDragHandleRef);
+ drop(rowRef);
+ preview(rowRef);
+
+ if (index == this.hoverIndex) {
+ classList?.append('DataGridView-tableRowHovered');
+ }
+
+ row.dragHandlerId = handlerId;
+ }
+}
diff --git a/web/pgadmin/static/js/SchemaView/DataGridView/features/search.js b/web/pgadmin/static/js/SchemaView/DataGridView/features/search.js
new file mode 100644
index 00000000000..ef99186b3d3
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/DataGridView/features/search.js
@@ -0,0 +1,54 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import {
+ booleanEvaluator, registerOptionEvaluator
+} from '../../options';
+
+import Feature from './feature';
+import { SEARCH_STATE_PATH } from '../SearchBox';
+
+
+registerOptionEvaluator('canSearch', booleanEvaluator, false, ['collection']);
+
+
+export default class GlobalSearch extends Feature {
+ constructor() {
+ super();
+ }
+
+ onTable({table, options}) {
+
+ if (!options.canSearch) {
+ const searchText = '';
+
+ table.setOptions((prev) => ({
+ ...prev,
+ state: {
+ ...prev.state,
+ globalFilter: searchText,
+ }
+ }));
+
+ return;
+ }
+
+ const searchText = this.schemaState.state(
+ this.accessPath.concat(SEARCH_STATE_PATH)
+ );
+
+ table.setOptions((prev) => ({
+ ...prev,
+ state: {
+ ...prev.state,
+ globalFilter: searchText,
+ }
+ }));
+ }
+}
diff --git a/web/pgadmin/static/js/SchemaView/DataGridView/formHeader.jsx b/web/pgadmin/static/js/SchemaView/DataGridView/formHeader.jsx
new file mode 100644
index 00000000000..bd5c3d55ed6
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/DataGridView/formHeader.jsx
@@ -0,0 +1,166 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import React, {
+ useCallback, useContext, useEffect, useRef, useState
+} from 'react';
+import { styled } from '@mui/material/styles';
+import Box from '@mui/material/Box';
+
+import { SCHEMA_STATE_ACTIONS } from 'sources/SchemaView/SchemaState';
+import { DefaultButton } from 'sources/components/Buttons';
+import CustomPropTypes from 'sources/custom_prop_types';
+import gettext from 'sources/gettext';
+import { requestAnimationAndFocus } from 'sources/utils';
+
+import { SchemaStateContext } from '../SchemaState';
+import { booleanEvaluator, registerOptionEvaluator } from '../options';
+import { View } from '../registry';
+
+import { SearchBox, SEARCH_STATE_PATH } from './SearchBox';
+import { DataGridContext } from './context';
+
+
+// Register the 'headerFormVisible' options for the collection
+registerOptionEvaluator(
+ 'headerFormVisible', booleanEvaluator, false, ['collection']
+);
+
+const StyledBox = styled(Box)(({theme}) => ({
+ '& .DataGridFormHeader-border': {
+ ...theme.mixins.panelBorder,
+ borderBottom: 0,
+ '& .DataGridFormHeader-body': {
+ padding: '0',
+ backgroundColor: theme.palette.grey[400],
+ '& .FormView-singleCollectionPanel': {
+ paddingBottom: 0,
+ },
+ '& .DataGridFormHeader-btn-group' :{
+ display: 'flex',
+ padding: theme.spacing(1),
+ paddingTop: 0,
+ '& .DataGridFormHeader-addBtn': {
+ marginLeft: 'auto',
+ },
+ },
+ '& [data-test="tabpanel"]': {
+ overflow: 'unset',
+ },
+ },
+ },
+}));
+
+export function DataGridFormHeader({tableEleRef}) {
+
+ const {
+ accessPath, field, dataDispatch, options, virtualizer, table,
+ viewHelperProps,
+ } = useContext(DataGridContext);
+ const {
+ addOnTop, canAddRow, canEdit, expandEditOnAdd, headerFormVisible
+ } = options;
+ const rows = table.getRowModel().rows;
+
+ const label = field.label || '';
+ const newRowIndex = useRef(-1);
+ const schemaState = useContext(SchemaStateContext);
+ const headerFormData = useRef({});
+ const [addDisabled, setAddDisabled] = useState(canAddRow);
+ const {headerSchema} = field;
+
+ const onAddClick = useCallback(() => {
+
+ if(!canAddRow) {
+ return;
+ }
+
+ let newRow = headerSchema.getNewData(headerFormData.current);
+
+ newRowIndex.current = addOnTop ? 0 : rows.length;
+
+ dataDispatch({
+ type: SCHEMA_STATE_ACTIONS.ADD_ROW,
+ path: accessPath,
+ value: newRow,
+ addOnTop: addOnTop
+ });
+
+ schemaState.setState(accessPath.concat(SEARCH_STATE_PATH), '');
+ headerSchema.state?.validate(headerSchema._defaults || {});
+ }, [canAddRow, rows?.length, addOnTop]);
+
+ useEffect(() => {
+ if (newRowIndex.current < -1) return;
+
+ virtualizer.scrollToIndex(newRowIndex.current);
+
+ // Try autofocus on newly added row.
+ setTimeout(() => {
+ const rowInput = tableEleRef.current?.querySelector(
+ `.pgrt-row[data-index="${newRowIndex.current}"] input`
+ );
+
+ if(!rowInput) return;
+
+ requestAnimationAndFocus(tableEleRef.current.querySelector(
+ `.pgrt-row[data-index="${newRowIndex.current}"] input`
+ ));
+
+ expandEditOnAdd && canEdit &&
+ rows[newRowIndex.current]?.toggleExpanded(true);
+
+ newRowIndex.current = undefined;
+ }, 50);
+ }, [rows?.length]);
+
+ const SchemaView = View('SchemaView');
+
+ return (
+
+
+
+ {label && {label}}
+
+
+
+
+ {headerFormVisible &&
+
+ Promise.resolve({})}
+ schema={headerSchema}
+ viewHelperProps={viewHelperProps}
+ showFooter={false}
+ onDataChange={(isDataChanged, dataChanged)=>{
+ headerFormData.current = dataChanged;
+ setAddDisabled(
+ canAddRow && headerSchema.addDisabled(headerFormData.current)
+ );
+ }}
+ hasSQL={false}
+ isTabView={false}
+ />
+
+
+ {gettext('Add')}
+
+
+ }
+
+
+ );
+}
+
+DataGridFormHeader.propTypes = {
+ tableEleRef: CustomPropTypes.ref,
+};
diff --git a/web/pgadmin/static/js/SchemaView/DataGridView/grid.jsx b/web/pgadmin/static/js/SchemaView/DataGridView/grid.jsx
new file mode 100644
index 00000000000..b60363f7e32
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/DataGridView/grid.jsx
@@ -0,0 +1,198 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import React, {
+ useContext, useEffect, useMemo, useRef, useState,
+} from 'react';
+
+import Box from '@mui/material/Box';
+import {
+ useReactTable,
+ getCoreRowModel,
+ getSortedRowModel,
+ getFilteredRowModel,
+} from '@tanstack/react-table';
+import { useVirtualizer } from '@tanstack/react-virtual';
+import _ from 'lodash';
+import PropTypes from 'prop-types';
+import { DndProvider } from 'react-dnd';
+import {HTML5Backend} from 'react-dnd-html5-backend';
+
+import { usePgAdmin } from 'sources/BrowserComponent';
+import {
+ PgReactTable, PgReactTableBody, PgReactTableHeader,
+ PgReactTableRow,
+} from 'sources/components/PgReactTableStyled';
+import CustomPropTypes from 'sources/custom_prop_types';
+
+import { StyleDataGridBox } from '../StyledComponents';
+import { SchemaStateContext } from '../SchemaState';
+import { useFieldOptions, useFieldValue } from '../hooks';
+import { registerView } from '../registry';
+import { listenDepChanges } from '../utils';
+
+import { DataGridContext } from './context';
+import { DataGridHeader } from './header';
+import { DataGridRow } from './row';
+import { FeatureSet } from './features';
+import { createGridColumns, GRID_STATE } from './utils';
+
+
+export default function DataGridView({
+ field, viewHelperProps, accessPath, dataDispatch, containerClassName
+}) {
+ const pgAdmin = usePgAdmin();
+ const [refreshKey, setRefreshKey] = useState(0);
+ const schemaState = useContext(SchemaStateContext);
+ const options = useFieldOptions(
+ accessPath, schemaState, refreshKey, setRefreshKey
+ );
+ const value = useFieldValue(accessPath, schemaState);
+ const schema = field.schema;
+ const features = useRef();
+
+ // Update refresh key on changing the number of rows.
+ useFieldValue(
+ [...accessPath, 'length'], schemaState, refreshKey,
+ (newKey) => {
+ setRefreshKey(newKey);
+ }
+ );
+
+ useEffect(() => {
+ return schemaState.subscribe(
+ accessPath.concat(GRID_STATE),
+ () => setRefreshKey(Date.now()), 'states'
+ );
+ }, [refreshKey]);
+
+ listenDepChanges(accessPath, field, options.visible, schemaState);
+
+ if (!features.current) {
+ features.current = new FeatureSet();
+ };
+
+ features.current.setContext({
+ accessPath, field, schema: schema, dataDispatch, viewHelperProps,
+ schemaState,
+ });
+
+ const [columns, columnVisibility] = useMemo(() => {
+
+ const [columns, columnVisibility] = createGridColumns({
+ schema, field, accessPath, viewHelperProps, dataDispatch,
+ });
+
+ features.current?.generateColumns({
+ pgAdmin, columns, columnVisibility, options
+ });
+
+ return [columns, columnVisibility];
+
+ }, [options]);
+
+ const table = useReactTable({
+ columns: columns|| [],
+ data: value || [],
+ autoResetAll: false,
+ state: {
+ columnVisibility: columnVisibility || {},
+ },
+ columnResizeMode: 'onChange',
+ getCoreRowModel: getCoreRowModel(),
+ getSortedRowModel: getSortedRowModel(),
+ getFilteredRowModel: getFilteredRowModel(),
+ });
+
+ const classList = [].concat(containerClassName);
+ features.current?.onTable({table, classList, options});
+
+ const rows = table.getRowModel().rows;
+ const tableEleRef = useRef();
+
+ const isResizing = _.flatMap(
+ table.getHeaderGroups(),
+ headerGroup => headerGroup.headers.map(
+ header => header.column.getIsResizing()
+ )
+ ).includes(true);
+
+ const virtualizer = useVirtualizer({
+ count: rows.length,
+ getScrollElement: () => tableEleRef.current,
+ estimateSize: () => 50,
+ measureElement:
+ typeof window !== 'undefined' &&
+ navigator.userAgent.indexOf('Firefox') === -1
+ ? element => element?.getBoundingClientRect().height
+ : undefined,
+ overscan: viewHelperProps.virtualiseOverscan ?? 10,
+ });
+
+ const GridHeader = field.GridHeader || DataGridHeader;
+ const GridRow = field.GridRow || DataGridRow;
+
+ if (!options.visible) return (<>>);
+
+ return (
+
+
+
+
+
+
+
+
+ {
+ virtualizer.getVirtualItems().map((virtualRow) => {
+ const row = rows[virtualRow.index];
+ return (
+ virtualizer.measureElement(node)}
+ style={{
+ // This should always be a `style` as it changes on
+ // scroll.
+ transform: `translateY(${virtualRow.start}px)`,
+ }}
+ >
+
+
+ );
+ })
+ }
+
+
+
+
+
+
+ );
+}
+
+DataGridView.propTypes = {
+ viewHelperProps: PropTypes.object,
+ schema: CustomPropTypes.schemaUI,
+ accessPath: PropTypes.array.isRequired,
+ dataDispatch: PropTypes.func,
+ containerClassName: PropTypes.oneOfType([
+ PropTypes.object, PropTypes.string
+ ]),
+ field: PropTypes.object,
+};
+
+registerView(DataGridView, 'DataGridView');
diff --git a/web/pgadmin/static/js/SchemaView/DataGridView/header.jsx b/web/pgadmin/static/js/SchemaView/DataGridView/header.jsx
new file mode 100644
index 00000000000..30bd3bd1c3c
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/DataGridView/header.jsx
@@ -0,0 +1,103 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import React, { useCallback, useContext, useEffect, useRef } from 'react';
+import Box from '@mui/material/Box';
+import AddIcon from '@mui/icons-material/AddOutlined';
+
+import { SCHEMA_STATE_ACTIONS } from 'sources/SchemaView/SchemaState';
+import { PgIconButton } from 'sources/components/Buttons';
+import CustomPropTypes from 'sources/custom_prop_types';
+import gettext from 'sources/gettext';
+import { requestAnimationAndFocus } from 'sources/utils';
+
+import { SchemaStateContext } from '../SchemaState';
+import { SearchBox, SEARCH_STATE_PATH } from './SearchBox';
+import { DataGridContext } from './context';
+
+
+export function DataGridHeader({tableEleRef}) {
+ const {
+ accessPath, field, dataDispatch, options, virtualizer, table,
+ } = useContext(DataGridContext);
+ const {
+ addOnTop, canAdd, canAddRow, canEdit, expandEditOnAdd
+ } = options;
+ const rows = table.getRowModel().rows;
+
+ const label = field.label || '';
+ const newRowIndex = useRef(-1);
+ const schemaState = useContext(SchemaStateContext);
+
+ const onAddClick = useCallback(() => {
+
+ if(!canAddRow) {
+ return;
+ }
+
+ const newRow = field.schema.getNewData();
+
+ newRowIndex.current = addOnTop ? 0 : rows.length;
+
+ dataDispatch({
+ type: SCHEMA_STATE_ACTIONS.ADD_ROW,
+ path: accessPath,
+ value: newRow,
+ addOnTop: addOnTop
+ });
+
+ schemaState.setState(accessPath.concat(SEARCH_STATE_PATH), '');
+ }, [canAddRow, rows?.length]);
+
+ useEffect(() => {
+ if (newRowIndex.current < -1) return;
+
+ virtualizer.scrollToIndex(newRowIndex.current);
+
+ // Try autofocus on newly added row.
+ setTimeout(() => {
+ const rowInput = tableEleRef.current?.querySelector(
+ `.pgrt-row[data-index="${newRowIndex.current}"] input`
+ );
+
+ if(!rowInput) return;
+
+ requestAnimationAndFocus(tableEleRef.current.querySelector(
+ `.pgrt-row[data-index="${newRowIndex.current}"] input`
+ ));
+
+ expandEditOnAdd && canEdit &&
+ rows[newRowIndex.current]?.toggleExpanded(true);
+
+ newRowIndex.current = undefined;
+ }, 50);
+ }, [rows?.length]);
+
+ return (
+
+ {label && {label}}
+
+
+
+
+ { canAdd &&
+ } className='DataGridView-gridControlsButton'
+ />
+ }
+
+
+ );
+}
+
+DataGridHeader.propTypes = {
+ tableEleRef: CustomPropTypes.ref,
+};
diff --git a/web/pgadmin/static/js/SchemaView/DataGridView/index.js b/web/pgadmin/static/js/SchemaView/DataGridView/index.js
new file mode 100644
index 00000000000..40206f1cbc3
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/DataGridView/index.js
@@ -0,0 +1,24 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+/* The DataGridView component is based on react-table component */
+
+import { DataGridFormHeader } from './formHeader.jsx';
+import { DataGridHeader } from './header';
+import { getMappedCell } from './mappedCell';
+import DataGridView from './grid';
+
+
+export default DataGridView;
+
+export {
+ DataGridFormHeader,
+ DataGridHeader,
+ getMappedCell,
+};
diff --git a/web/pgadmin/static/js/SchemaView/DataGridView/mappedCell.jsx b/web/pgadmin/static/js/SchemaView/DataGridView/mappedCell.jsx
new file mode 100644
index 00000000000..92e25c3a6d6
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/DataGridView/mappedCell.jsx
@@ -0,0 +1,105 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import React, { useContext, useMemo, useState } from 'react';
+import _ from 'lodash';
+import PropTypes from 'prop-types';
+
+import { evalFunc } from 'sources/utils';
+
+import { MappedCellControl } from '../MappedControl';
+import { SCHEMA_STATE_ACTIONS, SchemaStateContext } from '../SchemaState';
+import { flatternObject } from '../common';
+import { useFieldOptions, useFieldValue } from '../hooks';
+import { listenDepChanges } from '../utils';
+
+import { DataGridContext, DataGridRowContext } from './context';
+
+
+export function getMappedCell({field}) {
+ const Cell = ({reRenderRow, getValue}) => {
+
+ const [key, setKey] = useState(0);
+ const schemaState = useContext(SchemaStateContext);
+ const { dataDispatch } = useContext(DataGridContext);
+ const { rowAccessPath, row } = useContext(DataGridRowContext);
+ const colAccessPath = schemaState.accessPath(rowAccessPath, field.id);
+
+ let colOptions = useFieldOptions(colAccessPath, schemaState, key, setKey);
+ let value = useFieldValue(colAccessPath, schemaState, key, setKey);
+ let rowValue = useFieldValue(rowAccessPath, schemaState);
+
+ listenDepChanges(colAccessPath, field, true, schemaState);
+
+ if (!field.id) {
+ console.error(`No id set for the field: ${field}`);
+ value = getValue();
+ rowValue = row.original;
+ colOptions = { disabled: true, readonly: true };
+ } else {
+ colOptions['readonly'] = !colOptions['editable'];
+ rowValue = value;
+ }
+
+ let cellProps = {};
+
+ if (_.isFunction(field.cell) && field.id) {
+ cellProps = evalFunc(null, field.cell, rowValue);
+
+ if (typeof (cellProps) !== 'object')
+ cellProps = {cell: cellProps};
+ }
+
+ const props = {
+ ...field,
+ ...cellProps,
+ ...colOptions,
+ visible: true,
+ rowIndex: row.index,
+ value,
+ row,
+ dataDispatch,
+ onCellChange: (changeValue) => {
+ if (colOptions.disabled) return;
+ if(field.radioType) {
+ dataDispatch({
+ type: SCHEMA_STATE_ACTIONS.BULK_UPDATE,
+ path: rowAccessPath,
+ value: changeValue,
+ id: field.id
+ });
+ }
+ dataDispatch({
+ type: SCHEMA_STATE_ACTIONS.SET_VALUE,
+ path: colAccessPath,
+ value: changeValue,
+ });
+ },
+ reRenderRow: reRenderRow
+ };
+
+ if(_.isUndefined(field.cell)) {
+ console.error('cell is required ', field);
+ props.cell = 'unknown';
+ }
+
+ return useMemo(
+ () => ,
+ [...flatternObject(colOptions), value, row.index]
+ );
+ };
+
+ Cell.displayName = 'Cell';
+ Cell.propTypes = {
+ reRenderRow: PropTypes.func,
+ getValue: PropTypes.func,
+ };
+
+ return Cell;
+}
diff --git a/web/pgadmin/static/js/SchemaView/DataGridView/row.jsx b/web/pgadmin/static/js/SchemaView/DataGridView/row.jsx
new file mode 100644
index 00000000000..a95ca912f90
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/DataGridView/row.jsx
@@ -0,0 +1,93 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import React, { useContext, useMemo, useRef } from 'react';
+
+import { flexRender } from '@tanstack/react-table';
+
+import {
+ PgReactTableCell, PgReactTableRowContent, PgReactTableRowExpandContent,
+} from 'sources/components/PgReactTableStyled';
+
+import { SchemaStateContext } from '../SchemaState';
+import { useFieldOptions } from '../hooks';
+
+import { DataGridContext, DataGridRowContext } from './context';
+
+
+export function DataGridRow({rowId, isResizing}) {
+ const schemaState = useContext(SchemaStateContext);
+
+ const { accessPath, options, table, features } = useContext(
+ DataGridContext
+ );
+
+ const rowAccessPath = schemaState.accessPath(accessPath, rowId);
+ const rowOptions = useFieldOptions(rowAccessPath, schemaState);
+
+ const rowRef = useRef(null);
+ const row = table.getRowModel().rows[rowId];
+
+ /*
+ * Memoize the row to avoid unnecessary re-render. If table data changes,
+ * then react-table re-renders the complete tables.
+ *
+ * We can avoid re-render by if row data has not changed.
+ */
+ let classList = [];
+ let attributes = {};
+ let expandedRowContents = [];
+
+ features.current?.onRow({
+ index: rowId, row, rowRef, classList, attributes, expandedRowContents,
+ rowOptions, tableOptions: options
+ });
+
+ let depsMap = [
+ rowId, row?.getIsExpanded(), isResizing, expandedRowContents.length
+ ];
+
+ return useMemo(() => (
+ !row ? <>> :
+
+
+ {
+ row?.getVisibleCells().map((cell) => {
+ const columnDef = cell.column.columnDef;
+ const content = flexRender(
+ columnDef.cell, {
+ key: columnDef.cell?.type ?? columnDef.id,
+ row: row,
+ getValue: cell.getValue,
+ }
+ );
+
+ return (
+
+ {content}
+
+ );
+ })
+ }
+
+
+ {
+ expandedRowContents.length ?
+
+ {expandedRowContents}
+ : <>>
+ }
+
+ ), [...depsMap]);
+}
diff --git a/web/pgadmin/static/js/SchemaView/DataGridView/utils/createGridColumns.jsx b/web/pgadmin/static/js/SchemaView/DataGridView/utils/createGridColumns.jsx
new file mode 100644
index 00000000000..954ecec8748
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/DataGridView/utils/createGridColumns.jsx
@@ -0,0 +1,68 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import React from 'react';
+
+import { isModeSupportedByField } from 'sources/SchemaView/common';
+import { getMappedCell } from '../mappedCell';
+
+
+export function createGridColumns({schema, field, viewHelperProps}) {
+
+ const columns = field.columns;
+ const colunnFilterExp = _.isArray(columns) ?
+ ((f) => (columns.indexOf(f.id) > -1)) : (() => true);
+ const sortExp = _.isArray(columns) ?
+ ((firstF, secondF) => (
+ (columns.indexOf(firstF.id) < columns.indexOf(secondF.id)) ? -1 : 1
+ )) : (() => 0);
+ const columnVisibility = {};
+
+ const cols = schema.fields.filter(colunnFilterExp).sort(sortExp).map(
+ (field) => {
+ let widthParms = {};
+
+ if(field.width) {
+ widthParms.size = field.width;
+ widthParms.minSize = field.width;
+ } else {
+ widthParms.size = 75;
+ widthParms.minSize = 75;
+ }
+
+ if(field.minWidth) {
+ widthParms.minSize = field.minWidth;
+ }
+
+ if(field.maxWidth) {
+ widthParms.maxSize = field.maxWidth;
+ }
+
+ widthParms.enableResizing =
+ _.isUndefined(field.enableResizing) ? true : Boolean(
+ field.enableResizing
+ );
+ columnVisibility[field.id] = isModeSupportedByField(
+ field, viewHelperProps
+ );
+
+ return {
+ header: field.label||<> >,
+ accessorKey: field.id,
+ field: field,
+ enableResizing: true,
+ enableSorting: false,
+ ...widthParms,
+ cell: getMappedCell({field}),
+ };
+ }
+ );
+
+ return [cols, columnVisibility];
+}
diff --git a/web/pgadmin/static/js/SchemaView/DataGridView/utils/index.js b/web/pgadmin/static/js/SchemaView/DataGridView/utils/index.js
new file mode 100644
index 00000000000..5b8a481855a
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/DataGridView/utils/index.js
@@ -0,0 +1,16 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import { createGridColumns } from './createGridColumns';
+
+export const GRID_STATE = '__gridState';
+
+export {
+ createGridColumns,
+};
diff --git a/web/pgadmin/static/js/SchemaView/DepListener.js b/web/pgadmin/static/js/SchemaView/DepListener.js
index b8ea2344a72..fe7eaeb7548 100644
--- a/web/pgadmin/static/js/SchemaView/DepListener.js
+++ b/web/pgadmin/static/js/SchemaView/DepListener.js
@@ -37,7 +37,10 @@ export class DepListener {
if(dataPath.length > 0) {
data = _.get(state, dataPath);
}
- _.assign(data, listener.callback?.(data, listener.source, state, actionObj) || {});
+ _.assign(
+ data,
+ listener.callback?.(data, listener.source, state, actionObj) || {}
+ );
return state;
}
@@ -70,7 +73,10 @@ export class DepListener {
getDeferredDepChange(currPath, state, actionObj) {
let deferredList = [];
- let allListeners = _.filter(this._depListeners, (entry)=>_.join(currPath, '|').startsWith(_.join(entry.source, '|')));
+ let allListeners = _.filter(this._depListeners, (entry) => _.join(
+ currPath, '|'
+ ).startsWith(_.join(entry.source, '|')));
+
if(allListeners) {
for(const listener of allListeners) {
if(listener.defCallback) {
diff --git a/web/pgadmin/static/js/SchemaView/FieldControl.jsx b/web/pgadmin/static/js/SchemaView/FieldControl.jsx
new file mode 100644
index 00000000000..a5358b93115
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/FieldControl.jsx
@@ -0,0 +1,27 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import React, { useMemo } from 'react';
+
+export const FieldControl = ({schemaId, item}) => {
+ const Control = item.control;
+ const props = item.controlProps;
+ const children = item.controls;
+
+ return useMemo(() =>
+
+ {
+ children &&
+ children.map(
+ (child, idx) =>
+ )
+ }
+ , [schemaId, Control, props, children]
+ );
+};
diff --git a/web/pgadmin/static/js/SchemaView/FieldSetView.jsx b/web/pgadmin/static/js/SchemaView/FieldSetView.jsx
index 0812ef133a6..556b92305f5 100644
--- a/web/pgadmin/static/js/SchemaView/FieldSetView.jsx
+++ b/web/pgadmin/static/js/SchemaView/FieldSetView.jsx
@@ -7,156 +7,67 @@
//
//////////////////////////////////////////////////////////////
-import React, { useContext, useEffect } from 'react';
-
-import Grid from '@mui/material/Grid';
-import _ from 'lodash';
+import React, { useContext, useMemo, useState } from 'react';
import PropTypes from 'prop-types';
import FieldSet from 'sources/components/FieldSet';
import CustomPropTypes from 'sources/custom_prop_types';
-import { evalFunc } from 'sources/utils';
-
-import { MappedFormControl } from './MappedControl';
-import {
- getFieldMetaData, SCHEMA_STATE_ACTIONS, SchemaStateContext
-} from './common';
+import { FieldControl } from './FieldControl';
+import { SchemaStateContext } from './SchemaState';
+import { useFieldSchema, useFieldValue } from './hooks';
+import { registerView } from './registry';
+import { createFieldControls, listenDepChanges } from './utils';
-const INLINE_COMPONENT_ROWGAP = '8px';
export default function FieldSetView({
- value, schema={}, viewHelperProps, accessPath, dataDispatch,
- controlClassName, isDataGridForm=false, label, visible
+ field, accessPath, dataDispatch, viewHelperProps, controlClassName,
}) {
+ const [key, setRefreshKey] = useState(0);
+ const schema = field.schema;
const schemaState = useContext(SchemaStateContext);
+ const value = useFieldValue(accessPath, schemaState);
+ const options = useFieldSchema(
+ field, accessPath, value, viewHelperProps, schemaState, key, setRefreshKey
+ );
+ const label = field.label;
- useEffect(() => {
- // Calculate the fields which depends on the current field.
- if(!isDataGridForm && schemaState) {
- schema.fields.forEach((field) => {
- /* Self change is also dep change */
- if(field.depChange || field.deferredDepChange) {
- schemaState?.addDepListener(
- accessPath.concat(field.id), accessPath.concat(field.id),
- field.depChange, field.deferredDepChange
- );
- }
- (evalFunc(null, field.deps) || []).forEach((dep) => {
- let source = accessPath.concat(dep);
- if(_.isArray(dep)) {
- source = dep;
- }
- if(field.depChange) {
- schemaState?.addDepListener(
- source, accessPath.concat(field.id), field.depChange
- );
- }
- });
- });
- }
- }, []);
+ listenDepChanges(accessPath, field, options.visible, schemaState);
- let viewFields = [];
- let inlineComponents = [];
+ const fieldGroups = useMemo(
+ () => createFieldControls({
+ schema, schemaState, accessPath, viewHelperProps, dataDispatch
+ }),
+ [schema, schemaState, accessPath, viewHelperProps, dataDispatch]
+ );
- if(!visible) {
+ // We won't show empty feldset too.
+ if(!options.visible || !fieldGroups.length) {
return <>>;
}
- // Prepare the array of components based on the types.
- for(const field of schema.fields) {
- const {
- visible, disabled, readonly, modeSupported
- } = getFieldMetaData(field, schema, value, viewHelperProps);
-
- if(!modeSupported) continue;
-
- // Its a form control.
- const hasError = (field.id === schemaState?.errors.name);
-
- /*
- * When there is a change, the dependent values can also change.
- * Let's pass these changes to dependent for take them into effect to
- * generate new values.
- */
- const currentControl = {
- /* Get the changes on dependent fields as well */
- dataDispatch({
- type: SCHEMA_STATE_ACTIONS.SET_VALUE,
- path: accessPath.concat(field.id),
- value: changeValue,
- });
- }}
- hasError={hasError}
- className={controlClassName}
- memoDeps={[
- value[field.id],
- readonly,
- disabled,
- visible,
- hasError,
- controlClassName,
- ...(evalFunc(null, field.deps) || []).map((dep)=>value[dep]),
- ]}
- />;
-
- if(field.inlineNext) {
- inlineComponents.push(React.cloneElement(currentControl, {
- withContainer: false, controlGridBasis: 3
- }));
- } else if(inlineComponents?.length > 0) {
- inlineComponents.push(React.cloneElement(currentControl, {
- withContainer: false, controlGridBasis: 3
- }));
- viewFields.push(
-
- {inlineComponents}
-
- );
- inlineComponents = [];
- } else {
- viewFields.push(currentControl);
- }
- }
-
- if(inlineComponents?.length > 0) {
- viewFields.push(
-
- {inlineComponents}
-
- );
- }
-
return (
);
}
FieldSetView.propTypes = {
- value: PropTypes.any,
- schema: CustomPropTypes.schemaUI.isRequired,
viewHelperProps: PropTypes.object,
- isDataGridForm: PropTypes.bool,
accessPath: PropTypes.array.isRequired,
dataDispatch: PropTypes.func,
controlClassName: CustomPropTypes.className,
- label: PropTypes.string,
- visible: PropTypes.oneOfType([
- PropTypes.bool, PropTypes.func,
- ]),
+ field: PropTypes.object,
};
+
+registerView(FieldSetView, 'FieldSetView');
diff --git a/web/pgadmin/static/js/SchemaView/FormLoader.jsx b/web/pgadmin/static/js/SchemaView/FormLoader.jsx
new file mode 100644
index 00000000000..ee9af8d6bcb
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/FormLoader.jsx
@@ -0,0 +1,30 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import React, { useContext, useEffect, useState, useMemo } from 'react';
+
+import Loader from 'sources/components/Loader';
+
+import { SchemaStateContext } from './SchemaState';
+
+
+export const FormLoader = () => {
+ const [key, setKey] = useState(0);
+ const schemaState = useContext(SchemaStateContext);
+ const message = schemaState.loadingMessage;
+
+ useEffect(() => {
+ // Refresh on message changes.
+ return schemaState.subscribe(
+ ['message'], () => setKey(Date.now()), 'states'
+ );
+ }, [key]);
+
+ return useMemo(() => , [message, key]);
+};
diff --git a/web/pgadmin/static/js/SchemaView/FormView.jsx b/web/pgadmin/static/js/SchemaView/FormView.jsx
index b43ee505a68..fce33f112d3 100644
--- a/web/pgadmin/static/js/SchemaView/FormView.jsx
+++ b/web/pgadmin/static/js/SchemaView/FormView.jsx
@@ -8,80 +8,117 @@
//////////////////////////////////////////////////////////////
import React, {
- useContext, useEffect, useMemo, useRef, useState
+ useCallback, useContext, useEffect, useMemo, useRef, useState
} from 'react';
-import { Box, Tab, Tabs, Grid } from '@mui/material';
+import { Box, Tab, Tabs } from '@mui/material';
import _ from 'lodash';
import PropTypes from 'prop-types';
-import { FormNote, InputSQL } from 'sources/components/FormComponents';
+
+import {
+ FormFooterMessage, MESSAGE_TYPE, FormNote
+} from 'sources/components/FormComponents';
import TabPanel from 'sources/components/TabPanel';
import { useOnScreen } from 'sources/custom_hooks';
import CustomPropTypes from 'sources/custom_prop_types';
import gettext from 'sources/gettext';
-import { evalFunc } from 'sources/utils';
-
-import DataGridView from './DataGridView';
-import { MappedFormControl } from './MappedControl';
-import FieldSetView from './FieldSetView';
-import {
- SCHEMA_STATE_ACTIONS, SchemaStateContext, getFieldMetaData
-} from './common';
+import { FieldControl } from './FieldControl';
+import { SQLTab } from './SQLTab';
import { FormContentBox } from './StyledComponents';
+import { SchemaStateContext } from './SchemaState';
+import { useFieldSchema, useFieldValue } from './hooks';
+import { registerView, View } from './registry';
+import { createFieldControls, listenDepChanges } from './utils';
+const ErrorMessageBox = () => {
+ const [key, setKey] = useState(0);
+ const schemaState = useContext(SchemaStateContext);
+ const onErrClose = useCallback(() => {
+ const err = { ...schemaState.errors, message: '' };
+ // Unset the error message, but not the name.
+ schemaState.setError(err);
+ }, [schemaState]);
+ const errors = schemaState.errors;
+ const message = errors?.message || '';
-/* Optional SQL tab */
-function SQLTab({active, getSQLValue}) {
- const [sql, setSql] = useState('Loading...');
- useEffect(()=>{
- let unmounted = false;
- if(active) {
- setSql('Loading...');
- getSQLValue().then((value)=>{
- if(!unmounted) {
- setSql(value);
- }
- });
- }
- return ()=>{unmounted=true;};
- }, [active]);
+ useEffect(() => {
+ // Refresh on message changes.
+ return schemaState.subscribe(
+ ['errors', 'message'], () => setKey(Date.now()), 'states'
+ );
+ }, [key]);
- return ;
-}
-
-SQLTab.propTypes = {
- active: PropTypes.bool,
- getSQLValue: PropTypes.func.isRequired,
};
-/* The first component of schema view form */
+// The first component of schema view form.
export default function FormView({
- value, schema={}, viewHelperProps, isNested=false, accessPath, dataDispatch, hasSQLTab,
- getSQLValue, onTabChange, firstEleRef, className, isDataGridForm=false, isTabView=true, visible}) {
- let defaultTab = gettext('General');
- let tabs = {};
- let tabsClassname = {};
- const [tabValue, setTabValue] = useState(0);
+ accessPath, schema=null, isNested=false, dataDispatch, className,
+ hasSQLTab, getSQLValue, isTabView=true, viewHelperProps, field,
+ showError=false, resetKey, focusOnFirstInput=false
+}) {
+ const [key, setKey] = useState(0);
+ const schemaState = useContext(SchemaStateContext);
+ const value = useFieldValue(accessPath, schemaState);
+ const { visible } = useFieldSchema(
+ field, accessPath, value, viewHelperProps, schemaState, key, setKey
+ );
- const firstEleID = useRef();
+ const [tabValue, setTabValue] = useState(0);
const formRef = useRef();
const onScreenTracker = useRef(false);
- let groupLabels = {};
- const schemaRef = useRef(schema);
- const schemaState = useContext(SchemaStateContext);
-
let isOnScreen = useOnScreen(formRef);
- useEffect(()=>{
+ if (!schema) schema = field.schema;
+
+ // Set focus on the first focusable element.
+ useEffect(() => {
+ if (!focusOnFirstInput) return;
+ setTimeout(() => {
+ const formEle = formRef.current;
+ if (!formEle) return;
+ const activeTabElement = formEle.querySelector(
+ '[data-test="tabpanel"]:not([hidden])'
+ );
+ if (!activeTabElement) return;
+
+ // Find the first focusable input, which is either:
+ // * An editable Input element.
+ // * A select element, which is not disabled.
+ // * An href element.
+ // * Any element with 'tabindex', but - tabindex is not set to '-1'.
+ const firstFocussableElement = activeTabElement.querySelector([
+ 'button:not([role="tab"])',
+ '[href]',
+ 'input:not(disabled)',
+ 'select:not(disabled)',
+ 'textarea',
+ '[tabindex]:not([tabindex="-1"]):not([data-test="tabpanel"])',
+ ].join(', '));
+
+ if (firstFocussableElement) firstFocussableElement.focus();
+ }, 200);
+ }, [tabValue]);
+
+ useEffect(() => {
+ // Refresh on message changes.
+ return schemaState.subscribe(
+ ['errors', 'message'],
+ (newState, prevState) => {
+ if (_.isUndefined(newState) || _.isUndefined(prevState));
+ setKey(Date.now());
+ },
+ 'states'
+ );
+ }, [key]);
+
+ useEffect(() => {
+ if (!visible) return;
+
if(isOnScreen) {
/* Don't do it when the form is alredy visible */
if(!onScreenTracker.current) {
@@ -93,347 +130,184 @@ export default function FormView({
onScreenTracker.current = false;
}
}, [isOnScreen]);
+
+ listenDepChanges(accessPath, field, visible, schemaState);
- useEffect(()=>{
- /* Calculate the fields which depends on the current field */
- if(!isDataGridForm) {
- schemaRef.current.fields.forEach((field)=>{
- /* Self change is also dep change */
- if(field.depChange || field.deferredDepChange) {
- schemaState?.addDepListener(accessPath.concat(field.id), accessPath.concat(field.id), field.depChange, field.deferredDepChange);
- }
- (evalFunc(null, field.deps) || []).forEach((dep)=>{
- // when dep is a string then prepend the complete accessPath
- let source = accessPath.concat(dep);
-
- // but when dep is an array, then the intention is to provide the exact accesspath
- if(_.isArray(dep)) {
- source = dep;
- }
- if(field.depChange || field.deferredDepChange) {
- schemaState?.addDepListener(source, accessPath.concat(field.id), field.depChange, field.deferredDepChange);
- }
- if(field.depChange || field.deferredDepChange) {
- schemaState?.addDepListener(source, accessPath.concat(field.id), field.depChange, field.deferredDepChange);
- }
- });
- });
- return ()=>{
- /* Cleanup the listeners when unmounting */
- schemaState?.removeDepListener(accessPath);
- };
- }
- }, []);
-
- /* Upon reset, set the tab to first */
- useEffect(()=>{
- if (schemaState?.isReady)
+ // Upon reset, set the tab to first.
+ useEffect(() => {
+ if (!visible || !resetKey) return;
+ if (resetKey) {
setTabValue(0);
- }, [schemaState?.isReady]);
-
- let fullTabs = [];
- let inlineComponents = [];
- let inlineCompGroup = null;
-
- /* Prepare the array of components based on the types */
- for(const field of schemaRef.current.fields) {
- let {
- visible, disabled, readonly, canAdd, canEdit, canDelete, canReorder,
- canAddRow, modeSupported
- } = getFieldMetaData(field, schema, value, viewHelperProps);
-
- if(!modeSupported) continue;
-
- let {group, CustomControl} = field;
-
- if(field.type === 'group') {
- groupLabels[field.id] = field.label;
-
- if(!visible) {
- schemaRef.current.filterGroups.push(field.label);
- }
- continue;
- }
-
- group = groupLabels[group] || group || defaultTab;
-
- if(!tabs[group]) tabs[group] = [];
-
- // Lets choose the path based on type.
- if(field.type === 'nested-tab') {
- /* Pass on the top schema */
- if(isNested) {
- field.schema.top = schemaRef.current.top;
- } else {
- field.schema.top = schema;
- }
- tabs[group].push(
-
- );
- } else if(field.type === 'nested-fieldset') {
- /* Pass on the top schema */
- if(isNested) {
- field.schema.top = schemaRef.current.top;
- } else {
- field.schema.top = schema;
- }
- tabs[group].push(
-
- );
- } else if(field.type === 'collection') {
- /* If its a collection, let data grid view handle it */
- /* Pass on the top schema */
- if(isNested) {
- field.schema.top = schemaRef.current.top;
- } else {
- field.schema.top = schemaRef.current;
- }
-
- if(!_.isUndefined(field.fixedRows)) {
- canAdd = false;
- canDelete = false;
- }
-
- const ctrlProps = {
- key: field.id, ...field,
- value: value[field.id] || [], viewHelperProps: viewHelperProps,
- schema: field.schema, accessPath: accessPath.concat(field.id), dataDispatch: dataDispatch,
- containerClassName: 'FormView-controlRow',
- canAdd: canAdd, canReorder: canReorder,
- canEdit: canEdit, canDelete: canDelete,
- visible: visible, canAddRow: canAddRow, onDelete: field.onDelete, canSearch: field.canSearch,
- expandEditOnAdd: field.expandEditOnAdd,
- fixedRows: (viewHelperProps.mode == 'create' ? field.fixedRows : undefined),
- addOnTop: Boolean(field.addOnTop)
- };
-
- if(CustomControl) {
- tabs[group].push();
- } else {
- tabs[group].push();
- }
- } else {
- /* Its a form control */
- const hasError = _.isEqual(
- accessPath.concat(field.id), schemaState.errors?.name
- );
- /* When there is a change, the dependent values can change
- * lets pass the new changes to dependent and get the new values
- * from there as well.
- */
- if(field.isFullTab) {
- tabsClassname[group] ='FormView-fullSpace';
- fullTabs.push(group);
- }
-
- const id = field.id || `control${tabs[group].length}`;
- if(visible && !disabled && !firstEleID.current) {
- firstEleID.current = field.id;
- }
-
- let currentControl = {
- if(firstEleRef && firstEleID.current === field.id) {
- if(typeof firstEleRef == 'function') {
- firstEleRef(ele);
- } else {
- firstEleRef.current = ele;
- }
- }
- }}
- state={value}
- key={id}
- viewHelperProps={viewHelperProps}
- name={id}
- value={value[id]}
- {...field}
- id={id}
- readonly={readonly}
- disabled={disabled}
- visible={visible}
- onChange={(changeValue)=>{
- /* Get the changes on dependent fields as well */
- dataDispatch({
- type: SCHEMA_STATE_ACTIONS.SET_VALUE,
- path: accessPath.concat(id),
- value: changeValue,
- });
- }}
- hasError={hasError}
- className='FormView-controlRow'
- noLabel={field.isFullTab}
- memoDeps={[
- value[id],
- readonly,
- disabled,
- visible,
- hasError,
- 'FormView-controlRow',
- ...(evalFunc(null, field.deps) || []).map((dep)=>value[dep]),
- ]}
- />;
-
- if(field.isFullTab && field.helpMessage) {
- currentControl = (
-
- {currentControl}
- );
- }
-
- if(field.inlineNext) {
- inlineComponents.push(React.cloneElement(currentControl, {
- withContainer: false, controlGridBasis: 3
- }));
- inlineCompGroup = group;
- } else if(inlineComponents?.length > 0) {
- inlineComponents.push(React.cloneElement(currentControl, {
- withContainer: false, controlGridBasis: 3
- }));
- tabs[group].push(
-
- {inlineComponents}
-
- );
- inlineComponents = [];
- inlineCompGroup = null;
- } else {
- tabs[group].push(currentControl);
- }
}
- }
+ }, [resetKey]);
- if(inlineComponents?.length > 0) {
- tabs[inlineCompGroup].push(
-
- {inlineComponents}
-
- );
- }
-
- let finalTabs = _.pickBy(
- tabs, (v, tabName) => schemaRef.current.filterGroups.indexOf(tabName) <= -1
+ const finalGroups = useMemo(
+ () => createFieldControls({
+ schema, schemaState, accessPath, viewHelperProps, dataDispatch
+ }),
+ [schema, schemaState, accessPath, viewHelperProps, dataDispatch]
);
- // Add the SQL tab (if required)
- let sqlTabActive = false;
- let sqlTabName = gettext('SQL');
-
- if(hasSQLTab) {
- sqlTabActive = (Object.keys(finalTabs).length === tabValue);
- // Re-render and fetch the SQL tab when it is active.
- finalTabs[sqlTabName] = [
- ,
- ];
- tabsClassname[sqlTabName] = 'FormView-fullSpace';
- fullTabs.push(sqlTabName);
- }
-
- useEffect(() => {
- onTabChange?.(tabValue, Object.keys(tabs)[tabValue], sqlTabActive);
- }, [tabValue]);
-
- const isSingleCollection = useMemo(()=>{
- // we can check if it is a single-collection.
- // in that case, we could force virtualization of the collection.
- if(isTabView) return false;
-
- const visibleEle = Object.values(finalTabs)[0].filter(
- (c) => c.props.visible
- );
- return visibleEle.length == 1 && visibleEle[0]?.type == DataGridView;
- }, [isTabView, finalTabs]);
-
// Check whether form is kept hidden by visible prop.
- if(!_.isUndefined(visible) && !visible) {
+ if(!finalGroups || (!_.isUndefined(visible) && !visible)) {
return <>>;
}
- if(isTabView) {
+ const isSingleCollection = () => {
+ const DataGridView = View('DataGridView');
return (
-
-
- { setTabValue(selTabValue); }}
- variant="scrollable"
- scrollButtons="auto"
- action={(ref)=>ref?.updateIndicator()}
- >
- {Object.keys(finalTabs).map((tabName)=>{
- return ;
- })}
-
-
- {Object.keys(finalTabs).map((tabName, i)=>{
- let contentClassName = [(
- schemaState.errors?.message ? 'FormView-errorMargin': null
- )];
+ finalGroups.length == 1 &&
+ finalGroups[0].controls.length == 1 &&
+ finalGroups[0].controls[0].control == DataGridView
+ );
+ };
- if(fullTabs.indexOf(tabName) == -1) {
- contentClassName.push('FormView-nestedControl');
- } else {
- contentClassName.push('FormView-fullControl');
+ if(isTabView) {
+ return (
+ <>
+
+
+ { setTabValue(nextTabIndex); }}
+ variant="scrollable"
+ scrollButtons="auto"
+ action={(ref) => ref?.updateIndicator()}
+ >{
+ finalGroups.map((tabGroup, idx) =>
+
+ )
+ }{hasSQLTab &&
+
+ }
+
+ {
+ finalGroups.map((group, idx) => {
+ let contentClassName = [
+ group.isFullTab ?
+ 'FormView-fullControl' : 'FormView-nestedControl',
+ schemaState.errors?.message ? 'FormView-errorMargin' : null
+ ];
+
+ let id = group.id.replace(' ', '');
+
+ return (
+
+ {
+ group.isFullTab && group.field?.helpMessage ?
+ :
+ <>>
+ }
+ {
+ group.controls.map(
+ (item, idx) =>
+ )
+ }
+
+ );
+ })
}
-
- return (
-
- {finalTabs[tabName]}
-
- );
- })}
-
+ {
+ hasSQLTab &&
+
+
+
+ }
+
+ { showError && }
+ >
);
} else {
let contentClassName = [
- isSingleCollection ? 'FormView-singleCollectionPanelContent' :
+ isSingleCollection() ? 'FormView-singleCollectionPanelContent' :
'FormView-nonTabPanelContent',
(schemaState.errors?.message ? 'FormView-errorMargin' : null)
];
return (
-
-
- {Object.keys(finalTabs).map((tabName) => {
- return (
-
- {finalTabs[tabName]}
-
- );
- })}
-
-
+ <>
+
+
+ {
+ finalGroups.map((group, idx) =>
+ {
+ group.controls.map(
+ (item, idx) =>
+ )
+ }
+ )
+ }
+ {
+ hasSQLTab &&
+ }
+
+
+ { showError && }
+ >
);
}
}
+
FormView.propTypes = {
- value: PropTypes.any,
- schema: CustomPropTypes.schemaUI.isRequired,
+ schema: CustomPropTypes.schemaUI,
viewHelperProps: PropTypes.object,
isNested: PropTypes.bool,
- isDataGridForm: PropTypes.bool,
isTabView: PropTypes.bool,
- visible: PropTypes.oneOfType([
- PropTypes.bool, PropTypes.func,
- ]),
accessPath: PropTypes.array.isRequired,
dataDispatch: PropTypes.func,
hasSQLTab: PropTypes.bool,
getSQLValue: PropTypes.func,
- onTabChange: PropTypes.func,
- firstEleRef: CustomPropTypes.ref,
className: CustomPropTypes.className,
+ field: PropTypes.object,
+ showError: PropTypes.bool,
};
+
+registerView(FormView, 'FormView');
diff --git a/web/pgadmin/static/js/SchemaView/InlineView.jsx b/web/pgadmin/static/js/SchemaView/InlineView.jsx
new file mode 100644
index 00000000000..c692729b8ce
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/InlineView.jsx
@@ -0,0 +1,56 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import React, { useContext } from 'react';
+import { Grid } from '@mui/material';
+import _ from 'lodash';
+import PropTypes from 'prop-types';
+
+import { SchemaStateContext } from './SchemaState';
+import { useFieldOptions } from './hooks';
+import { registerView } from './registry';
+import { listenDepChanges } from './utils';
+
+
+// The first component of schema view form.
+export default function InlineView({
+ accessPath, field, children, viewHelperProps
+}) {
+ const { mode } = (viewHelperProps || {});
+ const isPropertyMode = mode === 'properties';
+ const schemaState = useContext(SchemaStateContext);
+ const { visible } =
+ accessPath ? useFieldOptions(accessPath, schemaState) : { visible: true };
+
+ if (!accessPath || isPropertyMode)
+ listenDepChanges(accessPath, field, visible, schemaState);
+
+ // Check whether form is kept hidden by visible prop.
+ // We don't support inline-view in 'property' mode
+ if((!_.isUndefined(visible) && !visible) || isPropertyMode) {
+ return <>>;
+ }
+ return (
+
+ {children}
+
+ );
+}
+
+
+InlineView.propTypes = {
+ accessPath: PropTypes.array,
+ field: PropTypes.object,
+ children : PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.node),
+ PropTypes.node
+ ])
+};
+
+registerView(InlineView, 'InlineView');
diff --git a/web/pgadmin/static/js/SchemaView/MappedControl.jsx b/web/pgadmin/static/js/SchemaView/MappedControl.jsx
index fd032406e23..1bdd9880076 100644
--- a/web/pgadmin/static/js/SchemaView/MappedControl.jsx
+++ b/web/pgadmin/static/js/SchemaView/MappedControl.jsx
@@ -7,22 +7,38 @@
//
//////////////////////////////////////////////////////////////
-import React, { useCallback, useMemo } from 'react';
+import React, { useCallback, useContext, useMemo, useState } from 'react';
import _ from 'lodash';
+import PropTypes from 'prop-types';
+
import {
- FormInputText, FormInputSelect, FormInputSwitch, FormInputCheckbox, FormInputColor,
- FormInputFileSelect, FormInputToggle, InputSwitch, FormInputSQL, InputSQL, FormNote, FormInputDateTimePicker, PlainString,
- InputSelect, InputText, InputCheckbox, InputDateTimePicker, InputFileSelect, FormInputKeyboardShortcut, FormInputQueryThreshold, FormInputSelectThemes, InputRadio, FormButton, InputTree
-} from '../components/FormComponents';
-import Privilege from '../components/Privilege';
+ FormButton, FormInputCheckbox, FormInputColor, FormInputDateTimePicker,
+ FormInputFileSelect, FormInputKeyboardShortcut, FormInputQueryThreshold,
+ FormInputSQL, FormInputSelect, FormInputSelectThemes, FormInputSwitch,
+ FormInputText, FormInputToggle, FormNote, InputCheckbox, InputDateTimePicker,
+ InputFileSelect, InputRadio, InputSQL,InputSelect, InputSwitch, InputText,
+ InputTree, PlainString,
+} from 'sources/components/FormComponents';
+import { SelectRefresh } from 'sources/components/SelectRefresh';
+import Privilege from 'sources/components/Privilege';
+import { useIsMounted } from 'sources/custom_hooks';
+import CustomPropTypes from 'sources/custom_prop_types';
import { evalFunc } from 'sources/utils';
-import PropTypes from 'prop-types';
-import CustomPropTypes from '../custom_prop_types';
-import { SelectRefresh } from '../components/SelectRefresh';
+
+import { SchemaStateContext } from './SchemaState';
+import { isValueEqual } from './common';
+import {
+ useFieldOptions, useFieldValue, useFieldError
+} from './hooks';
+import { listenDepChanges } from './utils';
+
/* Control mapping for form view */
-function MappedFormControlBase({ type, value, id, onChange, className, visible, inputRef, noLabel, onClick, withContainer, controlGridBasis, ...props }) {
- const name = id;
+function MappedFormControlBase({
+ id, type, state, onChange, className, inputRef, visible,
+ withContainer, controlGridBasis, noLabel, ...props
+}) {
+ let name = id;
const onTextChange = useCallback((e) => {
let val = e;
if(e?.target) {
@@ -30,6 +46,7 @@ function MappedFormControlBase({ type, value, id, onChange, className, visible,
}
onChange?.(val);
}, []);
+ const value = state;
const onSqlChange = useCallback((changedValue) => {
onChange?.(changedValue);
@@ -43,58 +60,114 @@ function MappedFormControlBase({ type, value, id, onChange, className, visible,
return <>>;
}
+ if (name && _.isNumber(name)) {
+ name = String('name');
+ }
+
/* The mapping uses Form* components as it comes with labels */
switch (type) {
case 'int':
- return ;
+ return ;
case 'numeric':
- return ;
+ return ;
case 'tel':
- return ;
+ return ;
case 'text':
- return ;
+ return ;
case 'multiline':
- return ;
+ return ;
case 'password':
- return ;
+ return ;
case 'select':
- return ;
+ return ;
case 'select-refresh':
- return ;
+ return ;
case 'switch':
- return onTextChange(e.target.checked, e.target.name)} className={className}
+ return onTextChange(e.target.checked, e.target.name)}
withContainer={withContainer} controlGridBasis={controlGridBasis}
- {...props} />;
+ {...props}
+ />;
case 'checkbox':
- return onTextChange(e.target.checked, e.target.name)} className={className}
- {...props} />;
+ return onTextChange(e.target.checked, e.target.name)}
+ {...props}
+ />;
case 'toggle':
- return ;
+ return ;
case 'color':
- return ;
+ return ;
case 'file':
- return ;
+ return ;
case 'sql':
- return ;
+ return ;
case 'note':
return ;
case 'datetimepicker':
- return ;
+ return ;
case 'keyboardShortcut':
- return ;
+ return ;
case 'threshold':
- return ;
+ return ;
case 'theme':
- return ;
+ return ;
case 'button':
- return ;
+ return ;
case 'tree':
- return ;
+ return ;
default:
return ;
}
@@ -105,7 +178,7 @@ MappedFormControlBase.propTypes = {
PropTypes.string, PropTypes.func,
]).isRequired,
value: PropTypes.any,
- id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
+ id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
onChange: PropTypes.func,
className: PropTypes.oneOfType([
PropTypes.string, PropTypes.object,
@@ -116,12 +189,17 @@ MappedFormControlBase.propTypes = {
onClick: PropTypes.func,
withContainer: PropTypes.bool,
controlGridBasis: PropTypes.number,
- treeData: PropTypes.oneOfType([PropTypes.array, PropTypes.instanceOf(Promise), PropTypes.func]),
+ treeData: PropTypes.oneOfType([
+ PropTypes.array, PropTypes.instanceOf(Promise), PropTypes.func]
+ ),
};
/* Control mapping for grid cell view */
-function MappedCellControlBase({ cell, value, id, optionsLoaded, onCellChange, visible, reRenderRow, inputRef, ...props }) {
- const name = id;
+function MappedCellControlBase({
+ cell, value, id, optionsLoaded, onCellChange, visible, reRenderRow, inputRef,
+ ...props
+}) {
+ let name = id;
const onTextChange = useCallback((e) => {
let val = e;
if (e?.target) {
@@ -156,6 +234,10 @@ function MappedCellControlBase({ cell, value, id, optionsLoaded, onCellChange, v
return <>>;
}
+ if (name && _.isNumber(name)) {
+ name = String('name');
+ }
+
/* The mapping does not need Form* components as labels are not needed for grid cells */
switch(cell) {
case 'int':
@@ -211,8 +293,9 @@ const ALLOWED_PROPS_FIELD_COMMON = [
'mode', 'value', 'readonly', 'disabled', 'hasError', 'id',
'label', 'options', 'optionsLoaded', 'controlProps', 'schema', 'inputRef',
'visible', 'autoFocus', 'helpMessage', 'className', 'optionsReloadBasis',
- 'orientation', 'isvalidate', 'fields', 'radioType', 'hideBrowseButton', 'btnName', 'hidden',
- 'withContainer', 'controlGridBasis', 'hasCheckbox', 'treeData', 'labelTooltip'
+ 'orientation', 'isvalidate', 'fields', 'radioType', 'hideBrowseButton',
+ 'btnName', 'hidden', 'withContainer', 'controlGridBasis', 'hasCheckbox',
+ 'treeData', 'labelTooltip'
];
const ALLOWED_PROPS_FIELD_FORM = [
@@ -220,50 +303,128 @@ const ALLOWED_PROPS_FIELD_FORM = [
];
const ALLOWED_PROPS_FIELD_CELL = [
- 'cell', 'onCellChange', 'row', 'reRenderRow', 'validate', 'disabled', 'readonly', 'radioType', 'hideBrowseButton', 'hidden'
+ 'cell', 'onCellChange', 'reRenderRow', 'validate', 'disabled',
+ 'readonly', 'radioType', 'hideBrowseButton', 'hidden', 'row',
];
+export const StaticMappedFormControl = ({accessPath, field, ...props}) => {
+ const schemaState = useContext(SchemaStateContext);
+ const state = schemaState.value(accessPath);
+ const newProps = {
+ ...props,
+ state,
+ noLabel: field.isFullTab,
+ ...field,
+ onChange: () => { /* Do nothing */ },
+ };
+ const visible = evalFunc(null, field.visible, state);
+
+ if (visible === false) return <>>;
+
+ return useMemo(
+ () => , []
+ );
+};
+
-export const MappedFormControl = ({memoDeps, ...props}) => {
- let newProps = { ...props };
- let typeProps = evalFunc(null, newProps.type, newProps.state);
- if (typeof (typeProps) === 'object') {
+export const MappedFormControl = ({
+ accessPath, dataDispatch, field, onChange, ...props
+}) => {
+ const checkIsMounted = useIsMounted();
+ const [key, setKey] = useState(0);
+ const schemaState = useContext(SchemaStateContext);
+ const state = schemaState.data;
+ const avoidRenderingWhenNotMounted = (newKey) => {
+ if (checkIsMounted()) {
+ setKey(newKey);
+ }
+ };
+ const value = useFieldValue(
+ accessPath, schemaState, key, avoidRenderingWhenNotMounted
+ );
+ const options = useFieldOptions(
+ accessPath, schemaState, key, avoidRenderingWhenNotMounted
+ );
+ const { hasError } = useFieldError(
+ accessPath, schemaState, key, avoidRenderingWhenNotMounted
+ );
+
+ const origOnChange = onChange;
+
+ onChange = (changedValue) => {
+ if (!origOnChange || !checkIsMounted()) return;
+
+ // We don't want the 'onChange' to be executed for the same value to avoid
+ // rerendering of the control, top component may still be rerendered on the
+ // change of the value.
+ const currValue = schemaState.value(accessPath);
+
+ if (!isValueEqual(changedValue, currValue)) origOnChange(changedValue);
+ };
+
+ listenDepChanges(accessPath, field, options.visible, schemaState);
+
+ let newProps = {
+ ...props,
+ state: value,
+ noLabel: field.isFullTab,
+ ...field,
+ onChange: onChange,
+ dataDispatch: dataDispatch,
+ ...options,
+ hasError,
+ };
+
+ if (typeof (field.type) === 'function') {
+ const typeProps = evalFunc(null, field.type, state);
newProps = {
...newProps,
...typeProps,
};
- } else {
- newProps.type = typeProps;
}
let origOnClick = newProps.onClick;
newProps.onClick = ()=>{
origOnClick?.();
- /* Consider on click as change for button.
- Just increase state val by 1 to inform the deps and self depChange */
- newProps.onChange?.((newProps.state[props.id]||0)+1);
};
+ // FIXME:: Get this list from the option registry.
+ const memDeps = ['disabled', 'visible', 'readonly'].map(
+ option => options[option]
+ );
+ memDeps.push(value);
+ memDeps.push(hasError);
+ memDeps.push(key);
+ memDeps.push(JSON.stringify(accessPath));
- /* Filter out garbage props if any using ALLOWED_PROPS_FIELD */
- return useMemo(()=>, memoDeps??[]);
+ // Filter out garbage props if any using ALLOWED_PROPS_FIELD.
+ return useMemo(
+ () => , [...memDeps]
+ );
};
MappedFormControl.propTypes = {
- id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired
+ id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
};
export const MappedCellControl = (props) => {
- let newProps = { ...props };
- let cellProps = evalFunc(null, newProps.cell, newProps.row.original);
- if (typeof (cellProps) === 'object') {
- newProps = {
- ...newProps,
- ...cellProps,
- };
- } else {
- newProps.cell = cellProps;
- }
+ const newProps = _.pick(
+ props, _.union(ALLOWED_PROPS_FIELD_COMMON, ALLOWED_PROPS_FIELD_CELL)
+ );;
- /* Filter out garbage props if any using ALLOWED_PROPS_FIELD */
- return ;
+ // Filter out garbage props if any using ALLOWED_PROPS_FIELD.
+ return ;
};
diff --git a/web/pgadmin/static/js/SchemaView/ResetButton.jsx b/web/pgadmin/static/js/SchemaView/ResetButton.jsx
new file mode 100644
index 00000000000..3c275eb5718
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/ResetButton.jsx
@@ -0,0 +1,42 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import React, { useContext, useEffect, useState } from 'react';
+
+import { DefaultButton } from 'sources/components/Buttons';
+import { SchemaStateContext } from './SchemaState';
+
+
+export function ResetButton({label, Icon, onClick}) {
+ const [key, setKey] = useState(0);
+ const schemaState = useContext(SchemaStateContext);
+ const checkDisabled = (state) => (state.isSaving || !state.isDirty);
+ const currState = schemaState.state();
+ const isDisabled = checkDisabled(currState);
+
+ useEffect(() => {
+ if (!schemaState) return;
+
+ const refreshOnDisableStateChanged = (newState) => {
+ if (isDisabled !== checkDisabled(newState)) setKey(Date.now());
+ };
+
+ return schemaState.subscribe([], refreshOnDisableStateChanged, 'states');
+ }, [key]);
+
+ return (
+
+ { label }
+
+ );
+}
diff --git a/web/pgadmin/static/js/SchemaView/SQLTab.jsx b/web/pgadmin/static/js/SchemaView/SQLTab.jsx
new file mode 100644
index 00000000000..22d29cea14d
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/SQLTab.jsx
@@ -0,0 +1,45 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import React, { useEffect, useState } from 'react';
+import PropTypes from 'prop-types';
+
+import { InputSQL } from 'sources/components/FormComponents';
+
+
+// Optional SQL tab.
+export function SQLTab({active, getSQLValue}) {
+ const [sql, setSql] = useState('Loading...');
+ useEffect(() => {
+ let unmounted = false;
+ if(active) {
+ setSql('Loading...');
+ getSQLValue().then((value) => {
+ if(!unmounted) {
+ setSql(value);
+ }
+ });
+ }
+ return () => {unmounted=true;};
+ }, [active]);
+
+ return ;
+}
+
+SQLTab.propTypes = {
+ active: PropTypes.bool,
+ getSQLValue: PropTypes.func.isRequired,
+};
diff --git a/web/pgadmin/static/js/SchemaView/SaveButton.jsx b/web/pgadmin/static/js/SchemaView/SaveButton.jsx
new file mode 100644
index 00000000000..a78c4395bda
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/SaveButton.jsx
@@ -0,0 +1,50 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import React, { useContext, useEffect, useState } from 'react';
+
+import { PrimaryButton } from 'sources/components/Buttons';
+import { SchemaStateContext } from './SchemaState';
+
+
+export function SaveButton({
+ label, Icon, checkDirtyOnEnableSave, onClick, mode,
+}) {
+ const [key, setKey] = useState(0);
+ const schemaState = useContext(SchemaStateContext);
+ const checkDisabled = (state) => {
+ const {isDirty, isSaving, errors} = state;
+ return (
+ isSaving ||
+ !(mode === 'edit' || checkDirtyOnEnableSave ? isDirty : true) ||
+ Boolean(errors.name)
+ );
+ };
+ const currState = schemaState.state();
+ const isDisabled = checkDisabled(currState);
+
+ useEffect(() => {
+ if (!schemaState) return;
+
+ const refreshOnDisableStateChanged = (newState) => {
+ if (isDisabled !== checkDisabled(newState)) setKey(Date.now());
+ };
+
+ return schemaState.subscribe([], refreshOnDisableStateChanged, 'states');
+ }, [key]);
+
+ return (
+
+ {label}
+
+ );
+}
diff --git a/web/pgadmin/static/js/SchemaView/SchemaDialogView.jsx b/web/pgadmin/static/js/SchemaView/SchemaDialogView.jsx
index e7983b37606..7a9da51ba9d 100644
--- a/web/pgadmin/static/js/SchemaView/SchemaDialogView.jsx
+++ b/web/pgadmin/static/js/SchemaView/SchemaDialogView.jsx
@@ -7,9 +7,7 @@
//
//////////////////////////////////////////////////////////////
-import React, {
- useCallback, useEffect, useRef, useState,
-} from 'react';
+import React, { useEffect, useMemo } from 'react';
import CloseIcon from '@mui/icons-material/Close';
import DoneIcon from '@mui/icons-material/Done';
@@ -25,23 +23,21 @@ import PropTypes from 'prop-types';
import { parseApiError } from 'sources/api_instance';
import { usePgAdmin } from 'sources/BrowserComponent';
-import Loader from 'sources/components/Loader';
import { useIsMounted } from 'sources/custom_hooks';
import {
- PrimaryButton, DefaultButton, PgIconButton
+ DefaultButton, PgIconButton
} from 'sources/components/Buttons';
-import {
- FormFooterMessage, MESSAGE_TYPE
-} from 'sources/components/FormComponents';
import CustomPropTypes from 'sources/custom_prop_types';
import gettext from 'sources/gettext';
+import { FormLoader } from './FormLoader';
import FormView from './FormView';
+import { ResetButton } from './ResetButton';
+import { SaveButton } from './SaveButton';
+import { SchemaStateContext } from './SchemaState';
import { StyledBox } from './StyledComponents';
-import { useSchemaState } from './useSchemaState';
-import {
- getForQueryParams, SchemaStateContext
-} from './common';
+import { useSchemaState } from './hooks';
+import { getForQueryParams } from './common';
/* If its the dialog */
@@ -50,30 +46,22 @@ export default function SchemaDialogView({
isTabView=true, checkDirtyOnEnableSave=false, ...props
}) {
// View helper properties
- const { mode, keepCid } = viewHelperProps;
const onDataChange = props.onDataChange;
- // Message to the user on long running operations.
- const [loaderText, setLoaderText] = useState('');
-
// Schema data state manager
- const {schemaState, dataDispatch, sessData, reset} = useSchemaState({
+ const {schemaState, dataDispatch, reset} = useSchemaState({
schema: schema, getInitData: getInitData, immutableData: {},
- mode: mode, keepCid: keepCid, onDataChange: onDataChange,
- });
-
- const [{isNew, isDirty, isReady, errors}, updateSchemaState] = useState({
- isNew: true, isDirty: false, isReady: false, errors: {}
+ viewHelperProps: viewHelperProps, onDataChange: onDataChange,
+ loadingText,
});
// Is saving operation in progress?
- const [saving, setSaving] = useState(false);
+ const setSaving = (val) => schemaState.isSaving = val;
+ const setLoaderText = (val) => schemaState.setMessage(val);
// First element to be set by the FormView to set the focus after loading
// the data.
- const firstEleRef = useRef();
const checkIsMounted = useIsMounted();
- const [data, setData] = useState({});
// Notifier object.
const pgAdmin = usePgAdmin();
@@ -84,7 +72,6 @@ export default function SchemaDialogView({
* Docker on load focusses itself, so our focus should execute later.
*/
let focusTimeout = setTimeout(()=>{
- firstEleRef.current?.focus();
}, 250);
// Clear the focus timeout if unmounted.
@@ -93,24 +80,13 @@ export default function SchemaDialogView({
};
}, []);
- useEffect(() => {
- setLoaderText(schemaState.message);
- }, [schemaState.message]);
-
- useEffect(() => {
- setData(sessData);
- updateSchemaState(schemaState);
- }, [sessData.__changeId]);
-
useEffect(()=>{
if (!props.resetKey) return;
reset();
}, [props.resetKey]);
-
const onResetClick = () => {
const resetIt = () => {
- firstEleRef.current?.focus();
reset();
return true;
};
@@ -128,7 +104,7 @@ export default function SchemaDialogView({
};
const save = (changeData) => {
- props.onSave(isNew, changeData)
+ props.onSave(schemaState.isNew, changeData)
.then(()=>{
if(schema.informText) {
Notifier.alert(
@@ -151,20 +127,23 @@ export default function SchemaDialogView({
const onSaveClick = () => {
// Do nothing when there is no change or there is an error
- if (!schemaState.changes || errors.name) return;
+ if (
+ !schemaState._changes || Object.keys(schemaState._changes) === 0 ||
+ schemaState.errors.name
+ ) return;
setSaving(true);
setLoaderText('Saving...');
if (!schema.warningText) {
- save(schemaState.Changes(true));
+ save(schemaState.changes(true));
return;
}
Notifier.confirm(
gettext('Warning'),
schema.warningText,
- ()=> { save(schemaState.Changes(true)); },
+ () => { save(schemaState.changes(true)); },
() => {
setSaving(false);
setLoaderText('');
@@ -173,29 +152,22 @@ export default function SchemaDialogView({
);
};
- const onErrClose = useCallback(() => {
- const err = { ...errors, message: '' };
- // Unset the error message, but not the name.
- schemaState.setError(err);
- updateSchemaState({isNew, isDirty, isReady, errors: err});
- });
-
const getSQLValue = () => {
// Called when SQL tab is active.
- if(!isDirty) {
+ if(!schemaState.isDirty) {
return Promise.resolve('-- ' + gettext('No updates.'));
}
- if(errors.name) {
+ if(schemaState.errors.name) {
return Promise.resolve('-- ' + gettext('Definition incomplete.'));
}
- const changeData = schemaState.changes;
+ const changeData = schemaState._changes;
/*
* Call the passed incoming getSQLValue func to get the SQL
* return of getSQLValue should be a promise.
*/
- return props.getSQLValue(isNew, getForQueryParams(changeData));
+ return props.getSQLValue(schemaState.isNew, getForQueryParams(changeData));
};
const getButtonIcon = () => {
@@ -207,29 +179,24 @@ export default function SchemaDialogView({
return ;
};
- const disableSaveBtn = saving ||
- !isReady ||
- !(mode === 'edit' || checkDirtyOnEnableSave ? isDirty : true) ||
- Boolean(errors.name && errors.name !== 'apierror');
-
let ButtonIcon = getButtonIcon();
/* I am Groot */
- return (
+ return useMemo(() =>
-
-
+
-
+ isTabView={isTabView}
+ className={props.formClassName}
+ showError={true} resetKey={props.resetKey}
+ focusOnFirstInput={true}
+ />
{showFooter &&
@@ -237,13 +204,13 @@ export default function SchemaDialogView({
(!props.disableSqlHelp || !props.disableDialogHelp) &&
props.onHelp(true, isNew)}
+ onClick={()=>props.onHelp(true, schemaState.isNew)}
icon={} disabled={props.disableSqlHelp}
className='Dialog-buttonMargin'
title={ gettext('SQL help for this object type.') }
/>
props.onHelp(false, isNew)}
+ onClick={()=>props.onHelp(false, schemaState.isNew)}
icon={} disabled={props.disableDialogHelp}
title={ gettext('Help for this dialog.') }
/>
@@ -254,23 +221,21 @@ export default function SchemaDialogView({
startIcon={} className='Dialog-buttonMargin'>
{ gettext('Close') }
- }
- disabled={(!isDirty) || saving }
- className='Dialog-buttonMargin'>
- { gettext('Reset') }
-
- {
- props.customSaveBtnName || gettext('Save')
- }
-
+ }
+ label={ gettext('Reset') }/>
+
}
-
+ , [schema._id, viewHelperProps.mode]
);
}
diff --git a/web/pgadmin/static/js/SchemaView/SchemaPropertiesView.jsx b/web/pgadmin/static/js/SchemaView/SchemaPropertiesView.jsx
index 63ceb912320..23614c3753e 100644
--- a/web/pgadmin/static/js/SchemaView/SchemaPropertiesView.jsx
+++ b/web/pgadmin/static/js/SchemaView/SchemaPropertiesView.jsx
@@ -7,7 +7,7 @@
//
//////////////////////////////////////////////////////////////
-import React, { useEffect, useState } from 'react';
+import React, { useEffect, useMemo } from 'react';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import InfoIcon from '@mui/icons-material/InfoRounded';
@@ -16,179 +16,95 @@ import Box from '@mui/material/Box';
import Accordion from '@mui/material/Accordion';
import AccordionSummary from '@mui/material/AccordionSummary';
import AccordionDetails from '@mui/material/AccordionDetails';
-import _ from 'lodash';
import PropTypes from 'prop-types';
import { usePgAdmin } from 'sources/BrowserComponent';
import gettext from 'sources/gettext';
-import Loader from 'sources/components/Loader';
import { PgIconButton, PgButtonGroup } from 'sources/components/Buttons';
import CustomPropTypes from 'sources/custom_prop_types';
-import DataGridView from './DataGridView';
-import FieldSetView from './FieldSetView';
-import { MappedFormControl } from './MappedControl';
-import { useSchemaState } from './useSchemaState';
-import { getFieldMetaData } from './common';
-
+import { FieldControl } from './FieldControl';
+import { FormLoader } from './FormLoader';
+import { SchemaStateContext } from './SchemaState';
import { StyledBox } from './StyledComponents';
+import { useSchemaState } from './hooks';
+import { createFieldControls } from './utils';
/* If its the properties tab */
export default function SchemaPropertiesView({
getInitData, viewHelperProps, schema={}, updatedData, ...props
}) {
- let defaultTab = 'General';
- let tabs = {};
- let tabsClassname = {};
- let groupLabels = {};
- const [loaderText, setLoaderText] = useState('');
-
const pgAdmin = usePgAdmin();
const Notifier = pgAdmin.Browser.notifier;
- const { mode, keepCid } = viewHelperProps;
// Schema data state manager
- const {schemaState, sessData} = useSchemaState({
+ const {schemaState} = useSchemaState({
schema: schema, getInitData: getInitData, immutableData: updatedData,
- mode: mode, keepCid: keepCid, onDataChange: null,
+ viewHelperProps: viewHelperProps, onDataChange: null,
});
- const [data, setData] = useState({});
useEffect(() => {
if (schemaState.errors?.response)
Notifier.pgRespErrorNotify(schemaState.errors.response);
}, [schemaState.errors?.name]);
- useEffect(() => {
- setData(sessData);
- }, [sessData.__changeId]);
-
- useEffect(() => {
- setLoaderText(schemaState.message);
- }, [schemaState.message]);
-
- /* A simple loop to get all the controls for the fields */
- schema.fields.forEach((field) => {
- let {group} = field;
- const {
- visible, disabled, readonly, modeSupported
- } = getFieldMetaData(field, schema, data, viewHelperProps);
- group = group || defaultTab;
-
- if(field.isFullTab) {
- tabsClassname[group] = 'Properties-noPadding';
- }
-
- if(!modeSupported) return;
-
- group = groupLabels[group] || group || defaultTab;
- if (field.helpMessageMode?.indexOf(viewHelperProps.mode) == -1)
- field.helpMessage = '';
-
- if(!tabs[group]) tabs[group] = [];
-
- if(field && field.type === 'nested-fieldset') {
- tabs[group].push(
-
- );
- } else if(field.type === 'collection') {
- tabs[group].push(
-
- );
- } else if(field.type === 'group') {
- groupLabels[field.id] = field.label;
-
- if(!visible) {
- schema.filterGroups.push(field.label);
- }
- } else {
- tabs[group].push(
-
- );
- }
- });
-
- let finalTabs = _.pickBy(
- tabs, (v, tabName) => schema.filterGroups.indexOf(tabName) <= -1
+ const finalTabs = useMemo(
+ () => createFieldControls({
+ schema, schemaState, viewHelperProps, dataDispatch: null, accessPath: []
+ }),
+ [schema._id, schemaState, viewHelperProps]
);
- return (
-
-
-
-
- props.onHelp(true, false)}
- icon={} disabled={props.disableSqlHelp}
- title="SQL help for this object type." />
- }
- title={gettext('Edit object...')} />
-
-
-
-
- {Object.keys(finalTabs).map((tabName)=>{
- let id = tabName.replace(' ', '');
- return (
-
- }
- aria-controls={`${id}-content`}
- id={`${id}-header`}
- >
- {tabName}
-
-
-
- {finalTabs[tabName]}
-
-
-
- );
- })}
+ if (!finalTabs) return <>>;
+
+ return useMemo(
+ () =>
+
+
+
+
+ props.onHelp(true, false)}
+ icon={} disabled={props.disableSqlHelp}
+ title="SQL help for this object type." />
+ }
+ title={gettext('Edit object...')} />
+
+
+
+
+ {finalTabs.map((group)=>{
+ let id = group.id.replace(' ', '');
+ return (
+
+ }
+ aria-controls={`${id}-content`}
+ id={`${id}-header`}
+ >
+ {group.label}
+
+
+
+ {
+ group.controls.map(
+ (item, idx) =>
+ )
+ }
+
+
+
+ );
+ })}
+
-
-
+
+ ,
+ [schema._id]
);
}
diff --git a/web/pgadmin/static/js/SchemaView/SchemaState/SchemaState.js b/web/pgadmin/static/js/SchemaView/SchemaState/SchemaState.js
new file mode 100644
index 00000000000..9082840c76d
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/SchemaState/SchemaState.js
@@ -0,0 +1,330 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import _ from 'lodash';
+
+import { parseApiError } from 'sources/api_instance';
+import gettext from 'sources/gettext';
+
+import { prepareData } from '../common';
+import { DepListener } from '../DepListener';
+import { FIELD_OPTIONS, schemaOptionsEvalulator } from '../options';
+
+import {
+ SCHEMA_STATE_ACTIONS,
+ flatPathGenerator,
+ getSchemaDataDiff,
+ validateSchema,
+} from './common';
+import { createStore } from './store';
+
+
+export const LOADING_STATE = {
+ INIT: 'initialising',
+ LOADING: 'loading',
+ LOADED: 'loaded',
+ ERROR: 'Error'
+};
+
+const PATH_SEPARATOR = '/';
+
+export class SchemaState extends DepListener {
+ constructor(
+ schema, getInitData, immutableData, onDataChange, viewHelperProps,
+ loadingText
+ ) {
+ super();
+
+ ////// Helper variables
+
+ // BaseUISchema instance
+ this.schema = schema;
+ this.viewHelperProps = viewHelperProps;
+ // Current mode of operation ('create', 'edit', 'properties')
+ this.mode = viewHelperProps.mode;
+ // Keep the 'cid' object during diff calculations.
+ this.keepcid = viewHelperProps.keepCid;
+ // Initialization callback
+ this.getInitData = getInitData;
+ // Data change callback
+ this.onDataChange = onDataChange;
+
+ ////// State variables
+
+ // Diff between the current snapshot and initial data.
+ // Internal state for keeping the changes
+ this._changes = {};
+ // Current Loading state
+ this.loadingState = LOADING_STATE.INIT;
+ this.customLoadingText = loadingText;
+
+ ////// Schema instance data
+
+ // Initial data after the ready state
+ this.initData = {};
+
+ // Immutable data
+ this.immutableData = immutableData;
+ // Pre-ready queue
+ this.preReadyQueue = [];
+
+ this.optionStore = createStore({});
+ this.dataStore = createStore({});
+ this.stateStore = createStore({
+ isNew: true, isDirty: false, isReady: false,
+ isSaving: false, errors: {},
+ message: '',
+ });
+
+ // Memoize the path using flatPathGenerator
+ this.__pathGenerator = flatPathGenerator(PATH_SEPARATOR);
+
+ this._id = Date.now();
+ }
+
+ updateOptions() {
+ let options = _.cloneDeep(this.optionStore.getState());
+
+ schemaOptionsEvalulator({
+ schema: this.schema, data: this.data, options: options,
+ viewHelperProps: this.viewHelperProps,
+ });
+
+ this.optionStore.setState(options);
+ }
+
+ setState(state, value) {
+ this.stateStore.set((prev) => _.set(prev, [].concat(state), value));
+ }
+
+ setError(err) {
+ this.setState('errors', err);
+ }
+
+ get errors() {
+ return this.stateStore.get(['errors']);
+ }
+
+ set errors(val) {
+ throw new Error('Property \'errors\' is readonly.', val);
+ }
+
+ get isReady() {
+ return this.stateStore.get(['isReady']);
+ }
+
+ setReady(val) {
+ this.setState('isReady', val);
+ }
+
+ get isSaving() {
+ return this.stateStore.get(['isSaving']);
+ }
+
+ set isSaving(val) {
+ this.setState('isSaving', val);
+ }
+
+ get loadingMessage() {
+ return this.stateStore.get(['message']);
+ }
+
+ setLoadingState(loadingState) {
+ this.loadingState = loadingState;
+ }
+
+ setMessage(msg) {
+ this.setState('message', msg);
+ }
+
+ // Initialise the data, and fetch the data from the backend (if required).
+ // 'force' flag can be used for reloading the data from the backend.
+ initialise(dataDispatch, force) {
+ let state = this;
+
+ // Don't attempt to initialize again (if it's already in progress).
+ if (
+ state.loadingState !== LOADING_STATE.INIT ||
+ (force && state.loadingState === LOADING_STATE.LOADING)
+ ) return;
+
+ state.setLoadingState(LOADING_STATE.LOADING);
+ state.setMessage(state.customLoadingText || gettext('Loading...'));
+
+ /*
+ * Fetch the data using getInitData(..) callback.
+ * `getInitData(..)` must be present in 'edit' mode.
+ */
+ if(state.mode === 'edit' && !state.getInitData) {
+ throw new Error('getInitData must be passed for edit');
+ }
+
+ const initDataPromise = state.getInitData?.() ||
+ Promise.resolve({});
+
+ initDataPromise.then((data) => {
+ data = data || {};
+
+ if(state.mode === 'edit') {
+ // Set the origData to incoming data, useful for comparing.
+ state.initData = prepareData({...data, ...state.immutableData});
+ } else {
+ // In create mode, merge with defaults.
+ state.initData = prepareData({
+ ...state.schema.defaults, ...data, ...state.immutableData
+ }, true);
+ }
+
+ state.schema.initialise(state.initData);
+
+ dataDispatch({
+ type: SCHEMA_STATE_ACTIONS.INIT,
+ payload: state.initData,
+ });
+
+ state.setLoadingState(LOADING_STATE.LOADED);
+ state.setMessage('');
+ state.setReady(true);
+ state.setState('isNew', state.schema.isNew(state.initData));
+ }).catch((err) => {
+ state.setMessage('');
+ state.setError({
+ name: 'apierror',
+ response: err,
+ message: _.escape(parseApiError(err)),
+ });
+ state.setLoadingState(LOADING_STATE.ERROR);
+ state.setReady(true);
+ });
+ }
+
+ validate(sessData) {
+ let state = this,
+ schema = state.schema;
+
+ // If schema does not have the data or does not have any 'onDataChange'
+ // callback, there is no need to validate the current data.
+ if(!state.isReady) return;
+
+ if(
+ !validateSchema(schema, sessData, (path, message) => {
+ message && state.setError({
+ name: state.accessPath(path), message: _.escape(message)
+ });
+ })
+ ) state.setError({});
+
+ state.data = sessData;
+ state._changes = state.changes();
+ state.updateOptions();
+ state.onDataChange && state.onDataChange(state.isDirty, state._changes);
+ }
+
+ changes(includeSkipChange=false) {
+ const state = this;
+ const sessData = state.data;
+ const schema = state.schema;
+
+ // Check if anything changed.
+ let dataDiff = getSchemaDataDiff(
+ schema, state.initData, sessData,
+ state.mode, state.keepCid, false, includeSkipChange
+ );
+
+ const isDirty = Object.keys(dataDiff).length > 0;
+ state.setState('isDirty', isDirty);
+
+
+ // Inform the callbacks about change in the data.
+ if(state.mode !== 'edit') {
+ // Merge the changed data with origData in 'create' mode.
+ dataDiff = _.assign({}, state.initData, dataDiff);
+
+ // Remove internal '__changeId' attribute.
+ delete dataDiff.__changeId;
+
+ // In case of 'non-edit' mode, changes are always there.
+ return dataDiff;
+ }
+
+ if (!isDirty) return {};
+
+ const idAttr = schema.idAttribute;
+ const idVal = state.initData[idAttr];
+
+ // Append 'idAttr' only if it actually exists
+ if (idVal) dataDiff[idAttr] = idVal;
+
+ return dataDiff;
+ }
+
+ get isNew() {
+ return this.stateStore.get(['isNew']);
+ }
+
+ set isNew(val) {
+ throw new Error('Property \'isNew\' is readonly.', val);
+ }
+
+ get isDirty() {
+ return this.stateStore.get(['isDirty']);
+ }
+
+ set isDirty(val) {
+ throw new Error('Property \'isDirty\' is readonly.', val);
+ }
+
+ get data() {
+ return this.dataStore.getState();
+ }
+
+ set data(_data) {
+ this.dataStore.setState(_data);
+ }
+
+ accessPath(path=[], key) {
+ return this.__pathGenerator.cached(
+ _.isUndefined(key) ? path : path.concat(key)
+ );
+ }
+
+ value(path) {
+ if (!path || !path.length) return this.data;
+ return _.get(this.data, path);
+ }
+
+ options(path) {
+ return this.optionStore.get(path.concat(FIELD_OPTIONS));
+ }
+
+ state(_state) {
+ return _state ?
+ this.stateStore.get([].concat(_state)) : this.stateStore.getState();
+ }
+
+ subscribe(path, listener, kind='options') {
+ switch(kind) {
+ case 'options':
+ return this.optionStore.subscribeForPath(
+ path.concat(FIELD_OPTIONS), listener
+ );
+ case 'states':
+ return this.stateStore.subscribeForPath(path, listener);
+ default:
+ return this.dataStore.subscribeForPath(path, listener);
+ }
+ }
+
+ subscribeOption(option, path, listener) {
+ return this.optionStore.subscribeForPath(
+ path.concat(FIELD_OPTIONS, option), listener
+ );
+ }
+
+}
diff --git a/web/pgadmin/static/js/SchemaView/schemaUtils.js b/web/pgadmin/static/js/SchemaView/SchemaState/common.js
similarity index 86%
rename from web/pgadmin/static/js/SchemaView/schemaUtils.js
rename to web/pgadmin/static/js/SchemaView/SchemaState/common.js
index 0fc204f50af..6e8b74f4801 100644
--- a/web/pgadmin/static/js/SchemaView/schemaUtils.js
+++ b/web/pgadmin/static/js/SchemaView/SchemaState/common.js
@@ -11,13 +11,27 @@ import diffArray from 'diff-arrays-of-objects';
import _ from 'lodash';
import gettext from 'sources/gettext';
+import { memoizeFn } from 'sources/utils';
import {
minMaxValidator, numberValidator, integerValidator, emptyValidator,
checkUniqueCol, isEmptyString
} from 'sources/validators';
-import BaseUISchema from './base_schema.ui';
-import { isModeSupportedByField, isObjectEqual, isValueEqual } from './common';
+import BaseUISchema from '../base_schema.ui';
+import { isModeSupportedByField, isObjectEqual, isValueEqual } from '../common';
+
+
+export const SCHEMA_STATE_ACTIONS = {
+ INIT: 'init',
+ SET_VALUE: 'set_value',
+ ADD_ROW: 'add_row',
+ DELETE_ROW: 'delete_row',
+ MOVE_ROW: 'move_row',
+ RERENDER: 'rerender',
+ CLEAR_DEFERRED_QUEUE: 'clear_deferred_queue',
+ DEFERRED_DEPCHANGE: 'deferred_depchange',
+ BULK_UPDATE: 'bulk_update',
+};
// Remove cid key added by prepareData
const cleanCid = (coll, keepCid=false) => (
@@ -276,9 +290,10 @@ export function validateSchema(
if(schema.idAttribute === field.id) {
continue;
}
-
// If the field is has nested schema, then validate the child schema.
if(field.schema && (field.schema instanceof BaseUISchema)) {
+ if (!field.schema.top) field.schema.top = schema;
+
// A collection is an array.
if(field.type === 'collection') {
if (validateCollectionSchema(field, sessData, accessPath, setError))
@@ -331,3 +346,40 @@ export function validateSchema(
sessData, (id, message) => setError(accessPath.concat(id), message)
);
}
+
+export const getDepChange = (currPath, newState, oldState, action) => {
+ if(action.depChange) {
+ newState = action.depChange(currPath, newState, {
+ type: action.type,
+ path: action.path,
+ value: action.value,
+ oldState: _.cloneDeep(oldState),
+ listener: action.listener,
+ });
+ }
+ return newState;
+};
+
+// It will help us generating the flat path, and it will return the same
+// object for the same path, which will help with the React componet rendering,
+// as it uses `Object.is(...)` for the comparison of the arguments.
+export const flatPathGenerator = (separator = '.' ) => {
+ const flatPathMap = new Map;
+
+ const setter = memoizeFn((path) => {
+ const flatPath = path.join(separator);
+ flatPathMap.set(flatPath, path);
+ return flatPath;
+ });
+
+ const getter = (flatPath) => {
+ return flatPathMap.get(flatPath);
+ };
+
+ return {
+ flatPath: setter,
+ path: getter,
+ // Get the same object every time.
+ cached: (path) => (getter(setter(path))),
+ };
+};
diff --git a/web/pgadmin/static/js/SchemaView/SchemaState/context.js b/web/pgadmin/static/js/SchemaView/SchemaState/context.js
new file mode 100644
index 00000000000..fc6fbe48826
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/SchemaState/context.js
@@ -0,0 +1,12 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import React from 'react';
+
+export const SchemaStateContext = React.createContext();
diff --git a/web/pgadmin/static/js/SchemaView/SchemaState/index.js b/web/pgadmin/static/js/SchemaView/SchemaState/index.js
new file mode 100644
index 00000000000..4efcaf27493
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/SchemaState/index.js
@@ -0,0 +1,21 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import { SchemaState } from './SchemaState';
+import { SchemaStateContext } from './context';
+import { SCHEMA_STATE_ACTIONS } from './common';
+import { sessDataReducer } from './reducer';
+
+
+export {
+ SCHEMA_STATE_ACTIONS,
+ SchemaState,
+ SchemaStateContext,
+ sessDataReducer,
+};
diff --git a/web/pgadmin/static/js/SchemaView/SchemaState/reducer.js b/web/pgadmin/static/js/SchemaView/SchemaState/reducer.js
new file mode 100644
index 00000000000..f80f815c948
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/SchemaState/reducer.js
@@ -0,0 +1,123 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import _ from 'lodash';
+import {
+ SCHEMA_STATE_ACTIONS, getDepChange,
+} from './common';
+
+const getDeferredDepChange = (currPath, newState, oldState, action) => {
+ if(action.deferredDepChange) {
+ return action.deferredDepChange(currPath, newState, {
+ type: action.type,
+ path: action.path,
+ value: action.value,
+ depChange: action.depChange,
+ oldState: _.cloneDeep(oldState),
+ });
+ }
+};
+
+/*
+ * The main function which manipulates the session state based on actions.
+ *
+ * The state is managed based on path array of a particular key.
+ * For Eg. if the state is
+ * {
+ * key1: {
+ * ckey1: [
+ * {a: 0, b: 0},
+ * {a: 1, b: 1}
+ * ]
+ * }
+ * }
+ *
+ * The path for b in first row will be '[key1, ckey1, 0, b]'.
+ * The path for second row of ckey1 will be '[key1, ckey1, 1]'.
+ *
+ * The path for key1 is '[key1]'.
+ * The state starts with path '[]'.
+ */
+export const sessDataReducer = (state, action) => {
+ let data = _.cloneDeep(state);
+ let rows, cid, deferredList;
+ data.__deferred__ = data.__deferred__ || [];
+
+ switch(action.type) {
+ case SCHEMA_STATE_ACTIONS.INIT:
+ data = action.payload;
+ break;
+
+ case SCHEMA_STATE_ACTIONS.BULK_UPDATE:
+ rows = _.get(data, action.path) || [];
+ rows.forEach((row) => { row[action.id] = false; });
+ _.set(data, action.path, rows);
+ break;
+
+ case SCHEMA_STATE_ACTIONS.SET_VALUE:
+ _.set(data, action.path, action.value);
+ // If there is any dep listeners get the changes.
+ data = getDepChange(action.path, data, state, action);
+ deferredList = getDeferredDepChange(action.path, data, state, action);
+ data.__deferred__ = deferredList || [];
+ break;
+
+ case SCHEMA_STATE_ACTIONS.ADD_ROW:
+ // Create id to identify a row uniquely, usefull when getting diff.
+ cid = _.uniqueId('c');
+ action.value['cid'] = cid;
+
+ if (action.addOnTop) {
+ rows = [].concat(action.value).concat(_.get(data, action.path) || []);
+ } else {
+ rows = (_.get(data, action.path) || []).concat(action.value);
+ }
+
+ _.set(data, action.path, rows);
+
+ // If there is any dep listeners get the changes.
+ data = getDepChange(action.path, data, state, action);
+
+ break;
+
+ case SCHEMA_STATE_ACTIONS.DELETE_ROW:
+ rows = _.get(data, action.path)||[];
+ rows.splice(action.value, 1);
+
+ _.set(data, action.path, rows);
+
+ // If there is any dep listeners get the changes.
+ data = getDepChange(action.path, data, state, action);
+
+ break;
+
+ case SCHEMA_STATE_ACTIONS.MOVE_ROW:
+ rows = _.get(data, action.path)||[];
+ var row = rows[action.oldIndex];
+ rows.splice(action.oldIndex, 1);
+ rows.splice(action.newIndex, 0, row);
+
+ _.set(data, action.path, rows);
+
+ break;
+
+ case SCHEMA_STATE_ACTIONS.CLEAR_DEFERRED_QUEUE:
+ data.__deferred__ = [];
+ return data;
+
+ case SCHEMA_STATE_ACTIONS.DEFERRED_DEPCHANGE:
+ data = getDepChange(action.path, data, state, action);
+ break;
+ }
+
+ data.__changeId = (data.__changeId || 0) + 1;
+
+ return data;
+};
+
diff --git a/web/pgadmin/static/js/SchemaView/SchemaState/store.js b/web/pgadmin/static/js/SchemaView/SchemaState/store.js
new file mode 100644
index 00000000000..991d9593c8e
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/SchemaState/store.js
@@ -0,0 +1,80 @@
+import _ from 'lodash';
+
+import { isValueEqual } from '../common';
+import { flatPathGenerator } from './common';
+
+
+export const createStore = (initialState) => {
+ let state = initialState;
+
+ const listeners = new Set();
+ const gen = flatPathGenerator('/');
+ const pathListeners = new Set();
+
+ // Exposed functions
+ // Don't attempt to manipulate the state directly.
+ const getState = () => state;
+ const setState = (nextState) => {
+ const prevState = state;
+ state = _.clone(nextState);
+
+ if (isValueEqual(state, prevState)) return;
+
+ listeners.forEach((listener) => {
+ listener();
+ });
+
+ const changeMemo = new Map();
+
+ pathListeners.forEach((pathListener) => {
+ const [ path, listener ] = pathListener;
+ const flatPath = gen.flatPath(path);
+
+ if (!changeMemo.has(flatPath)) {
+ const pathNextValue =
+ flatPath == '' ? nextState : _.get(nextState, path, undefined);
+ const pathPrevValue =
+ flatPath == '' ? prevState : _.get(prevState, path, undefined);
+
+ changeMemo.set(flatPath, [
+ isValueEqual(pathNextValue, pathPrevValue),
+ pathNextValue,
+ pathPrevValue,
+ ]);
+ }
+
+ const [isSame, pathNextValue, pathPrevValue] = changeMemo.get(flatPath);
+
+ if (!isSame) {
+ listener(pathNextValue, pathPrevValue);
+ }
+ });
+ };
+ const get = (path = []) => (_.get(state, path));
+ const set = (arg) => {
+ let nextState = _.isFunction(arg) ? arg(_.cloneDeep(state)) : arg;
+ setState(nextState);
+ };
+ const subscribe = (listener) => {
+ listeners.add(listener);
+ return () => listeners.delete(listener);
+ };
+ const subscribeForPath = (path, listner) => {
+ const data = [path, listner];
+
+ pathListeners.add(data);
+
+ return () => {
+ return pathListeners.delete(data);
+ };
+ };
+
+ return {
+ getState,
+ setState,
+ get,
+ set,
+ subscribe,
+ subscribeForPath,
+ };
+};
diff --git a/web/pgadmin/static/js/SchemaView/SchemaView.jsx b/web/pgadmin/static/js/SchemaView/SchemaView.jsx
index 3da1d55ea94..537658822f8 100644
--- a/web/pgadmin/static/js/SchemaView/SchemaView.jsx
+++ b/web/pgadmin/static/js/SchemaView/SchemaView.jsx
@@ -15,6 +15,7 @@ import ErrorBoundary from 'sources/helpers/ErrorBoundary';
import SchemaDialogView from './SchemaDialogView';
import SchemaPropertiesView from './SchemaPropertiesView';
+import { registerView } from './registry';
export default function SchemaView({formType, ...props}) {
@@ -32,3 +33,5 @@ export default function SchemaView({formType, ...props}) {
SchemaView.propTypes = {
formType: PropTypes.oneOf(['tab', 'dialog']),
};
+
+registerView(SchemaView, 'SchemaView');
diff --git a/web/pgadmin/static/js/SchemaView/StyledComponents.jsx b/web/pgadmin/static/js/SchemaView/StyledComponents.jsx
index 3529007a8c1..1d67efc4a33 100644
--- a/web/pgadmin/static/js/SchemaView/StyledComponents.jsx
+++ b/web/pgadmin/static/js/SchemaView/StyledComponents.jsx
@@ -41,7 +41,7 @@ export const StyledBox = styled(Box)(({theme}) => ({
padding: theme.spacing(1),
overflow: 'auto',
flexGrow: 1,
- '& .Properties-controlRow': {
+ '& .Properties-controlRow:not(:last-child)': {
marginBottom: theme.spacing(1),
},
},
diff --git a/web/pgadmin/static/js/SchemaView/base_schema.ui.js b/web/pgadmin/static/js/SchemaView/base_schema.ui.js
index 4c6578a57ea..955f1f6ef15 100644
--- a/web/pgadmin/static/js/SchemaView/base_schema.ui.js
+++ b/web/pgadmin/static/js/SchemaView/base_schema.ui.js
@@ -9,6 +9,8 @@
import _ from 'lodash';
+import { memoizeFn } from 'sources/utils';
+
/* This is the base schema class for SchemaView.
* A UI schema must inherit this to use SchemaView for UI.
*/
@@ -63,11 +65,11 @@ export default class BaseUISchema {
/*
* The session data, can be useful but setting this will not affect UI.
- * this._sessData is set by SchemaView directly. set sessData should not be
+ * this.sessData is set by SchemaView directly. set sessData should not be
* allowed anywhere.
*/
get sessData() {
- return this._sessData || {};
+ return this.state?.data;
}
set sessData(val) {
@@ -93,19 +95,28 @@ export default class BaseUISchema {
concat base fields with extraFields.
*/
get fields() {
- return this.baseFields
- .filter((field)=>{
- let retval;
-
- /* If any groups are to be filtered */
- retval = this.filterGroups.indexOf(field.group) == -1;
+ if (!this.__filteredFields) {
+ // Memoize the results
+ this.__filteredFields = memoizeFn(
+ (baseFields, keys, filterGroups) => baseFields.filter((field) => {
+ let retval;
+
+ // If any groups are to be filtered.
+ retval = filterGroups.indexOf(field.group) == -1;
+
+ // Select only keys, if specified.
+ if(retval && keys) {
+ retval = keys.indexOf(field.id) > -1;
+ }
+
+ return retval;
+ })
+ );
+ }
- /* Select only keys, if specified */
- if(this.keys) {
- retval = retval && this.keys.indexOf(field.id) > -1;
- }
- return retval;
- });
+ return this.__filteredFields(
+ this.baseFields, this.keys, this.filterGroups
+ );
}
initialise() {
@@ -190,4 +201,8 @@ export default class BaseUISchema {
}
return res;
}
+
+ toJSON() {
+ return this._id;
+ }
}
diff --git a/web/pgadmin/static/js/SchemaView/common.js b/web/pgadmin/static/js/SchemaView/common.js
index 7ad31c79b42..3c14727b89b 100644
--- a/web/pgadmin/static/js/SchemaView/common.js
+++ b/web/pgadmin/static/js/SchemaView/common.js
@@ -7,32 +7,16 @@
//
//////////////////////////////////////////////////////////////
-import React from 'react';
import { evalFunc } from 'sources/utils';
-export const SCHEMA_STATE_ACTIONS = {
- INIT: 'init',
- SET_VALUE: 'set_value',
- ADD_ROW: 'add_row',
- DELETE_ROW: 'delete_row',
- MOVE_ROW: 'move_row',
- RERENDER: 'rerender',
- CLEAR_DEFERRED_QUEUE: 'clear_deferred_queue',
- DEFERRED_DEPCHANGE: 'deferred_depchange',
- BULK_UPDATE: 'bulk_update',
-};
-
-export const SchemaStateContext = React.createContext();
-
export function generateTimeBasedRandomNumberString() {
return new Date().getTime() + '' + Math.floor(Math.random() * 1000001);
}
-export function isModeSupportedByField(field, helperProps) {
- if (!field || !field.mode) return true;
- return (field.mode.indexOf(helperProps.mode) > -1);
-}
+export const isModeSupportedByField = (field, helperProps) => (
+ !field.mode || field.mode.indexOf(helperProps.mode) > -1
+);
export function getFieldMetaData(
field, schema, value, viewHelperProps
@@ -81,13 +65,14 @@ export function getFieldMetaData(
retData.editable = !(
viewHelperProps.inCatalog || (viewHelperProps.mode == 'properties')
);
+
if(retData.editable) {
retData.editable = evalFunc(
schema, (_.isUndefined(editable) ? true : editable), value
);
}
- let {canAdd, canEdit, canDelete, canReorder, canAddRow } = field;
+ let {canAdd, canEdit, canDelete, canAddRow } = field;
retData.canAdd =
_.isUndefined(canAdd) ? retData.canAdd : evalFunc(schema, canAdd, value);
retData.canAdd = !retData.disabled && retData.canAdd;
@@ -99,10 +84,6 @@ export function getFieldMetaData(
schema, canDelete, value
);
retData.canDelete = !retData.disabled && retData.canDelete;
- retData.canReorder =
- _.isUndefined(canReorder) ? retData.canReorder : evalFunc(
- schema, canReorder, value
- );
retData.canAddRow =
_.isUndefined(canAddRow) ? retData.canAddRow : evalFunc(
schema, canAddRow, value
@@ -165,3 +146,38 @@ export function getForQueryParams(data) {
});
return retData;
}
+
+export function prepareData(val, createMode=false) {
+ if(_.isPlainObject(val)) {
+ _.forIn(val, function (el) {
+ if (_.isObject(el)) {
+ prepareData(el, createMode);
+ }
+ });
+ } else if(_.isArray(val)) {
+ val.forEach(function(el) {
+ if (_.isPlainObject(el)) {
+ /* The each row in collection need to have an id to identify them uniquely
+ This helps in easily getting what has changed */
+ /* Nested collection rows may or may not have idAttribute.
+ So to decide whether row is new or not set, the cid starts with
+ nn (not new) for existing rows. Newly added will start with 'c' (created)
+ */
+ el['cid'] = createMode ? _.uniqueId('c') : _.uniqueId('nn');
+ prepareData(el, createMode);
+ }
+ });
+ }
+ return val;
+}
+
+export const flatternObject = (obj, base=[]) => Object.keys(obj).sort().reduce(
+ (r, k) => {
+ r = r.concat(k);
+ const value = obj[k];
+ if (_.isFunction(value)) return r;
+ if (_.isArray(value)) return r.concat(...value);
+ if (_.isPlainObject(value)) return flatternObject(value, r);
+ return r.concat(value);
+ }, base
+);
diff --git a/web/pgadmin/static/js/SchemaView/hooks/index.js b/web/pgadmin/static/js/SchemaView/hooks/index.js
new file mode 100644
index 00000000000..c294172fd81
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/hooks/index.js
@@ -0,0 +1,23 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import { useFieldError } from './useFieldError';
+import { useFieldOptions } from './useFieldOptions';
+import { useFieldValue } from './useFieldValue';
+import { useSchemaState } from './useSchemaState';
+import { useFieldSchema } from './useFieldSchema';
+
+
+export {
+ useFieldError,
+ useFieldOptions,
+ useFieldValue,
+ useFieldSchema,
+ useSchemaState,
+};
diff --git a/web/pgadmin/static/js/SchemaView/hooks/useFieldError.js b/web/pgadmin/static/js/SchemaView/hooks/useFieldError.js
new file mode 100644
index 00000000000..07542725098
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/hooks/useFieldError.js
@@ -0,0 +1,34 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import { useEffect } from 'react';
+
+
+export const useFieldError = (
+ path, schemaState, key, setRefreshKey
+) => {
+ useEffect(() => {
+ if (!schemaState || !setRefreshKey) return;
+
+ const checkPathError = (newState, prevState) => {
+ if (prevState.name !== path && newState.name !== path) return;
+ // We don't need to redraw the control on message change.
+ if (prevState.name === newState.name) return;
+
+ setRefreshKey({id: Date.now()});
+ };
+
+ return schemaState.subscribe(['errors'], checkPathError, 'states');
+ }, [key, schemaState?._id]);
+
+ const errors = schemaState?.errors || {};
+ const error = errors.name === path ? errors.message : null;
+
+ return {hasError: !_.isNull(error), error};
+};
diff --git a/web/pgadmin/static/js/SchemaView/hooks/useFieldOptions.js b/web/pgadmin/static/js/SchemaView/hooks/useFieldOptions.js
new file mode 100644
index 00000000000..5763edc2426
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/hooks/useFieldOptions.js
@@ -0,0 +1,25 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import { useEffect } from 'react';
+
+
+export const useFieldOptions = (
+ path, schemaState, key, setRefreshKey
+) => {
+ useEffect(() => {
+ if (!schemaState) return;
+
+ return schemaState.subscribe(
+ path, () => setRefreshKey?.({id: Date.now()}), 'options'
+ );
+ }, [key, schemaState?._id]);
+
+ return schemaState?.options(path) || {visible: true};
+};
diff --git a/web/pgadmin/static/js/SchemaView/hooks/useFieldSchema.js b/web/pgadmin/static/js/SchemaView/hooks/useFieldSchema.js
new file mode 100644
index 00000000000..0cbd2bd94c4
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/hooks/useFieldSchema.js
@@ -0,0 +1,56 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import _ from 'lodash';
+import { useEffect } from 'react';
+
+import { booleanEvaluator } from '../options';
+
+
+export const useFieldSchema = (
+ field, accessPath, value, viewHelperProps, schemaState, key, setRefreshKey
+) => {
+ useEffect(() => {
+ if (!schemaState || !field) return;
+
+ // It already has 'id', 'options' is already evaluated.
+ if (field.id)
+ return schemaState.subscribe(
+ accessPath, () => setRefreshKey?.({id: Date.now()}), 'options'
+ );
+
+ // There are no dependencies.
+ if (!_.isArray(field?.deps)) return;
+
+ // Subscribe to all the dependents.
+ const unsubscribers = field.deps.map((dep) => (
+ schemaState.subscribe(
+ accessPath.concat(dep), () => setRefreshKey?.({id: Date.now()}),
+ 'value'
+ )
+ ));
+
+ return () => {
+ unsubscribers.forEach(unsubscribe => unsubscribe());
+ };
+ }, [key, schemaState?._id]);
+
+ if (!field) return { visible: true };
+ if (field.id) return schemaState?.options(accessPath);
+ if (!field.schema) return { visible: true };
+
+ value = value || {};
+
+ return {
+ visible: booleanEvaluator({
+ schema: field.schema, field, option: 'visible', value, viewHelperProps,
+ defaultVal: true,
+ }),
+ };
+};
diff --git a/web/pgadmin/static/js/SchemaView/hooks/useFieldValue.js b/web/pgadmin/static/js/SchemaView/hooks/useFieldValue.js
new file mode 100644
index 00000000000..0e92ae9e42a
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/hooks/useFieldValue.js
@@ -0,0 +1,25 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import { useEffect } from 'react';
+
+
+export const useFieldValue = (
+ path, schemaState, key, setRefreshKey
+) => {
+ useEffect(() => {
+ if (!schemaState || !setRefreshKey) return;
+
+ return schemaState.subscribe(
+ path, () => setRefreshKey({id: Date.now()}), 'value'
+ );
+ }, [key, schemaState?._id]);
+
+ return schemaState?.value(path);
+};
diff --git a/web/pgadmin/static/js/SchemaView/hooks/useSchemaState.js b/web/pgadmin/static/js/SchemaView/hooks/useSchemaState.js
new file mode 100644
index 00000000000..d22c9d3b1c6
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/hooks/useSchemaState.js
@@ -0,0 +1,143 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import { useEffect, useReducer } from 'react';
+import _ from 'lodash';
+
+import { prepareData } from '../common';
+import {
+ SCHEMA_STATE_ACTIONS,
+ SchemaState,
+ sessDataReducer,
+} from '../SchemaState';
+
+
+export const useSchemaState = ({
+ schema, getInitData, immutableData, onDataChange, viewHelperProps,
+ loadingText,
+}) => {
+
+ if (!schema)
+ return {
+ schemaState: null,
+ dataDispatch: null,
+ reset: null,
+ };
+
+ let state = schema.state;
+
+ if (!state) {
+ schema.state = state = new SchemaState(
+ schema, getInitData, immutableData, onDataChange, viewHelperProps,
+ loadingText,
+ );
+ state.updateOptions();
+ }
+
+ const [sessData, sessDispatch] = useReducer(
+ sessDataReducer, {...(_.cloneDeep(state.data)), __changeId: 0}
+ );
+
+ const sessDispatchWithListener = (action) => {
+ let dispatchPayload = {
+ ...action,
+ depChange: (...args) => state.getDepChange(...args),
+ deferredDepChange: (...args) => state.getDeferredDepChange(...args),
+ };
+ /*
+ * All the session changes coming before init should be queued up.
+ * They will be processed later when form is ready.
+ */
+ let preReadyQueue = state.preReadyQueue;
+
+ preReadyQueue ?
+ preReadyQueue.push(dispatchPayload) :
+ sessDispatch(dispatchPayload);
+ };
+
+ state.setUnpreparedData = (path, value) => {
+ if(path) {
+ let data = prepareData(value);
+ _.set(schema.initData, path, data);
+ sessDispatchWithListener({
+ type: SCHEMA_STATE_ACTIONS.SET_VALUE,
+ path: path,
+ value: data,
+ });
+ }
+ };
+
+ const resetData = () => {
+ const initData = _.cloneDeep(state.initData);
+ initData.__changeId = sessData.__changeId;
+ sessDispatch({
+ type: SCHEMA_STATE_ACTIONS.INIT,
+ payload: initData,
+ });
+ };
+
+ const reload = () => {
+ state.initialise(sessDispatch, true);
+ };
+
+ useEffect(() => {
+ state.initialise(sessDispatch);
+ }, [state.loadingState]);
+
+ useEffect(() => {
+ let preReadyQueue = state.preReadyQueue;
+
+ if (!state.isReady || !preReadyQueue) return;
+
+ for (const payload of preReadyQueue) {
+ sessDispatch(payload);
+ }
+
+ // Destroy the queue so that no one uses it.
+ state.preReadyQueue = null;
+ }, [state.isReady]);
+
+ useEffect(() => {
+ // Validate the schema on the change of the data.
+ if (state.isReady) state.validate(sessData);
+ }, [state.isReady, sessData.__changeId]);
+
+ useEffect(() => {
+ const items = sessData.__deferred__ || [];
+
+ if (items.length == 0) return;
+
+ sessDispatch({
+ type: SCHEMA_STATE_ACTIONS.CLEAR_DEFERRED_QUEUE,
+ });
+
+ items.forEach((item) => {
+ item.promise.then((resFunc) => {
+ sessDispatch({
+ type: SCHEMA_STATE_ACTIONS.DEFERRED_DEPCHANGE,
+ path: item.action.path,
+ depChange: item.action.depChange,
+ listener: {
+ ...item.listener,
+ callback: resFunc,
+ },
+ });
+ });
+ });
+ }, [sessData.__deferred__?.length]);
+
+ state.reload = reload;
+ state.reset = resetData;
+
+ return {
+ schemaState: state,
+ dataDispatch: sessDispatchWithListener,
+ reset: resetData,
+ };
+};
diff --git a/web/pgadmin/static/js/SchemaView/index.jsx b/web/pgadmin/static/js/SchemaView/index.jsx
index 11f6bfff480..fa91803bf09 100644
--- a/web/pgadmin/static/js/SchemaView/index.jsx
+++ b/web/pgadmin/static/js/SchemaView/index.jsx
@@ -7,43 +7,46 @@
//
//////////////////////////////////////////////////////////////
+import BaseUISchema from './base_schema.ui';
import DataGridView from './DataGridView';
import FieldSetView from './FieldSetView';
import FormView from './FormView';
+import InlineView from './InlineView';
import SchemaDialogView from './SchemaDialogView';
import SchemaPropertiesView from './SchemaPropertiesView';
import SchemaView from './SchemaView';
-import BaseUISchema from './base_schema.ui';
-import { useSchemaState } from './useSchemaState';
+import { useSchemaState, useFieldState } from './hooks';
import {
- SCHEMA_STATE_ACTIONS,
- SchemaStateContext,
generateTimeBasedRandomNumberString,
- isModeSupportedByField,
- getFieldMetaData,
isValueEqual,
isObjectEqual,
- getForQueryParams
+ getForQueryParams,
+ prepareData,
} from './common';
+import {
+ SCHEMA_STATE_ACTIONS,
+ SchemaStateContext,
+} from './SchemaState';
export default SchemaView;
export {
+ SCHEMA_STATE_ACTIONS,
+ BaseUISchema,
DataGridView,
FieldSetView,
FormView,
+ InlineView,
SchemaDialogView,
SchemaPropertiesView,
SchemaView,
- BaseUISchema,
- useSchemaState,
- SCHEMA_STATE_ACTIONS,
SchemaStateContext,
+ getForQueryParams,
generateTimeBasedRandomNumberString,
- isModeSupportedByField,
- getFieldMetaData,
isValueEqual,
isObjectEqual,
- getForQueryParams
+ prepareData,
+ useFieldState,
+ useSchemaState,
};
diff --git a/web/pgadmin/static/js/SchemaView/options/common.js b/web/pgadmin/static/js/SchemaView/options/common.js
new file mode 100644
index 00000000000..e27475119c3
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/options/common.js
@@ -0,0 +1,40 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import _ from 'lodash';
+import { evalFunc } from 'sources/utils';
+
+
+export const FIELD_OPTIONS = '__fieldOptions';
+
+export const booleanEvaluator = ({
+ schema, field, option, value, viewHelperProps, options, defaultVal,
+}) => (
+ _.isUndefined(field?.[option]) ? defaultVal :
+ Boolean(evalFunc(schema, field[option], value, viewHelperProps, options))
+);
+
+export const evalIfNotDisabled = ({ options, ...params }) => (
+ !options.disabled &&
+ booleanEvaluator({ options, ...params })
+);
+
+export const canAddOrDelete = ({
+ options, viewHelperProps, field, ...params
+}) => (
+ viewHelperProps?.mode != 'properties' &&
+ !(field?.fixedRow) &&
+ !options.disabled &&
+ booleanEvaluator({ options, viewHelperProps, field, ...params })
+);
+
+export const evalInNonPropertyMode = ({ viewHelperProps, ...params }) => (
+ viewHelperProps?.mode != 'properties' &&
+ booleanEvaluator({ viewHelperProps, ...params })
+);
diff --git a/web/pgadmin/static/js/SchemaView/options/index.js b/web/pgadmin/static/js/SchemaView/options/index.js
new file mode 100644
index 00000000000..dbdd6ce2001
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/options/index.js
@@ -0,0 +1,176 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import { evalFunc } from 'sources/utils';
+import {
+ booleanEvaluator,
+ canAddOrDelete,
+ evalIfNotDisabled,
+ evalInNonPropertyMode,
+ FIELD_OPTIONS
+} from './common';
+import {
+ evaluateFieldOptions,
+ evaluateFieldsOption,
+ registerOptionEvaluator,
+ schemaOptionsEvalulator,
+} from './registry';
+
+export {
+ FIELD_OPTIONS,
+ booleanEvaluator,
+ canAddOrDelete,
+ evaluateFieldOptions,
+ evaluateFieldsOption,
+ evalIfNotDisabled,
+ registerOptionEvaluator,
+ schemaOptionsEvalulator,
+};
+
+const VISIBLE = 'visible';
+
+// Default evaluators
+// 1. disabled
+// 2. visible (It also checks for the supported mode)
+// 3. readonly
+
+registerOptionEvaluator('disabled');
+registerOptionEvaluator(
+ VISIBLE,
+ // Evaluator
+ ({schema, field, value, viewHelperProps}) => (
+ (
+ !field.mode || field.mode.indexOf(viewHelperProps.mode) > -1
+ ) && (
+ // serverInfo not found
+ _.isUndefined(viewHelperProps.serverInfo) ||
+ // serverInfo found and it's within range
+ ((
+ _.isUndefined(field.server_type) ? true :
+ (viewHelperProps.serverInfo.type in field.server_type)
+ ) && (
+ _.isUndefined(field.min_version) ? true :
+ (viewHelperProps.serverInfo.version >= field.min_version)
+ ) && (
+ _.isUndefined(field.max_version) ? true :
+ (viewHelperProps.serverInfo.version <= field.max_version)
+ ))
+ ) && (
+ _.isUndefined(field[VISIBLE]) ? true :
+ Boolean(evalFunc(schema, field[VISIBLE], value))
+ )),
+);
+
+registerOptionEvaluator(
+ 'readonly',
+ // Evaluator
+ ({viewHelperProps, ...args}) => (
+ viewHelperProps.inCatalog ||
+ viewHelperProps.mode === 'properties' ||
+ booleanEvaluator({viewHelperProps, ...args })
+ ),
+ // Default value
+ false
+);
+
+
+// Collection evaluators
+// 1. canAdd
+// 2. canEdit
+// 3. canAddRow
+// 4. expandEditOnAdd
+// 5. addOnTop
+// 6. canSearch
+registerOptionEvaluator(
+ 'canAdd',
+ // Evaluator
+ canAddOrDelete,
+ // Default value
+ true,
+ ['collection']
+);
+
+registerOptionEvaluator(
+ 'canEdit',
+ // Evaluator
+ ({viewHelperProps, options, ...args}) => (
+ !viewHelperProps.inCatalog &&
+ viewHelperProps.mode !== 'properties' &&
+ !options.disabled &&
+ booleanEvaluator({viewHelperProps, options, ...args })
+ ),
+ // Default value
+ false,
+ ['collection']
+);
+
+registerOptionEvaluator(
+ 'canAddRow',
+ // Evaluator
+ ({options, ...args}) => (
+ options.canAdd &&
+ booleanEvaluator({options, ...args })
+ ),
+ // Default value
+ true,
+ ['collection']
+);
+
+registerOptionEvaluator(
+ 'expandEditOnAdd',
+ // Evaluator
+ evalInNonPropertyMode,
+ // Default value
+ false,
+ ['collection']
+);
+
+registerOptionEvaluator(
+ 'addOnTop',
+ // Evaluator
+ evalInNonPropertyMode,
+ // Default value
+ false,
+ ['collection']
+);
+
+registerOptionEvaluator(
+ 'canSearch',
+ // Evaluator
+ evalInNonPropertyMode,
+ // Default value
+ false,
+ ['collection']
+);
+
+// Row evaluators
+// 1. canEditRow
+registerOptionEvaluator(
+ 'canEditRow',
+ // Evaluator
+ evalInNonPropertyMode,
+ // Default value
+ true,
+ ['row']
+);
+
+// Grid cell evaluatiors
+// 1. editable
+registerOptionEvaluator(
+ 'editable',
+ // Evaluator
+ ({viewHelperProps, ...args}) => (
+ !viewHelperProps.inCatalog &&
+ viewHelperProps.mode !== 'properties' &&
+ booleanEvaluator({viewHelperProps, ...args })
+ ),
+ // Default value
+ true,
+ ['cell']
+);
diff --git a/web/pgadmin/static/js/SchemaView/options/registry.js b/web/pgadmin/static/js/SchemaView/options/registry.js
new file mode 100644
index 00000000000..dce57bcc021
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/options/registry.js
@@ -0,0 +1,156 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import _ from 'lodash';
+import { isModeSupportedByField } from '../common';
+import { FIELD_OPTIONS, booleanEvaluator } from './common';
+
+
+const COMMON_OPTIONS = '__common';
+const _optionEvaluators = { };
+
+
+export function registerOptionEvaluator(option, evaluator, defaultVal, types) {
+ types = types || [COMMON_OPTIONS];
+ evaluator = evaluator || booleanEvaluator;
+ defaultVal = _.isUndefined(defaultVal) ? false : defaultVal;
+
+ types.forEach((type) => {
+ const evaluators = _optionEvaluators[type] =
+ (_optionEvaluators[type] || []);
+
+ evaluators.push([option, evaluator, defaultVal]);
+ });
+}
+
+export function evaluateFieldOption({
+ option, schema, value, viewHelperProps, field, options, parentOptions,
+}) {
+ if (option && option in _optionEvaluators) {
+ const evaluators = _optionEvaluators[option];
+ evaluators?.forEach(([option, evaluator, defaultVal]) => {
+ options[option] = evaluator({
+ schema, field, option, value, viewHelperProps, options, defaultVal,
+ parentOptions
+ });
+ });
+ }
+}
+
+export function evaluateFieldOptions({
+ schema, value, viewHelperProps, field, options={}, parentOptions=null
+}) {
+ evaluateFieldOption({
+ option: COMMON_OPTIONS, schema, value, viewHelperProps, field, options,
+ parentOptions
+ });
+ evaluateFieldOption({
+ option: field.type, schema, value, viewHelperProps, field, options,
+ parentOptions
+ });
+}
+
+export function schemaOptionsEvalulator({
+ schema, data, accessPath=[], viewHelperProps, options, parentOptions=null,
+ inGrid=false
+}) {
+ schema?.fields?.forEach((field) => {
+ // We could have multiple entries of same `field.id` for each mode, hence -
+ // we should process the options only if the current field is support for
+ // the given mode.
+ if (!isModeSupportedByField(field, viewHelperProps)) return;
+
+ switch (field.type) {
+ case 'nested-tab':
+ case 'nested-fieldset':
+ case 'inline-groups':
+ {
+ if (!field.schema) return;
+ if (!field.schema.top) field.schema.top = schema.top || schema;
+
+ const path = field.id ? [...accessPath, field.id] : accessPath;
+
+ schemaOptionsEvalulator({
+ schema: field.schema, data, path, viewHelperProps, options,
+ parentOptions
+ });
+ }
+
+ break;
+
+ case 'collection':
+ {
+ if (!field.schema) return;
+ if (!field.schema.top) field.schema.top = schema.top || schema;
+
+ const fieldPath = [...accessPath, field.id];
+ const fieldOptionsPath = [...fieldPath, FIELD_OPTIONS];
+ const fieldOptions = _.get(options, fieldOptionsPath, {});
+ const rows = data[field.id];
+
+ evaluateFieldOptions({
+ schema, value: data, viewHelperProps, field,
+ options: fieldOptions, parentOptions,
+ });
+
+ _.set(options, fieldOptionsPath, fieldOptions);
+
+ const rowIndexes = [FIELD_OPTIONS];
+
+ rows?.forEach((row, idx) => {
+ const schemaPath = [...fieldPath, idx];
+ const schemaOptions = _.get(options, schemaPath, {});
+
+ _.set(options, schemaPath, schemaOptions);
+
+ schemaOptionsEvalulator({
+ schema: field.schema, data: row, accessPath: [],
+ viewHelperProps, options: schemaOptions,
+ parentOptions: fieldOptions, inGrid: true
+ });
+
+ const rowPath = [...schemaPath, FIELD_OPTIONS];
+ const rowOptions = _.get(options, rowPath, {});
+ _.set(options, rowPath, rowOptions);
+
+ evaluateFieldOption({
+ option: 'row', schema: field.schema, value: row, viewHelperProps,
+ field, options: rowOptions, parentOptions: fieldOptions
+ });
+
+ rowIndexes.push(idx);
+ });
+
+ }
+ break;
+
+ default:
+ {
+ const fieldPath = [...accessPath, field.id];
+ const fieldOptionsPath = [...fieldPath, FIELD_OPTIONS];
+ const fieldOptions = _.get(options, fieldOptionsPath, {});
+
+ evaluateFieldOptions({
+ schema, value: data, viewHelperProps, field, options: fieldOptions,
+ parentOptions,
+ });
+
+ if (inGrid) {
+ evaluateFieldOption({
+ option: 'cell', schema, value: data, viewHelperProps, field,
+ options: fieldOptions, parentOptions,
+ });
+ }
+
+ _.set(options, fieldOptionsPath, fieldOptions);
+ }
+ break;
+ }
+ });
+}
diff --git a/web/pgadmin/static/js/SchemaView/registry.js b/web/pgadmin/static/js/SchemaView/registry.js
new file mode 100644
index 00000000000..9392887a5ef
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/registry.js
@@ -0,0 +1,42 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+/*
+ * Using the factory pattern (registry) to avoid circular imports of the views.
+ */
+const _views = {};
+
+export function registerView(viewFunc, name) {
+ name = name || viewFunc.name;
+
+ if (name in _views) {
+ throw new Error(
+ `View type '${name}' is alredy registered.`
+ );
+ }
+
+ if (typeof viewFunc !== 'function') {
+ throw new Error(
+ `View '${name}' must be a function.`
+ );
+ }
+
+ _views[name] = viewFunc;
+}
+
+export function View(name) {
+ const view = _views[name];
+
+ if (view) return view;
+ throw new Error(`View ${name} is not found in the registry.`);
+}
+
+export function hasView(name) {
+ return (name in _views);
+}
diff --git a/web/pgadmin/static/js/SchemaView/useSchemaState.js b/web/pgadmin/static/js/SchemaView/useSchemaState.js
deleted file mode 100644
index 7bfc0308b63..00000000000
--- a/web/pgadmin/static/js/SchemaView/useSchemaState.js
+++ /dev/null
@@ -1,489 +0,0 @@
-/////////////////////////////////////////////////////////////
-//
-// pgAdmin 4 - PostgreSQL Tools
-//
-// Copyright (C) 2013 - 2024, The pgAdmin Development Team
-// This software is released under the PostgreSQL Licence
-//
-//////////////////////////////////////////////////////////////
-
-import React, { useEffect, useReducer } from 'react';
-
-import _ from 'lodash';
-
-import { parseApiError } from 'sources/api_instance';
-import gettext from 'sources/gettext';
-
-import { DepListener } from './DepListener';
-import {
- getSchemaDataDiff,
- validateSchema,
-} from './schemaUtils';
-
-
-export const SchemaStateContext = React.createContext();
-
-export const SCHEMA_STATE_ACTIONS = {
- INIT: 'init',
- SET_VALUE: 'set_value',
- ADD_ROW: 'add_row',
- DELETE_ROW: 'delete_row',
- MOVE_ROW: 'move_row',
- RERENDER: 'rerender',
- CLEAR_DEFERRED_QUEUE: 'clear_deferred_queue',
- DEFERRED_DEPCHANGE: 'deferred_depchange',
- BULK_UPDATE: 'bulk_update',
-};
-
-const getDepChange = (currPath, newState, oldState, action) => {
- if(action.depChange) {
- newState = action.depChange(currPath, newState, {
- type: action.type,
- path: action.path,
- value: action.value,
- oldState: _.cloneDeep(oldState),
- listener: action.listener,
- });
- }
- return newState;
-};
-
-const getDeferredDepChange = (currPath, newState, oldState, action) => {
- if(action.deferredDepChange) {
- return action.deferredDepChange(currPath, newState, {
- type: action.type,
- path: action.path,
- value: action.value,
- depChange: action.depChange,
- oldState: _.cloneDeep(oldState),
- });
- }
-};
-
-/*
- * The main function which manipulates the session state based on actions.
- *
- * The state is managed based on path array of a particular key.
- * For Eg. if the state is
- * {
- * key1: {
- * ckey1: [
- * {a: 0, b: 0},
- * {a: 1, b: 1}
- * ]
- * }
- * }
- *
- * The path for b in first row will be '[key1, ckey1, 0, b]'.
- * The path for second row of ckey1 will be '[key1, ckey1, 1]'.
- *
- * The path for key1 is '[key1]'.
- * The state starts with path '[]'.
- */
-const sessDataReducer = (state, action) => {
- let data = _.cloneDeep(state);
- let rows, cid, deferredList;
- data.__deferred__ = data.__deferred__ || [];
-
- switch(action.type) {
- case SCHEMA_STATE_ACTIONS.INIT:
- data = action.payload;
- break;
-
- case SCHEMA_STATE_ACTIONS.BULK_UPDATE:
- rows = (_.get(data, action.path)||[]);
- rows.forEach((row) => { row[action.id] = false; });
- _.set(data, action.path, rows);
- break;
-
- case SCHEMA_STATE_ACTIONS.SET_VALUE:
- _.set(data, action.path, action.value);
- // If there is any dep listeners get the changes.
- data = getDepChange(action.path, data, state, action);
- deferredList = getDeferredDepChange(action.path, data, state, action);
- data.__deferred__ = deferredList || [];
- break;
-
- case SCHEMA_STATE_ACTIONS.ADD_ROW:
- // Create id to identify a row uniquely, usefull when getting diff.
- cid = _.uniqueId('c');
- action.value['cid'] = cid;
- if (action.addOnTop) {
- rows = [].concat(action.value).concat(_.get(data, action.path)||[]);
- } else {
- rows = (_.get(data, action.path)||[]).concat(action.value);
- }
- _.set(data, action.path, rows);
- // If there is any dep listeners get the changes.
- data = getDepChange(action.path, data, state, action);
- break;
-
- case SCHEMA_STATE_ACTIONS.DELETE_ROW:
- rows = _.get(data, action.path)||[];
- rows.splice(action.value, 1);
- _.set(data, action.path, rows);
- // If there is any dep listeners get the changes.
- data = getDepChange(action.path, data, state, action);
- break;
-
- case SCHEMA_STATE_ACTIONS.MOVE_ROW:
- rows = _.get(data, action.path)||[];
- var row = rows[action.oldIndex];
- rows.splice(action.oldIndex, 1);
- rows.splice(action.newIndex, 0, row);
- _.set(data, action.path, rows);
- break;
-
- case SCHEMA_STATE_ACTIONS.CLEAR_DEFERRED_QUEUE:
- data.__deferred__ = [];
- return data;
-
- case SCHEMA_STATE_ACTIONS.DEFERRED_DEPCHANGE:
- data = getDepChange(action.path, data, state, action);
- break;
- }
-
- data.__changeId = (data.__changeId || 0) + 1;
-
- return data;
-};
-
-function prepareData(val, createMode=false) {
- if(_.isPlainObject(val)) {
- _.forIn(val, function (el) {
- if (_.isObject(el)) {
- prepareData(el, createMode);
- }
- });
- } else if(_.isArray(val)) {
- val.forEach(function(el) {
- if (_.isPlainObject(el)) {
- /* The each row in collection need to have an id to identify them uniquely
- This helps in easily getting what has changed */
- /* Nested collection rows may or may not have idAttribute.
- So to decide whether row is new or not set, the cid starts with
- nn (not new) for existing rows. Newly added will start with 'c' (created)
- */
- el['cid'] = createMode ? _.uniqueId('c') : _.uniqueId('nn');
- prepareData(el, createMode);
- }
- });
- }
- return val;
-}
-
-const LOADING_STATE = {
- INIT: 'initializing',
- LOADING: 'loading',
- LOADED: 'loaded',
- ERROR: 'Error'
-};
-
-export class SchemaState extends DepListener {
-
- constructor(
- schema, getInitData, immutableData, mode, keepCid, onDataChange
- ) {
- super();
-
- ////// Helper variables
-
- // BaseUISchema instance
- this.schema = schema;
- // Current mode of operation ('create', 'edit', 'properties')
- this.mode = mode;
- // Keep the 'cid' object during diff calculations.
- this.keepcid = keepCid;
- // Initialization callback
- this.getInitData = getInitData;
- // Data change callback
- this.onDataChange = onDataChange;
-
- ////// State variables
-
- // Is is ready to be consumed?
- this.isReady = false;
- // Diff between the current snapshot and initial data.
- this.changes = null;
- // Loading message (if any)
- this.message = null;
- // Current Loading state
- this.loadingState = LOADING_STATE.INIT;
- this.hasChanges = false;
-
- ////// Schema instance data
-
- // Initial data after the ready state
- this.initData = {};
- // Current state of the data
- this.data = {};
- // Immutable data
- this.immutableData = immutableData;
- // Current error
- this.errors = {};
- // Pre-ready queue
- this.preReadyQueue = [];
-
- this._id = Date.now();
- }
-
- setError(err) {
- this.errors = err;
- }
-
- setReady(state) {
- this.isReady = state;
- }
-
- setLoadingState(loadingState) {
- this.loadingState = loadingState;
- }
-
- setLoadingMessage(msg) {
- this.message = msg;
- }
-
- // Initialise the data, and fetch the data from the backend (if required).
- // 'force' flag can be used for reloading the data from the backend.
- initialise(dataDispatch, force) {
- let state = this;
-
- // Don't attempt to initialize again (if it's already in progress).
- if (
- state.loadingState !== LOADING_STATE.INIT ||
- (force && state.loadingState === LOADING_STATE.LOADING)
- ) return;
-
- state.setLoadingState(LOADING_STATE.LOADING);
- state.setLoadingMessage(gettext('Loading...'));
-
- /*
- * Fetch the data using getInitData(..) callback.
- * `getInitData(..)` must be present in 'edit' mode.
- */
- if(state.mode === 'edit' && !state.getInitData) {
- throw new Error('getInitData must be passed for edit');
- }
-
- const initDataPromise = state.getInitData?.() ||
- Promise.resolve({});
-
- initDataPromise.then((data) => {
- data = data || {};
-
- if(state.mode === 'edit') {
- // Set the origData to incoming data, useful for comparing.
- state.initData = prepareData({...data, ...state.immutableData});
- } else {
- // In create mode, merge with defaults.
- state.initData = prepareData({
- ...state.schema.defaults, ...data, ...state.immutableData
- }, true);
- }
-
- state.schema.initialise(state.initData);
-
- dataDispatch({
- type: SCHEMA_STATE_ACTIONS.INIT,
- payload: state.initData,
- });
-
- state.setLoadingState(LOADING_STATE.LOADED);
- state.setLoadingMessage('');
- state.setReady(true);
- }).catch((err) => {
- state.setLoadingMessage('');
- state.setError({
- name: 'apierror',
- response: err,
- message: _.escape(parseApiError(err)),
- });
- state.setLoadingState(LOADING_STATE.ERROR);
- state.setReady(true);
- });
- }
-
- validate(sessData) {
- let state = this,
- schema = state.schema;
-
- // If schema does not have the data or does not have any 'onDataChange'
- // callback, there is no need to validate the current data.
- if(!state.isReady) return;
-
- if(
- !validateSchema(schema, sessData, (path, message) => {
- message && state.setError({ name: path, message: _.escape(message) });
- })
- ) state.setError({});
-
- state.data = sessData;
- state.changes = state.Changes();
- state.onDataChange && state.onDataChange(state.hasChanges, state.changes);
- }
-
- Changes(includeSkipChange=false) {
- const state = this;
- const sessData = this.data;
- const schema = state.schema;
-
- // Check if anything changed.
- let dataDiff = getSchemaDataDiff(
- schema, state.initData, sessData,
- state.mode, state.keepCid, false, includeSkipChange
- );
- state.hasChanges = Object.keys(dataDiff).length > 0;
-
- // Inform the callbacks about change in the data.
- if(state.mode !== 'edit') {
- // Merge the changed data with origData in 'create' mode.
- dataDiff = _.assign({}, state.initData, dataDiff);
-
- // Remove internal '__changeId' attribute.
- delete dataDiff.__changeId;
-
- // In case of 'non-edit' mode, changes are always there.
- return dataDiff;
- } else if (state.hasChanges) {
- const idAttr = schema.idAttribute;
- const idVal = state.initData[idAttr];
- // Append 'idAttr' only if it actually exists
- if (idVal) dataDiff[idAttr] = idVal;
-
- return dataDiff;
- }
-
- return {};
- }
-
- get isNew() {
- return this.schema.isNew(this.initData);
- }
-
- set isNew(val) {
- throw new Error('Property \'isNew\' is readonly.', val);
- }
-
- get isDirty() {
- return this.hasChanges;
- }
-
- set isDirty(val) {
- throw new Error('Property \'isDirty\' is readonly.', val);
- }
-}
-
-export const useSchemaState = ({
- schema, getInitData, immutableData, mode, keepCid, onDataChange,
-}) => {
- let schemaState = schema.state;
-
- if (!schemaState) {
- schemaState = new SchemaState(
- schema, getInitData, immutableData, mode, keepCid, onDataChange
- );
- schema.state = schemaState;
- }
-
- const [sessData, sessDispatch] = useReducer(
- sessDataReducer, {...(_.cloneDeep(schemaState.data)), __changeId: 0}
- );
-
- const sessDispatchWithListener = (action) => {
- let dispatchPayload = {
- ...action,
- depChange: (...args) => schemaState.getDepChange(...args),
- deferredDepChange: (...args) => schemaState.getDeferredDepChange(...args),
- };
- /*
- * All the session changes coming before init should be queued up.
- * They will be processed later when form is ready.
- */
- let preReadyQueue = schemaState.preReadyQueue;
-
- preReadyQueue ?
- preReadyQueue.push(dispatchPayload) :
- sessDispatch(dispatchPayload);
- };
-
- schemaState.setUnpreparedData = (path, value) => {
- if(path) {
- let data = prepareData(value);
- _.set(schema.initData, path, data);
- sessDispatchWithListener({
- type: SCHEMA_STATE_ACTIONS.SET_VALUE,
- path: path,
- value: data,
- });
- }
- };
-
- const resetData = () => {
- const initData = _.cloneDeep(schemaState.initData);
- initData.__changeId = sessData.__changeId;
- sessDispatch({
- type: SCHEMA_STATE_ACTIONS.INIT,
- payload: initData,
- });
- };
-
- const reload = () => {
- schemaState.initialise(sessDispatch, true);
- };
-
- useEffect(() => {
- schemaState.initialise(sessDispatch);
- }, [schemaState.loadingState]);
-
- useEffect(() => {
- let preReadyQueue = schemaState.preReadyQueue;
-
- if (!schemaState.isReady || !preReadyQueue) return;
-
- for (const payload of preReadyQueue) {
- sessDispatch(payload);
- }
-
- // Destroy the queue so that no one uses it.
- schemaState.preReadyQueue = null;
- }, [schemaState.isReady]);
-
- useEffect(() => {
- // Validate the schema on the change of the data.
- schemaState.validate(sessData);
- }, [schemaState.isReady, sessData.__changeId]);
-
- useEffect(() => {
- const items = sessData.__deferred__ || [];
-
- if (items.length == 0) return;
-
- sessDispatch({
- type: SCHEMA_STATE_ACTIONS.CLEAR_DEFERRED_QUEUE,
- });
-
- items.forEach((item) => {
- item.promise.then((resFunc) => {
- sessDispatch({
- type: SCHEMA_STATE_ACTIONS.DEFERRED_DEPCHANGE,
- path: item.action.path,
- depChange: item.action.depChange,
- listener: {
- ...item.listener,
- callback: resFunc,
- },
- });
- });
- });
- }, [sessData.__deferred__?.length]);
-
- schemaState.reload = reload;
- schemaState.reset = resetData;
-
- return {
- schemaState,
- dataDispatch: sessDispatchWithListener,
- sessData,
- reset: resetData,
- };
-};
diff --git a/web/pgadmin/static/js/SchemaView/utils/createFieldControls.jsx b/web/pgadmin/static/js/SchemaView/utils/createFieldControls.jsx
new file mode 100644
index 00000000000..18f2c98c063
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/utils/createFieldControls.jsx
@@ -0,0 +1,205 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import _ from 'lodash';
+
+import gettext from 'sources/gettext';
+
+import { SCHEMA_STATE_ACTIONS } from '../SchemaState';
+import { isModeSupportedByField } from '../common';
+import { View, hasView } from '../registry';
+import { StaticMappedFormControl, MappedFormControl } from '../MappedControl';
+
+
+const DEFAULT_TAB = 'general';
+
+export const createFieldControls = ({
+ schema, schemaState, accessPath, viewHelperProps, dataDispatch
+}) => {
+
+ const { mode } = (viewHelperProps || {});
+ const isPropertyMode = mode === 'properties';
+ const groups = [];
+ const groupsById = {};
+ let currentGroup = null;
+
+ const createGroup = (id, label, visible, field, isFullTab) => {
+ const group = {
+ id: id,
+ label: label,
+ visible: visible,
+ field: field,
+ className: isFullTab ? (
+ isPropertyMode ? 'Properties-noPadding' : 'FormView-fullSpace'
+ ) : '',
+ controls: [],
+ inlineGroups: {},
+ isFullTab: isFullTab
+ };
+
+ groups.push(group);
+ groupsById[id] = group;
+
+ return group;
+ };
+
+ // Create default group - 'General'.
+ createGroup(DEFAULT_TAB, gettext('General'), true);
+
+ schema?.fields?.forEach((field) => {
+ if (!isModeSupportedByField(field, viewHelperProps)) return;
+
+ let inlineGroup = null;
+ const inlineGroupId = field[inlineGroup];
+
+ if(field.type === 'group') {
+
+ if (!field.id || (field.id in groups)) {
+ throw new Error('Group-id must be unique within a schema.');
+ }
+
+ const { visible } = schemaState.options(accessPath.concat(field.id));
+ createGroup(field.id, field.label, visible, field);
+
+ return;
+ }
+
+ if (field.isFullTab) {
+ if (field.type === inlineGroup)
+ throw new Error('Inline group can not be full tab control');
+
+ const { visible } = schemaState.options(accessPath.concat(field.id));
+ currentGroup = createGroup(
+ field.id, field.label, visible, field, true
+ );
+ } else {
+ const { group } = field;
+
+ currentGroup = groupsById[group || DEFAULT_TAB];
+
+ if (!currentGroup) {
+ const newGroup = createGroup(group, group, true);
+ currentGroup = newGroup;
+ }
+
+ // Generate inline-view if necessary, or use existing one.
+ if (inlineGroupId) {
+ inlineGroup = currentGroup.inlineGroups[inlineGroupId];
+ if (!inlineGroup) {
+ inlineGroup = currentGroup.inlineGroups[inlineGroupId] = {
+ control: View('InlineView'),
+ controlProps: {
+ viewHelperProps: viewHelperProps,
+ field: null,
+ },
+ controls: [],
+ };
+ currentGroup.controls.push(inlineGroup);
+ }
+ }
+ }
+
+ if (field.type === inlineGroup) {
+ if (inlineGroupId) {
+ throw new Error('inline-group can not be created within inline-group');
+ }
+ inlineGroup = currentGroup.inlineGroups[inlineGroupId];
+ if (inlineGroup) {
+ throw new Error('inline-group must be unique-id within a tab group');
+ }
+ inlineGroup = currentGroup.inlineGroups[inlineGroupId] = {
+ control: View('InlineView'),
+ controlProps: {
+ accessPath: schemaState.accessPath(accessPath, field.id),
+ viewHelperProps: viewHelperProps,
+ field: field,
+ },
+ controls: [],
+ };
+ currentGroup.controls.push(inlineGroup);
+ return;
+ }
+
+ let control = null;
+ const controlProps = {
+ key: field.id,
+ accessPath: schemaState.accessPath(accessPath, field.id),
+ viewHelperProps: viewHelperProps,
+ dataDispatch: dataDispatch,
+ field: field,
+ };
+
+ switch (field.type) {
+ case 'nested-tab':
+ // We don't support nested-tab in 'properties' mode.
+ if (isPropertyMode) return;
+
+ control = View('FormView');
+ controlProps['isNested'] = true;
+ break;
+ case 'nested-fieldset':
+ control = View('FieldSetView');
+ controlProps['controlClassName'] =
+ isPropertyMode ? 'Properties-controlRow' : 'FormView-controlRow';
+ break;
+ case 'collection':
+ control = View('DataGridView');
+ controlProps['containerClassName'] =
+ isPropertyMode ? 'Properties-controlRow' : 'FormView-controlRow';
+ break;
+ default:
+ {
+ control = (
+ hasView(field.type) ? View(field.type) : (
+ field.id ? MappedFormControl : StaticMappedFormControl
+ )
+ );
+
+ if (inlineGroup) {
+ controlProps['withContainer'] = false;
+ controlProps['controlGridBasis'] = 3;
+ }
+
+ controlProps['className'] = field.isFullTab ? '' : (
+ isPropertyMode ? 'Properties-controlRow' : 'FormView-controlRow'
+ );
+
+ if (field.id) {
+ controlProps['id'] = field.id;
+ controlProps['onChange'] = (changeValue) => {
+ // Get the changes on dependent fields as well.
+ dataDispatch?.({
+ type: SCHEMA_STATE_ACTIONS.SET_VALUE,
+ path: controlProps.accessPath,
+ value: changeValue,
+ });
+ };
+ }
+ }
+ break;
+ }
+
+ // Use custom control over the standard one.
+ if (field.CustomControl) {
+ control = field.CustomControl;
+ }
+
+ if (isPropertyMode) field.helpMessage = '';
+
+ // Its a form control.
+ if (_.isEqual(accessPath.concat(field.id), schemaState.errors?.name))
+ currentGroup.hasError = true;
+
+ (inlineGroup || currentGroup).controls.push({control, controlProps});
+ });
+
+ return groups.filter(
+ (group) => (group.visible && group.controls.length)
+ );
+};
diff --git a/web/pgadmin/static/js/SchemaView/utils/index.js b/web/pgadmin/static/js/SchemaView/utils/index.js
new file mode 100644
index 00000000000..b68395f1dfb
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/utils/index.js
@@ -0,0 +1,17 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import { createFieldControls } from './createFieldControls';
+import { listenDepChanges } from './listenDepChanges';
+
+
+export {
+ createFieldControls,
+ listenDepChanges,
+};
diff --git a/web/pgadmin/static/js/SchemaView/utils/listenDepChanges.js b/web/pgadmin/static/js/SchemaView/utils/listenDepChanges.js
new file mode 100644
index 00000000000..6214da30553
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/utils/listenDepChanges.js
@@ -0,0 +1,57 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import { useEffect } from 'react';
+import _ from 'lodash';
+
+import { evalFunc } from 'sources/utils';
+
+
+export const listenDepChanges = (accessPath, field, visible, schemaState) => {
+
+ useEffect(() => {
+ if (!visible || !schemaState || !field) return;
+
+ if(field.depChange || field.deferredDepChange) {
+ schemaState.addDepListener(
+ accessPath, accessPath,
+ field.depChange, field.deferredDepChange
+ );
+ }
+
+ if (field.deps) {
+ const parentPath = [...accessPath];
+
+ // Remove the last element.
+ if (field.id && field.id === parentPath[parentPath.length - 1]) {
+ parentPath.pop();
+ }
+
+ (evalFunc(null, field.deps) || []).forEach((dep) => {
+
+ // When dep is a string then prepend the complete accessPath,
+ // but - when dep is an array, then the intention is to provide
+ // the exact accesspath.
+ let source = _.isArray(dep) ? dep : parentPath.concat(dep);
+
+ if(field.depChange || field.deferredDepChange) {
+ schemaState.addDepListener(
+ source, accessPath, field.depChange, field.deferredDepChange
+ );
+ }
+ });
+ }
+
+ return () => {
+ // Cleanup the listeners when unmounting.
+ schemaState.removeDepListener(accessPath);
+ };
+ }, []);
+
+};
diff --git a/web/pgadmin/static/js/components/FormComponents.jsx b/web/pgadmin/static/js/components/FormComponents.jsx
index efd8aeffe75..95b49307a10 100644
--- a/web/pgadmin/static/js/components/FormComponents.jsx
+++ b/web/pgadmin/static/js/components/FormComponents.jsx
@@ -80,6 +80,9 @@ const Root = styled('div')(({theme}) => ({
backgroundColor: theme.otherVars.borderColor,
padding: theme.spacing(1),
},
+ '& .Form-plainstring': {
+ padding: theme.spacing(0.5),
+ }
}));
@@ -351,12 +354,23 @@ export const InputText = forwardRef(({
cid, helpid, readonly, disabled, value, onChange, controlProps, type, size, inputStyle, ...props }, ref) => {
const maxlength = typeof(controlProps?.maxLength) != 'undefined' ? controlProps.maxLength : 255;
-
const patterns = {
'numeric': '^-?[0-9]\\d*\\.?\\d*$',
'int': '^-?[0-9]\\d*$',
};
- let onChangeFinal = (e) => {
+
+ let finalValue = (_.isNull(value) || _.isUndefined(value)) ? '' : value;
+
+ if (controlProps?.formatter) {
+ finalValue = controlProps.formatter.fromRaw(finalValue);
+ }
+
+ if (_.isNull(finalValue) || _.isUndefined(finalValue)) finalValue = '';
+
+ const [val, setVal] = useState(finalValue);
+
+ useEffect(() => setVal(finalValue), [finalValue]);
+ const onChangeFinal = (e) => {
let changeVal = e.target.value;
/* For type number, we set type as tel with number regex to get validity.*/
@@ -368,14 +382,10 @@ export const InputText = forwardRef(({
if (controlProps?.formatter) {
changeVal = controlProps.formatter.toRaw(changeVal);
}
+ setVal(changeVal);
onChange?.(changeVal);
};
- let finalValue = (_.isNull(value) || _.isUndefined(value)) ? '' : value;
-
- if (controlProps?.formatter) {
- finalValue = controlProps.formatter.fromRaw(finalValue);
- }
const filteredProps = _.pickBy(props, (_v, key)=>(
/* When used in ButtonGroup, following props should be skipped */
@@ -403,7 +413,7 @@ export const InputText = forwardRef(({
disabled={Boolean(disabled)}
rows={4}
notched={false}
- value={(_.isNull(finalValue) || _.isUndefined(finalValue)) ? '' : finalValue}
+ value={val}
onChange={onChangeFinal}
{
...(controlProps?.onKeyDown && { onKeyDown: controlProps.onKeyDown })
@@ -626,7 +636,6 @@ export function InputRadio({ helpid, value, onChange, controlProps, readonly, la
inputProps={{ 'aria-label': value, 'aria-describedby': helpid }}
style={{ padding: 0 }}
disableRipple
- {...props}
/>
}
label={controlProps.label}
@@ -1110,7 +1119,9 @@ export function PlainString({ controlProps, value }) {
if (controlProps?.formatter) {
finalValue = controlProps.formatter.fromRaw(finalValue);
}
- return {finalValue};
+ return
+ {finalValue}
+ ;
}
PlainString.propTypes = {
controlProps: PropTypes.object,
diff --git a/web/pgadmin/static/js/components/PgReactTableStyled.jsx b/web/pgadmin/static/js/components/PgReactTableStyled.jsx
index 5726faa5bb5..c1fe9f763ed 100644
--- a/web/pgadmin/static/js/components/PgReactTableStyled.jsx
+++ b/web/pgadmin/static/js/components/PgReactTableStyled.jsx
@@ -14,7 +14,6 @@ import PropTypes from 'prop-types';
import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
-import DragIndicatorRoundedIcon from '@mui/icons-material/DragIndicatorRounded';
import EditRoundedIcon from '@mui/icons-material/EditRounded';
import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded';
import { PgIconButton } from './Buttons';
@@ -441,18 +440,6 @@ export function getCheckboxHeaderCell({title}) {
return Cell;
}
-export function getReorderCell() {
- const Cell = () => {
- return
-
-
;
- };
-
- Cell.displayName = 'ReorderCell';
-
- return Cell;
-}
-
export function getEditCell({isDisabled, title}) {
const Cell = ({ row }) => {
return } className='pgrt-cell-button'
diff --git a/web/pgadmin/static/js/components/PgTable.jsx b/web/pgadmin/static/js/components/PgTable.jsx
index 4fc1135acfa..b28bb66ba29 100644
--- a/web/pgadmin/static/js/components/PgTable.jsx
+++ b/web/pgadmin/static/js/components/PgTable.jsx
@@ -8,7 +8,10 @@
//////////////////////////////////////////////////////////////
import React, { useMemo, useRef } from 'react';
+import _ from 'lodash';
+import Box from '@mui/material/Box';
+import { styled } from '@mui/material/styles';
import {
useReactTable,
getCoreRowModel,
@@ -24,29 +27,29 @@ import {
keepPreviousData,
} from '@tanstack/react-query';
import { useVirtualizer } from '@tanstack/react-virtual';
-import { styled } from '@mui/material/styles';
import PropTypes from 'prop-types';
-import { InputText } from './FormComponents';
-import _ from 'lodash';
+
+import {
+ BaseUISchema, FormView, SchemaStateContext, useSchemaState, prepareData,
+} from 'sources/SchemaView';
import gettext from 'sources/gettext';
-import SchemaView from '../SchemaView';
+
import EmptyPanelMessage from './EmptyPanelMessage';
+import { InputText } from './FormComponents';
import { PgReactTable, PgReactTableBody, PgReactTableCell, PgReactTableHeader, PgReactTableRow, PgReactTableRowContent, PgReactTableRowExpandContent, getCheckboxCell, getCheckboxHeaderCell } from './PgReactTableStyled';
-import { Box } from '@mui/material';
+
const ROW_HEIGHT = 30;
-function TableRow({ index, style, schema, row, measureElement}) {
- const [expandComplete, setExpandComplete] = React.useState(false);
+
+function TableRow({index, style, schema, row, measureElement}) {
const rowRef = React.useRef();
React.useEffect(() => {
if (rowRef.current) {
- if (!expandComplete && rowRef.current.style.height == `${ROW_HEIGHT}px`) {
- return;
- }
+ if (rowRef.current.style.height == `${ROW_HEIGHT}px`) return;
measureElement(rowRef.current);
}
- }, [row.getIsExpanded(), expandComplete]);
+ }, [row.getIsExpanded()]);
return (
@@ -62,12 +65,10 @@ function TableRow({ index, style, schema, row, measureElement}) {
})}
- Promise.resolve(row.original)}
- viewHelperProps={{ mode: 'properties' }}
+ { setExpandComplete(true); }}
+ viewHelperProps={{ mode: 'properties' }}
/>
@@ -81,7 +82,43 @@ TableRow.propTypes = {
measureElement: PropTypes.func,
};
-export function Table({ columns, data, hasSelectRow, schema, sortOptions, tableProps, searchVal, loadNextPage, ...props }) {
+
+class TableUISchema extends BaseUISchema {
+ constructor(rowSchema) {
+ super();
+ this.rowSchema = rowSchema;
+ }
+
+ get baseFields() {
+ return [{
+ id: 'data', type: 'collection', mode: ['properties'],
+ schema: this.rowSchema,
+ }];
+ }
+}
+
+const getTableSchema = (schema) => {;
+ if (!schema) return null;
+ if (!schema.top) schema.top = new TableUISchema(schema);
+ return schema.top;
+};
+
+export function Table({
+ columns, data, hasSelectRow, schema, sortOptions, tableProps, searchVal,
+ loadNextPage, ...props
+}) {
+ const { schemaState } = useSchemaState({
+ schema: getTableSchema(schema),
+ getInitData: null,
+ viewHelperProps: {mode: 'properties'},
+ });
+
+ // We don't care about validation in static table, hence - initialising the
+ // data directly.
+ if (data.length && schemaState) {
+ schemaState.initData = schemaState.data = prepareData({'data': data});
+ }
+
const defaultColumn = React.useMemo(
() => ({
size: 150,
@@ -103,11 +140,15 @@ export function Table({ columns, data, hasSelectRow, schema, sortOptions, tableP
enableResizing: false,
maxSize: 35,
}] : []).concat(
- columns.filter((c)=>_.isUndefined(c.enableVisibility) ? true : c.enableVisibility).map((c)=>({
+ columns.filter(
+ (c) => _.isUndefined(c.enableVisibility) ? true : c.enableVisibility
+ ).map((c) => ({
...c,
// if data is null then global search doesn't work
// Use accessorFn to return empty string if data is null.
- accessorFn: c.accessorFn ?? (c.accessorKey ? (row)=>row[c.accessorKey] ?? '' : undefined),
+ accessorFn: c.accessorFn ?? (
+ c.accessorKey ? (row) => row[c.accessorKey] ?? '' : undefined
+ ),
}))
), [hasSelectRow, columns]);
@@ -118,24 +159,24 @@ export function Table({ columns, data, hasSelectRow, schema, sortOptions, tableP
let totalFetched = 0;
let totalDBRowCount = 0;
- //Infinite scrolling
- const { _data, fetchNextPage, isFetching } =
- useInfiniteQuery({
- queryKey: ['logs'],
- queryFn: async () => {
- const fetchedData = loadNextPage ? await loadNextPage() : [];
- return fetchedData;
- },
- initialPageParam: 0,
- getNextPageParam: (_lastGroup, groups) => groups.length,
- refetchOnWindowFocus: false,
- placeholderData: keepPreviousData,
- });
+ // Infinite scrolling
+ const { _data, fetchNextPage, isFetching } = useInfiniteQuery({
+ queryKey: ['logs'],
+ queryFn: async () => {
+ const fetchedData = loadNextPage ? await loadNextPage() : [];
+ return fetchedData;
+ },
+ initialPageParam: 0,
+ getNextPageParam: (_lastGroup, groups) => groups.length,
+ refetchOnWindowFocus: false,
+ placeholderData: keepPreviousData,
+ });
flatData = _data || [];
totalFetched = flatData.length;
- //called on scroll and possibly on mount to fetch more data as the user scrolls and reaches bottom of table
+ // Called on scroll and possibly on mount to fetch more data as the user
+ // scrolls and reaches bottom of table.
fetchMoreOnBottomReached = React.useCallback(
(containerRefElement = HTMLDivElement | null) => {
if (containerRefElement) {
@@ -194,22 +235,31 @@ export function Table({ columns, data, hasSelectRow, schema, sortOptions, tableP
});
return (
-
-
- {rows.length == 0 ?
- :
-
- {virtualizer.getVirtualItems().map((virtualRow) => {
- const row = rows[virtualRow.index];
- return ;
- })}
- }
-
+
+
+
+ {rows.length == 0 ? :
+
+ {virtualizer.getVirtualItems().map((virtualRow) => {
+ const row = rows[virtualRow.index];
+ return ;
+ })}
+ }
+
+
);
}
Table.propTypes = {
diff --git a/web/pgadmin/static/js/components/ReactCodeMirror/components/Editor.jsx b/web/pgadmin/static/js/components/ReactCodeMirror/components/Editor.jsx
index f7c22f74484..4a7d9231b06 100644
--- a/web/pgadmin/static/js/components/ReactCodeMirror/components/Editor.jsx
+++ b/web/pgadmin/static/js/components/ReactCodeMirror/components/Editor.jsx
@@ -10,7 +10,10 @@
import React, { useEffect, useMemo, useRef } from 'react';
import ReactDOMServer from 'react-dom/server';
import PropTypes from 'prop-types';
-import { checkTrojanSource } from '../../../utils';
+
+import { useIsMounted } from 'sources/custom_hooks';
+
+import { checkTrojanSource } from 'sources/utils';
import usePreferences from '../../../../../preferences/static/js/store';
import KeyboardArrowRightRoundedIcon from '@mui/icons-material/KeyboardArrowRightRounded';
import ExpandMoreRoundedIcon from '@mui/icons-material/ExpandMoreRounded';
@@ -147,9 +150,12 @@ const defaultExtensions = [
];
export default function Editor({
- currEditor, name, value, options, onCursorActivity, onChange, readonly, disabled, autocomplete = false,
- breakpoint = false, onBreakPointChange, showActiveLine=false,
- keepHistory = true, cid, helpid, labelledBy, customKeyMap, language='pgsql'}) {
+ currEditor, name, value, options, onCursorActivity, onChange, readonly,
+ disabled, autocomplete = false, breakpoint = false, onBreakPointChange,
+ showActiveLine=false, keepHistory = true, cid, helpid, labelledBy,
+ customKeyMap, language='pgsql'
+}) {
+ const checkIsMounted = useIsMounted();
const editorContainerRef = useRef();
const editor = useRef();
@@ -166,6 +172,7 @@ export default function Editor({
const editableConfig = useRef(new Compartment());
useEffect(() => {
+ if (!checkIsMounted()) return;
const finalOptions = { ...defaultOptions, ...options };
const finalExtns = [
(language == 'json') ? json() : sql({dialect: PgSQL}),
@@ -248,6 +255,7 @@ export default function Editor({
}, []);
useMemo(() => {
+ if (!checkIsMounted()) return;
if(editor.current) {
if(value != editor.current.getValue()) {
if(!_.isEmpty(value)) {
@@ -259,14 +267,19 @@ export default function Editor({
}
}, [value]);
- useEffect(()=>{
- const keys = keymap.of([customKeyMap??[], defaultKeymap, closeBracketsKeymap, historyKeymap, foldKeymap, completionKeymap].flat());
+ useEffect(() => {
+ if (!checkIsMounted()) return;
+ const keys = keymap.of([
+ customKeyMap??[], defaultKeymap, closeBracketsKeymap, historyKeymap,
+ foldKeymap, completionKeymap
+ ].flat());
editor.current?.dispatch({
effects: shortcuts.current.reconfigure(keys)
});
}, [customKeyMap]);
useEffect(() => {
+ if (!checkIsMounted()) return;
let pref = preferencesStore.getPreferencesForModule('sqleditor');
let newConfigExtn = [];
@@ -361,6 +374,7 @@ export default function Editor({
}, [preferencesStore]);
useMemo(() => {
+ if (!checkIsMounted()) return;
if (editor.current) {
if (value != editor.current.getValue()) {
editor.current.dispatch({
@@ -371,6 +385,7 @@ export default function Editor({
}, [value]);
useEffect(() => {
+ if (!checkIsMounted()) return;
editor.current?.dispatch({
effects: editableConfig.current.reconfigure([
EditorView.editable.of(editable),
@@ -379,7 +394,7 @@ export default function Editor({
});
}, [readonly, disabled, keepHistory]);
- return useMemo(()=>(
+ return useMemo(() => (
), []);
}
diff --git a/web/pgadmin/static/js/components/SearchInputText.jsx b/web/pgadmin/static/js/components/SearchInputText.jsx
new file mode 100644
index 00000000000..51cd06b3c63
--- /dev/null
+++ b/web/pgadmin/static/js/components/SearchInputText.jsx
@@ -0,0 +1,49 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { InputText } from 'sources/components/FormComponents';
+import gettext from 'sources/gettext';
+
+
+export const SEARCH_INPUT_SIZE = {
+ FULL: 'full',
+ HALF: 'half',
+};
+
+export const SEARCH_INPUT_ALIGNMENT = {
+ LEFT: 'left',
+ RIGHT: 'right'
+};
+
+export const SearchInputText = ({
+ searchText, onChange, placeholder, size, alignment
+}) => {
+ const props = {
+ placeholder: placeholder || gettext('Search'),
+ style: {
+ width: size == SEARCH_INPUT_SIZE.FULL ? '100%' : '50%',
+ float: alignment == SEARCH_INPUT_ALIGNMENT.RIGHT ? 'right' : 'left',
+ },
+ value: searchText,
+ onChange,
+ };
+
+ return ;
+};
+
+SearchInputText.propTypes = {
+ searchText: PropTypes.string.isRequired,
+ onChange: PropTypes.func,
+ placeholder: PropTypes.string,
+ size: PropTypes.oneOf(Object.values(SEARCH_INPUT_SIZE)),
+ alignment: PropTypes.oneOf(Object.values(SEARCH_INPUT_ALIGNMENT)),
+};
diff --git a/web/pgadmin/static/js/helpers/DataGridViewWithHeaderForm.jsx b/web/pgadmin/static/js/helpers/DataGridViewWithHeaderForm.jsx
deleted file mode 100644
index 95cfd429bfd..00000000000
--- a/web/pgadmin/static/js/helpers/DataGridViewWithHeaderForm.jsx
+++ /dev/null
@@ -1,103 +0,0 @@
-/////////////////////////////////////////////////////////////
-//
-// pgAdmin 4 - PostgreSQL Tools
-//
-// Copyright (C) 2013 - 2024, The pgAdmin Development Team
-// This software is released under the PostgreSQL Licence
-//
-//////////////////////////////////////////////////////////////
-
-import React, { useCallback, useEffect, useRef, useState } from 'react';
-import { styled } from '@mui/material/styles';
-import { Box } from '@mui/material';
-import DataGridView, { DataGridHeader } from '../SchemaView/DataGridView';
-import SchemaView, { SCHEMA_STATE_ACTIONS } from '../SchemaView';
-import { DefaultButton } from '../components/Buttons';
-import { evalFunc } from '../utils';
-import PropTypes from 'prop-types';
-import CustomPropTypes from '../custom_prop_types';
-import _ from 'lodash';
-
-const StyledBox = styled(Box)(({theme}) => ({
- '& .DataGridViewWithHeaderForm-border': {
- ...theme.mixins.panelBorder,
- borderBottom: 0,
- '& .DataGridViewWithHeaderForm-body': {
- padding: '0.25rem',
- '& .DataGridViewWithHeaderForm-addBtn': {
- marginLeft: 'auto',
- }
- },
- },
-}));
-
-export default function DataGridViewWithHeaderForm(props) {
- let {containerClassName, headerSchema, headerVisible, ...otherProps} = props;
-
- const headerFormData = useRef({});
- const schemaRef = useRef(otherProps.schema);
- const [addDisabled, setAddDisabled] = useState(true);
- const [headerFormResetKey, setHeaderFormResetKey] = useState(0);
- const onAddClick = useCallback(()=>{
- if(!otherProps.canAddRow) {
- return;
- }
-
- let newRow = headerSchema.getNewData(headerFormData.current);
- otherProps.dataDispatch({
- type: SCHEMA_STATE_ACTIONS.ADD_ROW,
- path: otherProps.accessPath,
- value: newRow,
- });
- setHeaderFormResetKey((preVal)=>preVal+1);
- }, []);
-
- useEffect(()=>{
- headerSchema.top = schemaRef.current.top;
- }, []);
-
- let state = schemaRef.current.top ? _.get(schemaRef.current.top.sessData, _.slice(otherProps.accessPath, 0, -1))
- : _.get(schemaRef.current.sessData);
-
- headerVisible = headerVisible && evalFunc(null, headerVisible, state);
- return (
-
-
- {props.label && }
- {headerVisible &&
- Promise.resolve({})}
- schema={headerSchema}
- viewHelperProps={props.viewHelperProps}
- showFooter={false}
- onDataChange={(isDataChanged, dataChanged)=>{
- headerFormData.current = dataChanged;
- setAddDisabled(headerSchema.addDisabled(headerFormData.current));
- }}
- hasSQL={false}
- isTabView={false}
- resetKey={headerFormResetKey}
- />
-
- Add
-
- }
-
-
-
- );
-}
-
-DataGridViewWithHeaderForm.propTypes = {
- label: PropTypes.string,
- value: PropTypes.array,
- viewHelperProps: PropTypes.object,
- formErr: PropTypes.object,
- headerSchema: CustomPropTypes.schemaUI.isRequired,
- headerVisible: PropTypes.func,
- schema: CustomPropTypes.schemaUI,
- accessPath: PropTypes.array.isRequired,
- dataDispatch: PropTypes.func.isRequired,
- containerClassName: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
-};
diff --git a/web/pgadmin/static/js/helpers/withStandardTabInfo.jsx b/web/pgadmin/static/js/helpers/withStandardTabInfo.jsx
index b22cfbf2f61..6521785a39c 100644
--- a/web/pgadmin/static/js/helpers/withStandardTabInfo.jsx
+++ b/web/pgadmin/static/js/helpers/withStandardTabInfo.jsx
@@ -23,7 +23,7 @@ export default function withStandardTabInfo(Component, tabId) {
const [isActive, setIsActive] = React.useState(false);
const layoutDocker = useContext(LayoutDockerContext);
- useEffect(()=>{
+ useEffect(() => {
const i = pgAdmin.Browser.tree?.selected();
if(i) {
setNodeInfo([true, i, pgAdmin.Browser.tree.itemData(i)]);
@@ -38,22 +38,24 @@ export default function withStandardTabInfo(Component, tabId) {
}
}, 100);
- const onUpdate = (item, data)=>{
- setNodeInfo([true, item, data]);
+ const onUpdate = () => {
+ // Only use the selected tree node item.
+ const item = pgAdmin.Browser.tree?.selected();
+ setNodeInfo([
+ true, item, item && pgAdmin.Browser.tree.itemData(item)
+ ]);
};
let destroyTree = pgAdmin.Browser.Events.on('pgadmin-browser:tree:destroyed', onUpdate);
let deregisterTree = pgAdmin.Browser.Events.on('pgadmin-browser:node:selected', onUpdate);
let deregisterTreeUpdate = pgAdmin.Browser.Events.on('pgadmin-browser:tree:updated', onUpdate);
let deregisterDbConnected = pgAdmin.Browser.Events.on('pgadmin:database:connected', onUpdate);
- let deregisterServerConnected = pgAdmin.Browser.Events.on('pgadmin:server:connected', (_sid, item, data)=>{
- setNodeInfo([true, item, data]);
- });
+ let deregisterServerConnected = pgAdmin.Browser.Events.on('pgadmin:server:connected', onUpdate);
let deregisterActive = layoutDocker.eventBus.registerListener(LAYOUT_EVENTS.ACTIVE, onTabActive);
// if there is any dock changes to the tab and it appears to be active/inactive
let deregisterChange = layoutDocker.eventBus.registerListener(LAYOUT_EVENTS.CHANGE, onTabActive);
- return ()=>{
+ return () => {
onTabActive?.cancel();
destroyTree();
deregisterTree();
diff --git a/web/pgadmin/static/js/utils.js b/web/pgadmin/static/js/utils.js
index 9c3dbfe3d34..edd3fdbc41f 100644
--- a/web/pgadmin/static/js/utils.js
+++ b/web/pgadmin/static/js/utils.js
@@ -677,3 +677,62 @@ export function getChartColor(index, theme='light', colorPalette=CHART_THEME_COL
// loop back if out of index;
return palette[index % palette.length];
}
+
+// Using this function instead of 'btoa' directly.
+// https://developer.mozilla.org/en-US/docs/Glossary/Base64#the_unicode_problem
+function stringToBase64(str) {
+ return btoa(
+ Array.from(
+ new TextEncoder().encode(str),
+ (byte) => String.fromCodePoint(byte),
+ ).join('')
+ );
+}
+
+/************************************
+ *
+ * Memoization of a function.
+ *
+ * NOTE: Please don't use the function, when:
+ * - One of the parameter in the arguments could have a 'circular' dependency.
+ * NOTE: We use `JSON.stringify(...)` for all the arguments.`You could
+ * introduce 'Object.prototype.toJSON(...)' function for the object
+ * with circular dependency, which should return a JSON object without
+ * it.
+ * - It returns a Promise object (asynchronous functions).
+ *
+ * Consider to use 'https://github.com/sindresorhus/p-memoize' for an
+ * asychronous functions.
+ *
+ **/
+export const memoizeFn = fn => new Proxy(fn, {
+ cache: new Map(),
+ apply (target, thisArg, argsList) {
+ let cacheKey = stringToBase64(JSON.stringify(argsList));
+ if(!this.cache.has(cacheKey)) {
+ this.cache.set(cacheKey, target.apply(thisArg, argsList));
+ }
+ return this.cache.get(cacheKey);
+ }
+});
+
+export const memoizeTimeout = (fn, time) => new Proxy(fn, {
+ cache: new Map(),
+ apply (target, thisArg, argsList) {
+ const cacheKey = stringToBase64(JSON.stringify(argsList));
+ const cached = this.cache.get(cacheKey);
+ const timeoutId = setTimeout(() => (this.cache.delete(cacheKey)), time);
+
+ if (cached) {
+ clearInterval(cached.timeoutId);
+ cached.timeoutId = timeoutId;
+
+ return cached.result;
+ }
+
+ const result = target.apply(thisArg, argsList);
+ this.cache.set(cacheKey, {result, timeoutId});
+
+ return result;
+ }
+});
diff --git a/web/pgadmin/tools/backup/static/js/backup.ui.js b/web/pgadmin/tools/backup/static/js/backup.ui.js
index 6cd4569d421..7514064cefd 100644
--- a/web/pgadmin/tools/backup/static/js/backup.ui.js
+++ b/web/pgadmin/tools/backup/static/js/backup.ui.js
@@ -40,7 +40,7 @@ export class SectionSchema extends BaseUISchema {
state.only_tablespaces ||
state.only_roles;
},
- inlineNext: true,
+ inlineGroup: 'section',
}, {
id: 'data',
label: gettext('Data'),
@@ -53,6 +53,7 @@ export class SectionSchema extends BaseUISchema {
state.only_tablespaces ||
state.only_roles;
},
+ inlineGroup: 'section',
}, {
id: 'post_data',
label: gettext('Post-data'),
@@ -65,6 +66,7 @@ export class SectionSchema extends BaseUISchema {
state.only_tablespaces ||
state.only_roles;
},
+ inlineGroup: 'section',
}
];
}
@@ -105,7 +107,7 @@ export class TypeObjSchema extends BaseUISchema {
state.only_tablespaces ||
state.only_roles;
},
- inlineNext: true,
+ inlineGroup: 'type_of_objects',
}, {
id: 'only_schema',
label: gettext('Only schemas'),
@@ -121,7 +123,7 @@ export class TypeObjSchema extends BaseUISchema {
state.only_tablespaces ||
state.only_roles;
},
- inlineNext: true,
+ inlineGroup: 'type_of_objects',
}, {
id: 'only_tablespaces',
label: gettext('Only tablespaces'),
@@ -137,8 +139,8 @@ export class TypeObjSchema extends BaseUISchema {
state.only_schema ||
state.only_roles;
},
- visible: isVisibleForObjectBackup(obj?._top?.backupType),
- inlineNext: true,
+ visible: isVisibleForObjectBackup(obj?.top?.backupType),
+ inlineGroup: 'type_of_objects',
}, {
id: 'only_roles',
label: gettext('Only roles'),
@@ -146,6 +148,7 @@ export class TypeObjSchema extends BaseUISchema {
group: gettext('Type of objects'),
deps: ['pre_data', 'data', 'post_data', 'only_data', 'only_schema',
'only_tablespaces'],
+ inlineGroup: 'type_of_objects',
disabled: function(state) {
return state.pre_data ||
state.data ||
@@ -154,14 +157,15 @@ export class TypeObjSchema extends BaseUISchema {
state.only_schema ||
state.only_tablespaces;
},
- visible: isVisibleForObjectBackup(obj?._top?.backupType)
+ visible: isVisibleForObjectBackup(obj?.top?.backupType)
}, {
id: 'blobs',
label: gettext('Blobs'),
type: 'switch',
group: gettext('Type of objects'),
+ inlineGroup: 'type_of_objects',
visible: function(state) {
- if (!isVisibleForServerBackup(obj?._top?.backupType)) {
+ if (!isVisibleForServerBackup(obj?.top?.backupType)) {
state.blobs = false;
return false;
}
@@ -200,43 +204,43 @@ export class SaveOptSchema extends BaseUISchema {
type: 'switch',
disabled: false,
group: gettext('Do not save'),
- inlineNext: true,
+ inlineGroup: 'do_not_save',
}, {
id: 'dns_no_role_passwords',
label: gettext('Role passwords'),
type: 'switch',
disabled: false,
group: gettext('Do not save'),
- visible: isVisibleForObjectBackup(obj?._top?.backupType),
- inlineNext: true,
+ visible: isVisibleForObjectBackup(obj?.top?.backupType),
+ inlineGroup: 'do_not_save',
}, {
id: 'dns_privilege',
label: gettext('Privileges'),
type: 'switch',
disabled: false,
group: gettext('Do not save'),
- inlineNext: true,
+ inlineGroup: 'do_not_save',
}, {
id: 'dns_tablespace',
label: gettext('Tablespaces'),
type: 'switch',
disabled: false,
group: gettext('Do not save'),
- inlineNext: true,
+ inlineGroup: 'do_not_save',
}, {
id: 'dns_unlogged_tbl_data',
label: gettext('Unlogged table data'),
type: 'switch',
disabled: false,
group: gettext('Do not save'),
- inlineNext: true,
+ inlineGroup: 'do_not_save',
}, {
id: 'dns_comments',
label: gettext('Comments'),
type: 'switch',
disabled: false,
group: gettext('Do not save'),
- inlineNext: true,
+ inlineGroup: 'do_not_save',
min_version: 110000
}, {
id: 'dns_publications',
@@ -244,7 +248,7 @@ export class SaveOptSchema extends BaseUISchema {
type: 'switch',
disabled: false,
group: gettext('Do not save'),
- inlineNext: true,
+ inlineGroup: 'do_not_save',
min_version: 110000
}, {
id: 'dns_subscriptions',
@@ -252,7 +256,7 @@ export class SaveOptSchema extends BaseUISchema {
type: 'switch',
disabled: false,
group: gettext('Do not save'),
- inlineNext: true,
+ inlineGroup: 'do_not_save',
min_version: 110000
}, {
id: 'dns_security_labels',
@@ -260,7 +264,7 @@ export class SaveOptSchema extends BaseUISchema {
type: 'switch',
disabled: false,
group: gettext('Do not save'),
- inlineNext: true,
+ inlineGroup: 'do_not_save',
min_version: 110000
}, {
id: 'dns_toast_compression',
@@ -268,7 +272,7 @@ export class SaveOptSchema extends BaseUISchema {
type: 'switch',
disabled: false,
group: gettext('Do not save'),
- inlineNext: true,
+ inlineGroup: 'do_not_save',
min_version: 140000
}, {
id: 'dns_table_access_method',
@@ -276,7 +280,7 @@ export class SaveOptSchema extends BaseUISchema {
type: 'switch',
disabled: false,
group: gettext('Do not save'),
- inlineNext: true,
+ inlineGroup: 'do_not_save',
min_version: 150000
}];
}
@@ -322,13 +326,14 @@ export class DisabledOptionSchema extends BaseUISchema {
disabled: function(state) {
return !(state.only_data);
},
- inlineNext: true,
+ inlineGroup: 'disable',
}, {
id: 'disable_quoting',
label: gettext('$ quoting'),
type: 'switch',
disabled: false,
group: gettext('Disable'),
+ inlineGroup: 'disable',
}];
}
}
@@ -364,28 +369,29 @@ export class MiscellaneousSchema extends BaseUISchema {
type: 'switch',
disabled: false,
group: gettext('Miscellaneous'),
- inlineNext: true,
+ inlineGroup: 'miscellaneous',
}, {
id: 'dqoute',
label: gettext('Force double quote on identifiers'),
type: 'switch',
disabled: false,
group: gettext('Miscellaneous'),
- inlineNext: true,
+ inlineGroup: 'miscellaneous',
}, {
id: 'use_set_session_auth',
label: gettext('Use SET SESSION AUTHORIZATION'),
type: 'switch',
disabled: false,
group: gettext('Miscellaneous'),
- inlineNext: true,
+ inlineGroup: 'miscellaneous',
}, {
id: 'exclude_schema',
label: gettext('Exclude schema'),
type: 'select',
disabled: false,
group: gettext('Miscellaneous'),
- visible: isVisibleForServerBackup(obj?._top?.backupType),
+ inlineGroup: 'miscellaneous',
+ visible: isVisibleForServerBackup(obj?.top?.backupType),
controlProps: { multiple: true, allowClear: false, creatable: true, noDropdown: true, placeholder: ' ' }
}, {
id: 'exclude_database',
@@ -394,7 +400,7 @@ export class MiscellaneousSchema extends BaseUISchema {
disabled: false,
min_version: 160000,
group: gettext('Miscellaneous'),
- visible: isVisibleForObjectBackup(obj?._top?.backupType),
+ visible: isVisibleForObjectBackup(obj?.top?.backupType),
controlProps: { multiple: true, allowClear: false, creatable: true, noDropdown: true, placeholder: ' ' }
}, {
id: 'extra_float_digits',
@@ -440,7 +446,7 @@ export class ExcludePatternsSchema extends BaseUISchema {
type: 'select',
disabled: false,
group: gettext('Table Options'),
- visible: isVisibleForServerBackup(obj?._top?.backupType),
+ visible: isVisibleForServerBackup(obj?.top?.backupType),
controlProps: { multiple: true, allowClear: false, creatable: true, noDropdown: true, placeholder: ' ' }
}, {
id: 'exclude_table_data',
@@ -448,7 +454,7 @@ export class ExcludePatternsSchema extends BaseUISchema {
type: 'select',
disabled: false,
group: gettext('Table Options'),
- visible: isVisibleForServerBackup(obj?._top?.backupType),
+ visible: isVisibleForServerBackup(obj?.top?.backupType),
controlProps: { multiple: true, allowClear: false, creatable: true, noDropdown: true, placeholder: ' ' }
}, {
id: 'exclude_table_and_children',
@@ -457,7 +463,7 @@ export class ExcludePatternsSchema extends BaseUISchema {
disabled: false,
group: gettext('Table Options'),
min_version: 160000,
- visible: isVisibleForServerBackup(obj?._top?.backupType),
+ visible: isVisibleForServerBackup(obj?.top?.backupType),
controlProps: { multiple: true, allowClear: false, creatable: true, noDropdown: true, placeholder: ' ' }
}, {
id: 'exclude_table_data_and_children',
@@ -466,7 +472,7 @@ export class ExcludePatternsSchema extends BaseUISchema {
disabled: false,
group: gettext('Table Options'),
min_version: 160000,
- visible: isVisibleForServerBackup(obj?._top?.backupType),
+ visible: isVisibleForServerBackup(obj?.top?.backupType),
controlProps: { multiple: true, allowClear: false, creatable: true, noDropdown: true, placeholder: ' ' }
}];
}
@@ -638,7 +644,7 @@ export default class BackupSchema extends BaseUISchema {
state.on_conflict_do_nothing = false;
return true;
},
- inlineNext: obj.backupType !== 'server',
+ inlineGroup: 'miscellaneous',
}, {
id: 'include_create_database',
label: gettext('Include CREATE DATABASE statement'),
@@ -646,7 +652,7 @@ export default class BackupSchema extends BaseUISchema {
disabled: false,
group: gettext('Query Options'),
visible: isVisibleForServerBackup(obj.backupType),
- inlineNext: true,
+ inlineGroup: 'miscellaneous',
}, {
id: 'include_drop_database',
label: gettext('Include DROP DATABASE statement'),
@@ -660,7 +666,7 @@ export default class BackupSchema extends BaseUISchema {
}
return false;
},
- inlineNext: true,
+ inlineGroup: 'miscellaneous',
}, {
id: 'if_exists',
label: gettext('Include IF EXISTS clause'),
@@ -674,6 +680,7 @@ export default class BackupSchema extends BaseUISchema {
state.if_exists = false;
return true;
},
+ inlineGroup: 'miscellaneous',
}, {
id: 'use_column_inserts',
label: gettext('Use Column INSERTS'),
diff --git a/web/pgadmin/tools/debugger/static/js/components/DebuggerArgumentComponent.jsx b/web/pgadmin/tools/debugger/static/js/components/DebuggerArgumentComponent.jsx
index b989662a131..8f306970e68 100644
--- a/web/pgadmin/tools/debugger/static/js/components/DebuggerArgumentComponent.jsx
+++ b/web/pgadmin/tools/debugger/static/js/components/DebuggerArgumentComponent.jsx
@@ -818,9 +818,9 @@ export default function DebuggerArgumentComponent({ debuggerInfo, restartDebug,
onDataChange={(isChanged, changedData) => {
let isValid = false;
let skipStep = false;
- if ('_sessData' in debuggerArgsSchema.current) {
+ if ('sessData' in debuggerArgsSchema.current) {
isValid = true;
- debuggerArgsSchema.current._sessData.aregsCollection.forEach((data) => {
+ debuggerArgsSchema.current.sessData.aregsCollection.forEach((data) => {
if (skipStep) { return; }
diff --git a/web/pgadmin/tools/maintenance/static/js/maintenance.ui.js b/web/pgadmin/tools/maintenance/static/js/maintenance.ui.js
index fea91b7dd98..79913fad4bf 100644
--- a/web/pgadmin/tools/maintenance/static/js/maintenance.ui.js
+++ b/web/pgadmin/tools/maintenance/static/js/maintenance.ui.js
@@ -37,7 +37,7 @@ export class VacuumSchema extends BaseUISchema {
deps: ['op'],
type: 'switch',
label: gettext('FULL'),
- inlineNext: true,
+ inlineGroup: 'operations',
visible: function(state) {
return obj.isApplicableForVacuum(state);
},
@@ -53,7 +53,7 @@ export class VacuumSchema extends BaseUISchema {
deps: ['op'],
type: 'switch',
label: gettext('FREEZE'),
- inlineNext: true,
+ inlineGroup: 'operations',
visible: function(state) {
return obj.isApplicableForVacuum(state);
},
@@ -69,7 +69,7 @@ export class VacuumSchema extends BaseUISchema {
deps: ['op'],
type: 'switch',
label: gettext('ANALYZE'),
- inlineNext: true,
+ inlineGroup: 'operations',
visible: function(state) {
return obj.isApplicableForVacuum(state);
},
@@ -85,7 +85,7 @@ export class VacuumSchema extends BaseUISchema {
deps: ['op', 'vacuum_full'],
type: 'switch',
label: gettext('DISABLE PAGE SKIPPING'),
- inlineNext: true,
+ inlineGroup: 'operations',
disabled: function(state) {
if (!obj.isApplicableForVacuum(state) || state.vacuum_full) {
state.vacuum_disable_page_skipping = false;
@@ -101,7 +101,7 @@ export class VacuumSchema extends BaseUISchema {
deps: ['op'],
type: 'switch',
label: gettext('SKIP LOCKED'),
- inlineNext: true,
+ inlineGroup: 'operations',
visible: function(state) {
return state?.op ? (state.op == 'VACUUM' || state.op == 'ANALYZE') : false;
},
@@ -118,7 +118,7 @@ export class VacuumSchema extends BaseUISchema {
deps: ['op', 'vacuum_full'],
type: 'switch',
label: gettext('TRUNCATE'),
- inlineNext: true,
+ inlineGroup: 'operations',
disabled: function(state) {
if (!obj.isApplicableForVacuum(state) || state.vacuum_full) {
state.vacuum_truncate = false;
@@ -135,7 +135,7 @@ export class VacuumSchema extends BaseUISchema {
deps: ['op'],
type: 'switch',
label: gettext('PROCESS TOAST'),
- inlineNext: true,
+ inlineGroup: 'operations',
visible: function(state) {
return obj.isApplicableForVacuum(state);
},
@@ -152,7 +152,7 @@ export class VacuumSchema extends BaseUISchema {
deps: ['op'],
type: 'switch',
label: gettext('PROCESS MAIN'),
- inlineNext: true,
+ inlineGroup: 'operations',
visible: function(state) {
return obj.isApplicableForVacuum(state);
},
@@ -169,7 +169,7 @@ export class VacuumSchema extends BaseUISchema {
deps: ['op'],
type: 'switch',
label: gettext('SKIP DATABASE STATS'),
- inlineNext: true,
+ inlineGroup: 'operations',
visible: function(state) {
return obj.isApplicableForVacuum(state);
},
@@ -186,7 +186,7 @@ export class VacuumSchema extends BaseUISchema {
deps: ['op'],
type: 'switch',
label: gettext('ONLY DATABASE STATS'),
- inlineNext: true,
+ inlineGroup: 'operations',
visible: function(state) {
return obj.isApplicableForVacuum(state);
},
@@ -202,6 +202,7 @@ export class VacuumSchema extends BaseUISchema {
id: 'vacuum_index_cleanup',
deps: ['op', 'vacuum_full'],
type: 'select',
+ inlineGroup: 'operations',
label: gettext('INDEX CLEANUP'),
controlProps: { allowClear: false, width: '100%' },
options: function () {
@@ -277,7 +278,7 @@ export class VacuumSchema extends BaseUISchema {
return obj.isApplicableForReindex(state);
},
disabled: function(state) {
- if (!obj.isApplicableForReindex(state) || obj?._top?.nodeInfo?.schema) {
+ if (!obj.isApplicableForReindex(state) || obj?.top?.nodeInfo?.schema) {
state.reindex_system = false;
return true;
}
diff --git a/web/pgadmin/tools/restore/static/js/restore.ui.js b/web/pgadmin/tools/restore/static/js/restore.ui.js
index c8e030c5313..52dd5245246 100644
--- a/web/pgadmin/tools/restore/static/js/restore.ui.js
+++ b/web/pgadmin/tools/restore/static/js/restore.ui.js
@@ -42,7 +42,7 @@ export class RestoreSectionSchema extends BaseUISchema {
label: gettext('Pre-data'),
type: 'switch',
group: gettext('Sections'),
- inlineNext: true,
+ inlineGroup: 'sections',
deps: ['only_data', 'only_schema'],
disabled: function(state) {
return obj.isDisabled(state);
@@ -52,7 +52,7 @@ export class RestoreSectionSchema extends BaseUISchema {
label: gettext('Data'),
type: 'switch',
group: gettext('Sections'),
- inlineNext: true,
+ inlineGroup: 'sections',
deps: ['only_data', 'only_schema'],
disabled: function(state) {
return obj.isDisabled(state);
@@ -62,6 +62,7 @@ export class RestoreSectionSchema extends BaseUISchema {
label: gettext('Post-data'),
type: 'switch',
group: gettext('Sections'),
+ inlineGroup: 'sections',
deps: ['only_data', 'only_schema'],
disabled: function(state) {
return obj.isDisabled(state);
@@ -97,7 +98,7 @@ export class RestoreTypeObjSchema extends BaseUISchema {
label: gettext('Only data'),
type: 'switch',
group: gettext('Type of objects'),
- inlineNext: true,
+ inlineGroup: 'types_of_data',
deps: ['pre_data', 'data', 'post_data', 'only_schema'],
disabled: function(state) {
if(obj.selectedNodeType == 'table') {
@@ -115,6 +116,7 @@ export class RestoreTypeObjSchema extends BaseUISchema {
label: gettext('Only schema'),
type: 'switch',
group: gettext('Type of objects'),
+ inlineGroup: 'types_of_data',
deps: ['pre_data', 'data', 'post_data', 'only_data'],
disabled: function(state) {
if(obj.selectedNodeType == 'index' || obj.selectedNodeType == 'function') {
@@ -159,28 +161,28 @@ export class RestoreSaveOptSchema extends BaseUISchema {
label: gettext('Owner'),
type: 'switch',
disabled: false,
- inlineNext: true,
+ inlineGroup: 'save_options',
group: gettext('Do not save'),
}, {
id: 'dns_privilege',
label: gettext('Privileges'),
type: 'switch',
disabled: false,
- inlineNext: true,
+ inlineGroup: 'save_options',
group: gettext('Do not save'),
}, {
id: 'dns_tablespace',
label: gettext('Tablespaces'),
type: 'switch',
disabled: false,
- inlineNext: true,
+ inlineGroup: 'save_options',
group: gettext('Do not save'),
}, {
id: 'dns_comments',
label: gettext('Comments'),
type: 'switch',
disabled: false,
- inlineNext: true,
+ inlineGroup: 'save_options',
group: gettext('Do not save'),
min_version: 110000
}, {
@@ -189,7 +191,7 @@ export class RestoreSaveOptSchema extends BaseUISchema {
type: 'switch',
disabled: false,
group: gettext('Do not save'),
- inlineNext: true,
+ inlineGroup: 'save_options',
min_version: 110000
}, {
id: 'dns_subscriptions',
@@ -197,7 +199,7 @@ export class RestoreSaveOptSchema extends BaseUISchema {
type: 'switch',
disabled: false,
group: gettext('Do not save'),
- inlineNext: true,
+ inlineGroup: 'save_options',
min_version: 110000
}, {
id: 'dns_security_labels',
@@ -205,7 +207,7 @@ export class RestoreSaveOptSchema extends BaseUISchema {
type: 'switch',
disabled: false,
group: gettext('Do not save'),
- inlineNext: true,
+ inlineGroup: 'save_options',
min_version: 110000
}, {
id: 'dns_table_access_method',
@@ -213,7 +215,7 @@ export class RestoreSaveOptSchema extends BaseUISchema {
type: 'switch',
disabled: false,
group: gettext('Do not save'),
- inlineNext: true,
+ inlineGroup: 'save_options',
min_version: 150000
}];
}
@@ -419,7 +421,7 @@ export default class RestoreSchema extends BaseUISchema {
label: gettext('Clean before restore'),
type: 'switch',
group: gettext('Query Options'),
- inlineNext: true,
+ inlineGroup: 'clean',
disabled: function(state) {
if(obj.selectedNodeType === 'function' || obj.selectedNodeType === 'trigger_function') {
state.clean = true;
@@ -431,6 +433,7 @@ export default class RestoreSchema extends BaseUISchema {
label: gettext('Include IF EXISTS clause'),
type: 'switch',
group: gettext('Query Options'),
+ inlineGroup: 'clean',
deps: ['clean'],
disabled: function(state) {
if (state.clean) {
diff --git a/web/pgadmin/tools/sqleditor/static/js/components/dialogs/MacrosDialog.jsx b/web/pgadmin/tools/sqleditor/static/js/components/dialogs/MacrosDialog.jsx
index 790d665f95a..8cd994faac5 100644
--- a/web/pgadmin/tools/sqleditor/static/js/components/dialogs/MacrosDialog.jsx
+++ b/web/pgadmin/tools/sqleditor/static/js/components/dialogs/MacrosDialog.jsx
@@ -27,7 +27,9 @@ class MacrosCollection extends BaseUISchema {
}
/* Returns the new data row for the schema based on defaults and input */
- getNewData(current_macros, data={}) {
+ getNewData(data={}) {
+ const current_macros = this?.top?.sessData.macro;
+
let newRow = {};
this.fields.forEach((field)=>{
newRow[field.id] = this.defaults[field.id];
@@ -36,7 +38,8 @@ class MacrosCollection extends BaseUISchema {
...newRow,
...data,
};
- if (current_macros){
+
+ if (current_macros) {
// Extract an array of existing names from the 'macro' collection
const existingNames = current_macros.map(macro => macro.name);
const newName = getRandomName(existingNames);
diff --git a/web/pgadmin/tools/sqleditor/static/js/show_view_data.js b/web/pgadmin/tools/sqleditor/static/js/show_view_data.js
index 4e6c399c3c6..f5eb2dc8b0c 100644
--- a/web/pgadmin/tools/sqleditor/static/js/show_view_data.js
+++ b/web/pgadmin/tools/sqleditor/static/js/show_view_data.js
@@ -161,7 +161,7 @@ function showFilterDialog(pgBrowser, item, queryToolMod, transId,
let helpUrl = url_for('help.static', {'filename': 'viewdata_filter.html'});
let okCallback = function() {
- queryToolMod.launch(transId, gridUrl, false, queryToolTitle, {sql_filter: schema._sessData.filter_sql});
+ queryToolMod.launch(transId, gridUrl, false, queryToolTitle, {sql_filter: schema.sessData.filter_sql});
};
pgBrowser.Events.trigger('pgadmin:utility:show', item,
diff --git a/web/pgadmin/tools/user_management/static/js/UserManagementDialog.jsx b/web/pgadmin/tools/user_management/static/js/UserManagementDialog.jsx
index 181703f62aa..08ff1724ad8 100644
--- a/web/pgadmin/tools/user_management/static/js/UserManagementDialog.jsx
+++ b/web/pgadmin/tools/user_management/static/js/UserManagementDialog.jsx
@@ -166,8 +166,8 @@ class UserManagementCollection extends BaseUISchema {
}
if (state.auth_source != AUTH_METHODS['INTERNAL']) {
- if (obj.isNew(state) && obj.top?._sessData?.userManagement) {
- for (let user of obj.top._sessData.userManagement) {
+ if (obj.isNew(state) && obj.top?.sessData?.userManagement) {
+ for (let user of obj.top.sessData.userManagement) {
if (user?.id &&
user.username.toLowerCase() == state.username.toLowerCase() &&
user.auth_source == state.auth_source) {
@@ -193,8 +193,8 @@ class UserManagementCollection extends BaseUISchema {
setError('email', null);
}
- if (obj.isNew(state) && obj.top?._sessData?.userManagement) {
- for (let user of obj.top._sessData.userManagement) {
+ if (obj.isNew(state) && obj.top?.sessData?.userManagement) {
+ for (let user of obj.top.sessData.userManagement) {
if (user?.id &&
user.email?.toLowerCase() == state.email?.toLowerCase()) {
msg = gettext('Email address \'%s\' already exists', state.email);
@@ -303,6 +303,7 @@ class UserManagementSchema extends BaseUISchema {
},
{
id: 'refreshBrowserTree', visible: false, type: 'switch',
+ mode: ['non_supported'],
deps: ['userManagement'], depChange: ()=> {
return { refreshBrowserTree: this.changeOwnership };
}
diff --git a/web/regression/README.md b/web/regression/README.md
index a565228a654..32178728f22 100644
--- a/web/regression/README.md
+++ b/web/regression/README.md
@@ -142,7 +142,7 @@ Python Tests:
'pgadmin4/web/pgadmin/utils/test.py' file.
- To run Feature Tests in parallel using selenoid(grid + docker), selenoid
- need to be installed nad should be run only with SERVER_MODE=True.
+ need to be installed and should be run only with SERVER_MODE=True.
Steps to install selenoid -
- Install & Start docker
diff --git a/web/regression/feature_tests/xss_checks_roles_control_test.py b/web/regression/feature_tests/xss_checks_roles_control_test.py
index c1173b7000e..e113c5aea18 100644
--- a/web/regression/feature_tests/xss_checks_roles_control_test.py
+++ b/web/regression/feature_tests/xss_checks_roles_control_test.py
@@ -73,7 +73,7 @@ def _check_role_membership_control(self):
edit_object = self.wait.until(EC.visibility_of_element_located(
(By.CSS_SELECTOR, NavMenuLocators.edit_obj_css)))
edit_object.click()
- membership_tab = WebDriverWait(self.page.driver, 4).until(
+ membership_tab = WebDriverWait(self.page.driver, 2).until(
EC.presence_of_element_located((
By.XPATH, "//button[normalize-space(text())='Membership']")))
membership_tab.click()
diff --git a/web/regression/feature_utils/pgadmin_page.py b/web/regression/feature_utils/pgadmin_page.py
index 2ec898a3d59..2b5a7f296d4 100644
--- a/web/regression/feature_utils/pgadmin_page.py
+++ b/web/regression/feature_utils/pgadmin_page.py
@@ -1147,29 +1147,39 @@ def check_if_element_exists_with_scroll(self, xpath):
bottom_ele = self.driver.find_element(
By.XPATH,
"//div[@id='id-object-explorer']"
- "/div/div/div/div/div[last()]")
- bottom_ele_location = int(
- bottom_ele.value_of_css_property('top').split("px")[0])
+ "/div/div/div/div/div/div[last()]")
+ bottom_ele_top = bottom_ele.value_of_css_property('top')
+ bottom_ele_location = 1
+
+ if (bottom_ele_top != 'auto'):
+ bottom_ele_location = int(
+ bottom_ele_top.split("px")[0]
+ )
if tree_height - bottom_ele_location < 25:
- f_scroll = 0
+ f_scroll = bottom_ele_location - 25
else:
self.driver.execute_script(
- self.js_executor_scrollintoview_arg, bottom_ele)
+ self.js_executor_scrollintoview_arg, bottom_ele
+ )
f_scroll -= 1
elif r_scroll > 0:
top_el = self.driver.find_element(
By.XPATH,
"//div[@id='id-object-explorer']"
"/div/div/div/div/div[1]")
- top_el_location = int(
- top_el.value_of_css_property('top').split("px")[0])
+ top_el_top = top_el.value_of_css_property('top')
+ top_el_location = 0
+
+ if (top_el_top != 'auto'):
+ top_el_location = int(top_el_top.split("px")[0])
if (tree_height - top_el_location) == tree_height:
r_scroll = 0
else:
- webdriver.ActionChains(self.driver).move_to_element(
- top_el).perform()
+ self.driver.execute_script(
+ self.js_executor_scrollintoview_arg, top_el
+ )
r_scroll -= 1
else:
break
diff --git a/web/regression/javascript/SchemaView/store.spec.js b/web/regression/javascript/SchemaView/store.spec.js
new file mode 100644
index 00000000000..16af37a206b
--- /dev/null
+++ b/web/regression/javascript/SchemaView/store.spec.js
@@ -0,0 +1,157 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2024, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import { isValueEqual } from '../../../pgadmin/static/js/SchemaView/common';
+import {
+ createStore
+} from '../../../pgadmin/static/js/SchemaView/SchemaState/store';
+
+const initData = {
+ id: 1,
+ field1: 'field1val',
+ field2: 1,
+ fieldcoll: [
+ {field3: 1, field4: 'field4val1', field5: 'field5val1'},
+ {field3: 2, field4: 'field4val2', field5: 'field5val2'},
+ ],
+ field3: 3,
+ field4: 'field4val',
+};
+
+describe('store', ()=>{
+ describe('', () => {
+
+ it('getState', () => {
+ const store = createStore(initData);
+ const data = store.getState();
+ expect(isValueEqual(data, initData)).toBe(true);
+ });
+
+ it('get', () => {
+ const store = createStore(initData);
+
+ const firstField3 = store.get(['fieldcoll', 0, 'field3']);
+ expect(firstField3 == 1).toBe(true);
+
+ const firstFieldCollRow = store.get(['fieldcoll', '0']);
+ // Sending a copy of the data, and not itself.
+ expect(isValueEqual(firstFieldCollRow, initData.fieldcoll[0])).toBe(true);
+ });
+
+ it('setState', () => {
+ const store = createStore(initData);
+ const newData = {a: 1};
+
+ store.setState(newData);
+
+ const newState = store.getState();
+ expect(Object.is(newState, newData)).toBe(false);
+ expect(isValueEqual(newState, newData)).toBe(true);
+ });
+
+ it ('set', () => {
+ const store = createStore(initData);
+ const newData = {a: 1};
+
+ store.set(newData);
+
+ let newState = store.getState();
+ expect(Object.is(newState, newData)).toBe(false);
+ expect(isValueEqual(newState, newData)).toBe(true);
+
+ store.set((prevState) => ({...prevState, initData}));
+
+ newState = store.getState();
+ expect(Object.is(newState, initData)).toBe(false);
+ expect(isValueEqual(newState, initData)).toBe(false);
+
+ delete newState['a'];
+
+ store.set(() => (newState));
+
+ newState = store.getState();
+ expect(isValueEqual(newState, initData)).toBe(false);
+ });
+
+ it ('subscribe', () => {
+ const store = createStore(initData);
+ const listener = jest.fn();
+
+ const unsubscribe1 = store.subscribe(listener);
+ store.set((prevState) => (prevState));
+
+ expect(listener).not.toHaveBeenCalled();
+
+ store.set((prevState) => {
+ prevState.id = 2;
+ return prevState;
+ });
+
+ expect(listener).toHaveBeenCalled();
+
+ const listenForFirstField3 = jest.fn();
+ const unsubscribe2 = store.subscribeForPath(
+ ['fieldcoll', '0', 'field3'], listenForFirstField3
+ );
+ const listenForSecondField3 = jest.fn();
+ const unsubscribe3 = store.subscribeForPath(
+ ['fieldcoll', '1', 'field3'], listenForSecondField3
+ );
+ let changeTo = 10;
+
+ store.set((prevState) => {
+ prevState.fieldcoll[0].field3 = changeTo;
+ return prevState;
+ });
+
+ expect(listenForFirstField3).toHaveBeenCalled();
+ expect(listener).toHaveBeenCalledTimes(2);
+ expect(listenForSecondField3).not.toHaveBeenCalled();
+
+ store.set((prevState) => {
+ // There is no actual change from previous state.
+ prevState.fieldcoll[0].field3 = 10;
+ return prevState;
+ });
+
+ // Not expecting it be called.
+ expect(listenForFirstField3).toHaveBeenCalledTimes(1);
+ expect(listener).toHaveBeenCalledTimes(2);
+ expect(listenForSecondField3).not.toHaveBeenCalled();
+
+ unsubscribe1();
+
+ store.set((prevState) => {
+ prevState.fieldcoll[0].field3 = 50;
+ return prevState;
+ });
+
+ // Don't expect this to be called again.
+ expect(listener).toHaveBeenCalledTimes(2);
+ // Expect this one to be called
+ expect(listenForFirstField3).toHaveBeenCalledTimes(2);
+ expect(listenForSecondField3).not.toHaveBeenCalled();
+
+ unsubscribe2();
+
+ store.set((prevState) => {
+ prevState.fieldcoll[0].field3 = 100;
+ return prevState;
+ });
+
+ // Don't expect any of them to be called.
+ expect(listener).toHaveBeenCalledTimes(2);
+ expect(listenForFirstField3).toHaveBeenCalledTimes(2);
+ expect(listenForSecondField3).not.toHaveBeenCalled();
+
+ unsubscribe3();
+ });
+
+ });
+});
diff --git a/web/regression/javascript/schema_ui_files/aggregate.ui.spec.js b/web/regression/javascript/schema_ui_files/aggregate.ui.spec.js
index 45de402b705..3da4324b8d8 100644
--- a/web/regression/javascript/schema_ui_files/aggregate.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/aggregate.ui.spec.js
@@ -13,7 +13,7 @@ import {genericBeforeEach, getCreateView, getEditView, getPropertiesView} from '
describe('AggregateSchema', ()=>{
- let schemaObj = new AggregateSchema();
+ let createSchemaObj = () => new AggregateSchema();
let getInitData = ()=>Promise.resolve({});
@@ -25,15 +25,15 @@ describe('AggregateSchema', ()=>{
});
it('create', async ()=>{
- await getCreateView(schemaObj);
+ await getCreateView(createSchemaObj());
});
it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ await getEditView(createSchemaObj(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createSchemaObj(), getInitData);
});
});
diff --git a/web/regression/javascript/schema_ui_files/cast.ui.spec.js b/web/regression/javascript/schema_ui_files/cast.ui.spec.js
index dfc378f84e8..5c9ff1c01e7 100644
--- a/web/regression/javascript/schema_ui_files/cast.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/cast.ui.spec.js
@@ -14,12 +14,13 @@ import {genericBeforeEach, getCreateView, getEditView, getPropertiesView} from '
describe('CastSchema', ()=>{
- let schemaObj = new CastSchema(
+ let createSchemaObj = () => new CastSchema(
{
getTypeOptions: ()=>[],
getFuncOptions: ()=>[],
},
);
+ const schemaObj = createSchemaObj();
let getInitData = ()=>Promise.resolve({});
@@ -29,15 +30,15 @@ describe('CastSchema', ()=>{
});
it('create', async ()=>{
- await getCreateView(schemaObj);
+ await getCreateView(createSchemaObj());
});
it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ await getEditView(createSchemaObj(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createSchemaObj(), getInitData);
});
it('srctyp depChange', ()=>{
diff --git a/web/regression/javascript/schema_ui_files/catalog.ui.spec.js b/web/regression/javascript/schema_ui_files/catalog.ui.spec.js
index 313e21febd6..87cfa858e23 100644
--- a/web/regression/javascript/schema_ui_files/catalog.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/catalog.ui.spec.js
@@ -13,7 +13,7 @@ import {genericBeforeEach, getCreateView, getEditView, getPropertiesView} from '
describe('CatalogSchema', ()=>{
- let catalogObj = new CatalogSchema(
+ let createCatalogObj = () => new CatalogSchema(
{
namespaceowner: '',
}
@@ -29,15 +29,15 @@ describe('CatalogSchema', ()=>{
});
it('create', async ()=>{
- await getCreateView(catalogObj);
+ await getCreateView(createCatalogObj());
});
it('edit', async ()=>{
- await getEditView(catalogObj, getInitData);
+ await getEditView(createCatalogObj(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(catalogObj, getInitData);
+ await getPropertiesView(createCatalogObj(), getInitData);
});
});
diff --git a/web/regression/javascript/schema_ui_files/catalog_object_column.ui.spec.js b/web/regression/javascript/schema_ui_files/catalog_object_column.ui.spec.js
index 4744bdedfed..9efd384d549 100644
--- a/web/regression/javascript/schema_ui_files/catalog_object_column.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/catalog_object_column.ui.spec.js
@@ -13,23 +13,19 @@ import {genericBeforeEach, getCreateView, getPropertiesView} from '../genericFun
describe('CatalogObjectColumn', ()=>{
- let schemaObj = new CatalogObjectColumn();
+ let createSchemaObj = () => new CatalogObjectColumn();
let getInitData = ()=>Promise.resolve({});
-
-
-
-
beforeEach(()=>{
genericBeforeEach();
});
it('create', async ()=>{
- await getCreateView(schemaObj);
+ await getCreateView(createSchemaObj());
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createSchemaObj(), getInitData);
});
});
diff --git a/web/regression/javascript/schema_ui_files/check_constraint.ui.spec.js b/web/regression/javascript/schema_ui_files/check_constraint.ui.spec.js
index 24dc14f11f3..102f53a001d 100644
--- a/web/regression/javascript/schema_ui_files/check_constraint.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/check_constraint.ui.spec.js
@@ -35,7 +35,8 @@ function getFieldDepChange(schema, id) {
describe('CheckConstraintSchema', ()=>{
- let schemaObj = new CheckConstraintSchema();
+ let createSchemaObj = () => new CheckConstraintSchema();
+ let schemaObj = createSchemaObj();
let getInitData = ()=>Promise.resolve({});
@@ -47,15 +48,15 @@ describe('CheckConstraintSchema', ()=>{
});
it('create', async ()=>{
- await getCreateView(schemaObj);
+ await getCreateView(createSchemaObj());
});
it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ await getEditView(createSchemaObj(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createSchemaObj(), getInitData);
});
it('create collection', async ()=>{
diff --git a/web/regression/javascript/schema_ui_files/collation.ui.spec.js b/web/regression/javascript/schema_ui_files/collation.ui.spec.js
index 06c72e1d8e7..9b9855e1ed4 100644
--- a/web/regression/javascript/schema_ui_files/collation.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/collation.ui.spec.js
@@ -12,8 +12,7 @@ import CollationSchema from '../../../pgadmin/browser/server_groups/servers/data
import {genericBeforeEach, getCreateView, getEditView, getPropertiesView} from '../genericFunctions';
describe('CollationsSchema', () => {
-
- let schemaObj = new CollationSchema(
+ const createSchemaObj = () => new CollationSchema(
{
rolesList: () => [],
schemaList: () => [],
@@ -24,24 +23,23 @@ describe('CollationsSchema', () => {
schema: ''
}
);
+ let schemaObj = createSchemaObj();
let getInitData = () => Promise.resolve({});
-
-
beforeEach(() => {
genericBeforeEach();
});
it('create', () => {
- getCreateView(schemaObj);
+ getCreateView(createSchemaObj());
});
it('edit', () => {
- getEditView(schemaObj, getInitData);
+ getEditView(createSchemaObj(), getInitData);
});
it('properties', () => {
- getPropertiesView(schemaObj, getInitData);
+ getPropertiesView(createSchemaObj(), getInitData);
});
it('validate', () => {
diff --git a/web/regression/javascript/schema_ui_files/column.ui.spec.js b/web/regression/javascript/schema_ui_files/column.ui.spec.js
index 95b36e84ef3..4f2ace8fbd5 100644
--- a/web/regression/javascript/schema_ui_files/column.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/column.ui.spec.js
@@ -46,13 +46,13 @@ function getFieldDepChange(schema, id) {
}
describe('ColumnSchema', ()=>{
-
- let schemaObj = new ColumnSchema(
+ const createSchemaObj = () => new ColumnSchema(
()=>new MockSchema(),
{},
()=>Promise.resolve([]),
()=>Promise.resolve([]),
);
+ let schemaObj = createSchemaObj();
let datatypes = [
{value: 'numeric', length: true, precision: true, min_val: 1, max_val: 140391},
{value: 'character varying', length: true, precision: false, min_val: 1, max_val: 140391},
@@ -64,15 +64,15 @@ describe('ColumnSchema', ()=>{
});
it('create', async ()=>{
- await getCreateView(schemaObj);
+ await getCreateView(createSchemaObj());
});
it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ await getEditView(createSchemaObj(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createSchemaObj(), getInitData);
});
it('create collection', async ()=>{
diff --git a/web/regression/javascript/schema_ui_files/compound_trigger.ui.spec.js b/web/regression/javascript/schema_ui_files/compound_trigger.ui.spec.js
index 39403b9973b..2bcae257e61 100644
--- a/web/regression/javascript/schema_ui_files/compound_trigger.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/compound_trigger.ui.spec.js
@@ -13,7 +13,7 @@ import {genericBeforeEach, getCreateView, getEditView, getPropertiesView} from '
describe('CompoundTriggerSchema', ()=>{
- let schemaObj = new CompoundTriggerSchema(
+ const createSchemaObj = () => new CompoundTriggerSchema(
{
columns: [],
},
@@ -23,26 +23,23 @@ describe('CompoundTriggerSchema', ()=>{
table: {}
}
);
+ let schemaObj = createSchemaObj();
let getInitData = ()=>Promise.resolve({});
-
-
-
-
beforeEach(()=>{
genericBeforeEach();
});
it('create', async ()=>{
- await getCreateView(schemaObj);
+ await getCreateView(createSchemaObj());
});
it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ await getEditView(createSchemaObj(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createSchemaObj(), getInitData);
});
it('validate', ()=>{
diff --git a/web/regression/javascript/schema_ui_files/database.ui.spec.js b/web/regression/javascript/schema_ui_files/database.ui.spec.js
index 1986228d2ae..de1f3581466 100644
--- a/web/regression/javascript/schema_ui_files/database.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/database.ui.spec.js
@@ -12,6 +12,7 @@ import _ from 'lodash';
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
import DatabaseSchema from '../../../pgadmin/browser/server_groups/servers/databases/static/js/database.ui';
import {genericBeforeEach, getCreateView, getEditView, getPropertiesView} from '../genericFunctions';
+import { initializeSchemaWithData } from './utils';
class MockSchema extends BaseUISchema {
get baseFields() {
@@ -21,7 +22,7 @@ class MockSchema extends BaseUISchema {
describe('DatabaseSchema', ()=>{
- let schemaObj = new DatabaseSchema(
+ const createSchemaObj = () => new DatabaseSchema(
()=>new MockSchema(),
()=>new MockSchema(),
{
@@ -35,30 +36,29 @@ describe('DatabaseSchema', ()=>{
{
datowner: 'postgres',
}
- );
+ );
+ let schemaObj = createSchemaObj();
let getInitData = ()=>Promise.resolve({});
-
-
-
beforeEach(()=>{
genericBeforeEach();
});
it('create', async ()=>{
- await getCreateView(schemaObj);
+ await getCreateView(createSchemaObj());
});
it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ await getEditView(createSchemaObj(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createSchemaObj(), getInitData);
});
- it('schema_res depChange', ()=>{
+ it('schema_res depChange', () => {
+ initializeSchemaWithData(schemaObj, {});
let depChange = _.find(schemaObj.fields, (f)=>f.id=='schema_res').depChange;
depChange({schema_res: 'abc'});
expect(schemaObj.informText).toBe('Please refresh the Schemas node to make changes to the schema restriction take effect.');
diff --git a/web/regression/javascript/schema_ui_files/domain.ui.spec.js b/web/regression/javascript/schema_ui_files/domain.ui.spec.js
index fb236e58072..14cc207b8fc 100644
--- a/web/regression/javascript/schema_ui_files/domain.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/domain.ui.spec.js
@@ -14,7 +14,7 @@ import {addNewDatagridRow, genericBeforeEach, getCreateView, getEditView, getPro
describe('DomainSchema', ()=>{
- let schemaObj = new DomainSchema(
+ const createSchemaObj = () => new DomainSchema(
{
role: ()=>[],
schema: ()=>[],
@@ -31,23 +31,20 @@ describe('DomainSchema', ()=>{
let getInitData = ()=>Promise.resolve({});
-
-
-
beforeEach(()=>{
genericBeforeEach();
});
it('create', async ()=>{
- await getCreateView(schemaObj);
+ await getCreateView(createSchemaObj());
});
it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ await getEditView(createSchemaObj(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createSchemaObj(), getInitData);
});
});
diff --git a/web/regression/javascript/schema_ui_files/domain_constraint.ui.spec.js b/web/regression/javascript/schema_ui_files/domain_constraint.ui.spec.js
index a992b29fa01..d986538dc6e 100644
--- a/web/regression/javascript/schema_ui_files/domain_constraint.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/domain_constraint.ui.spec.js
@@ -13,27 +13,24 @@ import {genericBeforeEach, getCreateView, getEditView, getPropertiesView} from '
describe('DomainConstraintSchema', ()=>{
- let schemaObj = new DomainConstraintSchema();
+ let createSchemaObj = () => new DomainConstraintSchema();
let getInitData = ()=>Promise.resolve({});
-
-
-
beforeEach(()=>{
genericBeforeEach();
});
it('create', async ()=>{
- await getCreateView(schemaObj);
+ await getCreateView(createSchemaObj());
});
it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ await getEditView(createSchemaObj(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createSchemaObj(), getInitData);
});
});
diff --git a/web/regression/javascript/schema_ui_files/edbfunc.ui.spec.js b/web/regression/javascript/schema_ui_files/edbfunc.ui.spec.js
index 7465b525def..0aeaae41370 100644
--- a/web/regression/javascript/schema_ui_files/edbfunc.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/edbfunc.ui.spec.js
@@ -13,7 +13,8 @@ import {genericBeforeEach, getCreateView, getEditView, getPropertiesView} from '
describe('EDBFuncSchema', ()=>{
- let edbFuncSchemaObj = new EDBFuncSchema(
+
+ let edbFuncSchemaObj = () => new EDBFuncSchema(
{}, {
name: 'sysfunc'
}
@@ -21,23 +22,20 @@ describe('EDBFuncSchema', ()=>{
let getInitData = ()=>Promise.resolve({});
-
-
-
beforeEach(()=>{
genericBeforeEach();
});
it('create', async ()=>{
- await getCreateView(edbFuncSchemaObj);
+ await getCreateView(edbFuncSchemaObj());
});
it('edit', async ()=>{
- await getEditView(edbFuncSchemaObj, getInitData);
+ await getEditView(edbFuncSchemaObj(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(edbFuncSchemaObj, getInitData);
+ await getPropertiesView(edbFuncSchemaObj(), getInitData);
});
});
diff --git a/web/regression/javascript/schema_ui_files/edbvar.ui.spec.js b/web/regression/javascript/schema_ui_files/edbvar.ui.spec.js
index 7418ae832ee..d889b84f6e2 100644
--- a/web/regression/javascript/schema_ui_files/edbvar.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/edbvar.ui.spec.js
@@ -13,27 +13,23 @@ import {genericBeforeEach, getCreateView, getEditView, getPropertiesView} from '
describe('EDBVarSchema', ()=>{
- let edbVarSchemaObj = new EDBVarSchema();
+ let edbVarSchemaObj = () => new EDBVarSchema();
let getInitData = ()=>Promise.resolve({});
-
-
-
-
beforeEach(()=>{
genericBeforeEach();
});
it('create', async ()=>{
- await getCreateView(edbVarSchemaObj);
+ await getCreateView(edbVarSchemaObj());
});
it('edit', async ()=>{
- await getEditView(edbVarSchemaObj, getInitData);
+ await getEditView(edbVarSchemaObj(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(edbVarSchemaObj, getInitData);
+ await getPropertiesView(edbVarSchemaObj(), getInitData);
});
});
diff --git a/web/regression/javascript/schema_ui_files/event_trigger.ui.spec.js b/web/regression/javascript/schema_ui_files/event_trigger.ui.spec.js
index c837452b72f..9657c6fc27b 100644
--- a/web/regression/javascript/schema_ui_files/event_trigger.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/event_trigger.ui.spec.js
@@ -13,7 +13,7 @@ import {genericBeforeEach, getCreateView, getEditView, getPropertiesView} from '
describe('EventTriggerSchema', ()=>{
- let schemaObj = new EventTriggerSchema(
+ const createSchemaObj = () => new EventTriggerSchema(
{
role: ()=>[],
function_names: ()=>[],
@@ -22,26 +22,24 @@ describe('EventTriggerSchema', ()=>{
eventowner: 'postgres'
}
);
+ let schemaObj = createSchemaObj();
let getInitData = ()=>Promise.resolve({});
-
-
-
beforeEach(()=>{
genericBeforeEach();
});
it('create', async ()=>{
- await getCreateView(schemaObj);
+ await getCreateView(createSchemaObj());
});
it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ await getEditView(createSchemaObj(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createSchemaObj(), getInitData);
});
it('validate', ()=>{
diff --git a/web/regression/javascript/schema_ui_files/exclusion_constraint.ui.spec.js b/web/regression/javascript/schema_ui_files/exclusion_constraint.ui.spec.js
index 6acc967fbe1..6df9630446b 100644
--- a/web/regression/javascript/schema_ui_files/exclusion_constraint.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/exclusion_constraint.ui.spec.js
@@ -40,13 +40,14 @@ function getFieldDepChange(schema, id) {
describe('ExclusionConstraintSchema', ()=>{
- let schemaObj;
+ const createSchemaObject = () => getNodeExclusionConstraintSchema(
+ {}, {}, {Nodes: {table: {}}}
+ );
let getInitData = ()=>Promise.resolve({});
beforeAll(()=>{
jest.spyOn(nodeAjax, 'getNodeAjaxOptions').mockReturnValue(Promise.resolve([]));
jest.spyOn(nodeAjax, 'getNodeListByName').mockReturnValue(Promise.resolve([]));
- schemaObj = getNodeExclusionConstraintSchema({}, {}, {Nodes: {table: {}}});
});
beforeEach(()=>{
@@ -54,18 +55,19 @@ describe('ExclusionConstraintSchema', ()=>{
});
it('create', async ()=>{
- await getCreateView(schemaObj);
+ await getCreateView(createSchemaObject());
});
it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ await getEditView(createSchemaObject(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createSchemaObject(), getInitData);
});
it('create collection', async ()=>{
+ let schemaObj = createSchemaObject();
let schemaCollObj = new SchemaInColl(schemaObj);
const {ctrl, user} = await getCreateView(schemaCollObj);
/* Make sure you hit every corner */
@@ -73,6 +75,7 @@ describe('ExclusionConstraintSchema', ()=>{
});
it('changeColumnOptions', ()=>{
+ let schemaObj = createSchemaObject();
jest.spyOn(schemaObj.exHeaderSchema, 'changeColumnOptions');
let columns = [{label: 'label', value: 'value'}];
schemaObj.changeColumnOptions(columns);
@@ -80,6 +83,7 @@ describe('ExclusionConstraintSchema', ()=>{
});
describe('ExclusionColHeaderSchema', ()=>{
+ let schemaObj = createSchemaObject();
it('getNewData', ()=>{
schemaObj.exHeaderSchema.columnOptions = [
{label: 'id', value: 'id', datatype: 'numeric'},
@@ -111,6 +115,7 @@ describe('ExclusionConstraintSchema', ()=>{
});
describe('ExclusionColumnSchema', ()=>{
+ let schemaObj = createSchemaObject();
it('isEditable', ()=>{
schemaObj.exColumnSchema.isNewExCons = false;
expect(schemaObj.exColumnSchema.isEditable()).toBe(false);
@@ -125,6 +130,7 @@ describe('ExclusionConstraintSchema', ()=>{
});
it('depChange', ()=>{
+ let schemaObj = createSchemaObject();
let state = {columns: [{column: 'id'}]};
schemaObj.top = new TableSchema({}, null);
@@ -169,6 +175,7 @@ describe('ExclusionConstraintSchema', ()=>{
});
it('columns formatter', ()=>{
+ let schemaObj = createSchemaObject();
let formatter = _.find(schemaObj.fields, (f)=>f.id=='columns').cell().controlProps.formatter;
expect(formatter.fromRaw([{
column: 'lid',
@@ -180,6 +187,7 @@ describe('ExclusionConstraintSchema', ()=>{
});
describe('amname change', ()=>{
+ let schemaObj = createSchemaObject();
let confirmSpy;
let deferredDepChange;
let operClassOptions = [
@@ -243,6 +251,7 @@ describe('ExclusionConstraintSchema', ()=>{
});
it('validate', ()=>{
+ let schemaObj = createSchemaObject();
let state = {};
let setError = jest.fn();
@@ -255,4 +264,3 @@ describe('ExclusionConstraintSchema', ()=>{
expect(schemaObj.validate(state, setError)).toBe(false);
});
});
-
diff --git a/web/regression/javascript/schema_ui_files/extension.ui.spec.js b/web/regression/javascript/schema_ui_files/extension.ui.spec.js
index d9e062186f3..a44f136f79c 100644
--- a/web/regression/javascript/schema_ui_files/extension.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/extension.ui.spec.js
@@ -13,32 +13,31 @@ import {genericBeforeEach, getCreateView, getEditView, getPropertiesView} from '
describe('ExtensionSchema', ()=>{
- let schemaObj = new ExtensionsSchema(
+ const createSchemaObj = () => new ExtensionsSchema(
{
extensionsList: ()=>[],
schemaList: ()=>[],
}
);
+ let schemaObj = createSchemaObj();
let getInitData = ()=>Promise.resolve({});
-
-
beforeEach(()=>{
genericBeforeEach();
});
it('create', async ()=>{
- await getCreateView(schemaObj);
+ await getCreateView(createSchemaObj());
});
it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ await getEditView(createSchemaObj(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createSchemaObj(), getInitData);
});
it('validate', ()=>{
diff --git a/web/regression/javascript/schema_ui_files/foreign_data_wrapper.ui.spec.js b/web/regression/javascript/schema_ui_files/foreign_data_wrapper.ui.spec.js
index 4c330a0a6f1..0570f77759f 100644
--- a/web/regression/javascript/schema_ui_files/foreign_data_wrapper.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/foreign_data_wrapper.ui.spec.js
@@ -20,7 +20,7 @@ class MockSchema extends BaseUISchema {
describe('ForeignDataWrapperSchema', ()=>{
- let schemaObj = new ForeignDataWrapperSchema(
+ const createSchemaObj = () => new ForeignDataWrapperSchema(
()=>new MockSchema(),
{
role: ()=>[],
@@ -34,23 +34,20 @@ describe('ForeignDataWrapperSchema', ()=>{
let getInitData = ()=>Promise.resolve({});
-
-
-
beforeEach(()=>{
genericBeforeEach();
});
it('create', async ()=>{
- await getCreateView(schemaObj);
+ await getCreateView(createSchemaObj());
});
it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ await getEditView(createSchemaObj(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createSchemaObj(), getInitData);
});
});
diff --git a/web/regression/javascript/schema_ui_files/foreign_key.ui.spec.js b/web/regression/javascript/schema_ui_files/foreign_key.ui.spec.js
index 25d1a4ae3d8..908c2d107c0 100644
--- a/web/regression/javascript/schema_ui_files/foreign_key.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/foreign_key.ui.spec.js
@@ -48,8 +48,6 @@ describe('ForeignKeySchema', ()=>{
schemaObj = getNodeForeignKeySchema({}, {}, {Nodes: {table: {}}});
});
-
-
beforeEach(()=>{
genericBeforeEach();
});
diff --git a/web/regression/javascript/schema_ui_files/foreign_server.ui.spec.js b/web/regression/javascript/schema_ui_files/foreign_server.ui.spec.js
index 5bce4de9fca..0d07db5c118 100644
--- a/web/regression/javascript/schema_ui_files/foreign_server.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/foreign_server.ui.spec.js
@@ -20,7 +20,7 @@ class MockSchema extends BaseUISchema {
describe('ForeignServerSchema', ()=>{
- let schemaObj = new ForeignServerSchema(
+ const createSchemaObj = () => new ForeignServerSchema(
()=>new MockSchema(),
{
role: ()=>[],
@@ -31,24 +31,20 @@ describe('ForeignServerSchema', ()=>{
);
let getInitData = ()=>Promise.resolve({});
-
-
-
-
beforeEach(()=>{
genericBeforeEach();
});
it('create', async ()=>{
- await getCreateView(schemaObj);
+ await getCreateView(createSchemaObj());
});
it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ await getEditView(createSchemaObj(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createSchemaObj(), getInitData);
});
});
diff --git a/web/regression/javascript/schema_ui_files/foreign_table.ui.spec.js b/web/regression/javascript/schema_ui_files/foreign_table.ui.spec.js
index a271497a428..b8de33f3498 100644
--- a/web/regression/javascript/schema_ui_files/foreign_table.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/foreign_table.ui.spec.js
@@ -19,8 +19,7 @@ class MockSchema extends BaseUISchema {
}
describe('ForeignTableSchema', ()=>{
-
- let schemaObj = new ForeignTableSchema(
+ const createSchemaObj = () => new ForeignTableSchema(
()=>new MockSchema(),
()=>new MockSchema(),
()=>new MockSchema(),
@@ -38,6 +37,7 @@ describe('ForeignTableSchema', ()=>{
}
}
);
+ let schemaObj = createSchemaObj();
let getInitData = ()=>Promise.resolve({});
beforeEach(()=>{
@@ -45,15 +45,15 @@ describe('ForeignTableSchema', ()=>{
});
it('create', async ()=>{
- await getCreateView(schemaObj);
+ await getCreateView(createSchemaObj());
});
it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ await getEditView(createSchemaObj(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createSchemaObj(), getInitData);
});
it('validate', ()=>{
diff --git a/web/regression/javascript/schema_ui_files/fts_configuration.ui.spec.js b/web/regression/javascript/schema_ui_files/fts_configuration.ui.spec.js
index af65849ddb5..05e926c071b 100644
--- a/web/regression/javascript/schema_ui_files/fts_configuration.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/fts_configuration.ui.spec.js
@@ -13,7 +13,7 @@ import {genericBeforeEach, getCreateView, getEditView, getPropertiesView} from '
describe('FTSConfigurationSchema', ()=>{
- let schemaObj = new FTSConfigurationSchema(
+ const createSchemaObj = () => new FTSConfigurationSchema(
{
role: ()=>[],
schema: ()=>[],
@@ -27,26 +27,23 @@ describe('FTSConfigurationSchema', ()=>{
schema: 'public',
}
);
- let getInitData = ()=>Promise.resolve({});
-
-
-
-
+ let schemaObj = createSchemaObj();
+ let getInitData = () => Promise.resolve({});
beforeEach(()=>{
genericBeforeEach();
});
it('create', async ()=>{
- await getCreateView(schemaObj);
+ await getCreateView(createSchemaObj());
});
it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ await getEditView(createSchemaObj(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createSchemaObj(), getInitData);
});
it('validate', ()=>{
diff --git a/web/regression/javascript/schema_ui_files/fts_dictionary.ui.spec.js b/web/regression/javascript/schema_ui_files/fts_dictionary.ui.spec.js
index cfafae3d9ea..3d34ba33b12 100644
--- a/web/regression/javascript/schema_ui_files/fts_dictionary.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/fts_dictionary.ui.spec.js
@@ -12,8 +12,7 @@ import FTSDictionarySchema from '../../../pgadmin/browser/server_groups/servers/
import {genericBeforeEach, getCreateView, getEditView, getPropertiesView} from '../genericFunctions';
describe('FTSDictionarySchema', ()=>{
-
- let schemaObj = new FTSDictionarySchema(
+ const createSchemaObj = () => new FTSDictionarySchema(
{
role: ()=>[],
schema: ()=>[],
@@ -28,23 +27,20 @@ describe('FTSDictionarySchema', ()=>{
let getInitData = ()=>Promise.resolve({});
-
-
-
beforeEach(()=>{
genericBeforeEach();
});
it('create', async ()=>{
- await getCreateView(schemaObj);
+ await getCreateView(createSchemaObj());
});
it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ await getEditView(createSchemaObj(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createSchemaObj(), getInitData);
});
});
diff --git a/web/regression/javascript/schema_ui_files/fts_parser.ui.spec.js b/web/regression/javascript/schema_ui_files/fts_parser.ui.spec.js
index f8ff68d7ef6..e8331849e65 100644
--- a/web/regression/javascript/schema_ui_files/fts_parser.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/fts_parser.ui.spec.js
@@ -13,39 +13,35 @@ import {genericBeforeEach, getCreateView, getEditView, getPropertiesView} from '
describe('FTSParserSchema', ()=>{
- let schemaObj = new FTSParserSchema(
+ const createSchemaObj = () => new FTSParserSchema(
{
- prsstartList: ()=> [{ label: '', value: ''}, { label: 'lb1', value: 'val1'}],
- prstokenList: ()=> [{ label: '', value: ''}, { label: 'lb1', value: 'val1'}],
- prsendList: ()=> [{ label: '', value: ''}, { label: 'lb1', value: 'val1'}],
- prslextypeList: ()=> [{ label: '', value: ''}, { label: 'lb1', value: 'val1'}],
- prsheadlineList: ()=> [{ label: '', value: ''}, { label: 'lb1', value: 'val1'}],
- schemaList: ()=> [],
+ prsstartList: () => [{ label: '', value: ''}, { label: 'lb1', value: 'val1'}],
+ prstokenList: () => [{ label: '', value: ''}, { label: 'lb1', value: 'val1'}],
+ prsendList: () => [{ label: '', value: ''}, { label: 'lb1', value: 'val1'}],
+ prslextypeList: () => [{ label: '', value: ''}, { label: 'lb1', value: 'val1'}],
+ prsheadlineList: () => [{ label: '', value: ''}, { label: 'lb1', value: 'val1'}],
+ schemaList: () => [],
},
{
schema: 123
}
);
- let getInitData = ()=>Promise.resolve({});
-
-
-
-
+ let getInitData = () => Promise.resolve({});
beforeEach(()=>{
genericBeforeEach();
});
- it('create', async ()=>{
- await getCreateView(schemaObj);
+ it('create', async () => {
+ await getCreateView(createSchemaObj());
});
- it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ it('edit', async () => {
+ await getEditView(createSchemaObj(), getInitData);
});
- it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ it('properties', async () => {
+ await getPropertiesView(createSchemaObj(), getInitData);
});
});
diff --git a/web/regression/javascript/schema_ui_files/functions.ui.spec.js b/web/regression/javascript/schema_ui_files/functions.ui.spec.js
index 8cd1da7b603..46a9c0524fe 100644
--- a/web/regression/javascript/schema_ui_files/functions.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/functions.ui.spec.js
@@ -21,7 +21,7 @@ class MockSchema extends BaseUISchema {
describe('FunctionSchema', ()=>{
//Procedure schema
- let procedureSchemaObj = new FunctionSchema(
+ const procedureSchemaObj = new FunctionSchema(
()=>new MockSchema(),
()=>new MockSchema(),
{
@@ -61,7 +61,7 @@ describe('FunctionSchema', ()=>{
);
- let schemaObj = new FunctionSchema(
+ const createSchemaObj = () => new FunctionSchema(
() => new MockSchema(),
() => new MockSchema(),
{
@@ -105,7 +105,8 @@ describe('FunctionSchema', ()=>{
funcowner: 'postgres',
pronamespace: 'public',
}
- );
+ );
+ let schemaObj = createSchemaObj();
let getInitData = ()=>Promise.resolve({});
@@ -117,19 +118,19 @@ describe('FunctionSchema', ()=>{
});
it('create', async ()=>{
- await getCreateView(schemaObj);
+ await getCreateView(createSchemaObj());
});
- it('create', async ()=>{
+ it('create procedure', async ()=>{
await getCreateView(procedureSchemaObj);
});
it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ await getEditView(createSchemaObj(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createSchemaObj(), getInitData);
});
it('proiswindow visible', async ()=>{
diff --git a/web/regression/javascript/schema_ui_files/import_export_servers.ui.spec.js b/web/regression/javascript/schema_ui_files/import_export_servers.ui.spec.js
index d5656fdc4da..cdf48e20697 100644
--- a/web/regression/javascript/schema_ui_files/import_export_servers.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/import_export_servers.ui.spec.js
@@ -13,19 +13,20 @@ import {genericBeforeEach, getCreateView} from '../genericFunctions';
describe('ImportExportServers', () => {
- let schemaObj = new ImportExportSelectionSchema();
beforeEach(() => {
genericBeforeEach();
});
it('import', async () => {
+ const schemaObj = new ImportExportSelectionSchema();
await getCreateView(schemaObj);
});
it('export', async () => {
- schemaObj = new ImportExportSelectionSchema(
- {imp_exp: 'e', filename: 'test.json'});
+ const schemaObj = new ImportExportSelectionSchema({
+ imp_exp: 'e', filename: 'test.json'
+ });
await getCreateView(schemaObj);
});
diff --git a/web/regression/javascript/schema_ui_files/index.ui.spec.js b/web/regression/javascript/schema_ui_files/index.ui.spec.js
index 27c1ab9bf8e..246dd7c91f4 100644
--- a/web/regression/javascript/schema_ui_files/index.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/index.ui.spec.js
@@ -30,23 +30,23 @@ class SchemaInColl extends BaseUISchema {
}
function getFieldDepChange(schema, id) {
- return _.find(schema.fields, (f)=>f.id==id)?.depChange;
+ return _.find(schema.fields, (f) => f.id==id)?.depChange;
}
-describe('IndexSchema', ()=>{
+describe('IndexSchema', () => {
let indexSchemaObj;
- let getInitData = ()=>Promise.resolve({});
+ let getInitData = () => Promise.resolve({});
- beforeAll(()=>{
+ beforeAll(() => {
jest.spyOn(nodeAjax, 'getNodeAjaxOptions').mockReturnValue(Promise.resolve([]));
jest.spyOn(nodeAjax, 'getNodeListByName').mockReturnValue(Promise.resolve([]));
indexSchemaObj = new IndexSchema(
{
- tablespaceList: ()=>[],
- amnameList : ()=>[{label:'abc', value:'abc'}],
- columnList: ()=>[{label:'abc', value:'abc'}],
- collationList: ()=>[{label:'abc', value:'abc'}],
- opClassList: ()=>[{label:'abc', value:'abc'}]
+ tablespaceList: () => [],
+ amnameList : () => [{label:'abc', value:'abc'}],
+ columnList: () => [{label:'abc', value:'abc'}],
+ collationList: () => [{label:'abc', value:'abc'}],
+ opClassList: () => [{label:'abc', value:'abc'}]
},
{
node_info: {'server': { 'version': 110000} }
@@ -57,23 +57,23 @@ describe('IndexSchema', ()=>{
);
});
- beforeEach(()=>{
+ beforeEach(() => {
genericBeforeEach();
});
- it('create', async ()=>{
+ it('create', async () => {
await getCreateView(indexSchemaObj);
});
- it('edit', async ()=>{
+ it('edit', async () => {
await getEditView(indexSchemaObj, getInitData);
});
- it('properties', async ()=>{
+ it('properties', async () => {
await getPropertiesView(indexSchemaObj, getInitData);
});
- it('create collection', async ()=>{
+ it('create collection', async () => {
let schemaCollObj = new SchemaInColl(indexSchemaObj);
let {ctrl, user} = await getCreateView(schemaCollObj);
/* Make sure you hit every corner */
@@ -82,15 +82,15 @@ describe('IndexSchema', ()=>{
await addNewDatagridRow(user, ctrl);
});
- it('changeColumnOptions', ()=>{
+ it('changeColumnOptions', () => {
jest.spyOn(indexSchemaObj.indexHeaderSchema, 'changeColumnOptions');
let columns = [{label: 'label', value: 'value'}];
indexSchemaObj.changeColumnOptions(columns);
expect(indexSchemaObj.indexHeaderSchema.changeColumnOptions).toHaveBeenCalledWith(columns);
});
- describe('IndexColHeaderSchema', ()=>{
- it('getNewData', ()=>{
+ describe('IndexColHeaderSchema', () => {
+ it('getNewData', () => {
indexSchemaObj.indexHeaderSchema.columnOptions = [
{label: 'id', value: 'id'},
{label: 'name', value: 'name'}
@@ -118,18 +118,18 @@ describe('IndexSchema', ()=>{
});
});
- describe('IndexColumnSchema', ()=>{
- it('column schema colname editable', ()=>{
- indexSchemaObj.indexColumnSchema._top = {
- _sessData: { amname: 'btree' }
+ describe('IndexColumnSchema', () => {
+ it('column schema colname editable', () => {
+ indexSchemaObj.indexColumnSchema.top = {
+ sessData: { amname: 'btree' }
};
- let cell = _.find(indexSchemaObj.indexColumnSchema.fields, (f)=>f.id=='op_class').cell;
+ let cell = _.find(indexSchemaObj.indexColumnSchema.fields, (f) => f.id=='op_class').cell;
cell();
});
- it('column schema sort_order depChange', ()=>{
+ it('column schema sort_order depChange', () => {
let topState = { amname: 'btree' };
- let depChange = _.find(indexSchemaObj.indexColumnSchema.fields, (f)=>f.id=='sort_order').depChange;
+ let depChange = _.find(indexSchemaObj.indexColumnSchema.fields, (f) => f.id=='sort_order').depChange;
let state = { sort_order: true };
depChange(state, {}, topState, { oldState: { sort_order: false } });
@@ -140,13 +140,13 @@ describe('IndexSchema', ()=>{
expect(state.is_sort_nulls_applicable).toBe(false);
});
- it('column schema sort_order editable', ()=>{
- indexSchemaObj.indexColumnSchema._top = {
- _sessData: { amname: 'btree' }
+ it('column schema sort_order editable', () => {
+ indexSchemaObj.indexColumnSchema.top = {
+ sessData: { amname: 'btree' }
};
let state = {};
jest.spyOn(indexSchemaObj.indexColumnSchema, 'inSchemaWithModelCheck').mockReturnValue(true);
- let editable = _.find(indexSchemaObj.indexColumnSchema.fields, (f)=>f.id=='sort_order').editable;
+ let editable = _.find(indexSchemaObj.indexColumnSchema.fields, (f) => f.id=='sort_order').editable;
let status = editable(state);
expect(status).toBe(false);
@@ -154,18 +154,18 @@ describe('IndexSchema', ()=>{
status = editable(state);
expect(status).toBe(true);
- indexSchemaObj.indexColumnSchema._top._sessData.amname = 'abc';
+ indexSchemaObj.indexColumnSchema.top.sessData.amname = 'abc';
status = editable(state);
expect(status).toBe(false);
});
- it('column schema nulls editable', ()=>{
- indexSchemaObj.indexColumnSchema._top = {
- _sessData: { amname: 'btree' }
+ it('column schema nulls editable', () => {
+ indexSchemaObj.indexColumnSchema.top = {
+ sessData: { amname: 'btree' }
};
let state = {};
jest.spyOn(indexSchemaObj.indexColumnSchema, 'inSchemaWithModelCheck').mockReturnValue(true);
- let editable = _.find(indexSchemaObj.indexColumnSchema.fields, (f)=>f.id=='nulls').editable;
+ let editable = _.find(indexSchemaObj.indexColumnSchema.fields, (f) => f.id=='nulls').editable;
let status = editable(state);
expect(status).toBe(false);
@@ -173,14 +173,14 @@ describe('IndexSchema', ()=>{
status = editable(state);
expect(status).toBe(true);
- indexSchemaObj.indexColumnSchema._top._sessData.amname = 'abc';
+ indexSchemaObj.indexColumnSchema.top.sessData.amname = 'abc';
status = editable(state);
expect(status).toBe(false);
});
- it('column schema setOpClassTypes', ()=>{
- indexSchemaObj.indexColumnSchema._top = {
- _sessData: { amname: 'btree' }
+ it('column schema setOpClassTypes', () => {
+ indexSchemaObj.indexColumnSchema.top = {
+ sessData: { amname: 'btree' }
};
let options = [];
indexSchemaObj.indexColumnSchema.op_class_types = [];
@@ -195,15 +195,15 @@ describe('IndexSchema', ()=>{
});
- it('depChange', ()=>{
+ it('depChange', () => {
let state = {};
expect(getFieldDepChange(indexSchemaObj, 'description')(state)).toEqual({
comment: '',
});
});
- it('columns formatter', ()=>{
- let formatter = _.find(indexSchemaObj.fields, (f)=>f.id=='columns').cell().controlProps.formatter;
+ it('columns formatter', () => {
+ let formatter = _.find(indexSchemaObj.fields, (f) => f.id=='columns').cell().controlProps.formatter;
expect(formatter.fromRaw([{
colname: 'lid',
},{
@@ -213,7 +213,7 @@ describe('IndexSchema', ()=>{
expect(formatter.fromRaw([])).toBe('');
});
- it('validate', ()=>{
+ it('validate', () => {
let state = { columns: [] };
let setError = jest.fn();
diff --git a/web/regression/javascript/schema_ui_files/language.ui.spec.js b/web/regression/javascript/schema_ui_files/language.ui.spec.js
index d28aa27113d..7b1957540fb 100644
--- a/web/regression/javascript/schema_ui_files/language.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/language.ui.spec.js
@@ -20,7 +20,7 @@ class MockSchema extends BaseUISchema {
describe('LanguageSchema', ()=>{
- let schemaObj = new LanguageSchema(
+ const createSchemaObj = () => new LanguageSchema(
()=>new MockSchema(),
{
lan_functions: ()=>[],
@@ -35,26 +35,23 @@ describe('LanguageSchema', ()=>{
},
},
);
+ let schemaObj = createSchemaObj();
let getInitData = ()=>Promise.resolve({});
-
-
-
-
beforeEach(()=>{
genericBeforeEach();
});
it('create', async ()=>{
- await getCreateView(schemaObj);
+ await getCreateView(createSchemaObj());
});
it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ await getEditView(createSchemaObj(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createSchemaObj(), getInitData);
});
it('validate', ()=>{
diff --git a/web/regression/javascript/schema_ui_files/membership.ui.spec.js b/web/regression/javascript/schema_ui_files/membership.ui.spec.js
index 18c409f008c..79bf42cb890 100644
--- a/web/regression/javascript/schema_ui_files/membership.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/membership.ui.spec.js
@@ -30,29 +30,24 @@ class SchemaInColl extends BaseUISchema {
}
describe('MembershipSchema', ()=>{
-
- let schemaObj = new MembershipSchema(
- ()=>[]);
+ const createSchemaObj = () => new MembershipSchema(()=>[]);
+ let schemaObj = createSchemaObj();
let getInitData = ()=>Promise.resolve({});
-
-
-
-
beforeEach(()=>{
genericBeforeEach();
});
it('create', async ()=>{
- await getCreateView(schemaObj);
+ await getCreateView(createSchemaObj());
});
it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ await getEditView(createSchemaObj(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createSchemaObj(), getInitData);
});
it('MembershipMemberSchema', async ()=>{
diff --git a/web/regression/javascript/schema_ui_files/mview.ui.spec.js b/web/regression/javascript/schema_ui_files/mview.ui.spec.js
index 0133038ba9b..90cf0d53ee4 100644
--- a/web/regression/javascript/schema_ui_files/mview.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/mview.ui.spec.js
@@ -11,6 +11,7 @@
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
import MViewSchema from '../../../pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/mview.ui';
import {genericBeforeEach, getCreateView, getEditView, getPropertiesView} from '../genericFunctions';
+import { initializeSchemaWithData } from './utils';
class MockSchema extends BaseUISchema {
get baseFields() {
@@ -20,7 +21,7 @@ class MockSchema extends BaseUISchema {
describe('MaterializedViewSchema', ()=>{
- let schemaObj = new MViewSchema(
+ const createSchemaObject = () => new MViewSchema(
()=>new MockSchema(),
()=>new MockSchema(),
{
@@ -32,7 +33,8 @@ describe('MaterializedViewSchema', ()=>{
owner: 'postgres',
schema: 'public'
}
- );
+ );
+ let schemaObj = createSchemaObject();
let getInitData = ()=>Promise.resolve({});
beforeEach(()=>{
@@ -40,18 +42,19 @@ describe('MaterializedViewSchema', ()=>{
});
it('create', async ()=>{
- await getCreateView(schemaObj);
+ await getCreateView(createSchemaObject());
});
it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ await getEditView(createSchemaObject(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createSchemaObject(), getInitData);
});
it('validate', ()=>{
+ initializeSchemaWithData(schemaObj, {});
let state = {};
let setError = jest.fn();
diff --git a/web/regression/javascript/schema_ui_files/operator.ui.spec.js b/web/regression/javascript/schema_ui_files/operator.ui.spec.js
index 9f88293c63d..acc4d5cb3bd 100644
--- a/web/regression/javascript/schema_ui_files/operator.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/operator.ui.spec.js
@@ -13,27 +13,23 @@ import {genericBeforeEach, getCreateView, getEditView, getPropertiesView} from '
describe('OperatorSchema', ()=>{
- let schemaObj = new OperatorSchema();
+ const createSchemaObject = () => new OperatorSchema();
let getInitData = ()=>Promise.resolve({});
-
-
-
-
beforeEach(()=>{
genericBeforeEach();
});
it('create', async ()=>{
- await getCreateView(schemaObj);
+ await getCreateView(createSchemaObject());
});
it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ await getEditView(createSchemaObject(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createSchemaObject(), getInitData);
});
});
diff --git a/web/regression/javascript/schema_ui_files/packages.ui.spec.js b/web/regression/javascript/schema_ui_files/packages.ui.spec.js
index ea3e4487181..659d9ba8d30 100644
--- a/web/regression/javascript/schema_ui_files/packages.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/packages.ui.spec.js
@@ -15,33 +15,32 @@ import { initializeSchemaWithData } from './utils';
describe('PackageSchema', ()=>{
- let packageSchemaObj = new PackageSchema(
- (privileges)=>getNodePrivilegeRoleSchema({}, {server: {user: {name: 'postgres'}}}, {}, privileges),
+ const createSchemaObject = () => new PackageSchema(
+ (privileges) => getNodePrivilegeRoleSchema(
+ {}, {server: {user: {name: 'postgres'}}}, {}, privileges
+ ),
{
schemas:() => [],
node_info: {'schema': []}
},
);
+ let packageSchemaObj = createSchemaObject();
let getInitData = ()=>Promise.resolve({});
-
-
-
-
beforeEach(()=>{
genericBeforeEach();
});
it('create', async ()=>{
- await getCreateView(packageSchemaObj);
+ await getCreateView(createSchemaObject());
});
it('edit', async ()=>{
- await getEditView(packageSchemaObj, getInitData);
+ await getEditView(createSchemaObject(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(packageSchemaObj, getInitData);
+ await getPropertiesView(createSchemaObject(), getInitData);
});
it('pkgheadsrc depChange', ()=>{
diff --git a/web/regression/javascript/schema_ui_files/partition.ui.spec.js b/web/regression/javascript/schema_ui_files/partition.ui.spec.js
index 7a9988345e9..a6382b89105 100644
--- a/web/regression/javascript/schema_ui_files/partition.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/partition.ui.spec.js
@@ -15,47 +15,46 @@ import {genericBeforeEach, getCreateView, getEditView, getPropertiesView} from '
describe('PartitionTableSchema', ()=>{
- let schemaObj;
+ const createSchemaObject = () => getNodePartitionTableSchema({
+ server: {
+ _id: 1,
+ },
+ schema: {
+ _label: 'public',
+ }
+ }, {}, {
+ Nodes: {table: {}},
+ serverInfo: {
+ 1: {
+ user: {
+ name: 'Postgres',
+ }
+ }
+ }
+ });
+ let schemaObj = createSchemaObject();
let getInitData = ()=>Promise.resolve({});
beforeAll(()=>{
jest.spyOn(nodeAjax, 'getNodeAjaxOptions').mockReturnValue(Promise.resolve([]));
jest.spyOn(nodeAjax, 'getNodeListByName').mockReturnValue(Promise.resolve([]));
- schemaObj = getNodePartitionTableSchema({
- server: {
- _id: 1,
- },
- schema: {
- _label: 'public',
- }
- }, {}, {
- Nodes: {table: {}},
- serverInfo: {
- 1: {
- user: {
- name: 'Postgres',
- }
- }
- }
- });
});
-
beforeEach(()=>{
genericBeforeEach();
});
it('create', async ()=>{
- await getCreateView(schemaObj);
+ await getCreateView(createSchemaObject());
});
it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ await getEditView(createSchemaObject(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createSchemaObject(), getInitData);
});
it('depChange', ()=>{
@@ -96,4 +95,3 @@ describe('PartitionTableSchema', ()=>{
expect(schemaObj.validate(state, setError)).toBe(false);
});
});
-
diff --git a/web/regression/javascript/schema_ui_files/partition.utils.ui.spec.js b/web/regression/javascript/schema_ui_files/partition.utils.ui.spec.js
index eca02bd6aa2..4f7d1280241 100644
--- a/web/regression/javascript/schema_ui_files/partition.utils.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/partition.utils.ui.spec.js
@@ -15,6 +15,7 @@ import { PartitionKeysSchema, PartitionsSchema } from '../../../pgadmin/browser/
import {addNewDatagridRow, genericBeforeEach, getCreateView, getEditView, getPropertiesView} from '../genericFunctions';
import { initializeSchemaWithData } from './utils';
+
function getFieldDepChange(schema, id) {
return _.find(schema.fields, (f)=>f.id==id)?.depChange;
}
@@ -41,24 +42,24 @@ class SchemaInColl extends BaseUISchema {
describe('PartitionKeysSchema', ()=>{
- let schemaObj;
+ const createSchemaObject = () => {
+ let partitionObj = new PartitionKeysSchema();
+ return new SchemaInColl(partitionObj);
+ };
+ let schemaObj = createSchemaObject();
let getInitData = ()=>Promise.resolve({});
beforeAll(()=>{
jest.spyOn(nodeAjax, 'getNodeAjaxOptions').mockReturnValue(Promise.resolve([]));
jest.spyOn(nodeAjax, 'getNodeListByName').mockReturnValue(Promise.resolve([]));
- let partitionObj = new PartitionKeysSchema();
- schemaObj = new SchemaInColl(partitionObj);
});
-
-
beforeEach(()=>{
genericBeforeEach();
});
it('create', async ()=>{
- const {ctrl, user} = await getCreateView(schemaObj);
+ const {ctrl, user} = await getCreateView(createSchemaObject());
/* Make sure you hit every corner */
@@ -67,11 +68,11 @@ describe('PartitionKeysSchema', ()=>{
});
it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ await getEditView(createSchemaObject(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createSchemaObject(), getInitData);
});
it('depChange', ()=>{
@@ -104,14 +105,17 @@ describe('PartitionKeysSchema', ()=>{
describe('PartitionsSchema', ()=>{
- let schemaObj;
+ const createSchemaObject = () => {
+ let schemaObj = new PartitionsSchema();
+ schemaObj.top = schemaObj;
+ return schemaObj;
+ };
+ let schemaObj = createSchemaObject();
let getInitData = ()=>Promise.resolve({});
beforeAll(()=>{
jest.spyOn(nodeAjax, 'getNodeAjaxOptions').mockReturnValue(Promise.resolve([]));
jest.spyOn(nodeAjax, 'getNodeListByName').mockReturnValue(Promise.resolve([]));
- schemaObj = new PartitionsSchema();
- schemaObj.top = schemaObj;
});
@@ -121,15 +125,15 @@ describe('PartitionsSchema', ()=>{
});
it('create', async ()=>{
- await getCreateView(schemaObj);
+ await getCreateView(createSchemaObject());
});
it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ await getEditView(createSchemaObject(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createSchemaObject(), getInitData);
});
it('create collection', async ()=>{
diff --git a/web/regression/javascript/schema_ui_files/pga_job.ui.spec.js b/web/regression/javascript/schema_ui_files/pga_job.ui.spec.js
index 93a58a1e20d..4993339cf33 100644
--- a/web/regression/javascript/schema_ui_files/pga_job.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/pga_job.ui.spec.js
@@ -20,32 +20,28 @@ class MockSchema extends BaseUISchema {
describe('PgaJobSchema', ()=>{
- let schemaObj = new PgaJobSchema(
+ const createSchemaObject = () => new PgaJobSchema(
{
jobjclid:()=>[],
},
()=>new MockSchema(),
- );
+ );
let getInitData = ()=>Promise.resolve({});
-
-
-
-
beforeEach(()=>{
genericBeforeEach();
});
it('create', async ()=>{
- await getCreateView(schemaObj);
+ await getCreateView(createSchemaObject());
});
it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ await getEditView(createSchemaObject(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createSchemaObject(), getInitData);
});
});
diff --git a/web/regression/javascript/schema_ui_files/pga_jobstep.ui.spec.js b/web/regression/javascript/schema_ui_files/pga_jobstep.ui.spec.js
index 124d0f2095a..b8b3af2a18d 100644
--- a/web/regression/javascript/schema_ui_files/pga_jobstep.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/pga_jobstep.ui.spec.js
@@ -13,22 +13,13 @@ import {genericBeforeEach, getCreateView, getEditView, getPropertiesView} from '
describe('PgaJobStepSchema', ()=>{
- let schemaObj = new PgaJobStepSchema(
- {
- databases: ()=>[],
- },
- [],
- {
- jstdbname: 'postgres',
- }
- );
+ let schemaObj;
let getInitData = ()=>Promise.resolve({});
-
-
-
-
beforeEach(()=>{
+ schemaObj = new PgaJobStepSchema(
+ { databases: ()=>[] }, [], { jstdbname: 'postgres' }
+ );
genericBeforeEach();
});
diff --git a/web/regression/javascript/schema_ui_files/pga_schedule.ui.spec.js b/web/regression/javascript/schema_ui_files/pga_schedule.ui.spec.js
index 5a3fe336d72..a9c365d4ad9 100644
--- a/web/regression/javascript/schema_ui_files/pga_schedule.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/pga_schedule.ui.spec.js
@@ -10,34 +10,38 @@
import PgaJobScheduleSchema, { ExceptionsSchema } from '../../../pgadmin/browser/server_groups/servers/pgagent/schedules/static/js/pga_schedule.ui';
import {genericBeforeEach, getCreateView, getEditView, getPropertiesView} from '../genericFunctions';
+import { initializeSchemaWithData } from './utils';
describe('PgaJobScheduleSchema', ()=>{
- let schemaObj = new PgaJobScheduleSchema([], {
+ const createSchemaObject = () => new PgaJobScheduleSchema([], {
jscweekdays:[true,true,true,true,false,false,true],
- jscexceptions:[{'jexid':81,'jexdate':'2021-08-05','jextime':'12:55:00'},{'jexid':83,'jexdate':'2021-08-17','jextime':'20:00:00'}],
+ jscexceptions:[
+ {'jexid':81,'jexdate':'2021-08-05','jextime':'12:55:00'},
+ {'jexid':83,'jexdate':'2021-08-17','jextime':'20:00:00'}
+ ],
});
- let getInitData = ()=>Promise.resolve({});
-
-
+ let getInitData = () => Promise.resolve({});
beforeEach(()=>{
genericBeforeEach();
});
- it('create', async ()=>{
- await getCreateView(schemaObj);
+ it('create', async () => {
+ await getCreateView(createSchemaObject());
});
- it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ it('edit', async () => {
+ await getEditView(createSchemaObject(), getInitData);
});
- it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ it('properties', async () => {
+ await getPropertiesView(createSchemaObject(), getInitData);
});
- it('validate', ()=>{
+ it('validate', () => {
+ let schemaObj = createSchemaObject();
+ initializeSchemaWithData(schemaObj, {});
let state = {};
let setError = jest.fn();
@@ -59,28 +63,30 @@ describe('PgaJobScheduleSchema', ()=>{
});
});
-describe('ExceptionsSchema', ()=>{
+describe('ExceptionsSchema', () => {
- let schemaObj = new ExceptionsSchema();
+ const createSchemaObject = () => new ExceptionsSchema();
let getInitData = ()=>Promise.resolve({});
beforeEach(()=>{
genericBeforeEach();
});
- it('create', async ()=>{
- await getCreateView(schemaObj);
+ it('create', async () => {
+ await getCreateView(createSchemaObject());
});
- it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ it('edit', async () => {
+ await getEditView(createSchemaObject(), getInitData);
});
- it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ it('properties', async () => {
+ await getPropertiesView(createSchemaObject(), getInitData);
});
- it('validate', ()=>{
+ it('validate', () => {
+ let schemaObj = createSchemaObject();
+ initializeSchemaWithData(schemaObj, {});
let state = {};
let setError = jest.fn();
@@ -95,4 +101,3 @@ describe('ExceptionsSchema', ()=>{
expect(setError).toHaveBeenCalledWith('jscdate', null);
});
});
-
diff --git a/web/regression/javascript/schema_ui_files/primary_key.ui.spec.js b/web/regression/javascript/schema_ui_files/primary_key.ui.spec.js
index 35294fe5dc5..ffdc0146d6f 100644
--- a/web/regression/javascript/schema_ui_files/primary_key.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/primary_key.ui.spec.js
@@ -47,8 +47,6 @@ describe('PrimaryKeySchema', ()=>{
}, {});
});
-
-
beforeEach(()=>{
genericBeforeEach();
});
diff --git a/web/regression/javascript/schema_ui_files/privilege.ui.spec.js b/web/regression/javascript/schema_ui_files/privilege.ui.spec.js
index 37517aff9ae..903399a40be 100644
--- a/web/regression/javascript/schema_ui_files/privilege.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/privilege.ui.spec.js
@@ -15,20 +15,17 @@ import {addNewDatagridRow, genericBeforeEach, getCreateView, getEditView, getPro
describe('PrivilegeSchema', ()=>{
- let schemaObj = new PrivilegeRoleSchema(
- ()=>[],
- ()=>[],
- null,
- {server: {user: {name: 'postgres'}}},
- ['X']
- );
+ let schemaObj;
let getInitData = ()=>Promise.resolve({});
-
-
-
-
beforeEach(()=>{
+ schemaObj = new PrivilegeRoleSchema(
+ ()=>[],
+ ()=>[],
+ null,
+ {server: {user: {name: 'postgres'}}},
+ ['X']
+ );
genericBeforeEach();
});
diff --git a/web/regression/javascript/schema_ui_files/publication.ui.spec.js b/web/regression/javascript/schema_ui_files/publication.ui.spec.js
index d989c6ccb4f..620ccee2e61 100644
--- a/web/regression/javascript/schema_ui_files/publication.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/publication.ui.spec.js
@@ -13,30 +13,28 @@ import {genericBeforeEach, getCreateView, getEditView, getPropertiesView} from '
describe('PublicationSchema', ()=>{
- let schemaObj = new PublicationSchema(
- {
- allTables: ()=>[],
- allSchemas:()=>[],
- getColumns: ()=>[],
- role: ()=>[],
- },
- {
- node_info: {
- connected: true,
- user: {id: 10, name: 'postgres', is_superuser: true, can_create_role: true, can_create_db: true},
- user_id: 1,
- username: 'postgres',
- version: 130005,
- },
- },
- );
+ let schemaObj;
let getInitData = ()=>Promise.resolve({});
-
-
-
-
beforeEach(()=>{
+ schemaObj = new PublicationSchema(
+ {
+ allTables: ()=>[],
+ allSchemas:()=>[],
+ getColumns: ()=>[],
+ role: ()=>[],
+ },
+ {
+ node_info: {
+ connected: true,
+ user: {id: 10, name: 'postgres', is_superuser: true, can_create_role: true, can_create_db: true},
+ user_id: 1,
+ username: 'postgres',
+ version: 130005,
+ },
+ },
+ );
+
genericBeforeEach();
});
diff --git a/web/regression/javascript/schema_ui_files/resource_group.ui.spec.js b/web/regression/javascript/schema_ui_files/resource_group.ui.spec.js
index 560e4ae54c2..929b1e37602 100644
--- a/web/regression/javascript/schema_ui_files/resource_group.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/resource_group.ui.spec.js
@@ -13,14 +13,12 @@ import {genericBeforeEach, getCreateView, getEditView, getPropertiesView} from '
describe('ResourceGroupSchema', ()=>{
- let schemaObj = new ResourceGroupSchema();
+ let schemaObj;
let getInitData = ()=>Promise.resolve({});
-
-
-
- beforeEach(()=>{
+ beforeEach(() => {
+ schemaObj = new ResourceGroupSchema();
genericBeforeEach();
});
diff --git a/web/regression/javascript/schema_ui_files/restore.ui.spec.js b/web/regression/javascript/schema_ui_files/restore.ui.spec.js
index 51a1a57846a..96065ab814a 100644
--- a/web/regression/javascript/schema_ui_files/restore.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/restore.ui.spec.js
@@ -14,10 +14,7 @@ import {getCreateView} from '../genericFunctions';
describe('RestoreSchema', ()=>{
-
-
-
- let restoreSchemaObj = new RestoreSchema(
+ const createSchemaObj = () => new RestoreSchema(
()=>getRestoreSectionSchema({selectedNodeType: 'table'}),
()=>getRestoreTypeObjSchema({selectedNodeType: 'table'}),
()=>getRestoreSaveOptSchema({nodeInfo: {server: {version: 11000}}}),
@@ -32,10 +29,11 @@ describe('RestoreSchema', ()=>{
);
it('restore dialog', async ()=>{
- await getCreateView(restoreSchemaObj);
+ await getCreateView(createSchemaObj());
});
it('restore validate', () => {
+ let restoreSchemaObj = createSchemaObj();
let state = { file: undefined }; //validating for empty file
let setError = jest.fn();
diff --git a/web/regression/javascript/schema_ui_files/role.ui.spec.js b/web/regression/javascript/schema_ui_files/role.ui.spec.js
index 9bd13d7a253..7bd8747cc41 100644
--- a/web/regression/javascript/schema_ui_files/role.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/role.ui.spec.js
@@ -20,7 +20,7 @@ class MockSchema extends BaseUISchema {
describe('RoleSchema', ()=>{
- let schemaObj = new RoleSchema(
+ const createSchemaObject = () => new RoleSchema(
()=>new MockSchema(),
()=>new MockSchema(),
{
@@ -30,24 +30,20 @@ describe('RoleSchema', ()=>{
);
let getInitData = ()=>Promise.resolve({});
-
-
-
-
beforeEach(()=>{
genericBeforeEach();
});
it('create', async ()=>{
- await getCreateView(schemaObj);
+ await getCreateView(createSchemaObject());
});
it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ await getEditView(createSchemaObject(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createSchemaObject(), getInitData);
});
});
diff --git a/web/regression/javascript/schema_ui_files/row_security_policy.ui.spec.js b/web/regression/javascript/schema_ui_files/row_security_policy.ui.spec.js
index 0c32e70bcab..79dee87c730 100644
--- a/web/regression/javascript/schema_ui_files/row_security_policy.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/row_security_policy.ui.spec.js
@@ -13,7 +13,7 @@ import {genericBeforeEach, getCreateView, getEditView, getPropertiesView} from '
describe('RowSecurityPolicySchema', ()=>{
- let schemaObj = new RowSecurityPolicySchema(
+ const createSchemaObject = () => new RowSecurityPolicySchema(
{
role: ()=>[],
nodeInfo: {server: {version: 90400}},
@@ -21,24 +21,20 @@ describe('RowSecurityPolicySchema', ()=>{
);
let getInitData = ()=>Promise.resolve({});
-
-
-
-
beforeEach(()=>{
genericBeforeEach();
});
it('create', async ()=>{
- await getCreateView(schemaObj);
+ await getCreateView(createSchemaObject());
});
it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ await getEditView(createSchemaObject(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createSchemaObject(), getInitData);
});
});
diff --git a/web/regression/javascript/schema_ui_files/rule.ui.spec.js b/web/regression/javascript/schema_ui_files/rule.ui.spec.js
index d96fbea2e1c..59ede27060e 100644
--- a/web/regression/javascript/schema_ui_files/rule.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/rule.ui.spec.js
@@ -13,7 +13,7 @@ import {genericBeforeEach, getCreateView, getEditView, getPropertiesView} from '
describe('RuleSchema', ()=>{
- let schemaObj = new RuleSchema(
+ const createSchemaObject = () => new RuleSchema(
{
nodeInfo: {schema: {label: 'public'}, server: {version: 90400}},
nodeData: {label: 'Test'}
@@ -21,24 +21,20 @@ describe('RuleSchema', ()=>{
);
let getInitData = ()=>Promise.resolve({});
-
-
-
-
beforeEach(()=>{
genericBeforeEach();
});
it('create', async ()=>{
- await getCreateView(schemaObj);
+ await getCreateView(createSchemaObject());
});
it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ await getEditView(createSchemaObject(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createSchemaObject(), getInitData);
});
});
diff --git a/web/regression/javascript/schema_ui_files/schema.ui.spec.js b/web/regression/javascript/schema_ui_files/schema.ui.spec.js
index 812db9d945b..0b18eb5fd16 100644
--- a/web/regression/javascript/schema_ui_files/schema.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/schema.ui.spec.js
@@ -14,25 +14,23 @@ import {genericBeforeEach, getCreateView, getEditView, getPropertiesView} from '
describe('PGSchema', ()=>{
- let schemaObj = new PGSchema(
+ const createSchemaObject = () => new PGSchema(
()=>getNodePrivilegeRoleSchema({}, {server: {user: {name: 'postgres'}}}, {}),
{
roles:() => [],
namespaceowner: '',
}
- );
+ );
+ let schemaObj = createSchemaObject();
let getInitData = ()=>Promise.resolve({});
-
-
-
beforeEach(()=>{
genericBeforeEach();
});
it('create', async ()=>{
- await getCreateView(schemaObj);
+ await getCreateView(createSchemaObject());
});
it('schema validate', () => {
@@ -48,11 +46,11 @@ describe('PGSchema', ()=>{
});
it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ await getEditView(createSchemaObject(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createSchemaObject(), getInitData);
});
});
diff --git a/web/regression/javascript/schema_ui_files/sequence.ui.spec.js b/web/regression/javascript/schema_ui_files/sequence.ui.spec.js
index a3b0eb67103..1d3364520a4 100644
--- a/web/regression/javascript/schema_ui_files/sequence.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/sequence.ui.spec.js
@@ -20,7 +20,7 @@ class MockSchema extends BaseUISchema {
describe('SequenceSchema', ()=>{
- let schemaObj = new SequenceSchema(
+ const createSchemaObject = () => new SequenceSchema(
()=>new MockSchema(),
{
role: ()=>[],
@@ -30,26 +30,23 @@ describe('SequenceSchema', ()=>{
schema: 'public',
}
);
- let getInitData = ()=>Promise.resolve({});
-
-
-
-
+ let schemaObj = createSchemaObject();
+ let getInitData = () => Promise.resolve({});
beforeEach(()=>{
genericBeforeEach();
});
it('create', async ()=>{
- await getCreateView(schemaObj);
+ await getCreateView(createSchemaObject());
});
it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ await getEditView(createSchemaObject(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createSchemaObject(), getInitData);
});
it('validate', ()=>{
diff --git a/web/regression/javascript/schema_ui_files/server.ui.spec.js b/web/regression/javascript/schema_ui_files/server.ui.spec.js
index 47a5d1727b1..e9ae2451d69 100644
--- a/web/regression/javascript/schema_ui_files/server.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/server.ui.spec.js
@@ -14,11 +14,12 @@ import {genericBeforeEach, getCreateView, getEditView, getPropertiesView} from '
describe('ServerSchema', ()=>{
- let schemaObj = new ServerSchema([{
+ const createSchemaObject = () => new ServerSchema([{
label: 'Servers', value: 1,
}], 0, {
user_id: 'jasmine',
});
+ let schemaObj = createSchemaObject();
let getInitData = ()=>Promise.resolve({});
beforeEach(()=>{
@@ -27,15 +28,15 @@ describe('ServerSchema', ()=>{
});
it('create', async ()=>{
- await getCreateView(schemaObj);
+ await getCreateView(createSchemaObject());
});
it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ await getEditView(createSchemaObject(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createSchemaObject(), getInitData);
});
it('validate', ()=>{
diff --git a/web/regression/javascript/schema_ui_files/server_group.ui.spec.js b/web/regression/javascript/schema_ui_files/server_group.ui.spec.js
index 17357e29509..3dbf59b94ff 100644
--- a/web/regression/javascript/schema_ui_files/server_group.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/server_group.ui.spec.js
@@ -13,26 +13,22 @@ import {genericBeforeEach, getCreateView, getEditView, getPropertiesView} from '
describe('ServerGroupSchema', ()=>{
- let schemaObj = new ServerGroupSchema();
+ const createSchemaObject = () => new ServerGroupSchema();
let getInitData = ()=>Promise.resolve({});
-
-
-
-
beforeEach(()=>{
genericBeforeEach();
});
it('create', async ()=>{
- await getCreateView(schemaObj);
+ await getCreateView(createSchemaObject());
});
it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ await getEditView(createSchemaObject(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createSchemaObject(), getInitData);
});
});
diff --git a/web/regression/javascript/schema_ui_files/subscription.ui.spec.js b/web/regression/javascript/schema_ui_files/subscription.ui.spec.js
index 9de21e98f5b..51f51cb3f61 100644
--- a/web/regression/javascript/schema_ui_files/subscription.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/subscription.ui.spec.js
@@ -11,63 +11,60 @@
import SubscriptionSchema from '../../../pgadmin/browser/server_groups/servers/databases/subscriptions/static/js/subscription.ui';
import {genericBeforeEach, getCreateView, getEditView, getPropertiesView} from '../genericFunctions';
-describe('SubscriptionSchema', ()=>{
-
- let schemaObj = new SubscriptionSchema(
- {
- getPublication: ()=>[],
- role: ()=>[],
- },
- {
- node_info: {
- connected: true,
- user: {id: 10, name: 'postgres', is_superuser: true, can_create_role: true, can_create_db: true},
- user_id: 1,
- username: 'postgres',
- version: 130005,
- server: {host: '127.0.0.1', port: 5432},
- },
- },
- {
- subowner : 'postgres'
- }
- );
- let getInitData = ()=>Promise.resolve({});
-
-
-
+describe('SubscriptionSchema', () => {
+ let schemaObj;
+ let getInitData = ()=>Promise.resolve({});
- beforeEach(()=>{
+ beforeEach(() => {
+ schemaObj = new SubscriptionSchema(
+ {
+ getPublication: ()=>[],
+ role: ()=>[],
+ },
+ {
+ node_info: {
+ connected: true,
+ user: {id: 10, name: 'postgres', is_superuser: true, can_create_role: true, can_create_db: true},
+ user_id: 1,
+ username: 'postgres',
+ version: 130005,
+ server: {host: '127.0.0.1', port: 5432},
+ },
+ },
+ {
+ subowner : 'postgres'
+ }
+ );
genericBeforeEach();
});
- it('create', async ()=>{
+ it('create', async () => {
await getCreateView(schemaObj);
});
- it('edit', async ()=>{
+ it('edit', async () => {
await getEditView(schemaObj, getInitData);
});
- it('properties', async ()=>{
+ it('properties', async () => {
await getPropertiesView(schemaObj, getInitData);
});
- it('copy_data_after_refresh readonly', ()=>{
+ it('copy_data_after_refresh readonly', () => {
let isReadonly = _.find(schemaObj.fields, (f)=>f.id=='copy_data_after_refresh').readonly;
let status = isReadonly({host: '127.0.0.1', port : 5432});
expect(status).toBe(true);
});
- it('copy_data_after_refresh readonly', ()=>{
+ it('copy_data_after_refresh readonly', () => {
let isReadonly = _.find(schemaObj.fields, (f)=>f.id=='copy_data_after_refresh').readonly;
let status = isReadonly({refresh_pub : true});
expect(status).toBe(false);
});
- it('validate', ()=>{
+ it('validate', () => {
let state = {};
let setError = jest.fn();
@@ -88,4 +85,3 @@ describe('SubscriptionSchema', ()=>{
expect(setError).toHaveBeenCalledWith('pub', 'Publication must be specified.');
});
});
-
diff --git a/web/regression/javascript/schema_ui_files/synonym.ui.spec.js b/web/regression/javascript/schema_ui_files/synonym.ui.spec.js
index 8d63a1317f2..d188e620fc6 100644
--- a/web/regression/javascript/schema_ui_files/synonym.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/synonym.ui.spec.js
@@ -11,45 +11,42 @@
import SynonymSchema from '../../../pgadmin/browser/server_groups/servers/databases/schemas/synonyms/static/js/synonym.ui';
import {genericBeforeEach, getCreateView, getEditView, getPropertiesView} from '../genericFunctions';
-describe('SynonymSchema', ()=>{
+describe('SynonymSchema', () => {
- let schemaObj = new SynonymSchema(
- {
- role: ()=>[],
- schema: ()=>[],
- synobjschema: ()=>[],
- getTargetObjectOptions: ()=>[],
- },
- [],
- {
- owner: 'postgres',
- schema: 'public',
- synobjschema: 'public',
- }
- );
+ let schemaObj;
let getInitData = ()=>Promise.resolve({});
-
-
-
-
- beforeEach(()=>{
+ beforeEach(() => {
+ schemaObj = new SynonymSchema(
+ {
+ role: ()=>[],
+ schema: ()=>[],
+ synobjschema: ()=>[],
+ getTargetObjectOptions: ()=>[],
+ },
+ [],
+ {
+ owner: 'postgres',
+ schema: 'public',
+ synobjschema: 'public',
+ }
+ );
genericBeforeEach();
});
- it('create', async ()=>{
+ it('create', async () => {
await getCreateView(schemaObj);
});
- it('edit', async ()=>{
+ it('edit', async () => {
await getEditView(schemaObj, getInitData);
});
- it('properties', async ()=>{
+ it('properties', async () => {
await getPropertiesView(schemaObj, getInitData);
});
- it('validate', ()=>{
+ it('validate', () => {
let state = {};
let setError = jest.fn();
diff --git a/web/regression/javascript/schema_ui_files/table.ui.spec.js b/web/regression/javascript/schema_ui_files/table.ui.spec.js
index 60b28a180ac..7a755c7b541 100644
--- a/web/regression/javascript/schema_ui_files/table.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/table.ui.spec.js
@@ -19,52 +19,50 @@ function getFieldDepChange(schema, id) {
return _.find(schema.fields, (f)=>f.id==id)?.depChange;
}
-describe('TableSchema', ()=>{
-
- let schemaObj;
+describe('TableSchema', () => {
+
+ const createTableSchemaObject = () => getNodeTableSchema({
+ server: {
+ _id: 1,
+ },
+ schema: {
+ _label: 'public',
+ }
+ }, {}, {
+ Nodes: {table: {}},
+ serverInfo: {
+ 1: {
+ user: {
+ name: 'Postgres',
+ }
+ }
+ }
+ });
+ let schemaObj = createTableSchemaObject();
let getInitData = ()=>Promise.resolve({});
- beforeAll(()=>{
+ beforeAll(() => {
jest.spyOn(nodeAjax, 'getNodeAjaxOptions').mockReturnValue(Promise.resolve([]));
jest.spyOn(nodeAjax, 'getNodeListByName').mockReturnValue(Promise.resolve([]));
- schemaObj = getNodeTableSchema({
- server: {
- _id: 1,
- },
- schema: {
- _label: 'public',
- }
- }, {}, {
- Nodes: {table: {}},
- serverInfo: {
- 1: {
- user: {
- name: 'Postgres',
- }
- }
- }
- });
});
-
-
- beforeEach(()=>{
+ beforeEach(() => {
genericBeforeEach();
});
- it('create', async ()=>{
- await getCreateView(schemaObj);
+ it('create', async () => {
+ await getCreateView(createTableSchemaObject());
});
- it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ it('edit', async () => {
+ await getEditView(createTableSchemaObject(), getInitData);
});
- it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ it('properties', async () => {
+ await getPropertiesView(createTableSchemaObject(), getInitData);
});
- it('getTableOid', ()=>{
+ it('getTableOid', () => {
schemaObj.inheritedTableList = [
{label: 'tab1', tid: 140391},
{label: 'tab2', tid: 180191}
@@ -72,12 +70,12 @@ describe('TableSchema', ()=>{
expect(schemaObj.getTableOid('tab2')).toBe(180191);
});
- it('canEditDeleteRowColumns', ()=>{
+ it('canEditDeleteRowColumns', () => {
expect(schemaObj.canEditDeleteRowColumns({inheritedfrom: 1234})).toBe(false);
expect(schemaObj.canEditDeleteRowColumns({inheritedfrom: null})).toBe(true);
});
- it('LikeSchema typname change', ()=>{
+ it('LikeSchema typname change', () => {
let likeSchemaObj = new LikeSchema([]);
/* Dummy */
likeSchemaObj.top = new LikeSchema([]);
@@ -96,13 +94,13 @@ describe('TableSchema', ()=>{
});
});
- describe('typname change', ()=>{
+ describe('typname change', () => {
let confirmSpy;
let deferredDepChange;
let oftypeColumns = [
{name: 'id'}
];
- beforeEach(()=>{
+ beforeEach(() => {
jest.spyOn(schemaObj, 'changeColumnOptions');
jest.spyOn(schemaObj, 'getTableOid').mockReturnValue(140391);
confirmSpy = jest.spyOn(pgAdmin.Browser.notifier, 'confirm');
@@ -184,7 +182,7 @@ describe('TableSchema', ()=>{
});
});
- describe('coll_inherits change', ()=>{
+ describe('coll_inherits change', () => {
let deferredDepChange;
let inheritCol = {name: 'id'};
let onRemoveAction = (depChange, state, done)=> {
@@ -197,7 +195,7 @@ describe('TableSchema', ()=>{
done();
};
- beforeEach(()=>{
+ beforeEach(() => {
jest.spyOn(schemaObj, 'changeColumnOptions');
jest.spyOn(schemaObj, 'getTableOid').mockReturnValue(140391);
jest.spyOn(schemaObj, 'getColumns').mockReturnValue(Promise.resolve([inheritCol]));
@@ -271,7 +269,7 @@ describe('TableSchema', ()=>{
});
});
- it('depChange', ()=>{
+ it('depChange', () => {
jest.spyOn(schemaObj, 'getTableOid').mockReturnValue(140391);
let state = {};
@@ -300,7 +298,7 @@ describe('TableSchema', ()=>{
});
});
- it('validate', ()=>{
+ it('validate', () => {
let state = {is_partitioned: true};
let setError = jest.fn();
diff --git a/web/regression/javascript/schema_ui_files/tablespace.ui.spec.js b/web/regression/javascript/schema_ui_files/tablespace.ui.spec.js
index bb3c46a047a..d486854c28d 100644
--- a/web/regression/javascript/schema_ui_files/tablespace.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/tablespace.ui.spec.js
@@ -20,23 +20,20 @@ class MockSchema extends BaseUISchema {
describe('TablespaceSchema', ()=>{
- let schemaObj = new TablespaceSchema(
- ()=>new MockSchema(),
- ()=>new MockSchema(),
- {
- role: ()=>[],
- },
- {
- spcuser: 'postgres'
- }
- );
+ let schemaObj;
let getInitData = ()=>Promise.resolve({});
-
-
-
-
beforeEach(()=>{
+ schemaObj = new TablespaceSchema(
+ ()=>new MockSchema(),
+ ()=>new MockSchema(),
+ {
+ role: ()=>[],
+ },
+ {
+ spcuser: 'postgres'
+ }
+ );
genericBeforeEach();
});
diff --git a/web/regression/javascript/schema_ui_files/trigger.ui.spec.js b/web/regression/javascript/schema_ui_files/trigger.ui.spec.js
index a792b187513..a38cccbaa86 100644
--- a/web/regression/javascript/schema_ui_files/trigger.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/trigger.ui.spec.js
@@ -13,7 +13,7 @@ import {genericBeforeEach, getCreateView, getEditView, getPropertiesView} from '
describe('TriggerSchema', ()=>{
- let schemaObj = new TriggerSchema(
+ let createSchemaObj = () => new TriggerSchema(
{
triggerFunction: [],
columns: [],
@@ -26,27 +26,24 @@ describe('TriggerSchema', ()=>{
);
let getInitData = ()=>Promise.resolve({});
-
-
-
-
beforeEach(()=>{
genericBeforeEach();
});
it('create', async ()=>{
- await getCreateView(schemaObj);
+ await getCreateView(createSchemaObj());
});
it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ await getEditView(createSchemaObj(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createSchemaObj(), getInitData);
});
it('validate', ()=>{
+ const schemaObj = createSchemaObj();
let state = {};
let setError = jest.fn();
@@ -148,7 +145,7 @@ describe('TriggerSchema', ()=>{
describe('TriggerEventsSchema', ()=>{
- let schemaObj = new EventSchema(
+ let createEventSchemaObj = () => new EventSchema(
{
nodeInfo: {
server: {user: {name:'postgres', id:0}, server_type: 'pg', version: 90400},
@@ -167,18 +164,19 @@ describe('TriggerEventsSchema', ()=>{
});
it('create', async ()=>{
- await getCreateView(schemaObj);
+ await getCreateView(createEventSchemaObj());
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createEventSchemaObj(), getInitData);
});
it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ await getEditView(createEventSchemaObj(), getInitData);
});
it('validate', ()=>{
+ const schemaObj = createEventSchemaObj();
let state = {};
let setError = jest.fn();
diff --git a/web/regression/javascript/schema_ui_files/trigger_function.ui.spec.js b/web/regression/javascript/schema_ui_files/trigger_function.ui.spec.js
index 3aa353b8a9e..9fc4a24aa5b 100644
--- a/web/regression/javascript/schema_ui_files/trigger_function.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/trigger_function.ui.spec.js
@@ -20,7 +20,7 @@ class MockSchema extends BaseUISchema {
describe('TriggerFunctionSchema', ()=>{
- let schemaObj = new TriggerFunctionSchema(
+ const createSchemaObject = () => new TriggerFunctionSchema(
()=>new MockSchema(),
()=>new MockSchema(),
{
@@ -33,32 +33,29 @@ describe('TriggerFunctionSchema', ()=>{
funcowner: 'postgres',
pronamespace: 'public'
}
- );
+ );
let getInitData = ()=>Promise.resolve({});
-
-
-
-
beforeEach(()=>{
genericBeforeEach();
});
it('create', async ()=>{
- await getCreateView(schemaObj);
+ await getCreateView(createSchemaObject());
});
it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ await getEditView(createSchemaObject(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createSchemaObject(), getInitData);
});
it('validate', ()=>{
let state = {};
let setError = jest.fn();
+ let schemaObj = createSchemaObject();
state.prosrc = null;
schemaObj.validate(state, setError);
diff --git a/web/regression/javascript/schema_ui_files/type.ui.spec.js b/web/regression/javascript/schema_ui_files/type.ui.spec.js
index bcbd6d0f792..c9b0dd37576 100644
--- a/web/regression/javascript/schema_ui_files/type.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/type.ui.spec.js
@@ -13,6 +13,68 @@ import { getNodePrivilegeRoleSchema } from '../../../pgadmin/browser/server_grou
import TypeSchema, { EnumerationSchema, getCompositeSchema, getExternalSchema, getRangeSchema, getDataTypeSchema } from '../../../pgadmin/browser/server_groups/servers/databases/schemas/types/static/js/type.ui';
import {genericBeforeEach, getCreateView, getEditView, getPropertiesView} from '../genericFunctions';
+const types = [{
+ label: '', value: ''
+}, {
+ label: 'lb1', value: 'numeric[]', length: true,
+ min_val: 10, max_val: 100, precision: true, is_collatable: true,
+}];
+
+const createCompositeSchemaObject = () => {
+ let compositeCollObj = getCompositeSchema(
+ {}, {server: {user: {name: 'postgres'}}}, {}
+ );
+ let collations = [
+ { label: '', value: ''}, { label: 'lb1', value: 'numeric[]'}
+ ];
+
+ jest.spyOn(compositeCollObj.fieldOptions, 'types').mockReturnValue(types);
+ jest.spyOn(compositeCollObj.fieldOptions, 'collations')
+ .mockReturnValue(collations);
+
+ return compositeCollObj;
+};
+
+const createExternalSchemaObject = () => {
+
+ let externalCollObj = getExternalSchema({}, {server: {user: {name: 'postgres'}}}, {});
+
+ jest.spyOn(externalCollObj.fieldOptions, 'externalFunctionsList')
+ .mockReturnValue([
+ { label: '', value: ''},
+ { label: 'lb1', cbtype: 'typmodin', value: 'val1'},
+ { label: 'lb2', cbtype: 'all', value: 'val2'}
+ ]);
+ jest.spyOn(externalCollObj.fieldOptions, 'types')
+ .mockReturnValue([{ label: '', value: ''}]);
+
+ return externalCollObj;
+};
+
+const createRangeSchemaObject = () => {
+ let rangeCollObj = getRangeSchema({}, {server: {user: {name: 'postgres'}}}, {});
+
+ jest.spyOn(rangeCollObj.fieldOptions, 'getSubOpClass').mockReturnValue([
+ { label: '', value: ''}, { label: 'lb1', value: 'val1'}
+ ]);
+ jest.spyOn(rangeCollObj.fieldOptions, 'getCanonicalFunctions')
+ .mockReturnValue([
+ { label: '', value: ''}, { label: 'lb1', value: 'val1'}
+ ]);
+ jest.spyOn(rangeCollObj.fieldOptions, 'getSubDiffFunctions')
+ .mockReturnValue([
+ { label: '', value: ''}, { label: 'lb1', value: 'val1'}
+ ]);
+ jest.spyOn(rangeCollObj.fieldOptions, 'typnameList').mockReturnValue([
+ { label: '', value: ''}, { label: 'lb1', value: 'val1'}
+ ]);
+ jest.spyOn(rangeCollObj.fieldOptions, 'collationsList').mockReturnValue([
+ { label: '', value: ''}, { label: 'lb1', value: 'val1'}
+ ]);
+
+ return rangeCollObj;
+};
+
describe('TypeSchema', ()=>{
let getInitData = ()=>Promise.resolve({});
@@ -21,15 +83,10 @@ describe('TypeSchema', ()=>{
});
describe('composite schema describe', () => {
+ let compositeCollObj = createCompositeSchemaObject();
- let compositeCollObj = getCompositeSchema({}, {server: {user: {name: 'postgres'}}}, {});
- let types = [{ label: '', value: ''}, { label: 'lb1', value: 'numeric[]', length: true, min_val: 10, max_val: 100, precision: true, is_collatable: true}];
- let collations = [{ label: '', value: ''}, { label: 'lb1', value: 'numeric[]'}];
-
- it('composite collection', async ()=>{
+ it('composite collection', async () => {
jest.spyOn(nodeAjax, 'getNodeAjaxOptions').mockReturnValue([]);
- jest.spyOn(compositeCollObj.fieldOptions, 'types').mockReturnValue(types);
- jest.spyOn(compositeCollObj.fieldOptions, 'collations').mockReturnValue(collations);
await getCreateView(compositeCollObj);
await getEditView(compositeCollObj, getInitData);
});
@@ -91,13 +148,10 @@ describe('TypeSchema', ()=>{
describe('external schema describe', () => {
- let externalCollObj = getExternalSchema({}, {server: {user: {name: 'postgres'}}}, {});
+ let externalCollObj = createExternalSchemaObject();
it('external collection', async ()=>{
-
jest.spyOn(nodeAjax, 'getNodeAjaxOptions').mockReturnValue([]);
- jest.spyOn(externalCollObj.fieldOptions, 'externalFunctionsList').mockReturnValue([{ label: '', value: ''}, { label: 'lb1', cbtype: 'typmodin', value: 'val1'}, { label: 'lb2', cbtype: 'all', value: 'val2'}]);
- jest.spyOn(externalCollObj.fieldOptions, 'types').mockReturnValue([{ label: '', value: ''}]);
await getCreateView(externalCollObj);
await getEditView(externalCollObj, getInitData);
@@ -118,16 +172,11 @@ describe('TypeSchema', ()=>{
describe('range schema describe', () => {
- let rangeCollObj = getRangeSchema({}, {server: {user: {name: 'postgres'}}}, {});
+ let rangeCollObj = createRangeSchemaObject();
- it('range collection', async ()=>{
+ it('range collection', async () => {
jest.spyOn(nodeAjax, 'getNodeAjaxOptions').mockReturnValue([]);
- jest.spyOn(rangeCollObj.fieldOptions, 'getSubOpClass').mockReturnValue([{ label: '', value: ''}, { label: 'lb1', value: 'val1'}]);
- jest.spyOn(rangeCollObj.fieldOptions, 'getCanonicalFunctions').mockReturnValue([{ label: '', value: ''}, { label: 'lb1', value: 'val1'}]);
- jest.spyOn(rangeCollObj.fieldOptions, 'getSubDiffFunctions').mockReturnValue([{ label: '', value: ''}, { label: 'lb1', value: 'val1'}]);
- jest.spyOn(rangeCollObj.fieldOptions, 'typnameList').mockReturnValue([{ label: '', value: ''}, { label: 'lb1', value: 'val1'}]);
- jest.spyOn(rangeCollObj.fieldOptions, 'collationsList').mockReturnValue([{ label: '', value: ''}, { label: 'lb1', value: 'val1'}]);
await getCreateView(rangeCollObj);
await getEditView(rangeCollObj, getInitData);
@@ -145,7 +194,13 @@ describe('TypeSchema', ()=>{
describe('data type schema describe', () => {
let dataTypeObj = getDataTypeSchema({}, {server: {user: {name: 'postgres'}}}, {});
- let types = [{ label: '', value: ''}, { label: 'lb1', value: 'numeric', length: true, min_val: 10, max_val: 100, precision: true}];
+ const types = [
+ { label: '', value: ''},
+ {
+ label: 'lb1', value: 'numeric', length: true,
+ min_val: 10, max_val: 100, precision: true,
+ }
+ ];
it('data type collection', async ()=>{
@@ -183,34 +238,40 @@ describe('TypeSchema', ()=>{
});
});
- let typeSchemaObj = new TypeSchema(
- (privileges)=>getNodePrivilegeRoleSchema({}, {server: {user: {name: 'postgres'}}}, {}, privileges),
- ()=>getCompositeSchema({}, {server: {user: {name: 'postgres'}}}, {}),
- ()=>getRangeSchema({}, {server: {user: {name: 'postgres'}}}, {}),
- ()=>getExternalSchema({}, {server: {user: {name: 'postgres'}}}, {}),
- ()=>getDataTypeSchema({}, {server: {user: {name: 'postgres'}}}, {}),
- {
- roles: ()=>[],
- schemas: ()=>[{ label: 'pg_demo', value: 'pg_demo'}],
- server_info: [],
- node_info: {'schema': []}
- },
- {
- typowner: 'postgres',
- schema: 'public',
- typtype: 'c'
- }
- );
+ const createTypeSchemaObject = () => {
+ jest.spyOn(nodeAjax, 'getNodeAjaxOptions').mockReturnValue([]);
+
+ return new TypeSchema(
+ (privileges)=>getNodePrivilegeRoleSchema(
+ {}, {server: {user: {name: 'postgres'}}}, {}, privileges
+ ),
+ ()=>createCompositeSchemaObject(),
+ ()=>createRangeSchemaObject(),
+ ()=>createExternalSchemaObject(),
+ ()=>getDataTypeSchema({}, {server: {user: {name: 'postgres'}}}, {}),
+ {
+ roles: ()=>[],
+ schemas: ()=>[{ label: 'pg_demo', value: 'pg_demo'}],
+ server_info: [],
+ node_info: {'schema': []}
+ },
+ {
+ typowner: 'postgres',
+ schema: 'public',
+ typtype: 'c'
+ }
+ );
+ };
it('create', async ()=>{
- await getCreateView(typeSchemaObj);
+ await getCreateView(createTypeSchemaObject());
});
it('edit', async ()=>{
- await getEditView(typeSchemaObj, getInitData);
+ await getEditView(createTypeSchemaObject(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(typeSchemaObj, getInitData);
+ await getPropertiesView(createTypeSchemaObject(), getInitData);
});
});
diff --git a/web/regression/javascript/schema_ui_files/unique_constraint.ui.spec.js b/web/regression/javascript/schema_ui_files/unique_constraint.ui.spec.js
index 6658ae67a0a..9b85d240448 100644
--- a/web/regression/javascript/schema_ui_files/unique_constraint.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/unique_constraint.ui.spec.js
@@ -47,8 +47,6 @@ describe('UniqueConstraintSchema', ()=>{
}, {});
});
-
-
beforeEach(()=>{
genericBeforeEach();
});
diff --git a/web/regression/javascript/schema_ui_files/user_mapping.ui.spec.js b/web/regression/javascript/schema_ui_files/user_mapping.ui.spec.js
index aaeba2db22e..515d4a56337 100644
--- a/web/regression/javascript/schema_ui_files/user_mapping.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/user_mapping.ui.spec.js
@@ -18,36 +18,33 @@ class MockSchema extends BaseUISchema {
}
}
-describe('UserMappingSchema', ()=>{
-
- let schemaObj = new UserMappingSchema(
- ()=>new MockSchema(),
- {
- role: ()=>[],
- },
- {
- name: 'postgres'
- }
- );
- let getInitData = ()=>Promise.resolve({});
-
-
-
+describe('UserMappingSchema', () => {
+ let schemaObj;
+ let getInitData = ()=>Promise.resolve({});
- beforeEach(()=>{
+ beforeEach(() => {
+ schemaObj = new UserMappingSchema(
+ ()=>new MockSchema(),
+ {
+ role: ()=>[],
+ },
+ {
+ name: 'postgres'
+ }
+ );
genericBeforeEach();
});
- it('create', async ()=>{
+ it('create', async () => {
await getCreateView(schemaObj);
});
- it('edit', async ()=>{
+ it('edit', async () => {
await getEditView(schemaObj, getInitData);
});
- it('properties', async ()=>{
+ it('properties', async () => {
await getPropertiesView(schemaObj, getInitData);
});
});
diff --git a/web/regression/javascript/schema_ui_files/utils.js b/web/regression/javascript/schema_ui_files/utils.js
index 7099f1b6a48..034cb6e9218 100644
--- a/web/regression/javascript/schema_ui_files/utils.js
+++ b/web/regression/javascript/schema_ui_files/utils.js
@@ -7,7 +7,9 @@
//
//////////////////////////////////////////////////////////////
-import { SchemaState } from '../../../pgadmin/static/js/SchemaView/useSchemaState';
+import {
+ SchemaState,
+} from '../../../pgadmin/static/js/SchemaView/SchemaState';
export function initializeSchemaWithData(schema, data) {
const state = schema.state = new SchemaState(
diff --git a/web/regression/javascript/schema_ui_files/variable.ui.spec.js b/web/regression/javascript/schema_ui_files/variable.ui.spec.js
index f50f636fbdd..bf347885df4 100644
--- a/web/regression/javascript/schema_ui_files/variable.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/variable.ui.spec.js
@@ -34,32 +34,29 @@ class MockSchema extends BaseUISchema {
describe('VariableSchema', ()=>{
- let schemaObj = new VariableSchema(
+ const createSchemaObject = () => new VariableSchema(
()=>[],
()=>[],
()=>[],
null
);
+ let schemaObj = createSchemaObject();
let getInitData = ()=>Promise.resolve({});
-
-
-
-
beforeEach(()=>{
genericBeforeEach();
});
it('create', async ()=>{
- await getCreateView(schemaObj);
+ await getCreateView(createSchemaObject());
});
it('edit', async ()=>{
- await getEditView(schemaObj, getInitData);
+ await getEditView(createSchemaObject(), getInitData);
});
it('properties', async ()=>{
- await getPropertiesView(schemaObj, getInitData);
+ await getPropertiesView(createSchemaObject(), getInitData);
});
it('getValueFieldProps', ()=>{
diff --git a/web/regression/javascript/schema_ui_files/view.ui.spec.js b/web/regression/javascript/schema_ui_files/view.ui.spec.js
index ef2aef34602..063e259892d 100644
--- a/web/regression/javascript/schema_ui_files/view.ui.spec.js
+++ b/web/regression/javascript/schema_ui_files/view.ui.spec.js
@@ -11,6 +11,7 @@
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
import ViewSchema from '../../../pgadmin/browser/server_groups/servers/databases/schemas/views/static/js/view.ui';
import {genericBeforeEach, getCreateView, getEditView, getPropertiesView} from '../genericFunctions';
+import { initializeSchemaWithData } from './utils';
class MockSchema extends BaseUISchema {
get baseFields() {
@@ -20,25 +21,22 @@ class MockSchema extends BaseUISchema {
describe('ViewSchema', ()=>{
- let schemaObj = new ViewSchema(
- ()=>new MockSchema(),
- {server: {server_type: 'pg'}},
- {
- role: ()=>[],
- schema: ()=>[],
- },
- {
- owner: 'postgres',
- schema: 'public'
- }
- );
+ let schemaObj;
let getInitData = ()=>Promise.resolve({});
-
-
-
-
beforeEach(()=>{
+ schemaObj = new ViewSchema(
+ ()=>new MockSchema(),
+ {server: {server_type: 'pg'}},
+ {
+ role: ()=>[],
+ schema: ()=>[],
+ },
+ {
+ owner: 'postgres',
+ schema: 'public'
+ }
+ );
genericBeforeEach();
});
@@ -57,6 +55,7 @@ describe('ViewSchema', ()=>{
it('validate', ()=>{
let state = {};
let setError = jest.fn();
+ initializeSchemaWithData(schemaObj, {});
state.definition = null;
schemaObj.validate(state, setError);
@@ -77,4 +76,3 @@ describe('ViewSchema', ()=>{
});
});
-
diff --git a/web/yarn.lock b/web/yarn.lock
index b9cb73d8bb7..4c2ca11d54e 100644
--- a/web/yarn.lock
+++ b/web/yarn.lock
@@ -88,15 +88,15 @@ __metadata:
languageName: node
linkType: hard
-"@babel/generator@npm:^7.25.0, @babel/generator@npm:^7.25.4, @babel/generator@npm:^7.7.2":
- version: 7.25.5
- resolution: "@babel/generator@npm:7.25.5"
+"@babel/generator@npm:^7.25.0, @babel/generator@npm:^7.25.6, @babel/generator@npm:^7.7.2":
+ version: 7.25.6
+ resolution: "@babel/generator@npm:7.25.6"
dependencies:
- "@babel/types": ^7.25.4
+ "@babel/types": ^7.25.6
"@jridgewell/gen-mapping": ^0.3.5
"@jridgewell/trace-mapping": ^0.3.25
jsesc: ^2.5.1
- checksum: d7713f02536a8144eca810e9b13ae854b05fec462348eaf52e7b50df2c0a312bc43bfff0e8e10d6dd982e8986d61175ac8e67d7358a8b4dad9db4d6733bf0c9c
+ checksum: b55975cd664f5602304d868bb34f4ee3bed6f5c7ce8132cd92ff27a46a53a119def28a182d91992e86f75db904f63094a81247703c4dc96e4db0c03fd04bcd68
languageName: node
linkType: hard
@@ -306,12 +306,12 @@ __metadata:
linkType: hard
"@babel/helpers@npm:^7.25.0":
- version: 7.25.0
- resolution: "@babel/helpers@npm:7.25.0"
+ version: 7.25.6
+ resolution: "@babel/helpers@npm:7.25.6"
dependencies:
"@babel/template": ^7.25.0
- "@babel/types": ^7.25.0
- checksum: 739e3704ff41a30f5eaac469b553f4d3ab02be6ced083f5925851532dfbd9efc5c347728e77b754ed0b262a4e5e384e60932a62c192d338db7e4b7f3adf9f4a7
+ "@babel/types": ^7.25.6
+ checksum: 5a548999db82049a5f7ac6de57576b4ed0d386ce07d058151698836ed411eae6230db12535487caeebb68a2ffc964491e8aead62364a5132ab0ae20e8b68e19f
languageName: node
linkType: hard
@@ -327,14 +327,14 @@ __metadata:
languageName: node
linkType: hard
-"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.25.0, @babel/parser@npm:^7.25.4":
- version: 7.25.4
- resolution: "@babel/parser@npm:7.25.4"
+"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.25.0, @babel/parser@npm:^7.25.6":
+ version: 7.25.6
+ resolution: "@babel/parser@npm:7.25.6"
dependencies:
- "@babel/types": ^7.25.4
+ "@babel/types": ^7.25.6
bin:
parser: ./bin/babel-parser.js
- checksum: fe4f083d4ad34f019dd7fad672cd007003004fb0a3df9b7315a5da9a5e8e56c1fed95acab6862e7d76cfccb2e8e364bcc307e9117718e6bb6dfb2e87ad065abf
+ checksum: 85b237ded09ee43cc984493c35f3b1ff8a83e8dbbb8026b8132e692db6567acc5a1659ec928e4baa25499ddd840d7dae9dee3062be7108fe23ec5f94a8066b1e
languageName: node
linkType: hard
@@ -500,24 +500,24 @@ __metadata:
linkType: hard
"@babel/plugin-syntax-import-assertions@npm:^7.24.7":
- version: 7.24.7
- resolution: "@babel/plugin-syntax-import-assertions@npm:7.24.7"
+ version: 7.25.6
+ resolution: "@babel/plugin-syntax-import-assertions@npm:7.25.6"
dependencies:
- "@babel/helper-plugin-utils": ^7.24.7
+ "@babel/helper-plugin-utils": ^7.24.8
peerDependencies:
"@babel/core": ^7.0.0-0
- checksum: c4d67be4eb1d4637e361477dbe01f5b392b037d17c1f861cfa0faa120030e137aab90a9237931b8040fd31d1e5d159e11866fa1165f78beef7a3be876a391a17
+ checksum: b3b251ace9f184c2d6369cde686ff01581050cb0796f2ff00ff4021f31cf86270b347df09579f2c0996e999e37e1dddafacec42ed1ef6aae21a265aff947e792
languageName: node
linkType: hard
"@babel/plugin-syntax-import-attributes@npm:^7.24.7":
- version: 7.24.7
- resolution: "@babel/plugin-syntax-import-attributes@npm:7.24.7"
+ version: 7.25.6
+ resolution: "@babel/plugin-syntax-import-attributes@npm:7.25.6"
dependencies:
- "@babel/helper-plugin-utils": ^7.24.7
+ "@babel/helper-plugin-utils": ^7.24.8
peerDependencies:
"@babel/core": ^7.0.0-0
- checksum: 590dbb5d1a15264f74670b427b8d18527672c3d6c91d7bae7e65f80fd810edbc83d90e68065088644cbad3f2457ed265a54a9956fb789fcb9a5b521822b3a275
+ checksum: 3b0928e73e42346e8a65760a3ff853c87ad693cdf11bb335a23e895e0b5b1f0601118521b3aff2a6946488a580a63afb6a5b5686153a7678b4dff0e4e4604dd7
languageName: node
linkType: hard
@@ -1489,12 +1489,12 @@ __metadata:
languageName: node
linkType: hard
-"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.25.0, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2":
- version: 7.25.4
- resolution: "@babel/runtime@npm:7.25.4"
+"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.25.4, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2":
+ version: 7.25.6
+ resolution: "@babel/runtime@npm:7.25.6"
dependencies:
regenerator-runtime: ^0.14.0
- checksum: 5c2aab03788e77f1f959d7e6ce714c299adfc9b14fb6295c2a17eb7cad0dd9c2ebfb2d25265f507f68c43d5055c5cd6f71df02feb6502cea44b68432d78bcbbe
+ checksum: ee1a69d3ac7802803f5ee6a96e652b78b8addc28c6a38c725a4ad7d61a059d9e6cb9f6550ed2f63cce67a1bd82e0b1ef66a1079d895be6bfb536a5cfbd9ccc32
languageName: node
linkType: hard
@@ -1510,28 +1510,28 @@ __metadata:
linkType: hard
"@babel/traverse@npm:^7.24.7, @babel/traverse@npm:^7.24.8, @babel/traverse@npm:^7.25.0, @babel/traverse@npm:^7.25.1, @babel/traverse@npm:^7.25.2, @babel/traverse@npm:^7.25.3, @babel/traverse@npm:^7.25.4":
- version: 7.25.4
- resolution: "@babel/traverse@npm:7.25.4"
+ version: 7.25.6
+ resolution: "@babel/traverse@npm:7.25.6"
dependencies:
"@babel/code-frame": ^7.24.7
- "@babel/generator": ^7.25.4
- "@babel/parser": ^7.25.4
+ "@babel/generator": ^7.25.6
+ "@babel/parser": ^7.25.6
"@babel/template": ^7.25.0
- "@babel/types": ^7.25.4
+ "@babel/types": ^7.25.6
debug: ^4.3.1
globals: ^11.1.0
- checksum: 3b6d879b9d843b119501585269b3599f047011ae21eb7820d00aef62fc3a2bcdaf6f4cdf2679795a2d7c0b6b5d218974916e422f08dea08613dc42188ef21e4b
+ checksum: 11ee47269aa4356f2d6633a05b9af73405b5ed72c09378daf644289b686ef852035a6ac9aa410f601991993c6bbf72006795b5478283b78eb1ca77874ada7737
languageName: node
linkType: hard
-"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.24.7, @babel/types@npm:^7.24.8, @babel/types@npm:^7.25.0, @babel/types@npm:^7.25.2, @babel/types@npm:^7.25.4, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4":
- version: 7.25.4
- resolution: "@babel/types@npm:7.25.4"
+"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.24.7, @babel/types@npm:^7.24.8, @babel/types@npm:^7.25.0, @babel/types@npm:^7.25.2, @babel/types@npm:^7.25.6, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4":
+ version: 7.25.6
+ resolution: "@babel/types@npm:7.25.6"
dependencies:
"@babel/helper-string-parser": ^7.24.8
"@babel/helper-validator-identifier": ^7.24.7
to-fast-properties: ^2.0.0
- checksum: 497f8b583c54a92a59c3ec542144695064cd5c384fcca46ba1aa301d5e5dd6c1d011f312ca024cb0f9c956da07ae82fb4c348c31a30afa31a074c027720d2aa8
+ checksum: 9b2f84ff3f874ad05b0b9bf06862c56f478b65781801f82296b4cc01bee39e79c20a7c0a06959fed0ee582c8267e1cb21638318655c5e070b0287242a844d1c9
languageName: node
linkType: hard
@@ -2507,11 +2507,10 @@ __metadata:
linkType: hard
"@mui/x-date-pickers@npm:^7.7.1":
- version: 7.14.0
- resolution: "@mui/x-date-pickers@npm:7.14.0"
+ version: 7.15.0
+ resolution: "@mui/x-date-pickers@npm:7.15.0"
dependencies:
- "@babel/runtime": ^7.25.0
- "@mui/system": ^5.16.7
+ "@babel/runtime": ^7.25.4
"@mui/utils": ^5.16.6
"@types/react-transition-group": ^4.4.11
clsx: ^2.1.1
@@ -2520,7 +2519,8 @@ __metadata:
peerDependencies:
"@emotion/react": ^11.9.0
"@emotion/styled": ^11.8.1
- "@mui/material": ^5.15.14
+ "@mui/material": ^5.15.14 || ^6.0.0
+ "@mui/system": ^5.15.14 || ^6.0.0
date-fns: ^2.25.0 || ^3.2.0
date-fns-jalali: ^2.13.0-0 || ^3.2.0-0
dayjs: ^1.10.7
@@ -2549,7 +2549,7 @@ __metadata:
optional: true
moment-jalaali:
optional: true
- checksum: b7a4c2f23e6136a50b6f520c285a2cf7be418ffe6b26b580b4540c80daa93d8f0ece3873c64e2e41cbe9651dadbf61cfcb0d73a36f6b3c331c6008eb4a836636
+ checksum: 00374f0072db2e759e4ae00f45d16cae12f778820d2658ce21ed33e963524d02ec95d4aafc0bbaee8e9559b5fa9ea9c240187d36647cba405a96d0e6e526cfc7
languageName: node
linkType: hard
@@ -3048,14 +3048,14 @@ __metadata:
linkType: hard
"@tanstack/react-virtual@npm:^3.8.4":
- version: 3.10.5
- resolution: "@tanstack/react-virtual@npm:3.10.5"
+ version: 3.10.6
+ resolution: "@tanstack/react-virtual@npm:3.10.6"
dependencies:
- "@tanstack/virtual-core": 3.10.5
+ "@tanstack/virtual-core": 3.10.6
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
- checksum: 9030eaf58eb948af44454698ececd512c52909202496299846472e0aa0b5ba5dfc188502936880c0249a7fb48a166f471e8dc253826de81a24438686858a80fd
+ checksum: 50624357867ce8eca4084bfe132b73fe7a2e0f9bcb2a7241b16425d58c0f7602c6f889f28d29a9416f7c3ab12d89239da48d0d84ca470ace94f77cf982d6246e
languageName: node
linkType: hard
@@ -3066,10 +3066,10 @@ __metadata:
languageName: node
linkType: hard
-"@tanstack/virtual-core@npm:3.10.5":
- version: 3.10.5
- resolution: "@tanstack/virtual-core@npm:3.10.5"
- checksum: 6751500bdfad8f176d7848feaa3c89a3853160169b88b4fa6e7aeea6ed460a9d7326da9e134a61267642a0de2ffcd5524be3b5cf496205a99cbf2dd44f6a098d
+"@tanstack/virtual-core@npm:3.10.6":
+ version: 3.10.6
+ resolution: "@tanstack/virtual-core@npm:3.10.6"
+ checksum: c7eeda2a2ad49b0b5065127094225877656b718301bcd972b80e55b19a7f2f063867a8d598544e824cc230d080124c9c34abeb8517c0ed3f5759c16c1f86acd8
languageName: node
linkType: hard
@@ -3800,9 +3800,9 @@ __metadata:
linkType: hard
"ace-builds@npm:^1.31.1":
- version: 1.36.0
- resolution: "ace-builds@npm:1.36.0"
- checksum: c32ce439489c0b5d294f7d8dfd71c5ffd1693132ecbfd8b77ba2b9ed86e46fe3b0ff1c8dc236b87b86a90f1870b2ce516605b81bf92508be240e02af691c91cd
+ version: 1.36.1
+ resolution: "ace-builds@npm:1.36.1"
+ checksum: 79a6fa893c775d07af17c2e29a985da209b9e8504936554b46c15761d7a9e2bdfb94840202fd9dd1df4e321b6f89d644b9cec404785bb91bee1ca469ea8feb41
languageName: node
linkType: hard
@@ -8286,13 +8286,13 @@ __metadata:
languageName: node
linkType: hard
-"html-dom-parser@npm:5.0.9":
- version: 5.0.9
- resolution: "html-dom-parser@npm:5.0.9"
+"html-dom-parser@npm:5.0.10":
+ version: 5.0.10
+ resolution: "html-dom-parser@npm:5.0.10"
dependencies:
domhandler: 5.0.3
htmlparser2: 9.1.0
- checksum: babc50f37e74521f777a304155eae59bdf4662568b880e17120187c5980e90a655a419e093effa1594d20f3f6992c2a6c71910dc2aea883315a3f4a1d5e2d491
+ checksum: ec0470f9f6046af7d4d591aea15b49ca0a178ce430d09cadbb098212749a0b2c9b078cf9ede5df50238bc2842c17c7895126788c51927ff726088aad36c98ab5
languageName: node
linkType: hard
@@ -8313,11 +8313,11 @@ __metadata:
linkType: hard
"html-react-parser@npm:^5.0.6":
- version: 5.1.14
- resolution: "html-react-parser@npm:5.1.14"
+ version: 5.1.15
+ resolution: "html-react-parser@npm:5.1.15"
dependencies:
domhandler: 5.0.3
- html-dom-parser: 5.0.9
+ html-dom-parser: 5.0.10
react-property: 2.0.2
style-to-js: 1.1.13
peerDependencies:
@@ -8326,7 +8326,7 @@ __metadata:
peerDependenciesMeta:
"@types/react":
optional: true
- checksum: fddfd507d932ebe289852596d5037f3ac826aff13f32bbbd20790ee5ad5abc45ab80025f76ffe0008d3f186c2972785ade8d72c1c59c1c4a9f3ac7734fd07acc
+ checksum: 83e21ac01809a85d20a5fd2c554cd837e94a6a7e3fa34f59d3bc56dfeb73ea49c4f22f3af79648d08ca59dda0f26b850c21c69104a22fec4e4017dd8d6e111cc
languageName: node
linkType: hard
@@ -13273,7 +13273,7 @@ __metadata:
languageName: node
linkType: hard
-"react-rnd@npm:^10.3.5":
+"react-rnd@npm:^10.4.12":
version: 10.4.12
resolution: "react-rnd@npm:10.4.12"
dependencies:
@@ -13880,7 +13880,7 @@ __metadata:
react-leaflet: ^4.2.1
react-new-window: ^1.0.1
react-resize-detector: ^11.0.1
- react-rnd: ^10.3.5
+ react-rnd: ^10.4.12
react-select: ^5.7.2
react-timer-hook: ^3.0.5
react-virtualized-auto-sizer: ^1.0.6
@@ -13907,7 +13907,7 @@ __metadata:
webpack-cli: ^5.1.4
wkx: ^0.5.0
yarn-audit-html: 4.0.0
- zustand: ^4.4.1
+ zustand: ^4.5.4
languageName: unknown
linkType: soft
@@ -15465,11 +15465,11 @@ __metadata:
linkType: hard
"uglify-js@npm:^3.1.4":
- version: 3.19.2
- resolution: "uglify-js@npm:3.19.2"
+ version: 3.19.3
+ resolution: "uglify-js@npm:3.19.3"
bin:
uglifyjs: bin/uglifyjs
- checksum: 2236220638223f72340d770daa46704a6f54bcd3022e04510a55bb693a40c32e38a9a439333703f16c9880226cc9952c0dddfe67e7b870c287d915b54757ab51
+ checksum: 7ed6272fba562eb6a3149cfd13cda662f115847865c03099e3995a0e7a910eba37b82d4fccf9e88271bb2bcbe505bb374967450f433c17fa27aa36d94a8d0553
languageName: node
linkType: hard
@@ -16387,7 +16387,7 @@ __metadata:
languageName: node
linkType: hard
-"zustand@npm:^4.4.1":
+"zustand@npm:^4.5.4":
version: 4.5.5
resolution: "zustand@npm:4.5.5"
dependencies: