mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Show auto-complete column names in filtered rows dialog of table and filter options of view/edit data tool. #3751
Allow setting NULL ordering for columns in view/edit data filter dialog. #3317
This commit is contained in:
parent
9ef5a53790
commit
88e515093c
@ -212,6 +212,7 @@ To add new column(s) in data sorting grid, click on the [+] icon.
|
||||
|
||||
* Use the drop-down *Column* to select the column you want to sort.
|
||||
* Use the drop-down *Order* to select the sort order for the column.
|
||||
* Use the drop-down *NULLs* to select the NULL values order for the column.
|
||||
|
||||
To delete a row from the grid, click the trash icon.
|
||||
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 47 KiB |
@ -20,9 +20,12 @@ Bundled PostgreSQL Utilities
|
||||
New features
|
||||
************
|
||||
|
||||
| `Issue #3317 <https://github.com/pgadmin-org/pgadmin4/issues/3317>`_ - Allow setting NULL ordering for columns in view/edit data filter dialog.
|
||||
| `Issue #3751 <https://github.com/pgadmin-org/pgadmin4/issues/3751>`_ - Show auto-complete column names in filtered rows dialog of table and filter options of view/edit data tool.
|
||||
| `Issue #5786 <https://github.com/pgadmin-org/pgadmin4/issues/5786>`_ - Allow the use of a pgpass file in the pgAdmin container via Docker secrets.
|
||||
| `Issue #6592 <https://github.com/pgadmin-org/pgadmin4/issues/6592>`_ - Fixed multiple issues and improved ERD auto-layout.
|
||||
| `Issue #8095 <https://github.com/pgadmin-org/pgadmin4/issues/8095>`_ - Added support for a builtin locale provider in the Database dialog.
|
||||
| `Issue #8134 <https://github.com/pgadmin-org/pgadmin4/issues/8134>`_ - Add a user preference to enable/disable alternating row background colors in the data output of query tool.
|
||||
|
||||
Housekeeping
|
||||
************
|
||||
|
@ -260,7 +260,8 @@ export default function FormView({
|
||||
let contentClassName = [
|
||||
isSingleCollection() ? 'FormView-singleCollectionPanelContent' :
|
||||
'FormView-nonTabPanelContent',
|
||||
(schemaState.errors?.message ? 'FormView-errorMargin' : null)
|
||||
(schemaState.errors?.message ? 'FormView-errorMargin' : null),
|
||||
(finalGroups.some((g)=>g.isFullTab) ? 'FormView-fullControl' : ''),
|
||||
];
|
||||
return (
|
||||
<>
|
||||
@ -275,7 +276,8 @@ export default function FormView({
|
||||
classNameRoot={[
|
||||
isSingleCollection() ?
|
||||
'FormView-singleCollectionPanel' : 'FormView-nonTabPanel',
|
||||
className
|
||||
className,
|
||||
(finalGroups.some((g)=>g.isFullTab) ? 'FormView-fullSpace' : ''),
|
||||
].join(' ')}
|
||||
className={contentClassName.join(' ')}>
|
||||
{
|
||||
|
@ -144,7 +144,7 @@ export const FormContentBox = styled(Box)(({theme}) => ({
|
||||
'& .FormView-fullControl': {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
'& .FormView-sqlTabInput': {
|
||||
'& .FormView-sqlTabInput, & .Form-sql': {
|
||||
border: 0,
|
||||
},
|
||||
}
|
||||
@ -152,7 +152,9 @@ export const FormContentBox = styled(Box)(({theme}) => ({
|
||||
'& .FormView-nonTabPanel': {
|
||||
...theme.mixins.tabPanel,
|
||||
'& .FormView-nonTabPanelContent': {
|
||||
'&:not(.FormView-fullControl)': {
|
||||
height: 'unset',
|
||||
},
|
||||
'& .FormView-controlRow': {
|
||||
marginBottom: theme.spacing(1),
|
||||
},
|
||||
|
@ -41,7 +41,7 @@ export default function UtilityView({dockerObj}) {
|
||||
<UtilityViewContent
|
||||
docker={docker.current}
|
||||
panelId={panelId}
|
||||
schema={dialogProps.schema}
|
||||
{...dialogProps}
|
||||
treeNodeInfo={treeNodeInfo}
|
||||
actionType={dialogProps.actionType??'create'}
|
||||
formType='dialog'
|
||||
@ -60,10 +60,6 @@ export default function UtilityView({dockerObj}) {
|
||||
onClose();
|
||||
})}
|
||||
extraData={dialogProps.extraData??{}}
|
||||
saveBtnName={dialogProps.saveBtnName}
|
||||
urlBase={dialogProps.urlBase}
|
||||
sqlHelpUrl={dialogProps.sqlHelpUrl}
|
||||
helpUrl={dialogProps.helpUrl}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
)
|
||||
|
@ -190,9 +190,10 @@ FormInput.propTypes = {
|
||||
labelTooltip: PropTypes.string
|
||||
};
|
||||
|
||||
export function InputSQL({ value, options, onChange, className, controlProps, inputRef, ...props }) {
|
||||
export function InputSQL({ value, options={}, onChange, className, controlProps, inputRef, ...props }) {
|
||||
|
||||
const editor = useRef();
|
||||
const { autocompleteProvider, autocompleteOnKeyPress } = options;
|
||||
|
||||
return (
|
||||
<Root style={{height: '100%'}}>
|
||||
@ -200,13 +201,18 @@ export function InputSQL({ value, options, onChange, className, controlProps, in
|
||||
currEditor={(obj) => {
|
||||
editor.current = obj;
|
||||
inputRef?.(obj);
|
||||
if(autocompleteProvider) {
|
||||
editor.current.registerAutocomplete(autocompleteProvider);
|
||||
}
|
||||
}}
|
||||
value={value || ''}
|
||||
options={{
|
||||
...options,
|
||||
..._.omit(options, ['autocompleteProvider', 'autocompleteOnKeyPress']),
|
||||
}}
|
||||
className={'Form-sql ' + className}
|
||||
onChange={onChange}
|
||||
autocomplete={true}
|
||||
autocompleteOnKeyPress={autocompleteOnKeyPress}
|
||||
{...controlProps}
|
||||
{...props}
|
||||
/>
|
||||
|
@ -165,7 +165,7 @@ const defaultExtensions = [
|
||||
|
||||
export default function Editor({
|
||||
currEditor, name, value, options, onCursorActivity, onChange, readonly,
|
||||
disabled, autocomplete = false, breakpoint = false, onBreakPointChange,
|
||||
disabled, autocomplete = false, autocompleteOnKeyPress, breakpoint = false, onBreakPointChange,
|
||||
showActiveLine=false, keepHistory = true, cid, helpid, labelledBy,
|
||||
customKeyMap, language='pgsql'
|
||||
}) {
|
||||
@ -331,7 +331,7 @@ export default function Editor({
|
||||
}],
|
||||
};
|
||||
if (autocomplete) {
|
||||
if (pref.autocomplete_on_key_press) {
|
||||
if (pref.autocomplete_on_key_press || autocompleteOnKeyPress) {
|
||||
newConfigExtn.push(autocompletion({
|
||||
...autoCompOptions,
|
||||
activateOnTyping: true,
|
||||
|
@ -27,7 +27,8 @@ class SortingCollection extends BaseUISchema {
|
||||
{
|
||||
id: 'name', label: gettext('Column'), cell: 'select', controlProps: {
|
||||
allowClear: false,
|
||||
}, noEmpty: true, options: this.columnOptions, optionsReloadBasis: this.reloadColOptions
|
||||
}, noEmpty: true, options: this.columnOptions, optionsReloadBasis: this.reloadColOptions,
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
id: 'order', label: gettext('Order'), cell: 'select', controlProps: {
|
||||
@ -35,7 +36,17 @@ class SortingCollection extends BaseUISchema {
|
||||
}, options: [
|
||||
{label: gettext('ASC'), value: 'asc'},
|
||||
{label: gettext('DESC'), value: 'desc'},
|
||||
]
|
||||
],
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
id: 'order_null', label: gettext('NULLs'), cell: 'select', controlProps: {
|
||||
allowClear: true,
|
||||
}, options: [
|
||||
{label: gettext('FIRST'), value: 'nulls first'},
|
||||
{label: gettext('LAST'), value: 'nulls last'},
|
||||
],
|
||||
width: 150,
|
||||
},
|
||||
];
|
||||
}
|
||||
@ -62,6 +73,23 @@ class FilterSchema extends BaseUISchema {
|
||||
options: {
|
||||
lineWrapping: true,
|
||||
},
|
||||
autocompleteOnKeyPress: true,
|
||||
autocompleteProvider: (context, onAvailable)=>{
|
||||
return new Promise((resolve)=>{
|
||||
const word = context.matchBefore(/\w*/);
|
||||
const fullSql = context.state.doc.toString();
|
||||
onAvailable();
|
||||
resolve({
|
||||
from: word.from,
|
||||
options: (this.sortingCollObj.columnOptions??[]).map((col)=>({
|
||||
label: col.label, type: 'property',
|
||||
})),
|
||||
validFor: (text, from)=>{
|
||||
return text.startsWith(fullSql.slice(from));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -14,16 +14,15 @@ import _ from 'lodash';
|
||||
import { isEmptyString } from 'sources/validators';
|
||||
import usePreferences from '../../../../preferences/static/js/store';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import { getNodeListByName } from '../../../../browser/static/js/node_ajax';
|
||||
|
||||
export default class DataFilterSchema extends BaseUISchema {
|
||||
constructor(fieldOptions = {}) {
|
||||
constructor(getColumns) {
|
||||
super({
|
||||
filter_sql: ''
|
||||
});
|
||||
|
||||
this.fieldOptions = {
|
||||
...fieldOptions,
|
||||
};
|
||||
this.getColumns = getColumns;
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
@ -31,6 +30,31 @@ export default class DataFilterSchema extends BaseUISchema {
|
||||
id: 'filter_sql',
|
||||
label: gettext('Data Filter'),
|
||||
type: 'sql', isFullTab: true, cell: 'text',
|
||||
controlProps: {
|
||||
autocompleteOnKeyPress: true,
|
||||
autocompleteProvider: (context, onAvailable)=>{
|
||||
return new Promise((resolve, reject)=>{
|
||||
const word = context.matchBefore(/\w*/);
|
||||
const fullSql = context.state.doc.toString();
|
||||
this.getColumns().then((columns) => {
|
||||
onAvailable();
|
||||
resolve({
|
||||
from: word.from,
|
||||
options: (columns??[]).map((col)=>({
|
||||
label: col.label, type: 'property',
|
||||
})),
|
||||
validFor: (text, from)=>{
|
||||
return text.startsWith(fullSql.slice(from));
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
onAvailable();
|
||||
reject(err instanceof Error ? err : Error(gettext('Something went wrong')));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
@ -64,8 +88,7 @@ export function showViewData(
|
||||
return;
|
||||
}
|
||||
|
||||
const parentData = pgBrowser.tree.getTreeNodeHierarchy( treeIdentifier
|
||||
);
|
||||
const parentData = pgBrowser.tree.getTreeNodeHierarchy(treeIdentifier);
|
||||
|
||||
if (hasServerOrDatabaseConfiguration(parentData)
|
||||
|| !hasSchemaOrCatalogOrViewInformation(parentData)) {
|
||||
@ -157,7 +180,11 @@ function generateFilterValidateUrl(nodeData, parentData) {
|
||||
function showFilterDialog(pgBrowser, item, queryToolMod, transId,
|
||||
gridUrl, queryToolTitle, validateUrl) {
|
||||
|
||||
let schema = new DataFilterSchema();
|
||||
const treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(item);
|
||||
const itemNodeData = pgBrowser.tree.findNodeByDomElement(item).getData();
|
||||
let schema = new DataFilterSchema(
|
||||
()=>getNodeListByName('column', treeNodeInfo, itemNodeData),
|
||||
);
|
||||
let helpUrl = url_for('help.static', {'filename': 'viewdata_filter.html'});
|
||||
|
||||
let okCallback = function() {
|
||||
@ -168,7 +195,7 @@ function showFilterDialog(pgBrowser, item, queryToolMod, transId,
|
||||
gettext('Data Filter - %s', queryToolTitle),{
|
||||
schema, urlBase: validateUrl, helpUrl, saveBtnName: gettext('OK'), isTabView: false,
|
||||
onSave: okCallback,
|
||||
}, pgBrowser.stdW.md, pgBrowser.stdH.sm
|
||||
}, pgBrowser.stdW.md, pgBrowser.stdH.md
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ WHERE {{ sql_filter }}
|
||||
{% endif %}
|
||||
{% if data_sorting and data_sorting|length > 0 %}
|
||||
ORDER BY {% for obj in data_sorting %}
|
||||
{{ conn|qtIdent(obj.name) }} {{ obj.order|upper }}{% if not loop.last %}, {% else %} {% endif %}
|
||||
{{ conn|qtIdent(obj.name) }} {{ obj.order|upper }}{% if obj.order_null %} {{ obj.order_null|upper }}{% endif %}{% if not loop.last %}, {% else %} {% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if limit > 0 %}
|
||||
|
1040
web/yarn.lock
1040
web/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user