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:
Aditya Toshniwal 2024-11-28 10:13:57 +05:30 committed by GitHub
parent 9ef5a53790
commit 88e515093c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 597 additions and 556 deletions

View File

@ -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

View File

@ -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
************

View File

@ -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(' ')}>
{

View File

@ -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),
},

View File

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

View File

@ -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}
/>

View File

@ -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,

View File

@ -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));
}
});
});
}
}
},
{

View File

@ -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
);
}

View File

@ -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 %}

File diff suppressed because it is too large Load Diff