Merge pull request #14741 from Budibase/budi-8708-single-and-multi-select-user-fields-appear-to-no-longer-be

Allow conditional search for users
This commit is contained in:
Adria Navarro 2024-10-09 11:04:29 +02:00 committed by GitHub
commit 47985748d2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 47 additions and 10 deletions

View File

@ -24,6 +24,7 @@ import * as context from "../context"
import { getGlobalDB } from "../context" import { getGlobalDB } from "../context"
import { isCreator } from "./utils" import { isCreator } from "./utils"
import { UserDB } from "./db" import { UserDB } from "./db"
import { dataFilters } from "@budibase/shared-core"
type GetOpts = { cleanup?: boolean } type GetOpts = { cleanup?: boolean }
@ -262,10 +263,17 @@ export async function paginatedUsers({
userList = await bulkGetGlobalUsersById(query?.oneOf?._id, { userList = await bulkGetGlobalUsersById(query?.oneOf?._id, {
cleanup: true, cleanup: true,
}) })
} else if (query) {
// TODO: this should use SQS search, but the logic is built in the 'server' package. Using the in-memory filtering to get this working meanwhile
const response = await db.allDocs<User>(
getGlobalUserParams(null, { ...opts, limit: undefined })
)
userList = response.rows.map(row => row.doc!)
userList = dataFilters.search(userList, { query, limit: opts.limit }).rows
} else { } else {
// no search, query allDocs // no search, query allDocs
const response = await db.allDocs(getGlobalUserParams(null, opts)) const response = await db.allDocs<User>(getGlobalUserParams(null, opts))
userList = response.rows.map((row: any) => row.doc) userList = response.rows.map(row => row.doc!)
} }
return pagination(userList, pageSize, { return pagination(userList, pageSize, {
paginate: true, paginate: true,

View File

@ -187,7 +187,6 @@ describe.each([
if (isInMemory) { if (isInMemory) {
return dataFilters.search(_.cloneDeep(rows), { return dataFilters.search(_.cloneDeep(rows), {
...this.query, ...this.query,
tableId: tableOrViewId,
}) })
} else { } else {
return config.api.row.search(tableOrViewId, this.query) return config.api.row.search(tableOrViewId, this.query)

View File

@ -639,19 +639,19 @@ export function fixupFilterArrays(filters: SearchFilters) {
return filters return filters
} }
export function search<T>( export function search<T extends Record<string, any>>(
docs: Record<string, T>[], docs: T[],
query: RowSearchParams query: Omit<RowSearchParams, "tableId">
): SearchResponse<Record<string, T>> { ): SearchResponse<T> {
let result = runQuery(docs, query.query) let result = runQuery(docs, query.query)
if (query.sort) { if (query.sort) {
result = sort(result, query.sort, query.sortOrder || SortOrder.ASCENDING) result = sort(result, query.sort, query.sortOrder || SortOrder.ASCENDING)
} }
let totalRows = result.length const totalRows = result.length
if (query.limit) { if (query.limit) {
result = limit(result, query.limit.toString()) result = limit(result, query.limit.toString())
} }
const response: SearchResponse<Record<string, any>> = { rows: result } const response: SearchResponse<T> = { rows: result }
if (query.countRows) { if (query.countRows) {
response.totalRows = totalRows response.totalRows = totalRows
} }

View File

@ -5,6 +5,7 @@ import {
SearchFilters, SearchFilters,
BasicOperator, BasicOperator,
ArrayOperator, ArrayOperator,
isLogicalSearchOperator,
} from "@budibase/types" } from "@budibase/types"
import * as Constants from "./constants" import * as Constants from "./constants"
import { removeKeyNumbering } from "./filters" import { removeKeyNumbering } from "./filters"
@ -97,10 +98,20 @@ export function isSupportedUserSearch(query: SearchFilters) {
{ op: BasicOperator.EQUAL, key: "_id" }, { op: BasicOperator.EQUAL, key: "_id" },
{ op: ArrayOperator.ONE_OF, key: "_id" }, { op: ArrayOperator.ONE_OF, key: "_id" },
] ]
for (let [key, operation] of Object.entries(query)) { for (const [key, operation] of Object.entries(query)) {
if (typeof operation !== "object") { if (typeof operation !== "object") {
return false return false
} }
if (isLogicalSearchOperator(key)) {
for (const condition of query[key]!.conditions) {
if (!isSupportedUserSearch(condition)) {
return false
}
}
return true
}
const fields = Object.keys(operation || {}) const fields = Object.keys(operation || {})
// this filter doesn't contain options - ignore // this filter doesn't contain options - ignore
if (fields.length === 0) { if (fields.length === 0) {

View File

@ -741,6 +741,25 @@ describe("/api/global/users", () => {
it("should throw an error if public query performed", async () => { it("should throw an error if public query performed", async () => {
await config.api.users.searchUsers({}, { status: 403, noHeaders: true }) await config.api.users.searchUsers({}, { status: 403, noHeaders: true })
}) })
it("should be able to search using logical conditions", async () => {
const user = await config.createUser()
const response = await config.api.users.searchUsers({
query: {
$and: {
conditions: [
{
$and: {
conditions: [{ string: { email: user.email } }],
},
},
],
},
},
})
expect(response.body.data.length).toBe(1)
expect(response.body.data[0].email).toBe(user.email)
})
}) })
describe("DELETE /api/global/users/:userId", () => { describe("DELETE /api/global/users/:userId", () => {