From b5ca884f4859d55fad61d55eb431b69f4f76d6f1 Mon Sep 17 00:00:00 2001 From: Stefano Ricci Date: Fri, 29 Nov 2024 11:13:57 +0100 Subject: [PATCH 1/3] data explorer: support isNotEmpty function --- core/expressionParser/toSql/toSql.js | 1 + 1 file changed, 1 insertion(+) diff --git a/core/expressionParser/toSql/toSql.js b/core/expressionParser/toSql/toSql.js index b5adb97714..18a1b4a03f 100644 --- a/core/expressionParser/toSql/toSql.js +++ b/core/expressionParser/toSql/toSql.js @@ -13,6 +13,7 @@ const stdlib2sql = { sum: 'sum', avg: 'avg', isEmpty: (param) => `coalesce(${param}, '') = ''`, + isNotEmpty: (param) => `coalesce(${param}, '') <> ''`, '!': (param) => `NOT (${param})`, } From 6c56fe9b4250c16ecea4ac270375c867d15c97d1 Mon Sep 17 00:00:00 2001 From: Stefano Ricci Date: Fri, 29 Nov 2024 11:14:28 +0100 Subject: [PATCH 2/3] sort editor: keep only first variable in composite attributes --- .../ButtonBar/ButtonSort/SortEditor/SortEditor.js | 4 +--- .../ButtonSort/SortEditor/store/useSortEditor.js | 11 +++++++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/webapp/components/DataQuery/ButtonBar/ButtonSort/SortEditor/SortEditor.js b/webapp/components/DataQuery/ButtonBar/ButtonSort/SortEditor/SortEditor.js index 3363aebcf0..ad48fdaed8 100644 --- a/webapp/components/DataQuery/ButtonBar/ButtonSort/SortEditor/SortEditor.js +++ b/webapp/components/DataQuery/ButtonBar/ButtonSort/SortEditor/SortEditor.js @@ -4,7 +4,6 @@ import PropTypes from 'prop-types' import { Button } from '@webapp/components/buttons' import PanelRight from '@webapp/components/PanelRight' -import { useI18n } from '@webapp/store/system' import { Sort, SortCriteria } from '@common/model/query' import { useSortEditor } from './store' @@ -13,11 +12,10 @@ import SortCriteriaEditor from './SortCriteriaEditor' const SortEditor = (props) => { const { onChange, onClose, query } = props - const i18n = useI18n() const { draft, sort, sortDraft, setSortDraft, variables, variablesAvailable } = useSortEditor({ query }) return ( - +
{sortDraft.map((sortCriteria, idx) => ( }) if (Objects.isEmpty(attributeDefUuids)) return variables - const variablesByUuid = ObjectUtils.toUuidIndexedObj(variables) + const variablesByUuid = variables.reduce((acc, variable) => { + const { uuid } = variable + if (!acc[uuid]) { + // keep only first variable + // variables for composite attributes have same UUID for different properties (e.g. taxon) + acc[uuid] = variable + } + return acc + }, {}) return attributeDefUuids.map((uuid) => variablesByUuid[uuid]).filter(Boolean) } From 227bf929d84f84db99149deff0650456d1c93766 Mon Sep 17 00:00:00 2001 From: Stefano Ricci Date: Fri, 29 Nov 2024 13:02:15 +0100 Subject: [PATCH 3/3] fixed filtering using function call --- core/expressionParser/toSql/toSql.js | 2 +- .../ButtonBar/ButtonFilter/ButtonFilter.js | 9 +++---- .../components/expression/expressionParser.js | 24 +++++++++++-------- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/core/expressionParser/toSql/toSql.js b/core/expressionParser/toSql/toSql.js index 18a1b4a03f..a049bf5918 100644 --- a/core/expressionParser/toSql/toSql.js +++ b/core/expressionParser/toSql/toSql.js @@ -130,7 +130,7 @@ export const identifier = (node, params) => { export const call = (node, params) => { const { callee, arguments: argumentsNode } = node - const { name } = callee + const name = callee.name ?? callee.value // callee can be literal or identifier const sqlFnNameOrFn = stdlib2sql[name] if (!sqlFnNameOrFn) { throw new SystemError('undefinedFunction', { name }) diff --git a/webapp/components/DataQuery/ButtonBar/ButtonFilter/ButtonFilter.js b/webapp/components/DataQuery/ButtonBar/ButtonFilter/ButtonFilter.js index 2f7a58075e..d879b835e8 100644 --- a/webapp/components/DataQuery/ButtonBar/ButtonFilter/ButtonFilter.js +++ b/webapp/components/DataQuery/ButtonBar/ButtonFilter/ButtonFilter.js @@ -6,16 +6,15 @@ import { Query } from '@common/model/query' import { ButtonIconFilter } from '@webapp/components/buttons' import ExpressionEditorPopup from '@webapp/components/expression/expressionEditorPopup' +import * as ExpressionParser from '@webapp/components/expression/expressionParser' import { DataExplorerHooks, DataExplorerSelectors } from '@webapp/store/dataExplorer' -import { useI18n } from '@webapp/store/system' import { State } from '../store' const ButtonFilter = (props) => { const { disabled, state, Actions } = props - const i18n = useI18n() const query = DataExplorerSelectors.useQuery() const onChangeQuery = DataExplorerHooks.useSetQuery() @@ -35,6 +34,7 @@ const ButtonFilter = (props) => { {State.isPanelFilterShown(state) && ( { excludeCurrentNodeDef={false} query={filter ? Expression.toString(filter) : ''} mode={Expression.modes.sql} - header={i18n.t('dataView.filterRecords.expressionEditorHeader')} + header="dataView.filterRecords.expressionEditorHeader" onChange={({ expr }) => { - onChangeQuery(Query.assocFilter(expr)(query)) + const exprNormalized = ExpressionParser.normalize({ expr, canBeCall: true }) + onChangeQuery(Query.assocFilter(exprNormalized)(query)) Actions.closePanels() }} onClose={Actions.closePanels} diff --git a/webapp/components/expression/expressionParser.js b/webapp/components/expression/expressionParser.js index 3f95b12d54..b39e767f9a 100644 --- a/webapp/components/expression/expressionParser.js +++ b/webapp/components/expression/expressionParser.js @@ -14,18 +14,22 @@ export const parseQuery = ({ query, mode, canBeConstant = false }) => { return Expression.newBinaryEmpty({ canBeConstant, exprQuery }) } +export const normalize = ({ expr, canBeConstant = false, canBeCall = false }) => { + if (canBeConstant || canBeCall) { + // expr can be a binary expression with an empty operator and right operand; + // formatting and parsing it again will keep only the left operand in the evaluation + const exprString = Expression.toString(expr) + if (isNotBlank(exprString)) { + return Expression.fromString(exprString) + } + } + return expr +} + export const isExprValid = ({ expr, canBeConstant = false, canBeCall = false }) => { try { - if (canBeConstant || canBeCall) { - // expr can be a binary expression with an empty operator and right operand; - // formatting and parsing it again allows to consider only the left operand in the evaluation - const exprString = Expression.toString(expr) - if (isNotBlank(exprString)) { - const exprToValidate = Expression.fromString(exprString) - return Expression.isValid(exprToValidate) - } - } - return Expression.isValid(expr) + const exprToValidate = normalize({ expr, canBeConstant, canBeCall }) + return Expression.isValid(exprToValidate) } catch (error) { return false }