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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 282 KiB

View File

@ -8,39 +8,36 @@ Use the *Import/Export data* dialog to copy data from a table to a file, or copy
data from a file into a table.
The *Import/Export data* dialog organizes the import/export of data through the
*Options* and *Columns* tabs.
*General*, *Options* and *Columns* tabs.
.. image:: images/import_export_general.png
:alt: Import Export data dialog general tab
:align: center
Use the fields in the *General* tab to specify import and export preferences:
* Move the *Import/Export* switch to the *Import* position to specify that the
server should import data to a table from a file. The default is *Import*.
* Enter the name of the source or target file in the *Filename* field.
Optionally, select the *Browser* icon (ellipsis) to the right to navigate
into a directory and select a file.
* Use the drop-down listbox in the *Format* field to specify the file type.
Select:
* *binary* for a .bin file.
* *csv* for a .csv file.
* *text* for a .txt file.
* Use the drop-down listbox in the *Encoding* field to specify the type of
character encoding.
.. image:: images/import_export_options.png
:alt: Import Export data dialog options tab
:align: center
Use the fields in the *Options* tab to specify import and export preferences:
* Move the *Import/Export* switch to the *Import* position to specify that the
server should import data to a table from a file. The default is *Export*.
* Use the fields in the *File Info* field box to specify information about the
source or target file:
* Enter the name of the source or target file in the *Filename* field.
Optionally, select the *Browser* icon (ellipsis) to the right to navigate
into a directory and select a file.
* Use the drop-down listbox in the *Format* field to specify the file type.
Select:
* *binary* for a .bin file.
* *csv* for a .csv file.
* *text* for a .txt file.
* Use the drop-down listbox in the *Encoding* field to specify the type of
character encoding.
.. image:: images/import_export_miscellaneous.png
:alt: Import Export data dialog miscellaneous tab
:align: center
* Use the fields in the *Miscellaneous* field box to specify additional
information:
* Use the fields in the *Options* tab to specify additional information:
* Move the *OID* switch to the *Yes* position to include the *OID* column.
The *OID* is a system-assigned value that may not be modified. The default
@ -57,6 +54,8 @@ Use the fields in the *Options* tab to specify import and export preferences:
be a single quote or a double quote.
* Specify a character that should appear before a data character that matches
the *QUOTE* value in the *Escape* field.
* Use the *NULL Strings* field to specify a string that will represent a null
value within the source or target file.
Click the *Columns* tab to continue.
@ -71,9 +70,8 @@ or exported:
columns from the drop-down listbox. To delete a selection, click the *x* to
the left of the column name. Click an empty spot inside the field to access
the drop-down list.
* Use the *NULL Strings* field to specify a string that will represent a null
value within the source or target file.
* If enabled, click inside the *Not null columns* field to select one or more
* If enabled, click inside the *NOT NULL columns* field to select one or more
columns that will not be checked for a NULL value. To delete a column, click
the *x* to the left of the column name.

View File

@ -23,11 +23,13 @@ Housekeeping
Bug fixes
*********
| `Issue #7428 <https://redmine.postgresql.org/issues/7428>`_ - Preserve the settings set by the user in the import/export data dialog.
| `Issue #7471 <https://redmine.postgresql.org/issues/7471>`_ - Ensure that the splash screen can be moved.
| `Issue #7508 <https://redmine.postgresql.org/issues/7508>`_ - Fixed an issue where comments on indexes are not displayed.
| `Issue #7512 <https://redmine.postgresql.org/issues/7512>`_ - Ensure that notices should not disappear from the messages tab.
| `Issue #7517 <https://redmine.postgresql.org/issues/7517>`_ - Enable the start debugging button once execution is completed.
| `Issue #7518 <https://redmine.postgresql.org/issues/7518>`_ - Ensure that dashboard graph API is not called after the panel has been closed.
| `Issue #7519 <https://redmine.postgresql.org/issues/7519>`_ - Ensure that geometry should be shown for all the selected cells.
| `Issue #7520 <https://redmine.postgresql.org/issues/7520>`_ - Fixed the JSON editor issue of hiding the first record.
| `Issue #7522 <https://redmine.postgresql.org/issues/7522>`_ - Added support for Azure PostgreSQL deployment in server mode.
| `Issue #7523 <https://redmine.postgresql.org/issues/7523>`_ - Fixed typo error for Statistics on the table header.

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) {

View File

@ -564,10 +564,10 @@ class PgadminPage:
child_node_ele = self.check_if_element_exists_with_scroll(
server_child_node_xpath)
if not child_node_ele:
databases_node = self.driver.find_element(
By.XPATH,
TreeAreaLocators.server_child_node(
server_name, 'Databases'))
databases_node_xpath = TreeAreaLocators.server_child_node(
server_name, 'Databases')
databases_node = self.check_if_element_exists_with_scroll(
databases_node_xpath)
webdriver.ActionChains(self.driver).double_click(
databases_node).perform()
child_node_ele = self.check_if_element_exists_with_scroll(