1) Preserve the settings set by the user in the import/export data dialog. Fixes #7428

2) Fixed the JSON editor issue of hiding the first record. Fixes #7520
This commit is contained in:
Yogesh Mahajan
2022-07-18 10:54:03 +05:30
committed by Akshay Joshi
parent 9dde195af4
commit 8f2bda2309
16 changed files with 407 additions and 359 deletions

View File

@@ -15,6 +15,7 @@ import {getHelpUrl, getEPASHelpUrl} from 'pgadmin.help';
import SchemaView from 'sources/SchemaView';
import 'wcdocker';
import Theme from '../../../static/js/Theme';
import url_for from 'sources/url_for';
/* The entry point for rendering React based view in properties, called in node.js */
export function getUtilityView(schema, treeNodeInfo, actionType, formType, container, containerPanel,
@@ -77,6 +78,28 @@ export function getUtilityView(schema, treeNodeInfo, actionType, formType, conta
inCatalog: inCatalog,
};
let initData = ()=>new Promise((resolve, reject)=>{
if(actionType === 'create') {
resolve({});
}else{
api.get(url_for('import_export.get_settings'))
.then((res)=>{
resolve(res.data.data);
})
.catch((err)=>{
if(err.response){
console.error('error resp', err.response);
} else if(err.request){
console.error('error req', err.request);
} else if(err.message){
console.error('error msg', err.message);
}reject(err);
});
}
});
let _schema = schema;
/* Fire at will, mount the DOM */
@@ -84,6 +107,7 @@ export function getUtilityView(schema, treeNodeInfo, actionType, formType, conta
<Theme>
<SchemaView
formType={formType}
getInitData={initData}
schema={_schema}
viewHelperProps={viewHelperProps}
customSaveBtnName={saveBtnName}

View File

@@ -461,24 +461,27 @@ class QueryToolJourneyTest(BaseFeatureTest):
retry = 2
while retry > 0:
try:
cell_el = self.page.find_by_css_selector(
QueryToolLocators.output_row_col.format(2, cell_index))
cell_el = self.page.find_by_xpath(
QueryToolLocators.output_cell_xpath.format(2, cell_index))
# Get existing value
cell_value = int(cell_el.text)
new_value = cell_value + 1
# Try to update value
ActionChains(self.driver).double_click(cell_el).perform()
time.sleep(0.1)
ActionChains(self.driver).send_keys(new_value).perform()
time.sleep(0.1)
ActionChains(self.driver).send_keys(Keys.TAB).perform()
time.sleep(0.5)
time.sleep(0.3)
# Check if the value was updated
# Finding element again to avoid stale element
# reference exception
cell_el = self.page. \
find_by_css_selector(QueryToolLocators.
output_row_col.format(2, cell_index))
find_by_xpath(QueryToolLocators.
output_cell_xpath.format(2, cell_index))
return int(cell_el.text) == new_value
except Exception as e:
traceback.print_exc()
print('Exception while reading cell value', file=sys.stderr)
retry -= 1
if retry == 0:

View File

@@ -218,6 +218,8 @@ CREATE TABLE public.nonintpkey
Returns: None
"""
cell_type = data[2]
value = data[0]
retry = 2
while retry > 0:
self.wait.until(EC.visibility_of_element_located(
@@ -226,10 +228,8 @@ CREATE TABLE public.nonintpkey
cell_el = self.page.find_by_xpath(xpath)
self.page.driver.execute_script(
"arguments[0].scrollIntoView(false)", cell_el)
ActionChains(self.driver).move_to_element(cell_el).\
double_click(cell_el).perform()
cell_type = data[2]
value = data[0]
ActionChains(self.driver).move_to_element(cell_el).perform()
ActionChains(self.driver).double_click(cell_el).perform()
if cell_type in ['int', 'int[]'] and \
self._update_numeric_cell(cell_el, value):
@@ -253,36 +253,34 @@ CREATE TABLE public.nonintpkey
if value == 'clear':
cell_el.find_element(By.CSS_SELECTOR, 'input').clear()
else:
ActionChains(self.driver).send_keys(value). \
send_keys(Keys.TAB).perform()
ActionChains(self.driver).send_keys(value).perform()
ActionChains(self.driver).send_keys(Keys.TAB).perform()
return True
except Exception:
traceback.print_exc()
print('Exception occurred while updating int cell',
file=sys.stderr)
return False
def _update_text_cell(self, cell_el, value):
retry = 2
while retry > 0:
try:
text_area_ele = WebDriverWait(self.driver, 2).until(
EC.visibility_of_element_located(
(By.CSS_SELECTOR,
QueryToolLocators.row_editor_text_area_css)))
text_area_ele.clear()
text_area_ele.click()
text_area_ele.send_keys(value)
# Click on editor's Save button
self.page.find_by_css_selector(
QueryToolLocators.text_editor_ok_btn_css).click()
return True
except Exception:
print('Exception occurred while updating text cell',
file=sys.stderr)
ActionChains(self.driver).move_to_element(cell_el). \
double_click(cell_el).perform()
retry -= 1
return False
try:
text_area_ele = WebDriverWait(self.driver, 2).until(
EC.visibility_of_element_located(
(By.CSS_SELECTOR,
QueryToolLocators.row_editor_text_area_css)))
text_area_ele.clear()
time.sleep(0.3)
text_area_ele.click()
ActionChains(self.driver).send_keys(value).perform()
# Click on editor's Save button
self.page.find_by_css_selector(
QueryToolLocators.text_editor_ok_btn_css).click()
return True
except Exception:
traceback.print_exc()
print('Exception occurred while updating text cell ',
file=sys.stderr)
return False
def _update_json_cell(self, cell_el, value):
platform = 'mac'
@@ -294,53 +292,45 @@ CREATE TABLE public.nonintpkey
key_to_press = Keys.COMMAND
else:
key_to_press = Keys.CONTROL
retry = 2
while retry > 0:
try:
WebDriverWait(self.driver, 2).until(
EC.visibility_of_element_located(
(By.CSS_SELECTOR,
QueryToolLocators.json_editor_text_area_css)))
actions = ActionChains(self.driver)
actions.key_down(key_to_press).send_keys('a').\
key_up(key_to_press).send_keys(Keys.DELETE).perform()
actions.send_keys(value).perform()
# Click on editor's Save button
self.page.find_by_css_selector(
QueryToolLocators.text_editor_ok_btn_css).click()
return True
except Exception:
print('Exception occurred while updating json cell',
file=sys.stderr)
ActionChains(self.driver).move_to_element(cell_el). \
double_click(cell_el).perform()
retry -= 1
return False
try:
WebDriverWait(self.driver, 2).until(
EC.visibility_of_element_located(
(By.CSS_SELECTOR,
QueryToolLocators.json_editor_text_area_css)))
actions = ActionChains(self.driver)
actions.key_down(key_to_press).send_keys('a').\
key_up(key_to_press).send_keys(Keys.DELETE).perform()
actions.send_keys(value).perform()
# Click on editor's Save button
self.page.find_by_css_selector(
QueryToolLocators.text_editor_ok_btn_css).click()
return True
except Exception:
traceback.print_exc()
print('Exception occurred while updating json cell ',
file=sys.stderr)
return False
def _update_boolean_cell(self, cell_el, value):
# Boolean editor test for to True click
retry = 2
while retry > 0:
try:
checkbox_el = self.page.find_by_css_selector(
QueryToolLocators.row_editor_checkbox_css)
if value == 'true':
checkbox_el.click()
# Boolean editor test for to False click
elif value == 'false':
# Sets true
checkbox_el.click()
# Sets false
ActionChains(self.driver).click(checkbox_el).perform()
ActionChains(self.driver).send_keys(Keys.TAB).perform()
return True
except Exception:
print('Exception occurred while updating boolean cell',
file=sys.stderr)
ActionChains(self.driver).move_to_element(cell_el). \
double_click(cell_el).perform()
retry -= 1
return False
try:
checkbox_el = self.page.find_by_css_selector(
QueryToolLocators.row_editor_checkbox_css)
if value == 'true':
checkbox_el.click()
# Boolean editor test for to False click
elif value == 'false':
# Sets true
checkbox_el.click()
# Sets false
ActionChains(self.driver).click(checkbox_el).perform()
ActionChains(self.driver).send_keys(Keys.TAB).perform()
return True
except Exception:
traceback.print_exc()
print('Exception occurred while updating boolean cell',
file=sys.stderr)
return False
def _view_data_grid(self, table_name):
self.page.driver.find_element(By.LINK_TEXT, "Object").click()
@@ -395,9 +385,9 @@ CREATE TABLE public.nonintpkey
items.sort(reverse=False)
for idx in items:
# rowindex starts with 2 and 1st colindex is rownum
time.sleep(0.5)
cell_xpath = CheckForViewDataTest\
._get_cell_xpath(str(idx + 1), row + 1)
time.sleep(0.5)
self._update_cell(cell_xpath, data[str(idx)])
self.page.find_by_css_selector(
QueryToolLocators.btn_save_data).click()
@@ -450,7 +440,7 @@ CREATE TABLE public.nonintpkey
except Exception:
print("stale reference exception at id:", idx)
retry -= 1
time.sleep(0.4)
time.sleep(0.4)
self.assertEqual(element.text, config_check_data[str(idx)][1])
# scroll browser back to the left

View File

@@ -51,7 +51,7 @@ export function AzureCredentials(props) {
}
})
.catch((error) => {
_eventBus.fireEvent('SET_ERROR_MESSAGE_FOR_CLOUD_WIZARD',[MESSAGE_TYPE.ERROR, gettext(`Error while verification Microsoft Azure: ${error.response.data.errormsg}`)]);
_eventBus.fireEvent('SET_ERROR_MESSAGE_FOR_CLOUD_WIZARD',[MESSAGE_TYPE.ERROR, gettext(`Error while verification Microsoft Azure: ${error}`)]);
reject(false);
});
});

View File

@@ -11,7 +11,7 @@
import simplejson as json
import os
import copy
from flask import url_for, Response, render_template, request, current_app
from flask_babel import gettext as _
from flask_security import login_required, current_user
@@ -23,7 +23,7 @@ from pgadmin.utils.ajax import make_json_response, bad_request
from config import PG_DEFAULT_DRIVER
from pgadmin.model import Server
from pgadmin.utils.constants import MIMETYPE_APP_JS
import config
from pgadmin.settings import get_setting, store_setting
MODULE_NAME = 'import_export'
@@ -42,7 +42,8 @@ class ImportExportModule(PgAdminModule):
Returns:
list: URL endpoints for backup module
"""
return ['import_export.create_job', 'import_export.utility_exists']
return ['import_export.create_job', 'import_export.utility_exists',
'import_export.get_settings']
blueprint = ImportExportModule(MODULE_NAME, __name__)
@@ -238,6 +239,30 @@ def _get_required_column_list(data, driver, conn):
return cols
def _save_import_export_settings(settings):
[settings.pop(key) for key in ['icolumns', 'columns', 'database',
'schema', 'table', 'save_btn_icon']]
if settings['is_import']:
settings['import_file_name'] = settings['filename']
else:
settings['export_file_name'] = settings['filename']
# Get existing setting -
old_settings = get_setting('import_export_setting')
if old_settings and old_settings != 'null':
old_settings = json.loads(old_settings)
old_settings.update(settings)
settings = json.dumps(settings)
else:
if 'import_file_name' not in settings:
settings['import_file_name'] = ''
elif 'export_file_name' not in settings:
settings['export_file_name'] = ''
settings = json.dumps(settings)
store_setting('import_export_setting', settings)
@blueprint.route('/job/<int:sid>', methods=['POST'], endpoint="create_job")
@login_required
def create_import_export_job(sid):
@@ -280,6 +305,8 @@ def create_import_export_job(sid):
success=0,
errormsg=ret_val
)
# Copy request data to store
new_settings = copy.deepcopy(data)
# Get the storage path from preference
storage_dir = get_storage_directory()
@@ -304,6 +331,9 @@ def create_import_export_job(sid):
icols = _get_ignored_column_list(data, driver, conn)
cols = _get_required_column_list(data, driver, conn)
# Save the settings
_save_import_export_settings(new_settings)
# Create the COPY FROM/TO from template
query = render_template(
'import_export/sql/cmd.sql',
@@ -359,6 +389,17 @@ def create_import_export_job(sid):
)
@blueprint.route('/get_settings/', methods=['GET'], endpoint='get_settings')
@login_required
def get_import_export_settings():
settings = get_setting('import_export_setting', None)
if settings is None:
return make_json_response(success=True, data={})
else:
data = json.loads(settings)
return make_json_response(success=True, data=data)
@blueprint.route(
'/utility_exists/<int:sid>', endpoint='utility_exists'
)

View File

@@ -10,7 +10,7 @@
import Notify from 'static/js/helpers/Notifier';
import {getUtilityView} from '../../../../browser/static/js/utility_view';
import getApiInstance from 'sources/api_instance';
import ImportExportSchema, {getFileInfoSchema, getMiscellaneousSchema} from './import_export.ui';
import ImportExportSchema from './import_export.ui';
import { getNodeListByName, getNodeAjaxOptions } from '../../../../browser/static/js/node_ajax';
define([
@@ -62,17 +62,16 @@ define([
let itemNodeData = pgBrowser.tree.findNodeByDomElement(selectedNode).getData();
return new ImportExportSchema(
()=>getFileInfoSchema(
{encoding: ()=>getNodeAjaxOptions('get_encodings', pgBrowser.Nodes['database'], treeNodeInfo, itemNodeData, {cacheNode: 'database',cacheLevel: 'server'})}
),
()=>getMiscellaneousSchema(),
{columns: ()=>getNodeListByName('column', treeNodeInfo, itemNodeData, { cacheLevel: 'column'}, ()=>true, (res)=>{
let columnsList = [];
res.forEach(d => {
columnsList.push({label: d.label, value: d.value, image:'icon-column', selected: true});
});
return columnsList;
})}
{
encoding: ()=>getNodeAjaxOptions('get_encodings', pgBrowser.Nodes['database'], treeNodeInfo, itemNodeData, {cacheNode: 'database',cacheLevel: 'server'}),
columns: ()=>getNodeListByName('column', treeNodeInfo, itemNodeData, { cacheLevel: 'column'}, ()=>true, (res)=>{
let columnsList = [];
res.forEach(d => {
columnsList.push({label: d.label, value: d.value, image:'icon-column', selected: true});
});
return columnsList;
}),
}
);
},

View File

@@ -11,253 +11,227 @@ import BaseUISchema from 'sources/SchemaView/base_schema.ui';
import gettext from 'sources/gettext';
import { isEmptyString } from 'sources/validators';
export class FileInfoSchema extends BaseUISchema{
constructor(fieldOptions={}) {
super();
export default class ImportExportSchema extends BaseUISchema {
constructor(fieldOptions = {}, initValues={}) {
super({
null_string: undefined,
is_import: true,
icolumns: [],
oid: undefined,
header: undefined,
delimiter: ',',
quote: '\"',
escape: '\'',
file: undefined,
format: 'csv',
...initValues,
});
this.fieldOptions = {
encoding: null,
columns:[],
encoding: fieldOptions.encoding,
...fieldOptions,
};
}
get baseFields() {
var obj = this;
return [{
id: 'filename',
label: gettext('Filename'),
type: (state) => {
return {
type: 'file',
controlProps: {
dialogType: state.is_import ? 'select_file' : 'create_file',
supportedTypes: ['csv', 'text','bin', '*'],
dialogTitle: 'Select file',
}
};
},
deps: ['is_import', 'format'],
disabled: false,
}, {
id: 'format',
label: gettext('Format'),
type: 'select',
controlProps: { allowClear: false, noEmpty: true },
options: [
{
label: gettext('binary'),
value: 'binary',
},
{
label: gettext('csv'),
value: 'csv',
},
{
label: gettext('text'),
value: 'text',
},
]
}, {
id: 'encoding',
label: gettext('Encoding'),
type: 'select',
options: obj.fieldOptions.encoding,
}
];
}
}
export function getFileInfoSchema(fieldOptions) {
return new FileInfoSchema(fieldOptions);
}
export class MiscellaneousSchema extends BaseUISchema {
constructor() {
super();
this.colums_selection_label = {e:'Columns to export', i:'Columns to import'};
this._type = 'e';
this.notNullColOptions = [];
}
isDisabled(state) {
return (state?.format != 'csv');
}
get baseFields() {
let obj = this;
return [{
id: 'oid',
label: gettext('OID'),
type: 'switch',
group: gettext('Miscellaneous')
}, {
id: 'header',
label: gettext('Header'),
type: 'switch',
group: gettext('Miscellaneous'),
disabled: obj.isDisabled
},{
id: 'delimiter',
label: gettext('Delimiter'),
group: gettext('Miscellaneous'),
type: 'select',
controlProps: { allowClear: false},
deps: ['format'],
options: [{
'label': ';',
'value': ';',
},
{
'label': ',',
'value': ',',
},
{
'label': '|',
'value': '|',
},
{
'label': '[tab]',
'value': '[tab]',
},
],
disabled: function(state) {
return (state?.format == 'binary');
},
helpMessage: gettext('Specifies the character that separates columns within each row (line) of the file. The default is a tab character in text format, a comma in CSV format. This must be a single one-byte character. This option is not allowed when using binary format.')
}, {
id: 'quote',
label: gettext('Quote'),
group: gettext('Miscellaneous'),
type: 'select',
options: [{
'label': '\"',
'value': '\"',
},
{
'label': '\'',
'value': '\'',
},
],
disabled: obj.isDisabled,
helpMessage: gettext('Specifies the quoting character to be used when a data value is quoted. The default is double-quote. This must be a single one-byte character. This option is allowed only when using CSV format.'),
}, {
id: 'escape',
label: gettext('Escape'),
group: gettext('Miscellaneous'),
type: 'select',
options: [{
'label': '\"',
'value': '\"',
},
{
'label': '\'',
'value': '\'',
},
],
disabled: obj.isDisabled,
helpMessage: gettext('Specifies the character that should appear before a data character that matches the QUOTE value. The default is the same as the QUOTE value (so that the quoting character is doubled if it appears in the data). This must be a single one-byte character. This option is allowed only when using CSV format.'),
}
];
}
}
export function getMiscellaneousSchema() {
return new MiscellaneousSchema();
}
export default class ImportExportSchema extends BaseUISchema {
constructor(_getFileInfoSchema, _getMiscellaneousSchema, fieldOptions = {}) {
super({
null_string: undefined,
is_import: false,
icolumns: [],
oid: undefined,
header: undefined,
delimiter: '',
quote: '\"',
escape: '\'',
file: undefined,
format: 'csv'
});
this.fieldOptions = {
columns:[],
...fieldOptions,
};
this.getFileInfoSchema = _getFileInfoSchema;
this.getMiscellaneousSchema = _getMiscellaneousSchema;
// e=export, i=import
this.colums_selection_label = {e:'Columns to export', i:'Columns to import'};
this._type = 'e';
this.notNullColOptions = [];
}
get baseFields() {
var obj = this;
return [{
id: 'is_import',
label: gettext('Import/Export'),
group: gettext('Options'),
type: 'toggle',
options: [
{ 'label': gettext('Import'), 'value': true },
{ 'label': gettext('Export'), 'value': false },
],
}, {
type: 'nested-fieldset',
label: gettext('File Info'),
group: gettext('Options'),
schema: obj.getFileInfoSchema(),
}, {
type: 'nested-fieldset',
label: gettext('Miscellaneous'),
group: gettext('Options'),
schema: obj.getMiscellaneousSchema(),
}, {
id: 'columns',
label: gettext(this.colums_selection_label[this._type]),
group: gettext('Columns'),
type: 'select',
options: obj.fieldOptions.columns,
optionsLoaded: (options) => {
obj.notNullColOptions = options.map((o) => {
return { ...o, selected: false };
});
return [
{
id: 'is_import',
label: gettext('Import/Export'),
group: gettext('General'),
type: 'toggle',
options: [
{ 'label': gettext('Import'), 'value': true },
{ 'label': gettext('Export'), 'value': false },
],
},
controlProps:{multiple: true, allowClear: false,
placeholder: this._type === 'i' ? gettext('Columns for importing...') : gettext('Columns for exporting...'),
{
id: 'filename',
label: gettext('Filename'),
group: gettext('General'),
deps: ['is_import', 'format'],
depChange:(state, source)=>{
if (source == 'is_import'){
let filename = state.is_import ? state.import_file_name : state.export_file_name;
return {filename: filename};
}
},
type: (state) => {
return {
type: 'file',
controlProps: {
dialogType: state.is_import ? 'select_file' : 'create_file',
supportedTypes: ['csv', 'text','bin', '*'],
dialogTitle: 'Select file',
}
};
},
disabled: false,
},
deps:['is_import'],
depChange:(state)=>{
this._type = state.is_import? 'i' : 'e';
{
id: 'format',
label: gettext('Format'),
group: gettext('General'),
type: 'select',
controlProps: { allowClear: false, noEmpty: true },
options: [
{
label: gettext('binary'),
value: 'binary',
},
{
label: gettext('csv'),
value: 'csv',
},
{
label: gettext('text'),
value: 'text',
},
]
},
helpMessage: gettext('An optional list of columns to be copied. If no column list is specified, all columns of the table will be copied.')
}, {
id: 'null_string',
label: gettext('NULL Strings'),
group: gettext('Columns'),
type: 'text',
deps: ['format'],
disabled: function(state) {
return (state?.format == 'binary');
{
id: 'encoding',
label: gettext('Encoding'),
group: gettext('General'),
type: 'select',
options: this.fieldOptions.encoding,
},
helpMessage: gettext('Specifies the string that represents a null value. The default is \\N (backslash-N) in text format, and an unquoted empty string in CSV format. You might prefer an empty string even in text format for cases where you don\'t want to distinguish nulls from empty strings. This option is not allowed when using binary format.'),
}, {
id: 'icolumns',
label: gettext('Not null columns'),
group: gettext('Columns'),
type: 'select',
deps: ['format', 'is_import'],
options: obj.notNullColOptions,
optionsReloadBasis: obj.notNullColOptions.length,
controlProps: {
multiple: true, allowClear: true, placeholder: gettext('Not null columns...'),
{
id: 'oid',
label: gettext('OID'),
type: 'switch',
group: gettext('Options')
},
disabled:function(state){
return (state?.format != 'csv' || !state?.is_import);
{
id: 'header',
label: gettext('Header'),
type: 'switch',
group: gettext('Options'),
disabled: this.isDisabled
},
helpMessage: gettext('Do not match the specified column values against the null string. In the default case where the null string is empty, this means that empty values will be read as zero-length strings rather than nulls, even when they are not quoted. This option is allowed only in import, and only when using CSV format.'),
}
{
id: 'delimiter',
label: gettext('Delimiter'),
group: gettext('Options'),
type: 'select',
controlProps: { allowClear: false, creatable: true},
deps: ['format'],
options: [{
'label': ';',
'value': ';',
},
{
'label': ',',
'value': ',',
},
{
'label': '|',
'value': '|',
},
{
'label': '[tab]',
'value': '[tab]',
},
],
disabled: function(state) {
return (state?.format == 'binary');
},
helpMessage: gettext('Specifies the character that separates columns within each row (line) of the file. The default is a tab character in text format, a comma in CSV format. This must be a single one-byte character. This option is not allowed when using binary format.')
},
{
id: 'quote',
label: gettext('Quote'),
group: gettext('Options'),
type: 'select',
controlProps: {creatable: true},
options: [{
'label': '\"',
'value': '\"',
},
{
'label': '\'',
'value': '\'',
},
],
disabled: this.isDisabled,
helpMessage: gettext('Specifies the quoting character to be used when a data value is quoted. The default is double-quote. This must be a single one-byte character. This option is allowed only when using CSV format.'),
},
{
id: 'escape',
label: gettext('Escape'),
group: gettext('Options'),
type: 'select',
controlProps: {creatable: true},
options: [{
'label': '\"',
'value': '\"',
},
{
'label': '\'',
'value': '\'',
},
],
disabled: obj.isDisabled,
helpMessage: gettext('Specifies the character that should appear before a data character that matches the QUOTE value. The default is the same as the QUOTE value (so that the quoting character is doubled if it appears in the data). This must be a single one-byte character. This option is allowed only when using CSV format.'),
},
{
id: 'null_string',
label: gettext('NULL Strings'),
group: gettext('Options'),
type: 'text',
deps: ['format'],
disabled: function(state) {
return (state?.format == 'binary');
},
helpMessage: gettext('Specifies the string that represents a null value. The default is \\N (backslash-N) in text format, and an unquoted empty string in CSV format. You might prefer an empty string even in text format for cases where you don\'t want to distinguish nulls from empty strings. This option is not allowed when using binary format.'),
},
{
id: 'columns',
label: gettext(this.colums_selection_label[this._type]),
group: gettext('Columns'),
type: 'select',
options: obj.fieldOptions.columns,
optionsLoaded: (options) => {
obj.notNullColOptions = options.map((o) => {
return { ...o, selected: false };
});
},
controlProps:{multiple: true, allowClear: false,
placeholder: this._type === 'i' ? gettext('Columns for importing...') : gettext('Columns for exporting...'),
},
deps:['is_import'],
depChange:(state)=>{
this._type = state.is_import? 'i' : 'e';
},
helpMessage: gettext('An optional list of columns to be copied. If no column list is specified, all columns of the table will be copied.')
},
{
id: 'icolumns',
label: gettext('NOT NULL columns'),
group: gettext('Columns'),
type: 'select',
deps: ['format', 'is_import'],
options: obj.notNullColOptions,
optionsReloadBasis: obj.notNullColOptions.length,
controlProps: {
multiple: true, allowClear: true, placeholder: gettext('Not null columns...'),
},
disabled:function(state){
return (state?.format != 'csv' || !state?.is_import);
},
helpMessage: gettext('Do not match the specified column values against the null string. In the default case where the null string is empty, this means that empty values will be read as zero-length strings rather than nulls, even when they are not quoted. This option is allowed only in import, and only when using CSV format.'),
}
];
}

View File

@@ -1 +1,18 @@
\copy {{ conn|qtIdent(data.schema, data.table) }} {% if columns %} {{ columns }} {% endif %} {% if data.is_import %}FROM{% else %}TO{% endif %} {{ data.filename|qtLiteral }} {% if data.oid %} OIDS {% endif %}{% if data.delimiter is defined and data.delimiter == '' and (data.format == 'csv' or data.format == 'text') %} {% elif data.delimiter and data.format != 'binary' and data.delimiter == '[tab]' %} DELIMITER E'\t' {% elif data.format != 'binary' and data.delimiter %} DELIMITER {{ data.delimiter|qtLiteral }}{% endif %}{% if data.format == 'csv' %} CSV {% endif %} {% if data.header %} HEADER {% endif %}{% if data.encoding %} ENCODING {{ data.encoding|qtLiteral }}{% endif %}{% if data.format == 'csv' and data.quote %} QUOTE {{ data.quote|qtLiteral }}{% endif %}{% if data.format != 'binary' and data.null_string %} NULL {{ data.null_string|qtLiteral }}{% endif %}{% if data.format == 'csv' and data.escape %} ESCAPE {{ data.escape|qtLiteral }}{% endif %}{% if data.format == 'csv' and data.is_import and ignore_column_list %} FORCE NOT NULL {{ ignore_column_list }} {% endif %};
\copy {{ conn|qtIdent(data.schema, data.table) }}
{% if columns %} {{ columns }} {% endif %}
{% if data.is_import %} FROM {% else %} TO {% endif %}
{{ data.filename|qtLiteral }}
{% if data.oid %} OIDS {% endif %}
{% if data.delimiter is defined and data.delimiter == '' and (data.format == 'csv' or data.format == 'text') %}
{% elif data.delimiter and data.format != 'binary' and data.delimiter == '[tab]' %}
DELIMITER E'\t'
{% elif data.format != 'binary' and data.delimiter %}
DELIMITER {{ data.delimiter|qtLiteral }}
{% endif %}
{% if data.format == 'csv' %} CSV {% endif %}
{% if data.format == 'csv' and data.header %} HEADER {% endif %}
{% if data.encoding %} ENCODING {{ data.encoding|qtLiteral }}{% endif %}
{% if data.format == 'csv' and data.quote %} QUOTE {{ data.quote|qtLiteral }}{% endif %}
{% if data.format != 'binary' and data.null_string %} NULL {{ data.null_string|qtLiteral }}{% endif %}
{% if data.format == 'csv' and data.escape %} ESCAPE {{ data.escape|qtLiteral }}{% endif %}
{% if data.format == 'csv' and data.is_import and ignore_column_list %} FORCE NOT NULL {{ ignore_column_list }} {% endif %};

View File

@@ -131,7 +131,7 @@ function setEditorPosition(cellEle, editorEle) {
let cellRect = cellEle.getBoundingClientRect();
let position = {
left: cellRect.left,
top: cellRect.top - editorEle.offsetHeight + 12,
top: Math.max(cellRect.top - editorEle.offsetHeight + 12, 0)
};
if ((position.left + editorEle.offsetWidth + 10) > gridEle.offsetWidth) {