Updates for view tests
This commit is contained in:
parent
c3415d66f7
commit
9ea74dcb45
|
@ -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 =
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue