diff --git a/lerna.json b/lerna.json index a57875986a..cf21e415f2 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.10.9-alpha.3", + "version": "2.10.9-alpha.5", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/backend-core/src/cache/tests/user.spec.ts b/packages/backend-core/src/cache/tests/user.spec.ts index 45537694c7..80e5bc3063 100644 --- a/packages/backend-core/src/cache/tests/user.spec.ts +++ b/packages/backend-core/src/cache/tests/user.spec.ts @@ -1,24 +1,15 @@ import { User } from "@budibase/types" -import { cache, tenancy } from "../.." import { generator, structures } from "../../../tests" import { DBTestConfiguration } from "../../../tests/extra" import { getUsers } from "../user" -import { getGlobalDB, getGlobalDBName } from "../../context" +import { getGlobalDB } from "../../context" import _ from "lodash" -import { getDB } from "../../db" -import type * as TenancyType from "../../tenancy" + import * as redis from "../../redis/init" +import { UserDB } from "../../users" const config = new DBTestConfiguration() -// This mock is required to ensure that getTenantDB returns always as a singleton. -// This will allow us to spy on the db -const staticDb = getDB(getGlobalDBName(config.tenantId)) -jest.mock("../../tenancy", (): typeof TenancyType => ({ - ...jest.requireActual("../../tenancy"), - getTenantDB: jest.fn().mockImplementation(() => staticDb), -})) - describe("user cache", () => { describe("getUsers", () => { const users: User[] = [] @@ -51,27 +42,21 @@ describe("user cache", () => { const userIdsToRequest = usersToRequest.map(x => x._id!) - jest.spyOn(staticDb, "allDocs") + jest.spyOn(UserDB, "bulkGet") - const results = await getUsers(userIdsToRequest, config.tenantId) + const results = await config.doInTenant(() => getUsers(userIdsToRequest)) - expect(results).toHaveLength(5) - expect(results).toEqual( - usersToRequest.map(u => ({ + expect(results.users).toHaveLength(5) + expect(results).toEqual({ + users: usersToRequest.map(u => ({ ...u, budibaseAccess: true, _rev: expect.any(String), - })) - ) - - expect(tenancy.getTenantDB).toBeCalledTimes(1) - expect(tenancy.getTenantDB).toBeCalledWith(config.tenantId) - expect(staticDb.allDocs).toBeCalledTimes(1) - expect(staticDb.allDocs).toBeCalledWith({ - keys: userIdsToRequest, - include_docs: true, - limit: 5, + })), }) + + expect(UserDB.bulkGet).toBeCalledTimes(1) + expect(UserDB.bulkGet).toBeCalledWith(userIdsToRequest) }) it("on a second all, all of them are retrieved from cache", async () => { @@ -79,23 +64,25 @@ describe("user cache", () => { const userIdsToRequest = usersToRequest.map(x => x._id!) - jest.spyOn(staticDb, "allDocs") + jest.spyOn(UserDB, "bulkGet") - await getUsers(userIdsToRequest, config.tenantId) - const resultsFromCache = await getUsers(userIdsToRequest, config.tenantId) + await config.doInTenant(() => getUsers(userIdsToRequest)) + const resultsFromCache = await config.doInTenant(() => + getUsers(userIdsToRequest) + ) - expect(resultsFromCache).toHaveLength(5) - expect(resultsFromCache).toEqual( - expect.arrayContaining( + expect(resultsFromCache.users).toHaveLength(5) + expect(resultsFromCache).toEqual({ + users: expect.arrayContaining( usersToRequest.map(u => ({ ...u, budibaseAccess: true, _rev: expect.any(String), })) - ) - ) + ), + }) - expect(staticDb.allDocs).toBeCalledTimes(1) + expect(UserDB.bulkGet).toBeCalledTimes(1) }) it("when some users are cached, only the missing ones are retrieved from db", async () => { @@ -103,32 +90,55 @@ describe("user cache", () => { const userIdsToRequest = usersToRequest.map(x => x._id!) - jest.spyOn(staticDb, "allDocs") + jest.spyOn(UserDB, "bulkGet") - await getUsers( - [userIdsToRequest[0], userIdsToRequest[3]], - config.tenantId + await config.doInTenant(() => + getUsers([userIdsToRequest[0], userIdsToRequest[3]]) ) - ;(staticDb.allDocs as jest.Mock).mockClear() + ;(UserDB.bulkGet as jest.Mock).mockClear() - const results = await getUsers(userIdsToRequest, config.tenantId) + const results = await config.doInTenant(() => getUsers(userIdsToRequest)) - expect(results).toHaveLength(5) - expect(results).toEqual( - expect.arrayContaining( + expect(results.users).toHaveLength(5) + expect(results).toEqual({ + users: expect.arrayContaining( usersToRequest.map(u => ({ ...u, budibaseAccess: true, _rev: expect.any(String), })) - ) - ) + ), + }) - expect(staticDb.allDocs).toBeCalledTimes(1) - expect(staticDb.allDocs).toBeCalledWith({ - keys: [userIdsToRequest[1], userIdsToRequest[2], userIdsToRequest[4]], - include_docs: true, - limit: 3, + expect(UserDB.bulkGet).toBeCalledTimes(1) + expect(UserDB.bulkGet).toBeCalledWith([ + userIdsToRequest[1], + userIdsToRequest[2], + userIdsToRequest[4], + ]) + }) + + it("requesting existing and unexisting ids will return found ones", async () => { + const usersToRequest = _.sampleSize(users, 3) + const missingIds = [generator.guid(), generator.guid()] + + const userIdsToRequest = _.shuffle([ + ...missingIds, + ...usersToRequest.map(x => x._id!), + ]) + + const results = await config.doInTenant(() => getUsers(userIdsToRequest)) + + expect(results.users).toHaveLength(3) + expect(results).toEqual({ + users: expect.arrayContaining( + usersToRequest.map(u => ({ + ...u, + budibaseAccess: true, + _rev: expect.any(String), + })) + ), + notFoundIds: expect.arrayContaining(missingIds), }) }) }) diff --git a/packages/backend-core/src/cache/user.ts b/packages/backend-core/src/cache/user.ts index 9742b41b65..b3fd7c08cd 100644 --- a/packages/backend-core/src/cache/user.ts +++ b/packages/backend-core/src/cache/user.ts @@ -6,6 +6,7 @@ import env from "../environment" import * as accounts from "../accounts" import { UserDB } from "../users" import { sdk } from "@budibase/shared-core" +import { User } from "@budibase/types" const EXPIRY_SECONDS = 3600 @@ -27,17 +28,18 @@ async function populateFromDB(userId: string, tenantId: string) { return user } -async function populateUsersFromDB(userIds: string[], tenantId: string) { - const db = tenancy.getTenantDB(tenantId) - const allDocsResponse = await db.allDocs({ - keys: userIds, - include_docs: true, - limit: userIds.length, - }) +async function populateUsersFromDB( + userIds: string[] +): Promise<{ users: User[]; notFoundIds?: string[] }> { + const getUsersResponse = await UserDB.bulkGet(userIds) + + // Handle missed user ids + const notFoundIds = userIds.filter((uid, i) => !getUsersResponse[i]) + + const users = getUsersResponse.filter(x => x) - const users = allDocsResponse.rows.map(r => r.doc) await Promise.all( - users.map(async user => { + users.map(async (user: any) => { user.budibaseAccess = true if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) { const account = await accounts.getAccount(user.email) @@ -49,7 +51,10 @@ async function populateUsersFromDB(userIds: string[], tenantId: string) { }) ) - return users + if (notFoundIds.length) { + return { users, notFoundIds } + } + return { users } } /** @@ -110,24 +115,26 @@ export async function getUser( * @param {*} tenantId the tenant of the users to get * @returns */ -export async function getUsers(userIds: string[], tenantId: string) { +export async function getUsers( + userIds: string[] +): Promise<{ users: User[]; notFoundIds?: string[] }> { const client = await redis.getUserClient() // try cache let usersFromCache = await client.bulkGet(userIds) const missingUsersFromCache = userIds.filter(uid => !usersFromCache[uid]) const users = Object.values(usersFromCache) + let notFoundIds if (missingUsersFromCache.length) { - const usersFromDb = await populateUsersFromDB( - missingUsersFromCache, - tenantId - ) - for (const userToCache of usersFromDb) { - await client.store(userToCache._id, userToCache, EXPIRY_SECONDS) + const usersFromDb = await populateUsersFromDB(missingUsersFromCache) + + notFoundIds = usersFromDb.notFoundIds + for (const userToCache of usersFromDb.users) { + await client.store(userToCache._id!, userToCache, EXPIRY_SECONDS) } - users.push(...usersFromDb) + users.push(...usersFromDb.users) } - return users + return { users, notFoundIds: notFoundIds } } export async function invalidateUser(userId: string) { diff --git a/packages/bbui/src/Tooltip/AbsTooltip.svelte b/packages/bbui/src/Tooltip/AbsTooltip.svelte index 9be7251445..92d5af26bb 100644 --- a/packages/bbui/src/Tooltip/AbsTooltip.svelte +++ b/packages/bbui/src/Tooltip/AbsTooltip.svelte @@ -126,8 +126,9 @@ transition: top 130ms ease-out, left 130ms ease-out; } .spectrum-Tooltip-label { - text-overflow: ellipsis; - white-space: nowrap; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; overflow: hidden; font-size: 12px; font-weight: 600; diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte index a2d7825fb5..0384e7dbea 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte @@ -564,7 +564,7 @@ {#if datasource?.source !== "ORACLE" && datasource?.source !== "SQL_SERVER"}
-
+