Port Types node to react. Fixes #6602

This commit is contained in:
Rahul Shirsat 2021-08-03 12:38:36 +05:30 committed by Akshay Joshi
parent af0a24a5bc
commit 1e07ef13cf
6 changed files with 2154 additions and 978 deletions

View File

@ -20,7 +20,7 @@ import { getFieldMetaData } from './FormView';
import FieldSet from '../components/FieldSet';
export default function FieldSetView({
value, formErr, schema={}, viewHelperProps, accessPath, dataDispatch, controlClassName, isDataGridForm=false, label}) {
value, formErr, schema={}, viewHelperProps, accessPath, dataDispatch, controlClassName, isDataGridForm=false, label, visible}) {
const depListener = useContext(DepListenerContext);
useEffect(()=>{
@ -91,6 +91,10 @@ export default function FieldSetView({
}
});
if(!visible) {
return <></>;
}
return (
<FieldSet title={label} className={controlClassName}>
{viewFields}
@ -108,4 +112,7 @@ FieldSetView.propTypes = {
dataDispatch: PropTypes.func,
controlClassName: CustomPropTypes.className,
label: PropTypes.string,
visible: PropTypes.oneOfType([
PropTypes.bool, PropTypes.func,
]),
};

View File

@ -122,7 +122,7 @@ export function getFieldMetaData(field, schema, value, viewHelperProps) {
/* The first component of schema view form */
export default function FormView({
value, formErr, schema={}, viewHelperProps, isNested=false, accessPath, dataDispatch, hasSQLTab,
getSQLValue, onTabChange, firstEleRef, className, isDataGridForm=false}) {
getSQLValue, onTabChange, firstEleRef, className, isDataGridForm=false, visible}) {
let defaultTab = 'General';
let tabs = {};
let tabsClassname = {};
@ -190,7 +190,8 @@ export default function FormView({
}
tabs[group].push(
<FormView key={`nested${tabs[group].length}`} value={value} viewHelperProps={viewHelperProps} formErr={formErr}
schema={field.schema} accessPath={accessPath} dataDispatch={dataDispatch} isNested={true} isDataGridForm={isDataGridForm} {...field}/>
schema={field.schema} accessPath={accessPath} dataDispatch={dataDispatch} isNested={true} isDataGridForm={isDataGridForm}
{...field} visible={visible}/>
);
} else if(field.type === 'nested-fieldset') {
/* Pass on the top schema */
@ -203,7 +204,7 @@ export default function FormView({
<FieldSetView key={`nested${tabs[group].length}`} value={value} viewHelperProps={viewHelperProps} formErr={formErr}
schema={field.schema} accessPath={accessPath} dataDispatch={dataDispatch} isNested={true} isDataGridForm={isDataGridForm}
controlClassName={classes.controlRow}
{...field} />
{...field} visible={visible}/>
);
} else if(field.type === 'collection') {
/* If its a collection, let data grid view handle it */
@ -305,6 +306,11 @@ export default function FormView({
onTabChange && onTabChange(tabValue, Object.keys(tabs)[tabValue], sqlTabActive);
}, [tabValue]);
/* check whether form is kept hidden by visible prop */
if(!_.isUndefined(visible) && !visible) {
return <></>;
}
return (
<>
<Box height="100%" display="flex" flexDirection="column" className={className} ref={formRef}>
@ -343,6 +349,9 @@ FormView.propTypes = {
viewHelperProps: PropTypes.object,
isNested: PropTypes.bool,
isDataGridForm: PropTypes.bool,
visible: PropTypes.oneOfType([
PropTypes.bool, PropTypes.func,
]),
accessPath: PropTypes.array.isRequired,
dataDispatch: PropTypes.func,
hasSQLTab: PropTypes.bool,

View File

@ -15,6 +15,7 @@ define('pgadmin.browser.messages',['sources/pgadmin'], function(pgAdmin) {
pgBrowser.messages = {
'CANNOT_BE_EMPTY': '\'%s\' cannot be empty.',
'MUST_BE_INT': '\'%s\' must be an integer.'
};
return pgBrowser;
});

View File

@ -0,0 +1,457 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import jasmineEnzyme from 'jasmine-enzyme';
import React from 'react';
import '../helper/enzyme.helper';
import pgAdmin from 'sources/pgadmin';
import {messages} from '../fake_messages';
import { createMount } from '@material-ui/core/test-utils';
import SchemaView from '../../../pgadmin/static/js/SchemaView';
import * as nodeAjax from '../../../pgadmin/browser/static/js/node_ajax';
import gettext from 'sources/gettext';
import { integerValidator } from 'sources/validators';
import { getNodePrivilegeRoleSchema } from '../../../pgadmin/browser/server_groups/servers/static/js/privilege.ui';
import TypeSchema, { EnumerationSchema, getCompositeSchema, getExternalSchema, getRangeSchema, getDataTypeSchema } from '../../../pgadmin/browser/server_groups/servers/databases/schemas/types/static/js/type.ui';
describe('TypeSchema', ()=>{
let mount;
let getInitData = ()=>Promise.resolve({});
/* Use createMount so that material ui components gets the required context */
/* https://material-ui.com/guides/testing/#api */
beforeAll(()=>{
mount = createMount();
});
afterAll(() => {
mount.cleanUp();
});
beforeEach(()=>{
jasmineEnzyme();
/* messages used by validators */
pgAdmin.Browser = pgAdmin.Browser || {};
pgAdmin.Browser.messages = pgAdmin.Browser.messages || messages;
pgAdmin.Browser.utils = pgAdmin.Browser.utils || {};
pgAdmin.Browser.utils.support_ssh_tunnel = true;
});
describe('composite schema describe', () => {
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', ()=>{
spyOn(nodeAjax, 'getNodeAjaxOptions').and.returnValue([]);
spyOn(compositeCollObj.fieldOptions, 'types').and.returnValue(types);
spyOn(compositeCollObj.fieldOptions, 'collations').and.returnValue(collations);
spyOn(compositeCollObj, 'type_options').and.returnValue(compositeCollObj.fieldOptions.types());
mount(<SchemaView
formType='dialog'
schema={compositeCollObj}
viewHelperProps={{
mode: 'create',
}}
onSave={()=>{}}
onClose={()=>{}}
onHelp={()=>{}}
onEdit={()=>{}}
onDataChange={()=>{}}
confirmOnCloseReset={false}
hasSQL={false}
disableSqlHelp={false}
/>);
mount(<SchemaView
formType='dialog'
schema={compositeCollObj}
viewHelperProps={{
mode: 'edit',
}}
onSave={()=>{}}
onClose={()=>{}}
getInitData={getInitData}
onHelp={()=>{}}
onEdit={()=>{}}
onDataChange={()=>{}}
confirmOnCloseReset={false}
hasSQL={false}
disableSqlHelp={false}
/>);
});
it('composite validate', () => {
let state = { typtype: 'b' }; //validating for ExternalSchema which is distinguish as r
let setError = jasmine.createSpy('setError');
compositeCollObj.validate(state, setError);
expect(setError).toHaveBeenCalledWith('member_name', 'Please specify the value for member name.');
state.member_name = 'demo_member';
compositeCollObj.validate(state, setError);
expect(setError).toHaveBeenCalledWith('type', 'Please specify the type.');
state.type = 'char';
state.min_val = 10;
state.max_val = 100;
state.is_tlength = true;
state.tlength = 9;
compositeCollObj.validate(state, setError);
expect(setError).toHaveBeenCalledWith('tlength', gettext('Length/Precision should not be less than %s.', state.min_val));
state.tlength = 200;
compositeCollObj.validate(state, setError);
expect(setError).toHaveBeenCalledWith('tlength', gettext('Length/Precision should not be greater than %s.', state.max_val));
state.tlength = 'ert';
compositeCollObj.validate(state, setError);
expect(setError).toHaveBeenCalledWith('tlength', integerValidator('Length/Precision', state.tlength));
state.tlength = 90;
state.is_precision = true;
state.precision = 'ert';
compositeCollObj.validate(state, setError);
expect(setError).toHaveBeenCalledWith('precision', integerValidator('Scale', state.precision));
state.precision = 9;
compositeCollObj.validate(state, setError);
expect(setError).toHaveBeenCalled();
});
it('tlength editable', ()=>{
compositeCollObj.type_options = types;
let editable = _.find(compositeCollObj.fields, (f)=>f.id=='tlength').editable;
let status = editable({type: 'numeric[]'});
expect(status).toBe(true);
});
it('precision editable', ()=>{
compositeCollObj.type_options = types;
let editable = _.find(compositeCollObj.fields, (f)=>f.id=='precision').editable;
let status = editable({type: 'numeric[]'});
expect(status).toBe(true);
});
it('collation editable', ()=>{
compositeCollObj.type_options = types;
let editable = _.find(compositeCollObj.fields, (f)=>f.id=='collation').editable;
let status = editable({type: 'numeric[]'});
expect(status).toBe(true);
});
it('setTypeOptions', ()=>{
compositeCollObj.setTypeOptions(types);
});
});
describe('enumeration schema describe', () => {
it('enumeration collection', ()=>{
let enumerationCollObj = new EnumerationSchema(
()=>[],
()=>[]
);
mount(<SchemaView
formType='dialog'
schema={enumerationCollObj}
viewHelperProps={{
mode: 'create',
}}
onSave={()=>{}}
onClose={()=>{}}
onHelp={()=>{}}
onEdit={()=>{}}
onDataChange={()=>{}}
confirmOnCloseReset={false}
hasSQL={false}
disableSqlHelp={false}
/>);
mount(<SchemaView
formType='dialog'
schema={enumerationCollObj}
viewHelperProps={{
mode: 'edit',
}}
onSave={()=>{}}
getInitData={getInitData}
onClose={()=>{}}
onHelp={()=>{}}
onEdit={()=>{}}
onDataChange={()=>{}}
confirmOnCloseReset={false}
hasSQL={false}
disableSqlHelp={false}
/>);
});
});
describe('external schema describe', () => {
let externalCollObj = getExternalSchema({}, {server: {user: {name: 'postgres'}}}, {});
it('external collection', ()=>{
spyOn(nodeAjax, 'getNodeAjaxOptions').and.returnValue([]);
spyOn(externalCollObj.fieldOptions, 'externalFunctionsList').and.returnValue([{ label: '', value: ''}, { label: 'lb1', cbtype: 'typmodin', value: 'val1'}, { label: 'lb2', cbtype: 'all', value: 'val2'}]);
spyOn(externalCollObj.fieldOptions, 'types').and.returnValue([{ label: '', value: ''}]);
mount(<SchemaView
formType='dialog'
schema={externalCollObj}
viewHelperProps={{
mode: 'create',
}}
onSave={()=>{}}
onClose={()=>{}}
onHelp={()=>{}}
onEdit={()=>{}}
onDataChange={()=>{}}
confirmOnCloseReset={false}
hasSQL={false}
disableSqlHelp={false}
/>);
mount(<SchemaView
formType='dialog'
schema={externalCollObj}
viewHelperProps={{
mode: 'edit',
}}
onSave={()=>{}}
onClose={()=>{}}
getInitData={getInitData}
onHelp={()=>{}}
onEdit={()=>{}}
onDataChange={()=>{}}
confirmOnCloseReset={false}
hasSQL={false}
disableSqlHelp={false}
/>);
});
it('external validate', () => {
let state = { typtype: 'b' }; //validating for ExternalSchema which is distinguish as r
let setError = jasmine.createSpy('setError');
externalCollObj.validate(state, setError);
expect(setError).toHaveBeenCalledWith('typinput', 'Input function cannot be empty');
state.typinput = 'demo_input';
externalCollObj.validate(state, setError);
expect(setError).toHaveBeenCalledWith('typoutput', 'Output function cannot be empty');
});
});
describe('range schema describe', () => {
let rangeCollObj = getRangeSchema({}, {server: {user: {name: 'postgres'}}}, {});
it('range collection', ()=>{
spyOn(nodeAjax, 'getNodeAjaxOptions').and.returnValue([]);
spyOn(rangeCollObj.fieldOptions, 'getSubOpClass').and.returnValue([{ label: '', value: ''}, { label: 'lb1', value: 'val1'}]);
spyOn(rangeCollObj.fieldOptions, 'getCanonicalFunctions').and.returnValue([{ label: '', value: ''}, { label: 'lb1', value: 'val1'}]);
spyOn(rangeCollObj.fieldOptions, 'getSubDiffFunctions').and.returnValue([{ label: '', value: ''}, { label: 'lb1', value: 'val1'}]);
spyOn(rangeCollObj.fieldOptions, 'typnameList').and.returnValue([{ label: '', value: ''}, { label: 'lb1', value: 'val1'}]);
spyOn(rangeCollObj.fieldOptions, 'collationsList').and.returnValue([{ label: '', value: ''}, { label: 'lb1', value: 'val1'}]);
mount(<SchemaView
formType='dialog'
schema={rangeCollObj}
viewHelperProps={{
mode: 'create',
}}
onSave={()=>{}}
onClose={()=>{}}
onHelp={()=>{}}
onEdit={()=>{}}
onDataChange={()=>{}}
confirmOnCloseReset={false}
hasSQL={false}
disableSqlHelp={false}
/>);
mount(<SchemaView
formType='dialog'
schema={rangeCollObj}
viewHelperProps={{
mode: 'edit',
}}
onSave={()=>{}}
onClose={()=>{}}
getInitData={getInitData}
onHelp={()=>{}}
onEdit={()=>{}}
onDataChange={()=>{}}
confirmOnCloseReset={false}
hasSQL={false}
disableSqlHelp={false}
/>);
});
it('range validate', () => {
let state = { typtype: 'r' }; //validating for RangeSchema which is distinguish as r
let setError = jasmine.createSpy('setError');
rangeCollObj.validate(state, setError);
expect(setError).toHaveBeenCalledWith('typname', 'Subtype cannot be empty');
});
});
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}];
it('data type collection', ()=>{
spyOn(nodeAjax, 'getNodeAjaxOptions').and.returnValue([]);
mount(<SchemaView
formType='dialog'
schema={dataTypeObj}
viewHelperProps={{
mode: 'create',
}}
onSave={()=>{}}
onClose={()=>{}}
onHelp={()=>{}}
onEdit={()=>{}}
onDataChange={()=>{}}
confirmOnCloseReset={false}
hasSQL={false}
disableSqlHelp={false}
/>);
mount(<SchemaView
formType='dialog'
schema={dataTypeObj}
viewHelperProps={{
mode: 'edit',
}}
onSave={()=>{}}
getInitData={getInitData}
onClose={()=>{}}
onHelp={()=>{}}
onEdit={()=>{}}
onDataChange={()=>{}}
confirmOnCloseReset={false}
hasSQL={false}
disableSqlHelp={false}
/>);
});
it('tlength editable', ()=>{
dataTypeObj.type_options = types;
let editable = _.find(dataTypeObj.fields, (f)=>f.id=='tlength').editable;
let status = editable({type: 'numeric', type_options: types});
expect(status).toBe(true);
});
it('tlength disabled', ()=>{
dataTypeObj.type_options = types;
let disabled = _.find(dataTypeObj.fields, (f)=>f.id=='tlength').disabled;
let status = disabled({type: 'numeric', type_options: types});
expect(status).toBe(false);
});
it('precision editable', ()=>{
dataTypeObj.type_options = types;
let editable = _.find(dataTypeObj.fields, (f)=>f.id=='precision').editable;
let status = editable({type: 'numeric', type_options: types});
expect(status).toBe(true);
});
it('precision disabled', ()=>{
dataTypeObj.type_options = types;
let disabled = _.find(dataTypeObj.fields, (f)=>f.id=='precision').disabled;
let status = disabled({type: 'numeric', type_options: types});
expect(status).toBe(false);
});
});
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'
}
);
it('create', ()=>{
mount(<SchemaView
formType='dialog'
schema={typeSchemaObj}
viewHelperProps={{
mode: 'create',
}}
onSave={()=>{}}
onClose={()=>{}}
onHelp={()=>{}}
onEdit={()=>{}}
onDataChange={()=>{}}
confirmOnCloseReset={false}
hasSQL={false}
disableSqlHelp={false}
/>);
});
it('edit', ()=>{
mount(<SchemaView
formType='dialog'
schema={typeSchemaObj}
viewHelperProps={{
mode: 'edit',
}}
onSave={()=>{}}
getInitData={getInitData}
onClose={()=>{}}
onHelp={()=>{}}
onEdit={()=>{}}
onDataChange={()=>{}}
confirmOnCloseReset={false}
hasSQL={false}
disableSqlHelp={false}
/>);
});
it('properties', ()=>{
mount(<SchemaView
formType='tab'
schema={typeSchemaObj}
getInitData={getInitData}
viewHelperProps={{
mode: 'properties',
}}
onHelp={()=>{}}
onEdit={()=>{}}
/>);
});
});