mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2024-11-22 08:46:39 -06:00
Port FTS Configurations node to react. Fixes #6638
This commit is contained in:
parent
42eac6f846
commit
4bfffa3806
@ -7,6 +7,9 @@
|
|||||||
//
|
//
|
||||||
//////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
import { getNodeAjaxOptions, getNodeListByName, getNodeListById} from '../../../../../../../static/js/node_ajax';
|
||||||
|
import FTSConfigurationSchema from './fts_configuration.ui';
|
||||||
|
|
||||||
define('pgadmin.node.fts_configuration', [
|
define('pgadmin.node.fts_configuration', [
|
||||||
'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'backbone',
|
'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'backbone',
|
||||||
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backform', 'pgadmin.backgrid',
|
'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backform', 'pgadmin.backgrid',
|
||||||
@ -17,398 +20,6 @@ define('pgadmin.node.fts_configuration', [
|
|||||||
schemaChild, schemaChildTreeNode
|
schemaChild, schemaChildTreeNode
|
||||||
) {
|
) {
|
||||||
|
|
||||||
// Model for tokens control
|
|
||||||
var TokenModel = pgAdmin.Browser.Node.Model.extend({
|
|
||||||
idAttribute: 'token',
|
|
||||||
defaults: {
|
|
||||||
token: undefined,
|
|
||||||
dictname: undefined,
|
|
||||||
},
|
|
||||||
keys: ['token'],
|
|
||||||
// Define the schema for the token/dictionary list
|
|
||||||
schema: [{
|
|
||||||
id: 'token', label: gettext('Token'), type:'text', group: null,
|
|
||||||
cellHeaderClasses:'width_percent_50',
|
|
||||||
editable: false, cell: 'string', url: 'tokens',
|
|
||||||
},{
|
|
||||||
id: 'dictname', label: gettext('Dictionaries'), type: 'text', group:null,
|
|
||||||
cellHeaderClasses:'width_percent_50', editable: true,
|
|
||||||
cell:Backgrid.Extension.MultiSelectAjaxCell, url: 'dictionaries',
|
|
||||||
cache_level:'fts_configuration', cache_node:'fts_configuration',
|
|
||||||
}],
|
|
||||||
// Validation for token and dictionary list
|
|
||||||
validate: function() {
|
|
||||||
// Clear any existing errors.
|
|
||||||
var msg;
|
|
||||||
this.errorModel.clear();
|
|
||||||
var token = this.get('token');
|
|
||||||
var dictionary = this.get('dictname');
|
|
||||||
|
|
||||||
if (_.isNull(token) ||
|
|
||||||
_.isUndefined(token) ||
|
|
||||||
String(token).replace(/^\s+|\s+$/g, '') == '') {
|
|
||||||
msg = gettext('Token cannot be empty.');
|
|
||||||
this.errorModel.set('token',msg);
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_.isNull(dictionary) ||
|
|
||||||
_.isUndefined(dictionary) ||
|
|
||||||
String(dictionary).replace(/^\s+|\s+$/g, '') == '') {
|
|
||||||
msg = gettext('Dictionary name cannot be empty.');
|
|
||||||
this.errorModel.set('dictname',msg);
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Customized control for token control
|
|
||||||
var TokenControl = Backform.TokenControl =
|
|
||||||
Backform.UniqueColCollectionControl.extend({
|
|
||||||
|
|
||||||
initialize: function() {
|
|
||||||
Backform.UniqueColCollectionControl.prototype.initialize.apply(
|
|
||||||
this, arguments
|
|
||||||
);
|
|
||||||
|
|
||||||
var self = this,
|
|
||||||
headerSchema = [{
|
|
||||||
id: 'token', label:'', type:'text', url: 'tokens',
|
|
||||||
node:'fts_configuration', canAdd: true, 'url_with_id': true,
|
|
||||||
|
|
||||||
// Defining control for tokens dropdown control in header
|
|
||||||
control: Backform.NodeAjaxOptionsControl.extend({
|
|
||||||
formatter: Backform.NodeAjaxOptionsControl.prototype.formatter,
|
|
||||||
initialize: function() {
|
|
||||||
Backform.NodeAjaxOptionsControl.prototype.initialize.apply(
|
|
||||||
this,
|
|
||||||
arguments
|
|
||||||
);
|
|
||||||
var _self = this,
|
|
||||||
url = _self.field.get('url') || _self.defaults.url,
|
|
||||||
m = _self.model.top || _self.model;
|
|
||||||
|
|
||||||
/* Fetch the tokens/dict list from '_self' node.
|
|
||||||
* Here '_self' refers to unique collection control where
|
|
||||||
* '_self' refers to nodeAjaxOptions control for dictionary
|
|
||||||
*/
|
|
||||||
if (url) {
|
|
||||||
var node = this.field.get('schema_node'),
|
|
||||||
node_info = this.field.get('node_info'),
|
|
||||||
full_url = node.generate_url.apply(
|
|
||||||
node, [
|
|
||||||
null, url, this.field.get('node_data'),
|
|
||||||
this.field.get('url_with_id') || false,
|
|
||||||
node_info,
|
|
||||||
]),
|
|
||||||
cache_level = this.field.get('cache_level') || node.type,
|
|
||||||
cache_node = this.field.get('cache_node');
|
|
||||||
|
|
||||||
cache_node = (cache_node &&
|
|
||||||
pgAdmin.Browser.Nodes[cache_node])
|
|
||||||
|| node;
|
|
||||||
|
|
||||||
// Clear the cache to get the latest dictionaries and parsers.
|
|
||||||
cache_node.clear_cache(this);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* We needs to check, if we have already cached data
|
|
||||||
* for this url. If yes - use it, and do not bother about
|
|
||||||
* fetching it again.
|
|
||||||
*/
|
|
||||||
var data = cache_node.cache(url, node_info, cache_level);
|
|
||||||
|
|
||||||
// Fetch token/dictionary list
|
|
||||||
if (this.field.get('version_compatible') &&
|
|
||||||
(_.isUndefined(data) || _.isNull(data))) {
|
|
||||||
m.trigger('pgadmin:view:fetching', m, _self.field);
|
|
||||||
$.ajax({
|
|
||||||
async: false,
|
|
||||||
url: full_url,
|
|
||||||
})
|
|
||||||
.done(function(res) {
|
|
||||||
/*
|
|
||||||
* We will cache this data for short period of time for
|
|
||||||
* avoiding same calls.
|
|
||||||
*/
|
|
||||||
data = cache_node.cache(url,
|
|
||||||
node_info,
|
|
||||||
cache_level,
|
|
||||||
res.data
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.fail(function() {
|
|
||||||
m.trigger('pgadmin:view:fetch:error', m, _self.field);
|
|
||||||
});
|
|
||||||
m.trigger('pgadmin:view:fetched', m, _self.field);
|
|
||||||
}
|
|
||||||
|
|
||||||
// It is feasible that the data may not have been fetched.
|
|
||||||
data = (data && data.data) || [];
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Transform the data
|
|
||||||
*/
|
|
||||||
var transform = (this.field.get('transform')
|
|
||||||
|| _self.defaults.transform);
|
|
||||||
if (transform && _.isFunction(transform)) {
|
|
||||||
_self.field.set('options', transform.bind(_self, data));
|
|
||||||
} else {
|
|
||||||
_self.field.set('options', data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
// Select2 control for adding new tokens
|
|
||||||
select2: {
|
|
||||||
allowClear: true, width: 'style',
|
|
||||||
placeholder: gettext('Select token'),
|
|
||||||
},
|
|
||||||
first_empty: true,
|
|
||||||
disabled: function() {
|
|
||||||
return _.isUndefined(self.model.get('oid'));
|
|
||||||
},
|
|
||||||
}],
|
|
||||||
headerDefaults = {token: null},
|
|
||||||
// Grid columns backgrid
|
|
||||||
gridCols = ['token', 'dictname'];
|
|
||||||
|
|
||||||
// Creating model for header control which is used to add new tokens
|
|
||||||
self.headerData = new (Backbone.Model.extend({
|
|
||||||
defaults: headerDefaults,
|
|
||||||
schema: headerSchema,
|
|
||||||
}))({});
|
|
||||||
|
|
||||||
// Creating view from header schema in tokens control
|
|
||||||
var headerGroups = Backform.generateViewSchema(
|
|
||||||
self.field.get('node_info'), self.headerData, 'create',
|
|
||||||
self.field.get('schema_node'), self.field.get('node_data')
|
|
||||||
),
|
|
||||||
fields = [];
|
|
||||||
|
|
||||||
_.each(headerGroups, function(o) {
|
|
||||||
fields = fields.concat(o.fields);
|
|
||||||
});
|
|
||||||
self.headerFields = new Backform.Fields(fields);
|
|
||||||
|
|
||||||
// creating grid using grid columns
|
|
||||||
self.gridSchema = Backform.generateGridColumnsFromModel(
|
|
||||||
self.field.get('node_info'), self.field.get('model'),
|
|
||||||
'edit', gridCols, self.field.get('schema_node')
|
|
||||||
);
|
|
||||||
|
|
||||||
// Providing behaviour control functions to header and grid control
|
|
||||||
self.controls = [];
|
|
||||||
self.listenTo(self.headerData, 'change', self.headerDataChanged);
|
|
||||||
self.listenTo(self.headerData, 'select2', self.headerDataChanged);
|
|
||||||
self.listenTo(self.collection, 'add', self.onAddorRemoveTokens);
|
|
||||||
self.listenTo(self.collection, 'remove', self.onAddorRemoveTokens);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Template for creating header view
|
|
||||||
generateHeader: function(data) {
|
|
||||||
var header = [
|
|
||||||
'<div class="subnode-header-form">',
|
|
||||||
' <div class="container-fluid">',
|
|
||||||
' <div class="row">',
|
|
||||||
' <div class="col-3">',
|
|
||||||
' <label class="control-label"><%-token_label%></label>',
|
|
||||||
' </div>',
|
|
||||||
' <div class="col-6" header="token"></div>',
|
|
||||||
' <div class="col-2">',
|
|
||||||
' <button class="btn btn-sm-sq btn-primary-icon add fa fa-plus" <%=canAdd ? "" : "disabled=\'disabled\'"%> ><span class="sr-only">' + gettext('Add Token') + '</span></button>',
|
|
||||||
' </div>',
|
|
||||||
' </div>',
|
|
||||||
' </div>',
|
|
||||||
'</div>'].join('\n');
|
|
||||||
|
|
||||||
_.extend(data, {
|
|
||||||
token_label: gettext('Tokens'),
|
|
||||||
});
|
|
||||||
|
|
||||||
var self = this,
|
|
||||||
headerTmpl = _.template(header),
|
|
||||||
$header = $(headerTmpl(data)),
|
|
||||||
controls = this.controls;
|
|
||||||
|
|
||||||
self.headerFields.each(function(field) {
|
|
||||||
var control = new (field.get('control'))({
|
|
||||||
field: field,
|
|
||||||
model: self.headerData,
|
|
||||||
});
|
|
||||||
|
|
||||||
$header.find('div[header="' + field.get('name') + '"]').append(
|
|
||||||
control.render().$el
|
|
||||||
);
|
|
||||||
|
|
||||||
control.$el.find('.control-label').remove();
|
|
||||||
controls.push(control);
|
|
||||||
});
|
|
||||||
|
|
||||||
// We should not show add button in properties mode
|
|
||||||
if (data.mode == 'properties') {
|
|
||||||
$header.find('button.add').remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable add button in token control in create mode
|
|
||||||
if(data.mode == 'create') {
|
|
||||||
$header.find('button.add').attr('disabled', true);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.$header = $header;
|
|
||||||
return $header;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Providing event handler for add button in header
|
|
||||||
events: _.extend(
|
|
||||||
{}, Backform.UniqueColCollectionControl.prototype.events,
|
|
||||||
{'click button.add': 'addTokens'}
|
|
||||||
),
|
|
||||||
|
|
||||||
// Show token/dictionary grid
|
|
||||||
showGridControl: function(data) {
|
|
||||||
|
|
||||||
var self = this,
|
|
||||||
titleTmpl = _.template('<div class=\'subnode-header\'></div>'),
|
|
||||||
$gridBody = $('<div></div>', {
|
|
||||||
class:'pgadmin-control-group backgrid form-group col-12 object subnode',
|
|
||||||
}).append(
|
|
||||||
titleTmpl({label: data.label})
|
|
||||||
);
|
|
||||||
|
|
||||||
$gridBody.append(self.generateHeader(data));
|
|
||||||
|
|
||||||
var gridColumns = _.clone(this.gridSchema.columns);
|
|
||||||
|
|
||||||
// Insert Delete Cell into Grid
|
|
||||||
if (data.disabled == false && data.canDelete) {
|
|
||||||
gridColumns.unshift({
|
|
||||||
name: 'pg-backform-delete', label: '',
|
|
||||||
cell: Backgrid.Extension.DeleteCell,
|
|
||||||
editable: false, cell_priority: -1,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.grid) {
|
|
||||||
self.grid.remove();
|
|
||||||
self.grid = null;
|
|
||||||
}
|
|
||||||
// Initialize a new Grid instance
|
|
||||||
var grid = self.grid = new Backgrid.Grid({
|
|
||||||
columns: gridColumns,
|
|
||||||
collection: self.collection,
|
|
||||||
className: 'backgrid table-bordered',
|
|
||||||
});
|
|
||||||
self.$grid = grid.render().$el;
|
|
||||||
|
|
||||||
$gridBody.append(self.$grid);
|
|
||||||
|
|
||||||
// Find selected dictionaries in grid and show it all together
|
|
||||||
setTimeout(function() {
|
|
||||||
self.headerData.set({
|
|
||||||
'token': self.$header.find(
|
|
||||||
'div[header="token"] select'
|
|
||||||
).val(),
|
|
||||||
}, {silent:true}
|
|
||||||
);
|
|
||||||
}, 10);
|
|
||||||
|
|
||||||
// Render node grid
|
|
||||||
return $gridBody;
|
|
||||||
},
|
|
||||||
|
|
||||||
// When user change the header control to add a new token
|
|
||||||
headerDataChanged: function() {
|
|
||||||
var self = this,
|
|
||||||
data = this.headerData.toJSON(),
|
|
||||||
inSelected = (_.isEmpty(data) || _.isUndefined(data));
|
|
||||||
|
|
||||||
if (!self.$header) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.$header.find('button.add').prop('disabled', inSelected);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Get called when user click on add button header
|
|
||||||
addTokens: function(ev) {
|
|
||||||
ev.preventDefault();
|
|
||||||
var self = this,
|
|
||||||
token = self.headerData.get('token');
|
|
||||||
|
|
||||||
if (token && token != '') {
|
|
||||||
var coll = self.model.get(self.field.get('name')),
|
|
||||||
m = new (self.field.get('model'))(
|
|
||||||
self.headerData.toJSON(), {
|
|
||||||
silent: true, top: self.model.top,
|
|
||||||
collection: coll, handler: coll,
|
|
||||||
}),
|
|
||||||
checkVars = ['token'],
|
|
||||||
idx = -1;
|
|
||||||
|
|
||||||
// Find if token exists in grid
|
|
||||||
self.collection.each(function(local_model) {
|
|
||||||
_.each(checkVars, function(v) {
|
|
||||||
var val = local_model.get(v);
|
|
||||||
if(val == token) {
|
|
||||||
idx = coll.indexOf(local_model);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// remove 'm' if duplicate value found.
|
|
||||||
if (idx == -1) {
|
|
||||||
coll.add(m);
|
|
||||||
idx = coll.indexOf(m);
|
|
||||||
}
|
|
||||||
self.$grid.find('.new').removeClass('new');
|
|
||||||
var newRow = self.grid.body.rows[idx].$el;
|
|
||||||
newRow.addClass('new');
|
|
||||||
$(newRow).pgMakeVisible('backform-tab');
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
// When user delete token/dictionary entry from grid
|
|
||||||
onAddorRemoveTokens: function() {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Wait for collection to be updated before checking for the button to
|
|
||||||
* be enabled, or not.
|
|
||||||
*/
|
|
||||||
setTimeout(function() {
|
|
||||||
self.collection.trigger('pgadmin:tokens:updated', self.collection);
|
|
||||||
self.headerDataChanged();
|
|
||||||
}, 10);
|
|
||||||
},
|
|
||||||
|
|
||||||
// When control is about to destroy
|
|
||||||
remove: function() {
|
|
||||||
/*
|
|
||||||
* Stop listening the events registered by this control.
|
|
||||||
*/
|
|
||||||
this.stopListening(this.headerData, 'change', this.headerDataChanged);
|
|
||||||
this.listenTo(this.headerData, 'select2', this.headerDataChanged);
|
|
||||||
this.listenTo(this.collection, 'remove', this.onAddorRemoveTokens);
|
|
||||||
|
|
||||||
// Remove header controls.
|
|
||||||
_.each(this.controls, function(control) {
|
|
||||||
control.remove();
|
|
||||||
});
|
|
||||||
TokenControl.__super__.remove.apply(this, arguments);
|
|
||||||
|
|
||||||
// Remove the header model
|
|
||||||
delete (this.headerData);
|
|
||||||
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// Extend the collection class for FTS Configuration
|
// Extend the collection class for FTS Configuration
|
||||||
if (!pgBrowser.Nodes['coll-fts_configuration']) {
|
if (!pgBrowser.Nodes['coll-fts_configuration']) {
|
||||||
pgAdmin.Browser.Nodes['coll-fts_configuration'] =
|
pgAdmin.Browser.Nodes['coll-fts_configuration'] =
|
||||||
@ -463,19 +74,30 @@ define('pgadmin.node.fts_configuration', [
|
|||||||
}]);
|
}]);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getSchema: function(treeNodeInfo, itemNodeData) {
|
||||||
|
return new FTSConfigurationSchema(
|
||||||
|
{
|
||||||
|
role: ()=>getNodeListByName('role', treeNodeInfo, itemNodeData),
|
||||||
|
schema: ()=>getNodeListById(pgBrowser.Nodes['schema'], treeNodeInfo, itemNodeData),
|
||||||
|
parsers: ()=>getNodeAjaxOptions('parsers', this, treeNodeInfo, itemNodeData),
|
||||||
|
copyConfig: ()=>getNodeAjaxOptions('copyConfig', this, treeNodeInfo, itemNodeData),
|
||||||
|
tokens: ()=>getNodeAjaxOptions('tokens', this, treeNodeInfo, itemNodeData, {urlWithId: true}),
|
||||||
|
dictionaries: ()=>getNodeAjaxOptions('dictionaries', this, treeNodeInfo, itemNodeData, {
|
||||||
|
cacheLevel: 'fts_configuration',
|
||||||
|
cacheNode: 'fts_configuration'
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
owner: pgBrowser.serverInfo[treeNodeInfo.server._id].user.name,
|
||||||
|
schema: itemNodeData._id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
// Defining model for FTS Configuration node
|
// Defining model for FTS Configuration node
|
||||||
model: pgAdmin.Browser.Node.Model.extend({
|
model: pgAdmin.Browser.Node.Model.extend({
|
||||||
idAttribute: 'oid',
|
idAttribute: 'oid',
|
||||||
defaults: {
|
|
||||||
name: undefined, // FTS Configuration name
|
|
||||||
owner: undefined, // FTS Configuration owner
|
|
||||||
is_sys_obj: undefined, // Is system object
|
|
||||||
description: undefined, // Comment on FTS Configuration
|
|
||||||
schema: undefined, // Schema name FTS Configuration belongs to
|
|
||||||
prsname: undefined, // FTS parser list for FTS Configuration node
|
|
||||||
copy_config: undefined, // FTS configuration list to copy from
|
|
||||||
tokens: undefined, // token/dictionary pair list for node
|
|
||||||
},
|
|
||||||
initialize: function(attrs, opts) {
|
initialize: function(attrs, opts) {
|
||||||
var isNew = (_.size(attrs) === 0);
|
var isNew = (_.size(attrs) === 0);
|
||||||
pgAdmin.Browser.Node.Model.prototype.initialize.apply(this, arguments);
|
pgAdmin.Browser.Node.Model.prototype.initialize.apply(this, arguments);
|
||||||
@ -492,106 +114,10 @@ define('pgadmin.node.fts_configuration', [
|
|||||||
schema: [{
|
schema: [{
|
||||||
id: 'name', label: gettext('Name'), cell: 'string',
|
id: 'name', label: gettext('Name'), cell: 'string',
|
||||||
type: 'text', cellHeaderClasses: 'width_percent_50',
|
type: 'text', cellHeaderClasses: 'width_percent_50',
|
||||||
},{
|
}, {
|
||||||
id: 'oid', label: gettext('OID'), cell: 'string',
|
|
||||||
editable: false, type: 'text', mode:['properties'],
|
|
||||||
},{
|
|
||||||
id: 'owner', label: gettext('Owner'), cell: 'string',
|
|
||||||
type: 'text', mode: ['properties', 'edit','create'], node: 'role',
|
|
||||||
control: Backform.NodeListByNameControl, select2: { allowClear: false },
|
|
||||||
},{
|
|
||||||
id: 'schema', label: gettext('Schema'), cell: 'string',
|
|
||||||
type: 'text', mode: ['create','edit'], node: 'schema',
|
|
||||||
control: 'node-list-by-id', cache_node: 'database',
|
|
||||||
cache_level: 'database',
|
|
||||||
},{
|
|
||||||
id: 'is_sys_obj', label: gettext('System FTS configuration?'),
|
|
||||||
cell:'boolean', type: 'switch', mode: ['properties'],
|
|
||||||
},{
|
|
||||||
id: 'description', label: gettext('Comment'), cell: 'string',
|
id: 'description', label: gettext('Comment'), cell: 'string',
|
||||||
type: 'multiline', cellHeaderClasses: 'width_percent_50',
|
type: 'multiline', cellHeaderClasses: 'width_percent_50',
|
||||||
},{
|
|
||||||
id: 'prsname', label: gettext('Parser'),type: 'text',
|
|
||||||
url: 'parsers', first_empty: true,
|
|
||||||
group: gettext('Definition'), control: 'node-ajax-options',
|
|
||||||
deps: ['copy_config'],
|
|
||||||
//disable parser when user select copy_config manually and vica-versa
|
|
||||||
disabled: function(m) {
|
|
||||||
var copy_config = m.get('copy_config');
|
|
||||||
return (_.isNull(copy_config) ||
|
|
||||||
_.isUndefined(copy_config) ||
|
|
||||||
copy_config === '') ? false : true;
|
|
||||||
},
|
|
||||||
readonly: function(m) {return !m.isNew();},
|
|
||||||
},{
|
|
||||||
id: 'copy_config', label: gettext('Copy config'),type: 'text',
|
|
||||||
mode: ['create'], group: gettext('Definition'),
|
|
||||||
control: 'node-ajax-options', url: 'copyConfig', deps: ['prsname'],
|
|
||||||
|
|
||||||
//disable copy_config when user select parser manually and vica-versa
|
|
||||||
disabled: function(m) {
|
|
||||||
var parser = m.get('prsname');
|
|
||||||
return (_.isNull(parser) ||
|
|
||||||
_.isUndefined(parser) ||
|
|
||||||
parser === '') ? false : true;
|
|
||||||
},
|
|
||||||
readonly: function(m) {return !m.isNew();},
|
|
||||||
},{
|
|
||||||
id: 'tokens', label: gettext('Tokens'), type: 'collection',
|
|
||||||
group: gettext('Tokens'), control: TokenControl,
|
|
||||||
model: TokenModel, columns: ['token', 'dictionary'],
|
|
||||||
uniqueCol : ['token'], mode: ['create','edit'],
|
|
||||||
canAdd: true, canEdit: false, canDelete: true,
|
|
||||||
}],
|
}],
|
||||||
|
|
||||||
/*
|
|
||||||
* Triggers control specific error messages for name,
|
|
||||||
* copy_config/parser and schema, if any one of them is not specified
|
|
||||||
* while creating new fts configuration
|
|
||||||
*/
|
|
||||||
validate: function() {
|
|
||||||
var msg;
|
|
||||||
var name = this.get('name');
|
|
||||||
var parser = this.get('prsname');
|
|
||||||
var copy_config_or_parser = !(parser === '' ||
|
|
||||||
_.isUndefined(parser) ||
|
|
||||||
_.isNull(parser)) ?
|
|
||||||
this.get('prsname') : this.get('copy_config');
|
|
||||||
var schema = this.get('schema');
|
|
||||||
|
|
||||||
// Clear the existing error model
|
|
||||||
this.errorModel.clear();
|
|
||||||
this.trigger('on-status-clear');
|
|
||||||
|
|
||||||
// Validate the name
|
|
||||||
if (_.isUndefined(name) ||
|
|
||||||
_.isNull(name) ||
|
|
||||||
String(name).replace(/^\s+|\s+$/g, '') == '') {
|
|
||||||
msg = gettext('Name must be specified.');
|
|
||||||
this.errorModel.set('name', msg);
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate parser or copy_config
|
|
||||||
else if (_.isUndefined(copy_config_or_parser) ||
|
|
||||||
_.isNull(copy_config_or_parser) ||
|
|
||||||
String(copy_config_or_parser).replace(/^\s+|\s+$/g, '') == '') {
|
|
||||||
msg = gettext('Select parser or configuration to copy.');
|
|
||||||
this.errorModel.set('parser', msg);
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate schema
|
|
||||||
else if (_.isUndefined(schema) ||
|
|
||||||
_.isNull(schema) ||
|
|
||||||
String(schema).replace(/^\s+|\s+$/g, '') == '') {
|
|
||||||
msg = gettext('Schema must be selected.');
|
|
||||||
this.errorModel.set('schema', msg);
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,182 @@
|
|||||||
|
/////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// 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 DataGridViewWithHeaderForm from 'sources/helpers/DataGridViewWithHeaderForm';
|
||||||
|
import { isEmptyString } from '../../../../../../../../static/js/validators';
|
||||||
|
|
||||||
|
class TokenHeaderSchema extends BaseUISchema {
|
||||||
|
constructor(tokenOptions) {
|
||||||
|
super({
|
||||||
|
token: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.tokenOptions = tokenOptions;
|
||||||
|
this.isNewFTSConf = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
addDisabled() {
|
||||||
|
return this.isNewFTSConf;
|
||||||
|
}
|
||||||
|
|
||||||
|
getNewData(data) {
|
||||||
|
return {
|
||||||
|
token: data.token,
|
||||||
|
dictname: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
get baseFields() {
|
||||||
|
let obj = this;
|
||||||
|
return [{
|
||||||
|
id: 'token', label: gettext('Tokens'), type:'select', editable: false,
|
||||||
|
options: this.tokenOptions, disabled: function() { return obj.isNewFTSConf; }
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TokenSchema extends BaseUISchema {
|
||||||
|
constructor(dictOptions) {
|
||||||
|
super({
|
||||||
|
token: undefined,
|
||||||
|
dictname: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.dictOptions = dictOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
get baseFields() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: 'token', label: gettext('Token'), type:'text',
|
||||||
|
editable: false, cell: '', minWidth: 150, noEmpty: true,
|
||||||
|
}, {
|
||||||
|
id: 'dictname', label: gettext('Dictionaries'),
|
||||||
|
editable: true, controlProps: {multiple: true}, cell:'select',
|
||||||
|
options: this.dictOptions, minWidth: 260, noEmpty: true,
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class FTSConfigurationSchema extends BaseUISchema {
|
||||||
|
constructor(fieldOptions={}, initValues) {
|
||||||
|
super({
|
||||||
|
name: undefined, // FTS Configuration name
|
||||||
|
owner: undefined, // FTS Configuration owner
|
||||||
|
is_sys_obj: undefined, // Is system object
|
||||||
|
description: undefined, // Comment on FTS Configuration
|
||||||
|
schema: undefined, // Schema name FTS Configuration belongs to
|
||||||
|
prsname: undefined, // FTS parser list for FTS Configuration node
|
||||||
|
copy_config: undefined, // FTS configuration list to copy from
|
||||||
|
tokens: undefined, // token/dictionary pair list for node
|
||||||
|
...initValues
|
||||||
|
});
|
||||||
|
this.fieldOptions = {
|
||||||
|
role: [],
|
||||||
|
schema: [],
|
||||||
|
parsers: [],
|
||||||
|
copyConfig: [],
|
||||||
|
tokens: [],
|
||||||
|
dictionaries: [],
|
||||||
|
...fieldOptions,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.tokHeaderSchema = new TokenHeaderSchema(this.fieldOptions.tokens);
|
||||||
|
this.tokColumnSchema = new TokenSchema(this.fieldOptions.dictionaries);
|
||||||
|
}
|
||||||
|
|
||||||
|
get idAttribute() {
|
||||||
|
return 'oid';
|
||||||
|
}
|
||||||
|
|
||||||
|
initialise(data) {
|
||||||
|
this.tokHeaderSchema.isNewFTSConf = this.isNew(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
get baseFields() {
|
||||||
|
let obj = this;
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: 'name', label: gettext('Name'), cell: 'text', type: 'text',
|
||||||
|
noEmpty: true,
|
||||||
|
}, {
|
||||||
|
id: 'oid', label: gettext('OID'), cell: 'text',
|
||||||
|
editable: false, type: 'text', mode:['properties'],
|
||||||
|
}, {
|
||||||
|
id: 'owner', label: gettext('Owner'), cell: 'text',
|
||||||
|
editable: false, type: 'select', options: this.fieldOptions.role,
|
||||||
|
mode: ['properties', 'edit','create'], noEmpty: true,
|
||||||
|
}, {
|
||||||
|
id: 'schema', label: gettext('Schema'),
|
||||||
|
editable: false, type: 'select', options: this.fieldOptions.schema,
|
||||||
|
mode: ['create', 'edit'], noEmpty: true,
|
||||||
|
}, {
|
||||||
|
id: 'is_sys_obj', label: gettext('System FTS configuration?'),
|
||||||
|
cell:'boolean', type: 'switch', mode: ['properties'],
|
||||||
|
}, {
|
||||||
|
id: 'description', label: gettext('Comment'), cell: 'text',
|
||||||
|
type: 'multiline',
|
||||||
|
}, {
|
||||||
|
id: 'prsname', label: gettext('Parser'),
|
||||||
|
editable: false, type: 'select', group: gettext('Definition'),
|
||||||
|
deps: ['copy_config'],
|
||||||
|
options: this.fieldOptions.parsers,
|
||||||
|
//disable parser when user select copy_config manually and vica-versa
|
||||||
|
disabled: function(state) {
|
||||||
|
var copy_config = state.copy_config;
|
||||||
|
return (_.isNull(copy_config) ||
|
||||||
|
_.isUndefined(copy_config) ||
|
||||||
|
copy_config === '') ? false : true;
|
||||||
|
},
|
||||||
|
readonly: function(state) { return !obj.isNew(state); },
|
||||||
|
}, {
|
||||||
|
id: 'copy_config', label: gettext('Copy config'),
|
||||||
|
editable: false, type: 'select', group: gettext('Definition'),
|
||||||
|
mode: ['create'], deps: ['prsname'],
|
||||||
|
options: this.fieldOptions.copyConfig,
|
||||||
|
//disable copy_config when user select parser manually and vica-versa
|
||||||
|
disabled: function(state) {
|
||||||
|
var parser = state.prsname;
|
||||||
|
return (_.isNull(parser) ||
|
||||||
|
_.isUndefined(parser) ||
|
||||||
|
parser === '') ? false : true;
|
||||||
|
},
|
||||||
|
readonly: function(state) { return !obj.isNew(state); },
|
||||||
|
}, {
|
||||||
|
id: 'tokens', label: '', type: 'collection',
|
||||||
|
group: gettext('Tokens'), mode: ['create','edit'],
|
||||||
|
editable: false, schema: this.tokColumnSchema,
|
||||||
|
headerSchema: this.tokHeaderSchema,
|
||||||
|
headerVisible: function() { return true;},
|
||||||
|
CustomControl: DataGridViewWithHeaderForm,
|
||||||
|
uniqueCol : ['token'],
|
||||||
|
canAdd: true, canEdit: false, canDelete: true,
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
validate(state, setError) {
|
||||||
|
let errmsg = null,
|
||||||
|
parser = state.prsname,
|
||||||
|
config = state.copy_config;
|
||||||
|
|
||||||
|
let copy_config_or_parser = !(parser === '' || _.isUndefined(parser)
|
||||||
|
|| _.isNull(parser)) ? parser : config;
|
||||||
|
|
||||||
|
if(isEmptyString(copy_config_or_parser)) {
|
||||||
|
errmsg = gettext('Select parser or configuration to copy.');
|
||||||
|
setError('prsname', errmsg);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
setError('prsname', null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -24,7 +24,7 @@ import {FormFooterMessage, MESSAGE_TYPE } from 'sources/components/FormComponent
|
|||||||
import Theme from 'sources/Theme';
|
import Theme from 'sources/Theme';
|
||||||
import { PrimaryButton, DefaultButton, PgIconButton } from 'sources/components/Buttons';
|
import { PrimaryButton, DefaultButton, PgIconButton } from 'sources/components/Buttons';
|
||||||
import Loader from 'sources/components/Loader';
|
import Loader from 'sources/components/Loader';
|
||||||
import { minMaxValidator, numberValidator, integerValidator, emptyValidator, checkUniqueCol } from '../validators';
|
import { minMaxValidator, numberValidator, integerValidator, emptyValidator, checkUniqueCol, isEmptyString} from '../validators';
|
||||||
import { MappedFormControl } from './MappedControl';
|
import { MappedFormControl } from './MappedControl';
|
||||||
import gettext from 'sources/gettext';
|
import gettext from 'sources/gettext';
|
||||||
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
|
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
|
||||||
@ -225,7 +225,7 @@ function validateSchema(schema, sessData, setError) {
|
|||||||
if(dupInd > 0) {
|
if(dupInd > 0) {
|
||||||
let uniqueColNames = _.filter(field.schema.fields, (uf)=>field.uniqueCol.indexOf(uf.id) > -1)
|
let uniqueColNames = _.filter(field.schema.fields, (uf)=>field.uniqueCol.indexOf(uf.id) > -1)
|
||||||
.map((uf)=>uf.label).join(', ');
|
.map((uf)=>uf.label).join(', ');
|
||||||
if (_.isUndefined(field.label) || _.isNull(field.label)) {
|
if (isEmptyString(field.label)) {
|
||||||
setError(field.uniqueCol[0], gettext('%s must be unique.', uniqueColNames));
|
setError(field.uniqueCol[0], gettext('%s must be unique.', uniqueColNames));
|
||||||
} else {
|
} else {
|
||||||
setError(field.uniqueCol[0], gettext('%s in %s must be unique.', uniqueColNames, field.label));
|
setError(field.uniqueCol[0], gettext('%s in %s must be unique.', uniqueColNames, field.label));
|
||||||
|
@ -53,7 +53,7 @@ export default function DataGridViewWithHeaderForm(props) {
|
|||||||
return (
|
return (
|
||||||
<Box className={containerClassName}>
|
<Box className={containerClassName}>
|
||||||
<Box className={classes.formBorder}>
|
<Box className={classes.formBorder}>
|
||||||
<DataGridHeader label={props.label} />
|
{props.label && <DataGridHeader label={props.label} />}
|
||||||
{headerVisible && <Box className={classes.form}>
|
{headerVisible && <Box className={classes.form}>
|
||||||
<SchemaView
|
<SchemaView
|
||||||
formType={'dialog'}
|
formType={'dialog'}
|
||||||
|
@ -0,0 +1,121 @@
|
|||||||
|
/////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// 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 FTSConfigurationSchema from '../../../pgadmin/browser/server_groups/servers/databases/schemas/fts_configurations/static/js/fts_configuration.ui';
|
||||||
|
|
||||||
|
describe('FTSConfigurationSchema', ()=>{
|
||||||
|
let mount;
|
||||||
|
let schemaObj = new FTSConfigurationSchema(
|
||||||
|
{
|
||||||
|
role: ()=>[],
|
||||||
|
schema: ()=>[],
|
||||||
|
parsers: ()=>[],
|
||||||
|
copyConfig: ()=>[],
|
||||||
|
tokens: ()=>[],
|
||||||
|
dictionaries: ()=>[],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
owner: 'postgres',
|
||||||
|
schema: 'public',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
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={schemaObj}
|
||||||
|
viewHelperProps={{
|
||||||
|
mode: 'create',
|
||||||
|
}}
|
||||||
|
onSave={()=>{}}
|
||||||
|
onClose={()=>{}}
|
||||||
|
onHelp={()=>{}}
|
||||||
|
onEdit={()=>{}}
|
||||||
|
onDataChange={()=>{}}
|
||||||
|
confirmOnCloseReset={false}
|
||||||
|
hasSQL={false}
|
||||||
|
disableSqlHelp={false}
|
||||||
|
disableDialogHelp={false}
|
||||||
|
/>);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('edit', ()=>{
|
||||||
|
mount(<SchemaView
|
||||||
|
formType='dialog'
|
||||||
|
schema={schemaObj}
|
||||||
|
getInitData={getInitData}
|
||||||
|
viewHelperProps={{
|
||||||
|
mode: 'edit',
|
||||||
|
}}
|
||||||
|
onSave={()=>{}}
|
||||||
|
onClose={()=>{}}
|
||||||
|
onHelp={()=>{}}
|
||||||
|
onEdit={()=>{}}
|
||||||
|
onDataChange={()=>{}}
|
||||||
|
confirmOnCloseReset={false}
|
||||||
|
hasSQL={false}
|
||||||
|
disableSqlHelp={false}
|
||||||
|
disableDialogHelp={false}
|
||||||
|
/>);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('properties', ()=>{
|
||||||
|
mount(<SchemaView
|
||||||
|
formType='tab'
|
||||||
|
schema={schemaObj}
|
||||||
|
getInitData={getInitData}
|
||||||
|
viewHelperProps={{
|
||||||
|
mode: 'properties',
|
||||||
|
}}
|
||||||
|
onHelp={()=>{}}
|
||||||
|
onEdit={()=>{}}
|
||||||
|
/>);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validate', ()=>{
|
||||||
|
let state = {};
|
||||||
|
let setError = jasmine.createSpy('setError');
|
||||||
|
|
||||||
|
state.prsname = '';
|
||||||
|
state.copy_config = '';
|
||||||
|
schemaObj.validate(state, setError);
|
||||||
|
expect(setError).toHaveBeenCalledWith('prsname', 'Select parser or configuration to copy.');
|
||||||
|
|
||||||
|
state.prsname = 'default';
|
||||||
|
schemaObj.validate(state, setError);
|
||||||
|
expect(setError).toHaveBeenCalledWith('prsname', null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in New Issue
Block a user