Move query logic to sdk
This commit is contained in:
parent
26638ace0a
commit
6e1cd6eb01
|
@ -3,14 +3,9 @@ import {
|
||||||
ViewV2,
|
ViewV2,
|
||||||
SearchRowResponse,
|
SearchRowResponse,
|
||||||
SearchViewRowRequest,
|
SearchViewRowRequest,
|
||||||
SearchFilterKey,
|
|
||||||
LogicalOperator,
|
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { dataFilters } from "@budibase/shared-core"
|
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
import { db, context, features } from "@budibase/backend-core"
|
import { context } from "@budibase/backend-core"
|
||||||
import { enrichSearchContext } from "./utils"
|
|
||||||
import { isExternalTableID } from "../../../integrations/utils"
|
|
||||||
|
|
||||||
export async function searchView(
|
export async function searchView(
|
||||||
ctx: UserCtx<SearchViewRowRequest, SearchRowResponse>
|
ctx: UserCtx<SearchViewRowRequest, SearchRowResponse>
|
||||||
|
@ -27,58 +22,23 @@ export async function searchView(
|
||||||
|
|
||||||
const { body } = ctx.request
|
const { body } = ctx.request
|
||||||
|
|
||||||
// Enrich saved query with ephemeral query params.
|
|
||||||
// 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.
|
|
||||||
let query = dataFilters.buildQuery(view.query || [])
|
|
||||||
if (body.query) {
|
|
||||||
// Delete extraneous search params that cannot be overridden
|
|
||||||
delete body.query.onEmptyFilter
|
|
||||||
|
|
||||||
if (
|
|
||||||
!isExternalTableID(view.tableId) &&
|
|
||||||
!(await features.flags.isEnabled("SQS"))
|
|
||||||
) {
|
|
||||||
// Extract existing fields
|
|
||||||
const existingFields =
|
|
||||||
view.query
|
|
||||||
?.filter(filter => filter.field)
|
|
||||||
.map(filter => db.removeKeyNumbering(filter.field)) || []
|
|
||||||
|
|
||||||
// Carry over filters for unused fields
|
|
||||||
Object.keys(body.query).forEach(key => {
|
|
||||||
const operator = key as Exclude<SearchFilterKey, LogicalOperator>
|
|
||||||
Object.keys(body.query[operator] || {}).forEach(field => {
|
|
||||||
if (!existingFields.includes(db.removeKeyNumbering(field))) {
|
|
||||||
query[operator]![field] = body.query[operator]![field]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
query = {
|
|
||||||
$and: {
|
|
||||||
conditions: [query, body.query],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await context.ensureSnippetContext(true)
|
await context.ensureSnippetContext(true)
|
||||||
|
|
||||||
const enrichedQuery = await enrichSearchContext(query, {
|
const result = await sdk.rows.search(
|
||||||
user: sdk.users.getUserContextBindings(ctx.user),
|
{
|
||||||
})
|
viewId: view.id,
|
||||||
|
tableId: view.tableId,
|
||||||
const result = await sdk.rows.search({
|
query: body.query,
|
||||||
viewId: view.id,
|
...getSortOptions(body, view),
|
||||||
tableId: view.tableId,
|
limit: body.limit,
|
||||||
query: enrichedQuery,
|
bookmark: body.bookmark,
|
||||||
...getSortOptions(body, view),
|
paginate: body.paginate,
|
||||||
limit: body.limit,
|
countRows: body.countRows,
|
||||||
bookmark: body.bookmark,
|
},
|
||||||
paginate: body.paginate,
|
{
|
||||||
countRows: body.countRows,
|
user: sdk.users.getUserContextBindings(ctx.user),
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
result.rows.forEach(r => (r._viewId = view.id))
|
result.rows.forEach(r => (r._viewId = view.id))
|
||||||
ctx.body = result
|
ctx.body = result
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
import {
|
import {
|
||||||
EmptyFilterOption,
|
EmptyFilterOption,
|
||||||
|
LogicalOperator,
|
||||||
Row,
|
Row,
|
||||||
RowSearchParams,
|
RowSearchParams,
|
||||||
|
SearchFilterKey,
|
||||||
|
SearchFilters,
|
||||||
SearchResponse,
|
SearchResponse,
|
||||||
SortOrder,
|
SortOrder,
|
||||||
Table,
|
Table,
|
||||||
|
@ -14,9 +17,10 @@ import { ExportRowsParams, ExportRowsResult } from "./search/types"
|
||||||
import { dataFilters } from "@budibase/shared-core"
|
import { dataFilters } from "@budibase/shared-core"
|
||||||
import sdk from "../../index"
|
import sdk from "../../index"
|
||||||
import { searchInputMapping } from "./search/utils"
|
import { searchInputMapping } from "./search/utils"
|
||||||
import { features } from "@budibase/backend-core"
|
import { db, features } from "@budibase/backend-core"
|
||||||
import tracer from "dd-trace"
|
import tracer from "dd-trace"
|
||||||
import { getQueryableFields, removeInvalidFilters } from "./queryUtils"
|
import { getQueryableFields, removeInvalidFilters } from "./queryUtils"
|
||||||
|
import { enrichSearchContext } from "../../../api/controllers/row/utils"
|
||||||
|
|
||||||
export { isValidFilter } from "../../../integrations/utils"
|
export { isValidFilter } from "../../../integrations/utils"
|
||||||
|
|
||||||
|
@ -34,7 +38,8 @@ function pickApi(tableId: any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function search(
|
export async function search(
|
||||||
options: RowSearchParams
|
options: RowSearchParams,
|
||||||
|
context?: Record<string, any>
|
||||||
): Promise<SearchResponse<Row>> {
|
): Promise<SearchResponse<Row>> {
|
||||||
return await tracer.trace("search", async span => {
|
return await tracer.trace("search", async span => {
|
||||||
span?.addTags({
|
span?.addTags({
|
||||||
|
@ -51,6 +56,69 @@ export async function search(
|
||||||
countRows: options.countRows,
|
countRows: options.countRows,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let source: Table | ViewV2
|
||||||
|
let table: Table
|
||||||
|
if (options.viewId) {
|
||||||
|
source = await sdk.views.get(options.viewId)
|
||||||
|
table = await sdk.views.getTable(source)
|
||||||
|
options = searchInputMapping(table, options)
|
||||||
|
} else if (options.tableId) {
|
||||||
|
source = await sdk.tables.getTable(options.tableId)
|
||||||
|
table = source
|
||||||
|
} else {
|
||||||
|
throw new Error(`Must supply either a view ID or a table ID`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.query) {
|
||||||
|
const visibleFields = (
|
||||||
|
options.fields || Object.keys(table.schema)
|
||||||
|
).filter(field => table.schema[field].visible !== false)
|
||||||
|
|
||||||
|
const queryableFields = await getQueryableFields(table, visibleFields)
|
||||||
|
options.query = removeInvalidFilters(options.query, queryableFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.viewId) {
|
||||||
|
const view = await sdk.views.get(options.viewId)
|
||||||
|
// Enrich saved query with ephemeral query params.
|
||||||
|
// 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.
|
||||||
|
let viewQuery = dataFilters.buildQuery(view.query || [])
|
||||||
|
|
||||||
|
if (!isExternalTable && !(await features.flags.isEnabled("SQS"))) {
|
||||||
|
// Lucene does not accept conditional filters, so we need to keep the old logic
|
||||||
|
const query: SearchFilters = {}
|
||||||
|
|
||||||
|
// Extract existing fields
|
||||||
|
const existingFields =
|
||||||
|
view.query
|
||||||
|
?.filter(filter => filter.field)
|
||||||
|
.map(filter => db.removeKeyNumbering(filter.field)) || []
|
||||||
|
|
||||||
|
// Carry over filters for unused fields
|
||||||
|
Object.keys(options.query).forEach(key => {
|
||||||
|
const operator = key as Exclude<SearchFilterKey, LogicalOperator>
|
||||||
|
Object.keys(options.query[operator] || {}).forEach(field => {
|
||||||
|
if (existingFields.includes(db.removeKeyNumbering(field))) {
|
||||||
|
query[operator]![field] = viewQuery[operator]![field]
|
||||||
|
} else {
|
||||||
|
query[operator]![field] = options.query[operator]![field]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
options.query = {
|
||||||
|
$and: {
|
||||||
|
conditions: [viewQuery, options.query],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context) {
|
||||||
|
options.query = await enrichSearchContext(options.query, context)
|
||||||
|
}
|
||||||
|
|
||||||
options.query = dataFilters.cleanupQuery(options.query || {})
|
options.query = dataFilters.cleanupQuery(options.query || {})
|
||||||
options.query = dataFilters.fixupFilterArrays(options.query)
|
options.query = dataFilters.fixupFilterArrays(options.query)
|
||||||
|
|
||||||
|
@ -72,28 +140,7 @@ export async function search(
|
||||||
options.sortOrder = options.sortOrder.toLowerCase() as SortOrder
|
options.sortOrder = options.sortOrder.toLowerCase() as SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
let source: Table | ViewV2
|
options = searchInputMapping(table, options)
|
||||||
let table: Table
|
|
||||||
if (options.viewId) {
|
|
||||||
source = await sdk.views.get(options.viewId)
|
|
||||||
table = await sdk.views.getTable(source)
|
|
||||||
options = searchInputMapping(table, options)
|
|
||||||
} else if (options.tableId) {
|
|
||||||
source = await sdk.tables.getTable(options.tableId)
|
|
||||||
table = source
|
|
||||||
options = searchInputMapping(table, options)
|
|
||||||
} else {
|
|
||||||
throw new Error(`Must supply either a view ID or a table ID`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.query) {
|
|
||||||
const visibleFields = (
|
|
||||||
options.fields || Object.keys(table.schema)
|
|
||||||
).filter(field => table.schema[field].visible !== false)
|
|
||||||
|
|
||||||
const queryableFields = await getQueryableFields(table, visibleFields)
|
|
||||||
options.query = removeInvalidFilters(options.query, queryableFields)
|
|
||||||
}
|
|
||||||
|
|
||||||
const isExternalTable = isExternalTableID(table._id!)
|
const isExternalTable = isExternalTableID(table._id!)
|
||||||
let result: SearchResponse<Row>
|
let result: SearchResponse<Row>
|
||||||
|
|
Loading…
Reference in New Issue