Skip to content

Commit

Permalink
Fix data explorer error when using function call as filter (#3675)
Browse files Browse the repository at this point in the history
* data explorer: support isNotEmpty function

* sort editor: keep only first variable in composite attributes

* fixed filtering using function call

---------

Co-authored-by: Stefano Ricci <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Nov 29, 2024
1 parent 39611ac commit a8bff39
Show file tree
Hide file tree
Showing 5 changed files with 31 additions and 20 deletions.
3 changes: 2 additions & 1 deletion core/expressionParser/toSql/toSql.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const stdlib2sql = {
sum: 'sum',
avg: 'avg',
isEmpty: (param) => `coalesce(${param}, '') = ''`,
isNotEmpty: (param) => `coalesce(${param}, '') <> ''`,
'!': (param) => `NOT (${param})`,
}

Expand Down Expand Up @@ -129,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 })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -35,16 +34,18 @@ const ButtonFilter = (props) => {

{State.isPanelFilterShown(state) && (
<ExpressionEditorPopup
canBeCall
nodeDefUuidContext={entityDefUuid}
nodeDefUuidCurrent={entityDefUuid}
includeAnalysis
isContextParent={false}
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}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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 (
<PanelRight onClose={onClose} header={i18n.t('common.orderBy')}>
<PanelRight onClose={onClose} header="common.orderBy">
<div className="sort-editor">
{sortDraft.map((sortCriteria, idx) => (
<SortCriteriaEditor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { useState } from 'react'
import { Objects } from '@openforis/arena-core'

import { Query, Sort } from '@common/model/query'
import * as ObjectUtils from '@core/objectUtils'
import * as Expression from '@core/expressionParser/expression'

import * as ExpressionVariables from '@webapp/components/expression/expressionVariables'
Expand All @@ -21,7 +20,15 @@ const getVariables = ({ survey, cycle, entityDef, attributeDefUuids, lang }) =>
})
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)
}

Expand Down
24 changes: 14 additions & 10 deletions webapp/components/expression/expressionParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down

0 comments on commit a8bff39

Please sign in to comment.