improve performance of adding users and groups

This commit is contained in:
Peter Clement 2022-07-11 15:29:39 +01:00
parent 0e3b1fc46f
commit 72a67adcf4
12 changed files with 186 additions and 81 deletions

View File

@ -144,8 +144,10 @@
on:mousedown={onClick} on:mousedown={onClick}
> >
<span class="spectrum-Picker-label"> <span class="spectrum-Picker-label">
{fieldText} <div>
</span> {fieldText}
</div></span
>
<svg <svg
class="spectrum-Icon spectrum-UIIcon-ChevronDown100 spectrum-Picker-menuIcon" class="spectrum-Icon spectrum-UIIcon-ChevronDown100 spectrum-Picker-menuIcon"
focusable="false" focusable="false"

View File

@ -12,25 +12,36 @@
import { Constants } from "@budibase/frontend-core" import { Constants } from "@budibase/frontend-core"
export let showOnboardingTypeModal export let showOnboardingTypeModal
const password = Math.random().toString(36).substring(2, 22)
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
let disabled let disabled
let userGroups = []
$: userData = [{ email: "", role: "", groups: [], error: null }] $: userData = [
{
email: "",
role: "appUser",
password,
forceResetPassword: true,
},
]
function addNewInput() { function addNewInput() {
userData = [...userData, { email: "", role: "" }] userData = [
} ...userData,
{
function setValue(e) { email: "",
userData.groups = e.detail role: "appUser",
password: Math.random().toString(36).substring(2, 22),
forceResetPassword: true,
},
]
} }
</script> </script>
<ModalContent <ModalContent
onConfirm={() => { onConfirm={() => {
showOnboardingTypeModal() showOnboardingTypeModal()
dispatch("change", userData) dispatch("change", { users: userData, groups: userGroups })
}} }}
size="M" size="M"
title="Add new user" title="Add new user"
@ -57,12 +68,12 @@
</Layout> </Layout>
<Multiselect <Multiselect
bind:value={userGroups}
placeholder="Select User Groups" placeholder="Select User Groups"
on:change={e => setValue(e)}
label="User Groups" label="User Groups"
options={$groups} options={$groups}
getOptionLabel={option => option.name} getOptionLabel={option => option.name}
getOptionValue={option => option.name} getOptionValue={option => option._id}
/> />
</ModalContent> </ModalContent>

View File

@ -2,7 +2,6 @@
import { Avatar } from "@budibase/bbui" import { Avatar } from "@budibase/bbui"
export let value export let value
console.log(value)
</script> </script>
<div class="align"> <div class="align">

View File

@ -2,6 +2,16 @@
import { Body, ModalContent, Table, Icon } from "@budibase/bbui" import { Body, ModalContent, Table, Icon } from "@budibase/bbui"
import PasswordCopyRenderer from "./PasswordCopyRenderer.svelte" import PasswordCopyRenderer from "./PasswordCopyRenderer.svelte"
export let userData
$: mappedData = userData.map(user => {
return {
email: user.email,
password: user.password,
}
})
$: console.log(mappedData)
const schema = { const schema = {
email: {}, email: {},
password: {}, password: {},
@ -33,9 +43,7 @@
<Table <Table
{schema} {schema}
data={[ data={mappedData}
{ email: "test", password: Math.random().toString(36).slice(2, 20) },
]}
allowEditColumns={false} allowEditColumns={false}
allowEditRows={false} allowEditRows={false}
allowSelectRows={false} allowSelectRows={false}

View File

@ -26,7 +26,6 @@
import OnboardingTypeModal from "./_components/OnboardingTypeModal.svelte" import OnboardingTypeModal from "./_components/OnboardingTypeModal.svelte"
import PasswordModal from "./_components/PasswordModal.svelte" import PasswordModal from "./_components/PasswordModal.svelte"
import ImportUsersModal from "./_components/ImportUsersModal.svelte" import ImportUsersModal from "./_components/ImportUsersModal.svelte"
import analytics, { Events } from "analytics"
import { createPaginationStore } from "helpers/pagination" import { createPaginationStore } from "helpers/pagination"
const schema = { const schema = {
@ -74,6 +73,7 @@
let pageInfo = createPaginationStore() let pageInfo = createPaginationStore()
let prevEmail = undefined, let prevEmail = undefined,
searchEmail = undefined searchEmail = undefined
$: page = $pageInfo.page $: page = $pageInfo.page
$: fetchUsers(page, searchEmail) $: fetchUsers(page, searchEmail)
@ -110,42 +110,30 @@
admin: true, admin: true,
}) })
notifications.success(res.message) notifications.success(res.message)
analytics.captureEvent(Events.USER.INVITE, { type: "Email onboarding" })
inviteConfirmationModal.show() inviteConfirmationModal.show()
} catch (error) { } catch (error) {
console.log(error) console.log(error)
notifications.error("Error inviting user") notifications.error("Error inviting user")
} }
} }
/*
async function createUser() { async function createUser() {
try { try {
await users.create({ await users.create(userData)
email: $email,
password,
builder,
admin,
forceResetPassword: true,
})
notifications.success("Successfully created user") notifications.success("Successfully created user")
await groups.actions.init()
passwordModal.show()
} catch (error) { } catch (error) {
console.log(error)
notifications.error("Error creating user") notifications.error("Error creating user")
} }
} }
*/
async function chooseCreationType(onboardingType) { async function chooseCreationType(onboardingType) {
if (onboardingType === "emailOnboarding") { if (onboardingType === "emailOnboarding") {
createUserFlow() createUserFlow()
} else { } else {
await users.create({ await createUser()
email: "auser5@test.com",
password: Math.random().toString(36).slice(2, 20),
builder: true,
admin: true,
forceResetPassword: true,
})
passwordModal.show()
} }
} }
@ -266,7 +254,7 @@
</Modal> </Modal>
<Modal bind:this={passwordModal}> <Modal bind:this={passwordModal}>
<PasswordModal /> <PasswordModal userData={userData.users} />
</Modal> </Modal>
<Modal bind:this={importUsersModal}> <Modal bind:this={importUsersModal}>

