1
0
Fork 0
mirror of synced 2024-06-28 02:50:50 +12:00

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}
>
<span class="spectrum-Picker-label">
{fieldText}
</span>
<div>
{fieldText}
</div></span
>
<svg
class="spectrum-Icon spectrum-UIIcon-ChevronDown100 spectrum-Picker-menuIcon"
focusable="false"

View file

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

View file

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

View file

@ -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 @@
<Table
{schema}
data={[
{ email: "test", password: Math.random().toString(36).slice(2, 20) },
]}
data={mappedData}
allowEditColumns={false}
allowEditRows={false}
allowSelectRows={false}

View file

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

View file

@ -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()
}

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.
* @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 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) => {

View file

@ -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: {},

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) => {
return !(param && param === "false")
}

View file

@ -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)

View file

@ -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))
}