mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
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:
committed by
Akshay Joshi
parent
9dde195af4
commit
8f2bda2309
@@ -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}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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'
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
}),
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
@@ -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.'),
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -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 %};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user