Updates for view tests

This commit is contained in:
Dean 2024-09-11 11:56:09 +01:00
parent c3415d66f7
commit 9ea74dcb45
3 changed files with 150 additions and 11 deletions

View File

@ -33,23 +33,26 @@ export async function searchView(
.map(([key]) => key) .map(([key]) => key)
const { body } = ctx.request const { body } = ctx.request
const sqsEnabled = await features.flags.isEnabled("SQS")
const supportsLogicalOperators = isExternalTableID(view.tableId) || sqsEnabled
// Enrich saved query with ephemeral query params. // Enrich saved query with ephemeral query params.
// We prevent searching on any fields that are saved as part of the query, as // We prevent searching on any fields that are saved as part of the query, as
// that could let users find rows they should not be allowed to access. // that could let users find rows they should not be allowed to access.
let query: any = dataFilters.buildQuery(view.query) let query: any = supportsLogicalOperators
? dataFilters.buildQuery(view.query)
: dataFilters.buildQueryLegacy(view.query as SearchFilter[])
delete query?.onEmptyFilter
if (body.query) { if (body.query) {
// Delete extraneous search params that cannot be overridden // Delete extraneous search params that cannot be overridden
delete body.query.onEmptyFilter delete body.query.onEmptyFilter
if ( if (!supportsLogicalOperators) {
!isExternalTableID(view.tableId) &&
!(await features.flags.isEnabled("SQS"))
) {
// In the unlikely event that a Grouped Filter is in a non-SQS environment // In the unlikely event that a Grouped Filter is in a non-SQS environment
// It needs to be ignored. Entirely // It needs to be ignored. Entirely
let queryFilters: SearchFilter[] = Array.isArray(view.query) let queryFilters: SearchFilter[] = Array.isArray(query) ? query : []
? view.query
: []
// Extract existing fields // Extract existing fields
const existingFields = const existingFields =

View File

@ -15,7 +15,9 @@ export const removeInvalidFilters = (
const result = cloneDeep(filters) const result = cloneDeep(filters)
validFields = validFields.map(f => f.toLowerCase()) validFields = validFields.map(f => f.toLowerCase())
for (const filterKey of Object.keys(result) as (keyof SearchFilters)[]) { for (const filterKey of Object.keys(
result || {}
) as (keyof SearchFilters)[]) {
const filter = result[filterKey] const filter = result[filterKey]
if (!filter || typeof filter !== "object") { if (!filter || typeof filter !== "object") {
continue continue
@ -24,7 +26,7 @@ export const removeInvalidFilters = (
const resultingConditions: SearchFilters[] = [] const resultingConditions: SearchFilters[] = []
for (const condition of filter.conditions) { for (const condition of filter.conditions) {
const resultingCondition = removeInvalidFilters(condition, validFields) const resultingCondition = removeInvalidFilters(condition, validFields)
if (Object.keys(resultingCondition).length) { if (Object.keys(resultingCondition || {}).length) {
resultingConditions.push(resultingCondition) resultingConditions.push(resultingCondition)
} }
} }

View File

@ -428,6 +428,126 @@ const builderFilter = (expression: SearchFilter) => {
return query return query
} }
export const buildQueryLegacy = (filter: SearchFilter[]) => {
//
let query: SearchFilters = {
string: {},
fuzzy: {},
range: {},
equal: {},
notEqual: {},
empty: {},
notEmpty: {},
contains: {},
notContains: {},
oneOf: {},
containsAny: {},
}
if (!Array.isArray(filter)) {
return query
}
filter.forEach(expression => {
let { operator, field, type, value, externalType, onEmptyFilter } =
expression
const queryOperator = operator as SearchFilterOperator
const isHbs =
typeof value === "string" && (value.match(HBS_REGEX) || []).length > 0
// Parse all values into correct types
if (operator === "allOr") {
query.allOr = true
return
}
if (onEmptyFilter) {
query.onEmptyFilter = onEmptyFilter
return
}
if (
type === "datetime" &&
!isHbs &&
queryOperator !== "empty" &&
queryOperator !== "notEmpty"
) {
// Ensure date value is a valid date and parse into correct format
if (!value) {
return
}
try {
value = new Date(value).toISOString()
} catch (error) {
return
}
}
if (type === "number" && typeof value === "string" && !isHbs) {
if (queryOperator === "oneOf") {
value = value.split(",").map(item => parseFloat(item))
} else {
value = parseFloat(value)
}
}
if (type === "boolean") {
value = `${value}`?.toLowerCase() === "true"
}
if (
["contains", "notContains", "containsAny"].includes(operator) &&
type === "array" &&
typeof value === "string"
) {
value = value.split(",")
}
if (operator.startsWith("range") && query.range) {
const minint =
SqlNumberTypeRangeMap[
externalType as keyof typeof SqlNumberTypeRangeMap
]?.min || Number.MIN_SAFE_INTEGER
const maxint =
SqlNumberTypeRangeMap[
externalType as keyof typeof SqlNumberTypeRangeMap
]?.max || Number.MAX_SAFE_INTEGER
if (!query.range[field]) {
query.range[field] = {
low: type === "number" ? minint : "0000-00-00T00:00:00.000Z",
high: type === "number" ? maxint : "9999-00-00T00:00:00.000Z",
}
}
if (operator === "rangeLow" && value != null && value !== "") {
query.range[field] = {
...query.range[field],
low: value,
}
} else if (operator === "rangeHigh" && value != null && value !== "") {
query.range[field] = {
...query.range[field],
high: value,
}
}
} else if (isLogicalSearchOperator(queryOperator)) {
// TODO
} else if (query[queryOperator] && operator !== "onEmptyFilter") {
if (type === "boolean") {
// Transform boolean filters to cope with null.
// "equals false" needs to be "not equals true"
// "not equals false" needs to be "equals true"
if (queryOperator === "equal" && value === false) {
query.notEqual = query.notEqual || {}
query.notEqual[field] = true
} else if (queryOperator === "notEqual" && value === false) {
query.equal = query.equal || {}
query.equal[field] = true
} else {
query[queryOperator] ??= {}
query[queryOperator]![field] = value
}
} else {
query[queryOperator] ??= {}
query[queryOperator]![field] = value
}
}
})
return query
}
export const buildQuery = (filter?: SearchFilterGroup | SearchFilter[]) => { export const buildQuery = (filter?: SearchFilterGroup | SearchFilter[]) => {
if (!filter) { if (!filter) {
return return
@ -891,9 +1011,23 @@ export const hasFilters = (query?: SearchFilters) => {
} }
const queryRoot = query[LogicalOperator.AND] ?? query[LogicalOperator.OR] const queryRoot = query[LogicalOperator.AND] ?? query[LogicalOperator.OR]
if (!queryRoot) {
const skipped = ["allOr", "onEmptyFilter"]
for (let [key, value] of Object.entries(query)) {
if (skipped.includes(key) || typeof value !== "object") {
continue
}
if (Object.keys(value || {}).length !== 0) {
return true
}
}
return false
}
return ( return (
(queryRoot?.conditions || []).reduce((acc, group) => { (queryRoot?.conditions || []).reduce((acc, group) => {
const groupRoot = group[LogicalOperator.AND] ?? group[LogicalOperator.OR] const groupRoot =
group?.[LogicalOperator.AND] ?? group?.[LogicalOperator.OR]
acc += groupRoot?.conditions?.length || 0 acc += groupRoot?.conditions?.length || 0
return acc return acc
}, 0) > 0 }, 0) > 0