Port Index node to react. Fixes #6661

pull/58/head
Rahul Shirsat 2021-08-11 12:19:52 +05:30 committed by Akshay Joshi
parent 1b7a77f5cb
commit e3992527fb
3 changed files with 745 additions and 429 deletions

View File

@ -7,6 +7,9 @@
//
//////////////////////////////////////////////////////////////
import IndexSchema, { getColumnSchema } from './index.ui';
import { getNodeAjaxOptions, getNodeListByName } from 'pgbrowser/node_ajax';
define('pgadmin.node.index', [
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
'backbone', 'sources/pgadmin', 'pgadmin.browser', 'pgadmin.alertifyjs',
@ -32,191 +35,6 @@ define('pgadmin.node.index', [
});
}
// Node-Ajax-Cell with Deps
var NodeAjaxOptionsDepsCell = Backgrid.Extension.NodeAjaxOptionsCell.extend({
initialize: function() {
Backgrid.Extension.NodeAjaxOptionsCell.prototype.initialize.apply(this, arguments);
Backgrid.Extension.DependentCell.prototype.initialize.apply(this, arguments);
},
dependentChanged: function () {
var model = this.model,
column = this.column,
editable = this.column.get('editable'),
input = this.$el.find('select').first();
var is_editable = _.isFunction(editable) ? !!editable.apply(column, [model]) : !!editable;
if (is_editable) {
this.$el.addClass('editable');
input.prop('disabled', false);
} else {
this.$el.removeClass('editable');
input.prop('disabled', true);
}
this.delegateEvents();
return this;
},
remove: Backgrid.Extension.DependentCell.prototype.remove,
});
// Model to create column collection control
var ColumnModel = pgAdmin.Browser.Node.Model.extend({
defaults: {
colname: undefined,
collspcname: undefined,
op_class: undefined,
sort_order: false,
nulls: false,
is_sort_nulls_applicable: true,
},
schema: [
{
id: 'colname', label: gettext('Column'), cell: 'node-list-by-name',
type: 'text', disabled: 'inSchema', readonly: 'isEditMode', editable: function(m) {
// Header cell then skip
if (m instanceof Backbone.Collection) {
return false;
}
return !(m.inSchemaWithModelCheck.apply(this, arguments));
},
control: 'node-list-by-name', node: 'column',
},{
id: 'collspcname', label: gettext('Collation'),
cell: NodeAjaxOptionsDepsCell,
type: 'text', disabled: 'inSchema', readonly: 'isEditMode', editable: function(m) {
// Header cell then skip
if (m instanceof Backbone.Collection) {
return false;
}
return !(m.inSchemaWithModelCheck.apply(this, arguments));
},
control: 'node-ajax-options', url: 'get_collations', node: 'index',
url_jump_after_node: 'schema',
},{
id: 'op_class', label: gettext('Operator class'),
cell: NodeAjaxOptionsDepsCell, tags: true,
type: 'text', disabled: 'checkAccessMethod',
editable: function(m) {
// Header cell then skip
if (m instanceof Backbone.Collection || m.inSchemaWithModelCheck.apply(this, arguments)) {
return false;
}
return !(m.checkAccessMethod.apply(this, arguments));
},
control: 'node-ajax-options', url: 'get_op_class', node: 'index',
url_jump_after_node: 'schema',
deps: ['amname'], transform: function(data, control) {
/* We need to extract data from collection according
* to access method selected by user if not selected
* send btree related op_class options
*/
var amname = control.model.top.get('amname'),
options = data['btree'];
if(_.isUndefined(amname))
return options;
_.each(data, function(v, k) {
if(amname === k) {
options = v;
}
});
return options;
},
},{
id: 'sort_order', label: gettext('Sort order'),
cell: Backgrid.Extension.TableChildSwitchCell, type: 'switch',
editable: function(m) {
// Header cell then skip
if (m instanceof Backbone.Collection) {
return false;
} else if (m.inSchemaWithModelCheck.apply(this, arguments)) {
return false;
} else if (m.top.get('amname') === 'btree') {
m.set('is_sort_nulls_applicable', true);
return true;
} else {
m.set('is_sort_nulls_applicable', false);
return false;
}
},
deps: ['amname'],
options: {
'onText': 'DESC', 'offText': 'ASC',
},
},{
id: 'nulls', label: gettext('NULLs'),
cell: Backgrid.Extension.TableChildSwitchCell, type: 'switch',
editable: function(m) {
// Header cell then skip
if (m instanceof Backbone.Collection) {
return false;
} else if (m.inSchemaWithModelCheck.apply(this, arguments)) {
return false;
} else if (m.top.get('amname') === 'btree') {
m.set('is_sort_nulls_applicable', true);
return true;
} else {
m.set('is_sort_nulls_applicable', false);
return false;
}
},
deps: ['amname', 'sort_order'],
options: {
'onText': 'FIRST', 'offText': 'LAST',
},
},
],
validate: function() {
this.errorModel.clear();
if (_.isUndefined(this.get('colname'))
|| String(this.get('colname')).replace(/^\s+|\s+$/g, '') == '') {
var msg = gettext('Column Name cannot be empty.');
this.errorModel.set('colname', msg);
return msg;
}
},
// We will check if we are under schema node
inSchema: function() {
if(this.node_info && 'catalog' in this.node_info) {
return true;
}
return false;
},
isEditMode: function(m) {
return !m.top.isNew();
},
// We will check if we are under schema node & in 'create' mode
inSchemaWithModelCheck: function(m) {
if(m.top.node_info && 'schema' in m.top.node_info) {
// We will disable control if it's in 'edit' mode
return !m.top.isNew();
}
return true;
},
// We will check if we are under schema node and added condition
checkAccessMethod: function(m) {
//Access method is empty or btree then do not disable field
var parent_model = m.top;
if(_.isUndefined(parent_model.get('amname')) ||
_.isNull(parent_model.get('amname')) ||
String(parent_model.get('amname')).replace(/^\s+|\s+$/g, '') == '' ||
parent_model.get('amname') === 'btree') {
// We need to set nulls to true if sort_order is set to desc
// nulls first is default for desc
if(m.get('sort_order') == true && m.previous('sort_order') == false) {
setTimeout(function() { m.set('nulls', true); }, 10);
}
}
else {
m.set('is_sort_nulls_applicable', false);
}
return false;
},
});
if (!pgBrowser.Nodes['index']) {
pgAdmin.Browser.Nodes['index'] = pgBrowser.Node.extend({
parent_type: ['table', 'view', 'mview', 'partition'],
@ -291,253 +109,11 @@ define('pgadmin.node.index', [
},{
id: 'oid', label: gettext('OID'), cell: 'string',
type: 'int', readonly: true, mode: ['properties'],
},{
id: 'spcname', label: gettext('Tablespace'), cell: 'string',
control: 'node-list-by-name', node: 'tablespace',
select2: {'allowClear': true},
type: 'text', mode: ['properties', 'create', 'edit'],
disabled: 'inSchema', filter: function(d) {
// If tablespace name is not "pg_global" then we need to exclude them
if(d && d.label.match(/pg_global/))
{
return false;
}
return true;
},
},{
id: 'amname', label: gettext('Access Method'), cell: 'string',
type: 'text', mode: ['properties', 'create', 'edit'],
disabled: 'inSchema', readonly: 'isEditMode', url: 'get_access_methods',
url_jump_after_node: 'schema',
group: gettext('Definition'), select2: {'allowClear': true},
control: Backform.NodeAjaxOptionsControl.extend({
// When access method changes we need to clear columns collection
onChange: function() {
Backform.NodeAjaxOptionsControl.prototype.onChange.apply(this, arguments);
var self = this,
// current access method
current_am = self.model.get('amname'),
// previous access method
previous_am = self.model.previous('amname');
if (current_am != previous_am && self.model.get('columns').length !== 0) {
var msg = gettext('Changing access method will clear columns collection');
Alertify.confirm(msg, function () {
// User clicks Ok, lets clear collection
var column_collection = self.model.get('columns'),
col_length = column_collection.length;
for (var i=(col_length-1);i>=0;i--) {
column_collection.remove(column_collection.models[i]);
}
}, function() {
// User clicks Cancel set previous value again in combo box
setTimeout(function(){
self.model.set('amname', previous_am);
}, 10);
});
}
},
}),
},{
id: 'columns_csv', label: gettext('Columns'), cell: 'string',
type: 'text', disabled: 'inSchema', mode: ['properties'],
group: gettext('Definition'),
},{
id: 'include', label: gettext('Include columns'),
type: 'array', group: gettext('Definition'),
editable: false,
canDelete: true, canAdd: true, mode: ['properties'],
disabled: 'inSchema', readonly: 'isEditMode',
visible: function(m) {
if(!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server)
&& !_.isUndefined(m.node_info.server.version) &&
m.node_info.server.version >= 110000)
return true;
return false;
},
control: Backform.MultiSelectAjaxControl.extend({
defaults: _.extend(
{},
Backform.NodeListByNameControl.prototype.defaults,
{
select2: {
allowClear: false,
width: 'style',
multiple: true,
placeholder: gettext('Select the column(s)'),
},
}
),
}),
transform : function(data){
var res = [];
if (data && _.isArray(data)) {
_.each(data, function(d) {
res.push({label: d.label, value: d.label, image:'icon-column'});
});
}
return res;
},
node:'column',
},{
id: 'fillfactor', label: gettext('Fill factor'), cell: 'string',
type: 'int', disabled: 'inSchema', mode: ['create', 'edit', 'properties'],
min: 10, max:100, group: gettext('Definition'),
},{
id: 'indisunique', label: gettext('Unique?'), cell: 'string',
type: 'switch', disabled: 'inSchema', readonly: 'isEditMode',
group: gettext('Definition'),
},{
id: 'indisclustered', label: gettext('Clustered?'), cell: 'string',
type: 'switch', disabled: 'inSchema',
group: gettext('Definition'),
},{
id: 'indisvalid', label: gettext('Valid?'), cell: 'string',
type: 'switch', mode: ['properties'],
group: gettext('Definition'),
},{
id: 'indisprimary', label: gettext('Primary?'), cell: 'string',
type: 'switch', mode: ['properties'],
group: gettext('Definition'),
},{
id: 'is_sys_idx', label: gettext('System index?'), cell: 'string',
type: 'switch', mode: ['properties'],
},{
id: 'isconcurrent', label: gettext('Concurrent build?'), cell: 'string',
type: 'switch', disabled: 'inSchema', readonly: 'isEditMode',
mode: ['create', 'edit'], group: gettext('Definition'),
},{
id: 'indconstraint', label: gettext('Constraint'), cell: 'string',
type: 'text', disabled: 'inSchema', readonly: 'isEditMode', mode: ['create', 'edit'],
control: 'sql-field', visible: true, group: gettext('Definition'),
},{
id: 'columns', label: gettext('Columns'), type: 'collection', deps: ['amname'],
group: gettext('Definition'), model: ColumnModel, mode: ['edit', 'create'],
canAdd: function(m) {
// We will disable it if it's in 'edit' mode
return m.isNew();
},
canEdit: false,
canDelete: function(m) {
// We will disable it if it's in 'edit' mode
return m.isNew();
},
control: 'unique-col-collection', uniqueCol : ['colname'],
columns: ['colname', 'op_class', 'sort_order', 'nulls', 'collspcname'],
},{
id: 'include', label: gettext('Include columns'),
type: 'array', group: gettext('Definition'),
editable: false,
canDelete: true, canAdd: true, mode: ['edit', 'create'],
disabled: 'inSchema', readonly: 'isEditMode',
visible: function(m) {
if(!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server)
&& !_.isUndefined(m.node_info.server.version) &&
m.node_info.server.version >= 110000)
return true;
return false;
},
control: Backform.MultiSelectAjaxControl.extend({
defaults: _.extend(
{},
Backform.NodeListByNameControl.prototype.defaults,
{
select2: {
allowClear: false,
width: 'style',
multiple: true,
placeholder: gettext('Select the column(s)'),
},
}
),
}),
transform : function(data){
var res = [];
if (data && _.isArray(data)) {
_.each(data, function(d) {
res.push({label: d.label, value: d.label, image:'icon-column'});
});
}
return res;
},
node:'column',
},{
}, {
id: 'description', label: gettext('Comment'), cell: 'string',
type: 'multiline', mode: ['properties', 'create', 'edit'],
disabled: 'inSchema',
},
],
validate: function(keys) {
var msg;
// Nothing to validate
if (keys && keys.length == 0) {
this.errorModel.clear();
return null;
} else {
this.errorModel.clear();
}
if (_.isUndefined(this.get('name'))
|| String(this.get('name')).replace(/^\s+|\s+$/g, '') == '') {
msg = gettext('Name cannot be empty.');
this.errorModel.set('name', msg);
return msg;
}
if (_.isUndefined(this.get('amname'))
|| String(this.get('amname')).replace(/^\s+|\s+$/g, '') == '') {
msg = gettext('Access method cannot be empty.');
this.errorModel.set('amname', msg);
return msg;
}
// Checks if all columns has names
var cols = this.get('columns');
if(cols && cols.length > 0) {
if(!_.every(cols.pluck('colname'))) {
msg = gettext('You must specify column name.');
this.errorModel.set('columns', msg);
return msg;
}
} else if(cols){
msg = gettext('You must specify at least one column.');
this.errorModel.set('columns', msg);
return msg;
}
return null;
},
// We will check if we are under schema node & in 'create' mode
inSchema: function() {
if(this.node_info && 'catalog' in this.node_info) {
return true;
}
return false;
},
isEditMode: function(m) {
return !m.isNew();
},
// We will check if we are under schema node & in 'create' mode
inSchemaWithModelCheck: function(m) {
if(this.node_info && 'schema' in this.node_info) {
// We will disable control if it's in 'edit' mode
return !m.isNew();
}
return true;
},
// Checks weather to enable/disable control
inSchemaWithColumnCheck: function(m) {
if(this.node_info && 'schema' in this.node_info) {
// We will disable control if it's system columns
// ie: it's position is less then 1
if (m.isNew()) {
return false;
} else {
// if we are in edit mode
return (_.isUndefined(m.get('attnum')) || m.get('attnum') < 1 );
}
}
return true;
},
}],
}),
// Below function will enable right click menu for creating column
canCreate: function(itemData, item, data) {
@ -574,6 +150,25 @@ define('pgadmin.node.index', [
return !is_immediate_parent_table_partitioned;
}
},
getSchema: (treeNodeInfo, itemNodeData) => {
let nodeObj = pgAdmin.Browser.Nodes['index'];
return new IndexSchema(
()=>getColumnSchema(nodeObj, treeNodeInfo, itemNodeData),
{
tablespaceList: ()=>getNodeListByName('tablespace', treeNodeInfo, itemNodeData, {}, (m)=>{
return (m.label != 'pg_global');
}),
amnameList : ()=>getNodeAjaxOptions('get_access_methods', nodeObj, treeNodeInfo, itemNodeData),
columnList: ()=>getNodeListByName('column', treeNodeInfo, itemNodeData, {}),
},
{
node_info: treeNodeInfo
},
{
amname: 'btree'
}
);
}
});
}

