From 8a77aca54021e0012040652855fd342e194cb012 Mon Sep 17 00:00:00 2001 From: Peter Clement Date: Fri, 29 Jul 2022 13:10:00 +0100 Subject: [PATCH 1/8] more efficient fetching of total users per app --- .../overview/_components/AssignmentModal.svelte | 5 ++--- packages/builder/src/stores/portal/users.js | 6 ++++++ packages/frontend-core/src/api/user.js | 11 +++++++++++ packages/worker/src/api/controllers/global/users.ts | 12 +++++++++++- packages/worker/src/api/routes/global/users.js | 1 + packages/worker/src/sdk/users/users.ts | 13 +++++++++++-- 6 files changed, 42 insertions(+), 6 deletions(-) diff --git a/packages/builder/src/pages/builder/portal/overview/_components/AssignmentModal.svelte b/packages/builder/src/pages/builder/portal/overview/_components/AssignmentModal.svelte index aee7a8aa7d..48503e56e6 100644 --- a/packages/builder/src/pages/builder/portal/overview/_components/AssignmentModal.svelte +++ b/packages/builder/src/pages/builder/portal/overview/_components/AssignmentModal.svelte @@ -13,7 +13,6 @@ export let app export let addData export let appUsers = [] - let prevSearch = undefined, search = undefined let pageInfo = createPaginationStore() @@ -32,7 +31,7 @@ prevSearch = search try { pageInfo.loading() - await users.search({ page, search }) + await users.search({ page, email: search }) pageInfo.fetched($users.hasNextPage, $users.nextPage) } catch (error) { notifications.error("Error getting user list") @@ -83,10 +82,10 @@ group.name} getPrimaryOptionValue={group => group.name} getPrimaryOptionIcon={group => group.icon} diff --git a/packages/builder/src/stores/portal/users.js b/packages/builder/src/stores/portal/users.js index 94fdf806e6..490d1bc9f6 100644 --- a/packages/builder/src/stores/portal/users.js +++ b/packages/builder/src/stores/portal/users.js @@ -61,6 +61,7 @@ export function createUsersStore() { break case "admin": body.admin = { global: true } + body.builder = { global: true } break } @@ -77,6 +78,10 @@ export function createUsersStore() { update(users => users.filter(user => user._id !== id)) } + async function getUserCountByApp({ appId }) { + return await API.getUserCountByApp({ appId }) + } + async function bulkDelete(userIds) { await API.deleteUsers(userIds) } @@ -99,6 +104,7 @@ export function createUsersStore() { create, save, bulkDelete, + getUserCountByApp, delete: del, } } diff --git a/packages/frontend-core/src/api/user.js b/packages/frontend-core/src/api/user.js index b2ecafdb35..17223a80e6 100644 --- a/packages/frontend-core/src/api/user.js +++ b/packages/frontend-core/src/api/user.js @@ -172,4 +172,15 @@ export const buildUserEndpoints = API => ({ }, }) }, + + /** + * Accepts an invite to join the platform and creates a user. + * @param inviteCode the invite code sent in the email + * @param password the password for the newly created user + */ + getUserCountByApp: async ({ appId }) => { + return await API.get({ + url: `/api/global/users/count/${appId}`, + }) + }, }) diff --git a/packages/worker/src/api/controllers/global/users.ts b/packages/worker/src/api/controllers/global/users.ts index ff630efae8..17e655edb3 100644 --- a/packages/worker/src/api/controllers/global/users.ts +++ b/packages/worker/src/api/controllers/global/users.ts @@ -3,7 +3,7 @@ import { checkInviteCode } from "../../../utilities/redis" import { sendEmail } from "../../../utilities/email" import { users } from "../../../sdk" import env from "../../../environment" -import { User, CloudAccount, UserGroup } from "@budibase/types" +import { User, CloudAccount } from "@budibase/types" import { events, errors, @@ -114,6 +114,16 @@ export const adminUser = async (ctx: any) => { }) } +export const countByApp = async (ctx: any) => { + const appId = ctx.params.appId + try { + const response = await users.countUsersByApp(appId) + ctx.body = response + } catch (err: any) { + ctx.throw(err.status || 400, err) + } +} + export const destroy = async (ctx: any) => { const id = ctx.params.id diff --git a/packages/worker/src/api/routes/global/users.js b/packages/worker/src/api/routes/global/users.js index a1aa9fca7f..e62e996443 100644 --- a/packages/worker/src/api/routes/global/users.js +++ b/packages/worker/src/api/routes/global/users.js @@ -64,6 +64,7 @@ router .post("/api/global/users/search", builderOrAdmin, controller.search) .delete("/api/global/users/:id", adminOnly, controller.destroy) .post("/api/global/users/bulkDelete", adminOnly, controller.bulkDelete) + .get("/api/global/users/count/:appId", adminOnly, controller.countByApp) .get("/api/global/roles/:appId") .post( "/api/global/users/invite", diff --git a/packages/worker/src/sdk/users/users.ts b/packages/worker/src/sdk/users/users.ts index 48e4f0db02..d6c667dbfb 100644 --- a/packages/worker/src/sdk/users/users.ts +++ b/packages/worker/src/sdk/users/users.ts @@ -20,7 +20,7 @@ import { groups as groupUtils } from "@budibase/pro" const PAGE_LIMIT = 8 -export const allUsers = async (newDb?: any) => { +export const allUsers = async () => { const db = tenancy.getGlobalDB() const response = await db.allDocs( dbUtils.getGlobalUserParams(null, { @@ -30,6 +30,15 @@ export const allUsers = async (newDb?: any) => { return response.rows.map((row: any) => row.doc) } +export const countUsersByApp = async (appId: string) => { + let response: any = await usersCore.searchGlobalUsersByApp(appId, { + include_docs: true, + }) + return { + userCount: response.length, + } +} + export const paginatedUsers = async ({ page, email, @@ -56,7 +65,7 @@ export const paginatedUsers = async ({ userList = await usersCore.searchGlobalUsersByEmail(email, opts) property = "email" } else { - // no search, query allDocs + // no search, query allDcso const response = await db.allDocs(dbUtils.getGlobalUserParams(null, opts)) userList = response.rows.map((row: any) => row.doc) } From 23dc9f612875448958dc83b57b2f01782ad05af7 Mon Sep 17 00:00:00 2001 From: Peter Clement Date: Fri, 29 Jul 2022 13:13:41 +0100 Subject: [PATCH 2/8] fetching of users via new api in app assignment --- .../overview/_components/AccessTab.svelte | 32 +++++++++++++------ .../overview/_components/OverviewTab.svelte | 9 ++++-- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/packages/builder/src/pages/builder/portal/overview/_components/AccessTab.svelte b/packages/builder/src/pages/builder/portal/overview/_components/AccessTab.svelte index 65bb90194c..565dfc7aa2 100644 --- a/packages/builder/src/pages/builder/portal/overview/_components/AccessTab.svelte +++ b/packages/builder/src/pages/builder/portal/overview/_components/AccessTab.svelte @@ -29,7 +29,6 @@ let pageInfo = createPaginationStore() let fixedAppId $: page = $pageInfo.page - $: fetchUsers(page, search) $: hasGroupsLicense = $auth.user?.license.features.includes( Constants.Features.USER_GROUPS @@ -37,12 +36,6 @@ $: fixedAppId = apps.getProdAppID(app.devId) - $: appUsers = - $users.data?.filter(x => { - return Object.keys(x.roles).find(y => { - return y === fixedAppId - }) - }) || [] $: appGroups = $groups.filter(x => { return x.apps.includes(app.appId) }) @@ -130,6 +123,12 @@ pageInfo.loading() await users.search({ page, appId: fixedAppId }) pageInfo.fetched($users.hasNextPage, $users.nextPage) + appUsers = + $users.data?.filter(x => { + return Object.keys(x.roles).find(y => { + return y === fixedAppId + }) + }) || [] } catch (error) { notifications.error("Error getting user list") } @@ -137,6 +136,8 @@ onMount(async () => { try { + await fetchUsers(page, search) + await groups.actions.init() await apps.load() await roles.fetch() @@ -212,8 +213,14 @@ page={$pageInfo.pageNumber} hasPrevPage={$pageInfo.loading ? false : $pageInfo.hasPrevPage} hasNextPage={$pageInfo.loading ? false : $pageInfo.hasNextPage} - goToPrevPage={pageInfo.prevPage} - goToNextPage={pageInfo.nextPage} + goToPrevPage={async () => { + await pageInfo.prevPage() + fetchUsers(page, search) + }} + goToNextPage={async () => { + await pageInfo.nextPage() + fetchUsers(page, search) + }} /> {/if} @@ -264,4 +271,11 @@ justify-content: space-between; align-items: center; } + + .pagination { + display: flex; + flex-direction: row; + justify-content: flex-end; + margin-top: var(--spacing-xl); + } diff --git a/packages/builder/src/pages/builder/portal/overview/_components/OverviewTab.svelte b/packages/builder/src/pages/builder/portal/overview/_components/OverviewTab.svelte index 6693c285ff..3e8e15fb2c 100644 --- a/packages/builder/src/pages/builder/portal/overview/_components/OverviewTab.svelte +++ b/packages/builder/src/pages/builder/portal/overview/_components/OverviewTab.svelte @@ -11,7 +11,7 @@ export let app export let deployments export let navigateTab - + let userCount const dispatch = createEventDispatcher() const unpublishApp = () => { @@ -40,7 +40,9 @@ } onMount(async () => { - await users.search({ page: undefined, appId: "app_" + app.appId }) + let resp = await users.getUserCountByApp({ appId: "app_" + app.appId }) + userCount = resp.userCount + await users.search({ appId: "app_" + app.appId, limit: 4 }) }) @@ -155,7 +157,8 @@
- {$users?.data.length} users have access to this app + {userCount} + {userCount > 1 ? `users have` : `user has`} access to this app
{:else} From 5477ffe5755247651d513a6f6b87a1857a64394b Mon Sep 17 00:00:00 2001 From: Peter Clement Date: Fri, 29 Jul 2022 13:17:17 +0100 Subject: [PATCH 3/8] improve email validation --- .../bbui/src/Form/Core/InputDropdown.svelte | 10 ++++++ .../bbui/src/Form/Core/PickerDropdown.svelte | 35 ++++++++----------- packages/bbui/src/Form/PickerDropdown.svelte | 7 ++++ .../users/_components/AddUserModal.svelte | 9 ++++- .../_components/NameTableRenderer.svelte | 2 +- 5 files changed, 41 insertions(+), 22 deletions(-) diff --git a/packages/bbui/src/Form/Core/InputDropdown.svelte b/packages/bbui/src/Form/Core/InputDropdown.svelte index 723b8ba9b1..8865ee3ddc 100644 --- a/packages/bbui/src/Form/Core/InputDropdown.svelte +++ b/packages/bbui/src/Form/Core/InputDropdown.svelte @@ -115,6 +115,16 @@ class:is-disabled={disabled} class:is-focused={focus} > + {#if error} + + {/if} + { - return x.name === primaryFieldText - }) - */ + + const updateSearch = e => { + dispatch("search", e.detail) + } + const updateValue = newValue => { if (readonly) { return @@ -107,16 +103,6 @@ updateValue(event.target.value) } } - - const getFilteredOptions = (options, term, getLabel) => { - if (autocomplete && term) { - const lowerCaseTerm = term.toLowerCase() - return options.filter(option => { - return `${getLabel(option)}`.toLowerCase().includes(lowerCaseTerm) - }) - } - return options - }
+ {#if autocomplete} + updateSearch(event)} + {disabled} + placeholder="Search" + /> + {/if} +
    {#if placeholderOption}
  • { + searchTerm = e.detail + } {/each}
    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 af61ea2d57..a4b65c4d62 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 @@ -17,7 +17,7 @@
    {value} {:else} -
    Not Available
    +
    -
    {/if}
From 00ee5ccf31258f1a1ee88d7ad3b7e39550767df9 Mon Sep 17 00:00:00 2001 From: Peter Clement Date: Fri, 29 Jul 2022 13:21:42 +0100 Subject: [PATCH 4/8] handle undefined user name and avatar initials --- .../src/pages/builder/portal/_layout.svelte | 13 ++++-- .../portal/manage/users/[userId].svelte | 41 ++++++++++++++----- .../builder/portal/manage/users/index.svelte | 16 ++++---- .../overview/[application]/index.svelte | 2 +- 4 files changed, 49 insertions(+), 23 deletions(-) diff --git a/packages/builder/src/pages/builder/portal/_layout.svelte b/packages/builder/src/pages/builder/portal/_layout.svelte index 5da8b34700..dcebe0e2c9 100644 --- a/packages/builder/src/pages/builder/portal/_layout.svelte +++ b/packages/builder/src/pages/builder/portal/_layout.svelte @@ -45,6 +45,15 @@ }, ]) } + if (isEnabled(FEATURE_FLAGS.USER_GROUPS)) { + menu = menu.concat([ + { + title: "User Groups", + href: "/builder/portal/manage/groups", + }, + ]) + } + if (admin) { menu = menu.concat([ { @@ -52,10 +61,6 @@ href: "/builder/portal/manage/users", heading: "Manage", }, - { - title: "User Groups", - href: "/builder/portal/manage/groups", - }, { title: "Auth", href: "/builder/portal/manage/auth" }, { title: "Email", href: "/builder/portal/manage/email" }, diff --git a/packages/builder/src/pages/builder/portal/manage/users/[userId].svelte b/packages/builder/src/pages/builder/portal/manage/users/[userId].svelte index 28c5aa2593..ed30c0347d 100644 --- a/packages/builder/src/pages/builder/portal/manage/users/[userId].svelte +++ b/packages/builder/src/pages/builder/portal/manage/users/[userId].svelte @@ -41,6 +41,11 @@ let allAppList = [] let user $: fetchUser(userId) + + $: fullName = $userFetch?.data?.firstName + ? $userFetch?.data?.firstName + " " + $userFetch?.data?.lastName + : "" + $: hasGroupsLicense = $auth.user?.license.features.includes( Constants.Features.USER_GROUPS ) @@ -127,7 +132,7 @@ if (detail === "developer") { toggleFlags({ admin: { global: false }, builder: { global: true } }) } else if (detail === "admin") { - toggleFlags({ admin: { global: true }, builder: { global: false } }) + toggleFlags({ admin: { global: true }, builder: { global: true } }) } else if (detail === "appUser") { toggleFlags({ admin: { global: false }, builder: { global: false } }) } @@ -186,15 +191,25 @@
- -
- {$userFetch?.data?.firstName + - " " + - $userFetch?.data?.lastName} - {$userFetch?.data?.email} -
+ x[0]) + .join("")} + /> + + {#if fullName} +
+ {fullName} + + {$userFetch?.data?.email} +
+ {:else} +
+ {$userFetch?.data?.email} +
+ {/if}
@@ -372,4 +387,10 @@ display: flex; flex-direction: column; } + + .alignEmail { + display: flex; + align-items: center; + margin-left: var(--spacing-m); + } diff --git a/packages/builder/src/pages/builder/portal/manage/users/index.svelte b/packages/builder/src/pages/builder/portal/manage/users/index.svelte index 952acaf324..5a6c58aed1 100644 --- a/packages/builder/src/pages/builder/portal/manage/users/index.svelte +++ b/packages/builder/src/pages/builder/portal/manage/users/index.svelte @@ -72,19 +72,12 @@ name: {}, email: {}, role: { - noPropagation: true, sortable: false, }, ...(hasGroupsLicense && { userGroups: { sortable: false, displayName: "User groups" }, }), - apps: { width: "120px" }, - settings: { - sortable: false, - width: "60px", - displayName: "", - align: "Right", - }, + apps: {}, } $: userData = [] @@ -323,6 +316,13 @@