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:
parent
9dde195af4
commit
8f2bda2309
Binary file not shown.
Before Width: | Height: | Size: 224 KiB After Width: | Height: | Size: 150 KiB |
BIN
docs/en_US/images/import_export_general.png
Normal file
BIN
docs/en_US/images/import_export_general.png
Normal file
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 |
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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) {
|
||||
|
@ -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(
|
||||
|
Loading…
Reference in New Issue
Block a user