View File

@ -0,0 +1,479 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2021, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import gettext from 'sources/gettext';
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
import { getNodeAjaxOptions, getNodeListByName } from '../../../../../../../../static/js/node_ajax';
import _ from 'lodash';
import { pgAlertify } from 'sources/helpers/legacyConnector';
import { isEmptyString } from 'sources/validators';
export function getColumnSchema(nodeObj, treeNodeInfo, itemNodeData) {
return new ColumnSchema(
{
columnList: ()=>getNodeListByName('column', treeNodeInfo, itemNodeData, {}),
collationList: ()=>getNodeAjaxOptions('get_collations', nodeObj, treeNodeInfo, itemNodeData),
opClassList: ()=>getNodeAjaxOptions('get_op_class', nodeObj, treeNodeInfo, itemNodeData)
}, {
node_info: treeNodeInfo
}
);
}
export class ColumnSchema extends BaseUISchema {
constructor(fieldOptions = {}, nodeData, initValues) {
super({
name: null,
oid: undefined,
description: '',
is_sys_obj: false,
colname: undefined,
collspcname: undefined,
op_class: undefined,
sort_order: false,
nulls: false,
is_sort_nulls_applicable: true,
...initValues
});
this.fieldOptions = {
columnList: [],
collationList: [],
opClassList: [],
...fieldOptions
};
this.node_info = {
...nodeData.node_info
};
this.op_class_types = [];
}
get idAttribute() {
return 'oid';
}
// We will check if we are under schema node & in 'create' mode
inSchemaWithModelCheck(state) {
if(this.node_info && 'schema' in this.node_info) {
// We will disable control if it's in 'edit' mode
return !this.isNew(state);
}
return true;
}
setOpClassTypes(options) {
if(!options || (_.isArray(options) && options.length == 0))
return this.op_class_types;
if(this.op_class_types.length == 0)
this.op_class_types = options;
}
get baseFields() {
let columnSchemaObj = this;
return [
{
id: 'colname', label: gettext('Column'),
type: 'select', cell: 'select', noEmpty: true,
disabled: () => inSchema(columnSchemaObj.node_info),
readonly: function (state) {
return columnSchemaObj.inSchemaWithModelCheck(state);
},
options: columnSchemaObj.fieldOptions.columnList,
node: 'column',
},{
id: 'collspcname', label: gettext('Collation'),
type: 'select',
cell: 'select',
disabled: () => inSchema(columnSchemaObj.node_info),
readonly: function (state) {
return columnSchemaObj.inSchemaWithModelCheck(state);
},
options: columnSchemaObj.fieldOptions.collationList,
node: 'index',
url_jump_after_node: 'schema',
},{
id: 'op_class', label: gettext('Operator class'),
tags: true, type: 'select',
cell: () => {
return {
cell: 'select',
options: columnSchemaObj.fieldOptions.opClassList,
optionsLoaded: (options)=>{columnSchemaObj.setOpClassTypes(options);},
controlProps: {
allowClear: true,
filter: (options) => {
/* We need to extract data from collection according
* to access method selected by user if not selected
* send btree related op_class options
*/
var amname = columnSchemaObj._top._sessData ? columnSchemaObj._top._sessData.amname : columnSchemaObj._top._origData.amname;
if(_.isUndefined(amname))
return options;
_.each(this.op_class_types, function(v, k) {
if(amname === k) {
options = v;
return;
}
});
return options;
}
}
};
},
readonly: function (state) {
return columnSchemaObj.inSchemaWithModelCheck(state);
},
node: 'index',
url_jump_after_node: 'schema',
deps: ['amname'],
},{
id: 'sort_order', label: gettext('Sort order'),
type: 'switch',
cell: 'switch',
depChange: (state, source, topState, actionObj) => {
//Access method is empty or btree then do not disable field
if(isEmptyString(topState.amname) || topState.amname === 'btree') {
// We need to set nulls to true if sort_order is set to desc
// nulls first is default for desc
if(state.sort_order == true && actionObj.oldState.sort_order == false) {
setTimeout(function() {
state.nulls = true;
}, 10);
}
}
else {
state.is_sort_nulls_applicable = false;
}
},
editable: function(state) {
let topObj = columnSchemaObj._top;
if(columnSchemaObj.inSchemaWithModelCheck(state)) {
return false;
} else if (topObj._sessData && topObj._sessData.amname === 'btree') {
state.is_sort_nulls_applicable = true;
return true;
} else {
state.is_sort_nulls_applicable = false;
return false;
}
},
deps: ['amname'],
options: {
'onText': 'DESC', 'offText': 'ASC',
},
},{
id: 'nulls', label: gettext('NULLs'),
type: 'switch',
cell: 'switch',
editable: function(state) {
let topObj = columnSchemaObj._top;
if(columnSchemaObj.inSchemaWithModelCheck(state)) {
return false;
} else if (topObj._sessData && topObj._sessData.amname === 'btree') {
state.is_sort_nulls_applicable = true;
return true;
} else {
state.is_sort_nulls_applicable = false;
return false;
}
},
deps: ['amname', 'sort_order'],
options: {
'onText': 'FIRST', 'offText': 'LAST',
},
},
];
}
}
function inSchema(node_info) {
if(node_info && 'catalog' in node_info)
{
return true;
}
return false;
}
export default class IndexSchema extends BaseUISchema {
constructor(getColumnSchema, fieldOptions = {}, nodeData, initValues) {
super({
name: undefined,
oid: undefined,
description: '',
is_sys_obj: false,
nspname: undefined,
tabname: undefined,
spcname: undefined,
amname: undefined,
columns: [],
...initValues
});
this.fieldOptions = {
tablespaceList: [],
amnameList: [],
columnList: [],
...fieldOptions
};
this.node_info = {
...nodeData.node_info
};
this.getColumnSchema = getColumnSchema;
}
get idAttribute() {
return 'oid';
}
get baseFields() {
let indexSchemaObj = this;
return [
{
id: 'name', label: gettext('Name'), cell: 'string',
type: 'text', noEmpty: true,
disabled: () => inSchema(indexSchemaObj.node_info),
},{
id: 'oid', label: gettext('OID'), cell: 'string',
type: 'int', readonly: true, mode: ['properties'],
},{
id: 'spcname', label: gettext('Tablespace'), cell: 'string',
node: 'tablespace',
mode: ['properties', 'create', 'edit'],
disabled: () => inSchema(indexSchemaObj.node_info),
type: 'select',
options: indexSchemaObj.fieldOptions.tablespaceList,
controlProps: { allowClear: true },
},{
id: 'amname', label: gettext('Access Method'), cell: 'string',
mode: ['properties', 'create', 'edit'], noEmpty: true,
disabled: () => inSchema(indexSchemaObj.node_info),
readonly: function (state) {
return !indexSchemaObj.isNew(state);
},
url_jump_after_node: 'schema',
group: gettext('Definition'),
type: () => {
return {
type: 'select',
options: indexSchemaObj.fieldOptions.amnameList,
optionsLoaded: (options) => { indexSchemaObj.fieldOptions.amnameList = options; },
controlProps: {
allowClear: true,
filter: (options) => {
let res = [];
if (options && _.isArray(options)) {
_.each(options, function(d) {
if(d.label != '')
res.push({label: d.label, value: d.value, data:d});
});
}
return res;
}
}
};
},
deferredDepChange: (state, source, topState, actionObj) => {
const setColumns = (resolve)=>{
resolve(()=>{
state.columns.splice(0, state.columns.length);
return {
columns: state.columns,
};
});
};
if(state.amname != actionObj.oldState.amname) {
return new Promise((resolve)=>{
pgAlertify().confirm(
gettext('Changing access method will clear columns collection'),
function () {
setColumns(resolve);
},
function() {
resolve(()=>{
state.amname = actionObj.oldState.amname;
return {
amname: state.amname,
};
});
}
);
});
} else {
return Promise.resolve(()=>{});
}
},
},{
id: 'columns_csv', label: gettext('Columns'), cell: 'string',
type: 'text',
disabled: () => inSchema(indexSchemaObj.node_info),
mode: ['properties'],
group: gettext('Definition'),
},{
id: 'include', label: gettext('Include columns'),
group: gettext('Definition'),
editable: false, canDelete: true, canAdd: true, mode: ['properties'],
disabled: () => inSchema(indexSchemaObj.node_info),
readonly: function (state) {
return !indexSchemaObj.isNew(state);
},
type: () => {
return {
type: 'select',
options: indexSchemaObj.fieldOptions.columnList,
optionsLoaded: (options) => { indexSchemaObj.fieldOptions.columnList = options; },
controlProps: {
allowClear: false,
multiple: true,
placeholder: gettext('Select the column(s)'),
width: 'style',
filter: (options) => {
let res = [];
if (options && _.isArray(options)) {
_.each(options, function(d) {
if(d.label != '')
res.push({label: d.label, value: d.value, image:'icon-column'});
});
}
return res;
}
}
};
},
visible: function() {
if(!_.isUndefined(this.node_info) && !_.isUndefined(this.node_info.server)
&& !_.isUndefined(this.node_info.server.version) &&
this.node_info.server.version >= 110000)
return true;
return false;
},
node:'column',
},{
id: 'fillfactor', label: gettext('Fill factor'), cell: 'string',
type: 'int', disabled: () => inSchema(indexSchemaObj.node_info),
mode: ['create', 'edit', 'properties'],
min: 10, max:100, group: gettext('Definition'),
},{
id: 'indisunique', label: gettext('Unique?'), cell: 'string',
type: 'switch', disabled: () => inSchema(indexSchemaObj.node_info),
readonly: function (state) {
return !indexSchemaObj.isNew(state);
},
group: gettext('Definition'),
},{
id: 'indisclustered', label: gettext('Clustered?'), cell: 'string',
type: 'switch', disabled: () => inSchema(indexSchemaObj.node_info),
group: gettext('Definition'),
},{
id: 'indisvalid', label: gettext('Valid?'), cell: 'string',
type: 'switch', mode: ['properties'],
group: gettext('Definition'),
},{
id: 'indisprimary', label: gettext('Primary?'), cell: 'string',
type: 'switch', mode: ['properties'],
group: gettext('Definition'),
},{
id: 'is_sys_idx', label: gettext('System index?'), cell: 'string',
type: 'switch', mode: ['properties'],
},{
id: 'isconcurrent', label: gettext('Concurrent build?'), cell: 'string',
type: 'switch', disabled: () => inSchema(indexSchemaObj.node_info),
readonly: function (state) {
return !indexSchemaObj.isNew(state);
},
mode: ['create', 'edit'], group: gettext('Definition'),
},{
id: 'indconstraint', label: gettext('Constraint'), cell: 'string',
type: 'sql', controlProps: {className:['custom_height_css_class']},
disabled: () => inSchema(indexSchemaObj.node_info),
readonly: function (state) {
return !indexSchemaObj.isNew(state);
},
mode: ['create', 'edit'],
control: 'sql-field', visible: true, group: gettext('Definition'),
}, {
id: 'columns', label: gettext('Columns'), type: 'collection', deps: ['amname'],
group: gettext('Definition'), schema: indexSchemaObj.getColumnSchema(),
mode: ['edit', 'create'],
canAdd: function(state) {
// We will disable it if it's in 'edit' mode
return indexSchemaObj.isNew(state);
},
canEdit: false,
canDelete: function(state) {
// We will disable it if it's in 'edit' mode
return indexSchemaObj.isNew(state);
},
uniqueCol : ['colname'],
columns: ['colname', 'op_class', 'sort_order', 'nulls', 'collspcname']
}, {
id: 'include', label: gettext('Include columns'),
type: () => {
return {
type: 'select',
options: indexSchemaObj.fieldOptions.columnList,
optionsLoaded: (options) => { indexSchemaObj.fieldOptions.columnList = options; },
controlProps: {
allowClear: false,
multiple: true,
placeholder: gettext('Select the column(s)'),
width: 'style',
filter: (options) => {
let res = [];
if (options && _.isArray(options)) {
_.each(options, function(d) {
if(d.label != '')
res.push({label: d.label, value: d.value, image:'icon-column'});
});
}
return res;
}
}
};
},
group: gettext('Definition'),
editable: false,
canDelete: true, canAdd: true, mode: ['edit', 'create'],
disabled: () => inSchema(indexSchemaObj.node_info),
readonly: function (state) {
return !indexSchemaObj.isNew(state);
},
visible: function() {
if(!_.isUndefined(this.node_info) && !_.isUndefined(this.node_info.server)
&& !_.isUndefined(this.node_info.server.version) &&
this.node_info.server.version >= 110000)
return true;
return false;
},
node:'column',
},{
id: 'description', label: gettext('Comment'), cell: 'string',
type: 'multiline', mode: ['properties', 'create', 'edit'],
disabled: () => inSchema(indexSchemaObj.node_info),
},
];
}
validate(state, setError) {
var msg;
// Checks if columns is empty
var cols = state.columns;
if(_.isArray(cols) && cols.length == 0){
msg = gettext('You must specify at least one column.');
setError('columns', msg);
return true;
}
return null;
}
}

