Updating user fetch functionality to send up lucene syntax for searching to global user endpoint.
This commit is contained in:
parent
702ee4d504
commit
6bbce23910
|
@ -10,25 +10,30 @@ export const buildUserEndpoints = API => ({
|
|||
|
||||
/**
|
||||
* Gets a list of users in the current tenant.
|
||||
* @param {string} page The page to retrieve
|
||||
* @param {string} search The starts with string to search username/email by.
|
||||
* @param {string} bookmark The page to retrieve
|
||||
* @param {object} query search filters for lookup by user (all operators not supported).
|
||||
* @param {string} appId Facilitate app/role based user searching
|
||||
* @param {boolean} paginated Allow the disabling of pagination
|
||||
* @param {boolean} paginate Allow the disabling of pagination
|
||||
* @param {number} limit How many users to retrieve in a single search
|
||||
*/
|
||||
searchUsers: async ({ paginated, page, email, appId } = {}) => {
|
||||
searchUsers: async ({ paginate, bookmark, query, appId, limit } = {}) => {
|
||||
const opts = {}
|
||||
if (page) {
|
||||
opts.page = page
|
||||
if (bookmark) {
|
||||
opts.bookmark = bookmark
|
||||
}
|
||||
if (email) {
|
||||
opts.email = email
|
||||
if (query) {
|
||||
opts.query = query
|
||||
}
|
||||
if (appId) {
|
||||
opts.appId = appId
|
||||
}
|
||||
if (typeof paginated === "boolean") {
|
||||
opts.paginated = paginated
|
||||
if (typeof paginate === "boolean") {
|
||||
opts.paginate = paginate
|
||||
}
|
||||
if (limit) {
|
||||
opts.limit = limit
|
||||
}
|
||||
console.log(opts)
|
||||
return await API.post({
|
||||
url: `/api/global/users/search`,
|
||||
body: opts,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { get } from "svelte/store"
|
||||
import DataFetch from "./DataFetch.js"
|
||||
import { TableNames } from "../constants"
|
||||
import { LuceneUtils } from "../utils"
|
||||
|
||||
export default class UserFetch extends DataFetch {
|
||||
constructor(opts) {
|
||||
|
@ -27,16 +28,25 @@ export default class UserFetch extends DataFetch {
|
|||
}
|
||||
|
||||
async getData() {
|
||||
const { limit, paginate } = this.options
|
||||
const { cursor, query } = get(this.store)
|
||||
let finalQuery
|
||||
// convert old format to new one - we now allow use of the lucene format
|
||||
const { appId, paginated, ...rest} = query
|
||||
if (!LuceneUtils.isLuceneFilter(query) && rest.email) {
|
||||
finalQuery = { string: { email: rest.email }}
|
||||
} else {
|
||||
finalQuery = rest
|
||||
}
|
||||
try {
|
||||
// "query" normally contains a lucene query, but users uses a non-standard
|
||||
// search endpoint so we use query uniquely here
|
||||
const res = await this.API.searchUsers({
|
||||
page: cursor,
|
||||
email: query.email,
|
||||
appId: query.appId,
|
||||
paginated: query.paginated,
|
||||
})
|
||||
const opts = {
|
||||
bookmark: cursor,
|
||||
query: finalQuery,
|
||||
appId: appId,
|
||||
paginate: paginated || paginate,
|
||||
limit,
|
||||
}
|
||||
const res = await this.API.searchUsers(opts)
|
||||
return {
|
||||
rows: res?.data || [],
|
||||
hasNextPage: res?.hasNextPage || false,
|
||||
|
|
|
@ -15,7 +15,7 @@ import {
|
|||
QuotaUsageType,
|
||||
RelationshipType,
|
||||
Row,
|
||||
SaveTableRequest,
|
||||
SaveTableRequest, SearchQueryOperators,
|
||||
SortOrder,
|
||||
SortType,
|
||||
StaticQuotaName,
|
||||
|
@ -1141,7 +1141,7 @@ describe.each([
|
|||
)
|
||||
|
||||
const createViewResponse = await config.createView({
|
||||
query: [{ operator: "equal", field: "age", value: 40 }],
|
||||
query: [{ operator: SearchQueryOperators.EQUAL, field: "age", value: 40 }],
|
||||
schema: viewSchema,
|
||||
})
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import {
|
|||
CreateViewRequest,
|
||||
FieldSchema,
|
||||
FieldType,
|
||||
SearchQueryOperators,
|
||||
SortOrder,
|
||||
SortType,
|
||||
Table,
|
||||
|
@ -10,8 +11,8 @@ import {
|
|||
UpdateViewRequest,
|
||||
ViewV2,
|
||||
} from "@budibase/types"
|
||||
import { generator } from "@budibase/backend-core/tests"
|
||||
import { generateDatasourceID } from "../../../db/utils"
|
||||
import {generator} from "@budibase/backend-core/tests"
|
||||
import {generateDatasourceID} from "../../../db/utils"
|
||||
|
||||
function priceTable(): Table {
|
||||
return {
|
||||
|
@ -89,7 +90,7 @@ describe.each([
|
|||
name: generator.name(),
|
||||
tableId: table._id!,
|
||||
primaryDisplay: generator.word(),
|
||||
query: [{ operator: "equal", field: "field", value: "value" }],
|
||||
query: [{ operator: SearchQueryOperators.EQUAL, field: "field", value: "value" }],
|
||||
sort: {
|
||||
field: "fieldToSort",
|
||||
order: SortOrder.DESCENDING,
|
||||
|
@ -184,7 +185,7 @@ describe.each([
|
|||
const tableId = table._id!
|
||||
await config.api.viewV2.update({
|
||||
...view,
|
||||
query: [{ operator: "equal", field: "newField", value: "thatValue" }],
|
||||
query: [{ operator: SearchQueryOperators.EQUAL, field: "newField", value: "thatValue" }],
|
||||
})
|
||||
|
||||
expect((await config.api.table.get(tableId)).views).toEqual({
|
||||
|
@ -207,7 +208,7 @@ describe.each([
|
|||
primaryDisplay: generator.word(),
|
||||
query: [
|
||||
{
|
||||
operator: "equal",
|
||||
operator: SearchQueryOperators.EQUAL,
|
||||
field: generator.word(),
|
||||
value: generator.word(),
|
||||
},
|
||||
|
@ -279,7 +280,7 @@ describe.each([
|
|||
{
|
||||
...view,
|
||||
tableId: generator.guid(),
|
||||
query: [{ operator: "equal", field: "newField", value: "thatValue" }],
|
||||
query: [{ operator: SearchQueryOperators.EQUAL, field: "newField", value: "thatValue" }],
|
||||
},
|
||||
{ expectStatus: 404 }
|
||||
)
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
import {
|
||||
Datasource,
|
||||
FieldSubtype,
|
||||
FieldType,
|
||||
SortDirection,
|
||||
SortType,
|
||||
SearchFilter,
|
||||
SearchQuery,
|
||||
SearchQueryFields,
|
||||
FieldSubtype,
|
||||
SearchQueryOperators,
|
||||
SortDirection,
|
||||
SortType,
|
||||
} from "@budibase/types"
|
||||
import { OperatorOptions, SqlNumberTypeRangeMap } from "./constants"
|
||||
import { deepGet } from "./helpers"
|
||||
import {OperatorOptions, SqlNumberTypeRangeMap} from "./constants"
|
||||
import {deepGet} from "./helpers"
|
||||
|
||||
const HBS_REGEX = /{{([^{].*?)}}/g
|
||||
|
||||
|
@ -238,6 +239,18 @@ export const buildLuceneQuery = (filter: SearchFilter[]) => {
|
|||
return query
|
||||
}
|
||||
|
||||
// type unknown
|
||||
export const isLuceneFilter = (search: any) => {
|
||||
if (typeof search !== "object") {
|
||||
return false
|
||||
}
|
||||
const operators = Object.values(SearchQueryOperators) as string[]
|
||||
const anySearchKey = Object.keys(search).find(key => {
|
||||
return operators.includes(key) && typeof search[key] === "object"
|
||||
})
|
||||
return !!anySearchKey
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a client-side lucene search on an array of data
|
||||
* @param docs the data
|
||||
|
@ -273,14 +286,14 @@ export const runLuceneQuery = (docs: any[], query?: SearchQuery) => {
|
|||
}
|
||||
|
||||
// Process a string match (fails if the value does not start with the string)
|
||||
const stringMatch = match("string", (docValue: string, testValue: string) => {
|
||||
const stringMatch = match(SearchQueryOperators.STRING, (docValue: string, testValue: string) => {
|
||||
return (
|
||||
!docValue || !docValue?.toLowerCase().startsWith(testValue?.toLowerCase())
|
||||
)
|
||||
})
|
||||
|
||||
// Process a fuzzy match (treat the same as starts with when running locally)
|
||||
const fuzzyMatch = match("fuzzy", (docValue: string, testValue: string) => {
|
||||
const fuzzyMatch = match(SearchQueryOperators.FUZZY, (docValue: string, testValue: string) => {
|
||||
return (
|
||||
!docValue || !docValue?.toLowerCase().startsWith(testValue?.toLowerCase())
|
||||
)
|
||||
|
@ -288,7 +301,7 @@ export const runLuceneQuery = (docs: any[], query?: SearchQuery) => {
|
|||
|
||||
// Process a range match
|
||||
const rangeMatch = match(
|
||||
"range",
|
||||
SearchQueryOperators.RANGE,
|
||||
(
|
||||
docValue: string | number | null,
|
||||
testValue: { low: number; high: number }
|
||||
|
@ -304,7 +317,7 @@ export const runLuceneQuery = (docs: any[], query?: SearchQuery) => {
|
|||
|
||||
// Process an equal match (fails if the value is different)
|
||||
const equalMatch = match(
|
||||
"equal",
|
||||
SearchQueryOperators.EQUAL,
|
||||
(docValue: any, testValue: string | null) => {
|
||||
return testValue != null && testValue !== "" && docValue !== testValue
|
||||
}
|
||||
|
@ -312,24 +325,24 @@ export const runLuceneQuery = (docs: any[], query?: SearchQuery) => {
|
|||
|
||||
// Process a not-equal match (fails if the value is the same)
|
||||
const notEqualMatch = match(
|
||||
"notEqual",
|
||||
SearchQueryOperators.NOT_EQUAL,
|
||||
(docValue: any, testValue: string | null) => {
|
||||
return testValue != null && testValue !== "" && docValue === testValue
|
||||
}
|
||||
)
|
||||
|
||||
// Process an empty match (fails if the value is not empty)
|
||||
const emptyMatch = match("empty", (docValue: string | null) => {
|
||||
const emptyMatch = match(SearchQueryOperators.EMPTY, (docValue: string | null) => {
|
||||
return docValue != null && docValue !== ""
|
||||
})
|
||||
|
||||
// Process a not-empty match (fails is the value is empty)
|
||||
const notEmptyMatch = match("notEmpty", (docValue: string | null) => {
|
||||
const notEmptyMatch = match(SearchQueryOperators.NOT_EMPTY, (docValue: string | null) => {
|
||||
return docValue == null || docValue === ""
|
||||
})
|
||||
|
||||
// Process an includes match (fails if the value is not included)
|
||||
const oneOf = match("oneOf", (docValue: any, testValue: any) => {
|
||||
const oneOf = match(SearchQueryOperators.ONE_OF, (docValue: any, testValue: any) => {
|
||||
if (typeof testValue === "string") {
|
||||
testValue = testValue.split(",")
|
||||
if (typeof docValue === "number") {
|
||||
|
@ -339,19 +352,19 @@ export const runLuceneQuery = (docs: any[], query?: SearchQuery) => {
|
|||
return !testValue?.includes(docValue)
|
||||
})
|
||||
|
||||
const containsAny = match("containsAny", (docValue: any, testValue: any) => {
|
||||
const containsAny = match(SearchQueryOperators.CONTAINS_ANY, (docValue: any, testValue: any) => {
|
||||
return !docValue?.includes(...testValue)
|
||||
})
|
||||
|
||||
const contains = match(
|
||||
"contains",
|
||||
SearchQueryOperators.CONTAINS,
|
||||
(docValue: string | any[], testValue: any[]) => {
|
||||
return !testValue?.every((item: any) => docValue?.includes(item))
|
||||
}
|
||||
)
|
||||
|
||||
const notContains = match(
|
||||
"notContains",
|
||||
SearchQueryOperators.NOT_CONTAINS,
|
||||
(docValue: string | any[], testValue: any[]) => {
|
||||
return testValue?.every((item: any) => docValue?.includes(item))
|
||||
}
|
||||
|
|
|
@ -10,43 +10,57 @@ export type SearchFilter = {
|
|||
externalType?: string
|
||||
}
|
||||
|
||||
export enum SearchQueryOperators {
|
||||
STRING = "string",
|
||||
FUZZY = "fuzzy",
|
||||
RANGE = "range",
|
||||
EQUAL = "equal",
|
||||
NOT_EQUAL = "notEqual",
|
||||
EMPTY = "empty",
|
||||
NOT_EMPTY = "notEmpty",
|
||||
ONE_OF = "oneOf",
|
||||
CONTAINS = "contains",
|
||||
NOT_CONTAINS = "notContains",
|
||||
CONTAINS_ANY = "containsAny",
|
||||
}
|
||||
|
||||
export type SearchQuery = {
|
||||
allOr?: boolean
|
||||
onEmptyFilter?: EmptyFilterOption
|
||||
string?: {
|
||||
[SearchQueryOperators.STRING]?: {
|
||||
[key: string]: string
|
||||
}
|
||||
fuzzy?: {
|
||||
[SearchQueryOperators.FUZZY]?: {
|
||||
[key: string]: string
|
||||
}
|
||||
range?: {
|
||||
[SearchQueryOperators.RANGE]?: {
|
||||
[key: string]: {
|
||||
high: number | string
|
||||
low: number | string
|
||||
}
|
||||
}
|
||||
equal?: {
|
||||
[SearchQueryOperators.EQUAL]?: {
|
||||
[key: string]: any
|
||||
}
|
||||
notEqual?: {
|
||||
[SearchQueryOperators.NOT_EQUAL]?: {
|
||||
[key: string]: any
|
||||
}
|
||||
empty?: {
|
||||
[SearchQueryOperators.EMPTY]?: {
|
||||
[key: string]: any
|
||||
}
|
||||
notEmpty?: {
|
||||
[SearchQueryOperators.NOT_EMPTY]?: {
|
||||
[key: string]: any
|
||||
}
|
||||
oneOf?: {
|
||||
[SearchQueryOperators.ONE_OF]?: {
|
||||
[key: string]: any[]
|
||||
}
|
||||
contains?: {
|
||||
[SearchQueryOperators.CONTAINS]?: {
|
||||
[key: string]: any[]
|
||||
}
|
||||
notContains?: {
|
||||
[SearchQueryOperators.NOT_CONTAINS]?: {
|
||||
[key: string]: any[]
|
||||
}
|
||||
containsAny?: {
|
||||
[SearchQueryOperators.CONTAINS_ANY]?: {
|
||||
[key: string]: any[]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21750,7 +21750,7 @@ vlq@^0.2.2:
|
|||
resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.3.tgz#8f3e4328cf63b1540c0d67e1b2778386f8975b26"
|
||||
integrity sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==
|
||||
|
||||
vm2@^3.9.19:
|
||||
vm2@^3.9.19, vm2@^3.9.8:
|
||||
version "3.9.19"
|
||||
resolved "https://registry.yarnpkg.com/vm2/-/vm2-3.9.19.tgz#be1e1d7a106122c6c492b4d51c2e8b93d3ed6a4a"
|
||||
integrity sha512-J637XF0DHDMV57R6JyVsTak7nIL8gy5KH4r1HiwWLf/4GBbb5MKL5y7LpmF4A8E2nR6XmzpmMFQ7V7ppPTmUQg==
|
||||
|
|
Loading…
Reference in New Issue