diff --git a/lerna.json b/lerna.json index bacdcb782f..906e9fba50 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.22.13", + "version": "2.22.14", "npmClient": "yarn", "packages": [ "packages/*", diff --git a/packages/account-portal b/packages/account-portal index 63ce32bca8..360ad2dc29 160000 --- a/packages/account-portal +++ b/packages/account-portal @@ -1 +1 @@ -Subproject commit 63ce32bca871f0a752323f5f7ebb5ec16bbbacc3 +Subproject commit 360ad2dc29c3f1fd5a1182ae258c45666b7f5eb1 diff --git a/packages/backend-core/src/users/users.ts b/packages/backend-core/src/users/users.ts index 638da4a5b1..48920a3771 100644 --- a/packages/backend-core/src/users/users.ts +++ b/packages/backend-core/src/users/users.ts @@ -14,16 +14,16 @@ import { } from "../db" import { BulkDocsResponse, + ContextUser, + CouchFindOptions, + DatabaseQueryOpts, SearchQuery, SearchQueryOperators, SearchUsersRequest, User, - ContextUser, - DatabaseQueryOpts, - CouchFindOptions, } from "@budibase/types" -import { getGlobalDB } from "../context" import * as context from "../context" +import { getGlobalDB } from "../context" import { isCreator } from "./utils" import { UserDB } from "./db" @@ -48,6 +48,7 @@ export function isSupportedUserSearch(query: SearchQuery) { const allowed = [ { op: SearchQueryOperators.STRING, key: "email" }, { op: SearchQueryOperators.EQUAL, key: "_id" }, + { op: SearchQueryOperators.ONE_OF, key: "_id" }, ] for (let [key, operation] of Object.entries(query)) { if (typeof operation !== "object") { @@ -285,6 +286,10 @@ export async function paginatedUsers({ } else if (query?.string?.email) { userList = await searchGlobalUsersByEmail(query?.string?.email, opts) property = "email" + } else if (query?.oneOf?._id) { + userList = await bulkGetGlobalUsersById(query?.oneOf?._id, { + cleanup: true, + }) } else { // no search, query allDocs const response = await db.allDocs(getGlobalUserParams(null, opts)) diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte index 7fa2401c88..0632993cf0 100644 --- a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte +++ b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte @@ -31,7 +31,7 @@ import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte" import CodeEditor from "components/common/CodeEditor/CodeEditor.svelte" import BindingSidePanel from "components/common/bindings/BindingSidePanel.svelte" - import { BindingHelpers } from "components/common/bindings/utils" + import { BindingHelpers, BindingType } from "components/common/bindings/utils" import { bindingsToCompletions, hbAutocomplete, @@ -576,6 +576,7 @@ { js: true, dontDecode: true, + type: BindingType.RUNTIME, } )} mode="javascript" diff --git a/packages/builder/src/components/common/HelpMenu.svelte b/packages/builder/src/components/common/HelpMenu.svelte index baff9a5a27..63156676d2 100644 --- a/packages/builder/src/components/common/HelpMenu.svelte +++ b/packages/builder/src/components/common/HelpMenu.svelte @@ -5,7 +5,7 @@ import { licensing } from "stores/portal" import { isPremiumOrAbove } from "helpers/planTitle" - $: premiumOrAboveLicense = isPremiumOrAbove($licensing?.license.plan.type) + $: premiumOrAboveLicense = isPremiumOrAbove($licensing?.license?.plan?.type) let show let hide diff --git a/packages/builder/src/components/common/bindings/utils.js b/packages/builder/src/components/common/bindings/utils.js index c60374f0f7..77e4a1dfb1 100644 --- a/packages/builder/src/components/common/bindings/utils.js +++ b/packages/builder/src/components/common/bindings/utils.js @@ -1,6 +1,11 @@ import { decodeJSBinding } from "@budibase/string-templates" import { hbInsert, jsInsert } from "components/common/CodeEditor" +export const BindingType = { + READABLE: "readableBinding", + RUNTIME: "runtimeBinding", +} + export class BindingHelpers { constructor(getCaretPosition, insertAtPos, { disableWrapping } = {}) { this.getCaretPosition = getCaretPosition @@ -25,16 +30,20 @@ export class BindingHelpers { } // Adds a data binding to the expression - onSelectBinding(value, binding, { js, dontDecode }) { + onSelectBinding( + value, + binding, + { js, dontDecode, type = BindingType.READABLE } + ) { const { start, end } = this.getCaretPosition() if (js) { const jsVal = dontDecode ? value : decodeJSBinding(value) - const insertVal = jsInsert(jsVal, start, end, binding.readableBinding, { + const insertVal = jsInsert(jsVal, start, end, binding[type], { disableWrapping: this.disableWrapping, }) this.insertAtPos({ start, end, value: insertVal }) } else { - const insertVal = hbInsert(value, start, end, binding.readableBinding) + const insertVal = hbInsert(value, start, end, binding[type]) this.insertAtPos({ start, end, value: insertVal }) } } diff --git a/packages/builder/src/stores/portal/apps.js b/packages/builder/src/stores/portal/apps.js index 84c4348075..6af9fa56ac 100644 --- a/packages/builder/src/stores/portal/apps.js +++ b/packages/builder/src/stores/portal/apps.js @@ -148,7 +148,7 @@ export const enrichedApps = derived([appsStore, auth], ([$store, $auth]) => { deployed: app.status === AppStatus.DEPLOYED, lockedYou: app.lockedBy && app.lockedBy.email === $auth.user?.email, lockedOther: app.lockedBy && app.lockedBy.email !== $auth.user?.email, - favourite: $auth?.user.appFavourites?.includes(app.appId), + favourite: $auth.user?.appFavourites?.includes(app.appId), })) : [] diff --git a/packages/worker/src/api/controllers/global/users.ts b/packages/worker/src/api/controllers/global/users.ts index 93f35b4c37..0c1342fa08 100644 --- a/packages/worker/src/api/controllers/global/users.ts +++ b/packages/worker/src/api/controllers/global/users.ts @@ -229,7 +229,7 @@ export const search = async (ctx: Ctx) => { } // Validate we aren't trying to search on any illegal fields if (!userSdk.core.isSupportedUserSearch(body.query)) { - ctx.throw(400, "Can only search by string.email or equal._id") + ctx.throw(400, "Can only search by string.email, equal._id or oneOf._id") } } diff --git a/packages/worker/src/api/routes/global/tests/users.spec.ts b/packages/worker/src/api/routes/global/tests/users.spec.ts index 2198757be1..fe6089a621 100644 --- a/packages/worker/src/api/routes/global/tests/users.spec.ts +++ b/packages/worker/src/api/routes/global/tests/users.spec.ts @@ -649,6 +649,24 @@ describe("/api/global/users", () => { expect(response.body.data[0]._id).toBe(user._id) }) + it("should be able to search by oneOf _id", async () => { + const [user, user2, user3] = await Promise.all([ + config.createUser(), + config.createUser(), + config.createUser(), + ]) + const response = await config.api.users.searchUsers({ + query: { oneOf: { _id: [user._id, user2._id] } }, + }) + expect(response.body.data.length).toBe(2) + const foundUserIds = response.body.data.map((user: User) => user._id) + expect(foundUserIds).toContain(user._id) + expect(foundUserIds).toContain(user2._id) + expect( + response.body.data.find((user: User) => user._id === user3._id) + ).toBeUndefined() + }) + it("should be able to search by _id with numeric prefixing", async () => { const user = await config.createUser() const response = await config.api.users.searchUsers({