View File

@ -37,28 +37,35 @@ export function createUsersStore() {
}) })
} }
async function create({ async function create(data) {
email, let mappedUsers = data.users.map(user => {
password, console.log(user)
admin, const body = {
builder, email: user.email,
forceResetPassword, password: user.password,
}) { roles: {},
const body = { }
email, if (user.forceResetPassword) {
password, body.forceResetPassword = user.forceResetPassword
roles: {}, }
}
if (forceResetPassword) { switch (user.role) {
body.forceResetPassword = forceResetPassword case "appUser":
} body.builder = { global: false }
if (builder) { body.admin = { global: false }
body.builder = { global: true } break
} case "developer":
if (admin) { body.builder = { global: true }
body.admin = { global: true } break
} case "admin":
await API.saveUser(body) body.admin = { global: true }
break
}
return body
})
await API.saveUsers({ users: mappedUsers, groups: data.groups })
// re-search from first page // re-search from first page
await search() await search()
} }

View File

@ -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. * Deletes a user from the curernt tenant.
* @param userId the ID of the user to delete * @param userId the ID of the user to delete

View File

@ -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 db = getAppDB()
const query = await db.get(ctx.params.queryId) const query = await db.get(ctx.params.queryId)
const datasource = await db.get(query.datasourceId) 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 || {} const enrichedParameters = ctx.request.body.parameters || {}
// make sure parameters are fully enriched with defaults // 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) { export async function executeV1(ctx: any) {
return execute(ctx, { rowsOnly: true }) return execute(ctx, { rowsOnly: true, isAutomation: false })
} }
export async function executeV2(ctx: any) { export async function executeV2(ctx: any, isAutomation?: any) {
return execute(ctx, { rowsOnly: false }) return execute(ctx, { rowsOnly: false, isAutomation: isAutomation })
} }
const removeDynamicVariables = async (queryId: any) => { const removeDynamicVariables = async (queryId: any) => {

View File

@ -72,14 +72,18 @@ exports.run = async function ({ inputs, appId, emitter }) {
}) })
try { try {
await queryController.executeV2(ctx) await queryController.executeV2(ctx, true)
const { data, ...rest } = ctx.body const { data, ...rest } = ctx.body
console.log(data)
return { return {
response: data, response: data,
info: rest, info: rest,
success: true, success: true,
} }
} catch (err) { } catch (err) {
console.log(err)
return { return {
success: false, success: false,
info: {}, info: {},

View File

@ -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) => { const parseBooleanParam = (param: any) => {
return !(param && param === "false") return !(param && param === "false")
} }

View File

@ -53,6 +53,13 @@ router
users.buildUserSaveValidation(), users.buildUserSaveValidation(),
controller.save controller.save
) )
.post(
"/api/global/users/bulkSave",
adminOnly,
users.buildUserBulkSaveValidation(),
controller.bulkSave
)
.get("/api/global/users", builderOrAdmin, controller.fetch) .get("/api/global/users", builderOrAdmin, controller.fetch)
.post("/api/global/users/search", builderOrAdmin, controller.search) .post("/api/global/users/search", builderOrAdmin, controller.search)
.delete("/api/global/users/:id", adminOnly, controller.destroy) .delete("/api/global/users/:id", adminOnly, controller.destroy)

View File

@ -1,22 +1,23 @@
import joiValidator from "../../../middleware/joi-validator" import joiValidator from "../../../middleware/joi-validator"
import Joi from "joi" 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) => { 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) { if (!isSelf) {
schema = { schema = {
...schema, ...schema,
@ -26,3 +27,19 @@ export const buildUserSaveValidation = (isSelf = false) => {
} }
return joiValidator.body(Joi.object(schema).required().unknown(true)) 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))
}