View File

@ -0,0 +1,242 @@
/////////////////////////////////////////////////////////////
//
// 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 { createMount } from '@material-ui/core/test-utils';
import pgAdmin from 'sources/pgadmin';
import {messages} from '../fake_messages';
import SchemaView from '../../../pgadmin/static/js/SchemaView';
import * as nodeAjax from '../../../pgadmin/browser/static/js/node_ajax';
import IndexSchema, { getColumnSchema } from '../../../pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/static/js/index.ui';
describe('IndexSchema', ()=>{
let mount;
describe('column schema describe', () => {
let columnSchemaObj = getColumnSchema({}, {server: {user: {name: 'postgres'}}}, {});
it('column schema collection', ()=>{
spyOn(nodeAjax, 'getNodeAjaxOptions').and.returnValue([]);
spyOn(nodeAjax, 'getNodeListByName').and.returnValue([]);
mount(<SchemaView
formType='dialog'
schema={columnSchemaObj}
viewHelperProps={{
mode: 'create',
}}
onSave={()=>{}}
onClose={()=>{}}
onHelp={()=>{}}
onEdit={()=>{}}
onDataChange={()=>{}}
confirmOnCloseReset={false}
hasSQL={false}
disableSqlHelp={false}
disableDialogHelp={false}
/>);
mount(<SchemaView
formType='dialog'
schema={columnSchemaObj}
viewHelperProps={{
mode: 'edit',
}}
onSave={()=>{}}
onClose={()=>{}}
getInitData={getInitData}
onHelp={()=>{}}
onEdit={()=>{}}
onDataChange={()=>{}}
confirmOnCloseReset={false}
hasSQL={false}
disableSqlHelp={false}
disableDialogHelp={false}
/>);
});
it('column schema colname editable', ()=>{
columnSchemaObj._top = {
_sessData: { amname: 'btree' }
};
let cell = _.find(columnSchemaObj.fields, (f)=>f.id=='op_class').cell;
cell();
});
it('column schema sort_order depChange', ()=>{
let topState = { amname: 'btree' };
let depChange = _.find(columnSchemaObj.fields, (f)=>f.id=='sort_order').depChange;
let state = { sort_order: true };
depChange(state, {}, topState, { oldState: { sort_order: false } });
state.sort_order = false;
topState.amname = 'abc';
depChange(state, {}, topState, { oldState: { sort_order: false } });
expect(state.is_sort_nulls_applicable).toBe(false);
});
it('column schema sort_order editable', ()=>{
columnSchemaObj._top = {
_sessData: { amname: 'btree' }
};
let state = {};
spyOn(columnSchemaObj, 'inSchemaWithModelCheck').and.returnValue(true);
let editable = _.find(columnSchemaObj.fields, (f)=>f.id=='sort_order').editable;
let status = editable(state);
expect(status).toBe(false);
spyOn(columnSchemaObj, 'inSchemaWithModelCheck').and.returnValue(false);
status = editable(state);
expect(status).toBe(true);
columnSchemaObj._top._sessData.amname = 'abc';
status = editable(state);
expect(status).toBe(false);
});
it('column schema nulls editable', ()=>{
columnSchemaObj._top = {
_sessData: { amname: 'btree' }
};
let state = {};
spyOn(columnSchemaObj, 'inSchemaWithModelCheck').and.returnValue(true);
let editable = _.find(columnSchemaObj.fields, (f)=>f.id=='nulls').editable;
let status = editable(state);
expect(status).toBe(false);
spyOn(columnSchemaObj, 'inSchemaWithModelCheck').and.returnValue(false);
status = editable(state);
expect(status).toBe(true);
columnSchemaObj._top._sessData.amname = 'abc';
status = editable(state);
expect(status).toBe(false);
});
it('column schema setOpClassTypes', ()=>{
columnSchemaObj._top = {
_sessData: { amname: 'btree' }
};
let options = [];
columnSchemaObj.op_class_types = [];
let status = columnSchemaObj.setOpClassTypes(options);
expect(status).toEqual([]);
columnSchemaObj.op_class_types = [];
options.push({label: '', value: ''});
status = columnSchemaObj.setOpClassTypes(options);
expect(columnSchemaObj.op_class_types.length).toBe(1);
});
});
let indexSchemaObj = new IndexSchema(
()=>getColumnSchema({}, {server: {user: {name: 'postgres'}}}, {}),
{
tablespaceList: ()=>[],
amnameList : ()=>[{label:'abc', value:'abc'}],
columnList: ()=>[{label:'abc', value:'abc'}],
},
{
node_info: {'server': { 'version': 110000} }
},
{
amname: 'btree'
}
);
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 || {};
});
it('create', ()=>{
mount(<SchemaView
formType='dialog'
schema={indexSchemaObj}
viewHelperProps={{
mode: 'create',
}}
onSave={()=>{}}
onClose={()=>{}}
onHelp={()=>{}}
onEdit={()=>{}}
onDataChange={()=>{}}
confirmOnCloseReset={false}
hasSQL={false}
disableSqlHelp={false}
disableDialogHelp={false}
/>);
});
it('edit', ()=>{
mount(<SchemaView
formType='dialog'
schema={indexSchemaObj}
getInitData={getInitData}
viewHelperProps={{
mode: 'create',
}}
onSave={()=>{}}
onClose={()=>{}}
onHelp={()=>{}}
onEdit={()=>{}}
onDataChange={()=>{}}
confirmOnCloseReset={false}
hasSQL={false}
disableSqlHelp={false}
disableDialogHelp={false}
/>);
});
it('properties', ()=>{
mount(<SchemaView
formType='tab'
schema={indexSchemaObj}
getInitData={getInitData}
viewHelperProps={{
mode: 'properties',
}}
onHelp={()=>{}}
onEdit={()=>{}}
/>);
});
it('validate', ()=>{
let state = { columns: [] };
let setError = jasmine.createSpy('setError');
indexSchemaObj.validate(state, setError);
expect(setError).toHaveBeenCalledWith('columns', 'You must specify at least one column.');
state.columns.push({});
let status = indexSchemaObj.validate(state, setError);
expect(status).toBe(null);
});
});