Merge branch 'develop' into feature/user-column-type
This commit is contained in:
commit
6879c65a5b
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "2.10.9-alpha.3",
|
||||
"version": "2.10.9-alpha.5",
|
||||
"npmClient": "yarn",
|
||||
"packages": [
|
||||
"packages/*"
|
||||
|
|
|
@ -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),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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<any>({
|
||||
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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -564,7 +564,7 @@
|
|||
</div>
|
||||
{#if datasource?.source !== "ORACLE" && datasource?.source !== "SQL_SERVER"}
|
||||
<div>
|
||||
<div>
|
||||
<div class="row">
|
||||
<Label>Time zones</Label>
|
||||
<AbsTooltip
|
||||
position="top"
|
||||
|
@ -763,18 +763,19 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tooltip-alignment {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.label-length {
|
||||
flex-basis: 40%;
|
||||
}
|
||||
|
||||
.input-length {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.row {
|
||||
gap: 8px;
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
||||
|
|
Loading…
Reference in New Issue