From 72a67adcf442dca9de35ef9ea36b721e2fc66f52 Mon Sep 17 00:00:00 2001 From: Peter Clement Date: Mon, 11 Jul 2022 15:29:39 +0100 Subject: [PATCH] improve performance of adding users and groups --- .../bbui/src/Form/Core/InputDropdown.svelte | 6 ++- .../users/_components/AddUserModal.svelte | 33 ++++++++---- .../_components/NameTableRenderer.svelte | 1 - .../users/_components/PasswordModal.svelte | 14 +++-- .../builder/portal/manage/users/index.svelte | 30 ++++------- packages/builder/src/stores/portal/users.js | 51 +++++++++++-------- packages/frontend-core/src/api/user.js | 14 +++++ .../server/src/api/controllers/query/index.ts | 16 ++++-- .../src/automations/steps/executeQuery.js | 6 ++- .../src/api/controllers/global/users.ts | 42 +++++++++++++++ .../worker/src/api/routes/global/users.js | 7 +++ .../worker/src/api/routes/validation/users.ts | 47 +++++++++++------ 12 files changed, 186 insertions(+), 81 deletions(-) diff --git a/packages/bbui/src/Form/Core/InputDropdown.svelte b/packages/bbui/src/Form/Core/InputDropdown.svelte index da38942809..723b8ba9b1 100644 --- a/packages/bbui/src/Form/Core/InputDropdown.svelte +++ b/packages/bbui/src/Form/Core/InputDropdown.svelte @@ -144,8 +144,10 @@ on:mousedown={onClick} > - {fieldText} - +
+ {fieldText} +
{ showOnboardingTypeModal() - dispatch("change", userData) + dispatch("change", { users: userData, groups: userGroups }) }} size="M" title="Add new user" @@ -57,12 +68,12 @@ setValue(e)} label="User Groups" options={$groups} getOptionLabel={option => option.name} - getOptionValue={option => option.name} + getOptionValue={option => option._id} /> diff --git a/packages/builder/src/pages/builder/portal/manage/users/_components/NameTableRenderer.svelte b/packages/builder/src/pages/builder/portal/manage/users/_components/NameTableRenderer.svelte index 70c4cd1e82..255e63a611 100644 --- a/packages/builder/src/pages/builder/portal/manage/users/_components/NameTableRenderer.svelte +++ b/packages/builder/src/pages/builder/portal/manage/users/_components/NameTableRenderer.svelte @@ -2,7 +2,6 @@ import { Avatar } from "@budibase/bbui" export let value - console.log(value)
diff --git a/packages/builder/src/pages/builder/portal/manage/users/_components/PasswordModal.svelte b/packages/builder/src/pages/builder/portal/manage/users/_components/PasswordModal.svelte index a5581924b6..df6238b9f7 100644 --- a/packages/builder/src/pages/builder/portal/manage/users/_components/PasswordModal.svelte +++ b/packages/builder/src/pages/builder/portal/manage/users/_components/PasswordModal.svelte @@ -2,6 +2,16 @@ import { Body, ModalContent, Table, Icon } from "@budibase/bbui" import PasswordCopyRenderer from "./PasswordCopyRenderer.svelte" + export let userData + + $: mappedData = userData.map(user => { + return { + email: user.email, + password: user.password, + } + }) + + $: console.log(mappedData) const schema = { email: {}, password: {}, @@ -33,9 +43,7 @@ - + diff --git a/packages/builder/src/stores/portal/users.js b/packages/builder/src/stores/portal/users.js index f628a451c5..60738f94c8 100644 --- a/packages/builder/src/stores/portal/users.js +++ b/packages/builder/src/stores/portal/users.js @@ -37,28 +37,35 @@ export function createUsersStore() { }) } - async function create({ - email, - password, - admin, - builder, - forceResetPassword, - }) { - const body = { - email, - password, - roles: {}, - } - if (forceResetPassword) { - body.forceResetPassword = forceResetPassword - } - if (builder) { - body.builder = { global: true } - } - if (admin) { - body.admin = { global: true } - } - await API.saveUser(body) + async function create(data) { + let mappedUsers = data.users.map(user => { + console.log(user) + const body = { + email: user.email, + password: user.password, + roles: {}, + } + if (user.forceResetPassword) { + body.forceResetPassword = user.forceResetPassword + } + + switch (user.role) { + case "appUser": + body.builder = { global: false } + body.admin = { global: false } + break + case "developer": + body.builder = { global: true } + break + case "admin": + body.admin = { global: true } + break + } + + return body + }) + await API.saveUsers({ users: mappedUsers, groups: data.groups }) + // re-search from first page await search() } diff --git a/packages/frontend-core/src/api/user.js b/packages/frontend-core/src/api/user.js index a78697aaa6..b00ddfdf51 100644 --- a/packages/frontend-core/src/api/user.js +++ b/packages/frontend-core/src/api/user.js @@ -83,6 +83,20 @@ export const buildUserEndpoints = API => ({ }) }, + /** + * Creates multiple users. + * @param users the array of user objects to create + */ + saveUsers: async ({ users, groups }) => { + return await API.post({ + url: "/api/global/users/bulkSave", + body: { + users, + groups, + }, + }) + }, + /** * Deletes a user from the curernt tenant. * @param userId the ID of the user to delete diff --git a/packages/server/src/api/controllers/query/index.ts b/packages/server/src/api/controllers/query/index.ts index ce6eeda7c7..bfc3039ac8 100644 --- a/packages/server/src/api/controllers/query/index.ts +++ b/packages/server/src/api/controllers/query/index.ts @@ -166,13 +166,19 @@ export async function preview(ctx: any) { } } -async function execute(ctx: any, opts = { rowsOnly: false }) { +async function execute( + ctx: any, + opts = { rowsOnly: false, isAutomation: false } +) { const db = getAppDB() const query = await db.get(ctx.params.queryId) const datasource = await db.get(query.datasourceId) - const authConfigCtx: any = getAuthConfig(ctx) + let authConfigCtx: any = {} + if (!opts.isAutomation) { + authConfigCtx = getAuthConfig(ctx) + } const enrichedParameters = ctx.request.body.parameters || {} // make sure parameters are fully enriched with defaults @@ -214,11 +220,11 @@ async function execute(ctx: any, opts = { rowsOnly: false }) { } export async function executeV1(ctx: any) { - return execute(ctx, { rowsOnly: true }) + return execute(ctx, { rowsOnly: true, isAutomation: false }) } -export async function executeV2(ctx: any) { - return execute(ctx, { rowsOnly: false }) +export async function executeV2(ctx: any, isAutomation?: any) { + return execute(ctx, { rowsOnly: false, isAutomation: isAutomation }) } const removeDynamicVariables = async (queryId: any) => { diff --git a/packages/server/src/automations/steps/executeQuery.js b/packages/server/src/automations/steps/executeQuery.js index 156df504e9..a0c664b7df 100644 --- a/packages/server/src/automations/steps/executeQuery.js +++ b/packages/server/src/automations/steps/executeQuery.js @@ -72,14 +72,18 @@ exports.run = async function ({ inputs, appId, emitter }) { }) try { - await queryController.executeV2(ctx) + await queryController.executeV2(ctx, true) + const { data, ...rest } = ctx.body + console.log(data) + return { response: data, info: rest, success: true, } } catch (err) { + console.log(err) return { success: false, info: {}, diff --git a/packages/worker/src/api/controllers/global/users.ts b/packages/worker/src/api/controllers/global/users.ts index d355ed9aed..82a6b2dd8e 100644 --- a/packages/worker/src/api/controllers/global/users.ts +++ b/packages/worker/src/api/controllers/global/users.ts @@ -22,6 +22,48 @@ export const save = async (ctx: any) => { } } +export const bulkSave = async (ctx: any) => { + let { users: newUsers, groups } = ctx.request.body + let usersToSave: any[] = [] + let groupsToSave: any[] = [] + const db = tenancy.getGlobalDB() + + newUsers.forEach((user: any) => { + usersToSave.push( + users.save(user, { + hashPassword: false, + requirePassword: user.requirePassword, + bulkCreate: true, + }) + ) + + if (groups.length) { + groups.forEach(async (groupId: string) => { + let oldGroup = await db.get(groupId) + groupsToSave.push(oldGroup) + }) + } + }) + try { + const allUsers = await Promise.all(usersToSave) + let response = await db.bulkDocs(allUsers) + + // delete passwords and add to group + allUsers.forEach(user => { + delete user.password + }) + + groupsToSave.forEach(async group => { + group.users = [...group.users, ...allUsers] + await db.put(group) + }) + + ctx.body = response + } catch (err: any) { + ctx.throw(err.status || 400, err) + } +} + const parseBooleanParam = (param: any) => { return !(param && param === "false") } diff --git a/packages/worker/src/api/routes/global/users.js b/packages/worker/src/api/routes/global/users.js index bac17ac01e..45d64b04e8 100644 --- a/packages/worker/src/api/routes/global/users.js +++ b/packages/worker/src/api/routes/global/users.js @@ -53,6 +53,13 @@ router users.buildUserSaveValidation(), controller.save ) + .post( + "/api/global/users/bulkSave", + adminOnly, + users.buildUserBulkSaveValidation(), + controller.bulkSave + ) + .get("/api/global/users", builderOrAdmin, controller.fetch) .post("/api/global/users/search", builderOrAdmin, controller.search) .delete("/api/global/users/:id", adminOnly, controller.destroy) diff --git a/packages/worker/src/api/routes/validation/users.ts b/packages/worker/src/api/routes/validation/users.ts index 81372358ff..e7ad4cca18 100644 --- a/packages/worker/src/api/routes/validation/users.ts +++ b/packages/worker/src/api/routes/validation/users.ts @@ -1,22 +1,23 @@ import joiValidator from "../../../middleware/joi-validator" import Joi from "joi" +let schema: any = { + email: Joi.string().allow(null, ""), + password: Joi.string().allow(null, ""), + forceResetPassword: Joi.boolean().optional(), + firstName: Joi.string().allow(null, ""), + lastName: Joi.string().allow(null, ""), + builder: Joi.object({ + global: Joi.boolean().optional(), + apps: Joi.array().optional(), + }) + .unknown(true) + .optional(), + // maps appId -> roleId for the user + roles: Joi.object().pattern(/.*/, Joi.string()).required().unknown(true), +} + export const buildUserSaveValidation = (isSelf = false) => { - let schema: any = { - email: Joi.string().allow(null, ""), - password: Joi.string().allow(null, ""), - forceResetPassword: Joi.boolean().optional(), - firstName: Joi.string().allow(null, ""), - lastName: Joi.string().allow(null, ""), - builder: Joi.object({ - global: Joi.boolean().optional(), - apps: Joi.array().optional(), - }) - .unknown(true) - .optional(), - // maps appId -> roleId for the user - roles: Joi.object().pattern(/.*/, Joi.string()).required().unknown(true), - } if (!isSelf) { schema = { ...schema, @@ -26,3 +27,19 @@ export const buildUserSaveValidation = (isSelf = false) => { } return joiValidator.body(Joi.object(schema).required().unknown(true)) } + +export const buildUserBulkSaveValidation = (isSelf = false) => { + if (!isSelf) { + schema = { + ...schema, + _id: Joi.string(), + _rev: Joi.string(), + } + } + let bulkSaveSchema = { + groups: Joi.array().optional(), + users: Joi.array().items(Joi.object(schema).required().unknown(true)), + } + + return joiValidator.body(Joi.object(bulkSaveSchema).required().unknown(true)) +}