diff --git a/packages/bbui/src/Badge/Badge.svelte b/packages/bbui/src/Badge/Badge.svelte index e4ec7d4f33..dadbaa3317 100644 --- a/packages/bbui/src/Badge/Badge.svelte +++ b/packages/bbui/src/Badge/Badge.svelte @@ -11,6 +11,7 @@ export let active = false export let inactive = false export let hoverable = false + export let outlineColor = null @@ -29,6 +30,7 @@ class:spectrum-Label--seafoam={seafoam} class:spectrum-Label--active={active} class:spectrum-Label--inactive={inactive} + style={outlineColor ? `border: 2px solid ${outlineColor}` : ""} > diff --git a/packages/bbui/src/Icon/Icon.svelte b/packages/bbui/src/Icon/Icon.svelte index 2f12935f53..46b3140be8 100644 --- a/packages/bbui/src/Icon/Icon.svelte +++ b/packages/bbui/src/Icon/Icon.svelte @@ -28,6 +28,9 @@ + import { Icon } from "@budibase/bbui" + import { helpers } from "@budibase/shared-core" + + export let groups = [] + function tooltip(groups) { + const sortedNames = groups + .sort((a, b) => a.name.localeCompare(b.name)) + .map(group => group.name) + return `Member of ${helpers.lists.punctuateList(sortedNames)}` + } + + +
+ +
+ + diff --git a/packages/builder/src/pages/builder/app/[application]/_components/BuilderSidePanel.svelte b/packages/builder/src/pages/builder/app/[application]/_components/BuilderSidePanel.svelte index 2260892913..737edd69f7 100644 --- a/packages/builder/src/pages/builder/app/[application]/_components/BuilderSidePanel.svelte +++ b/packages/builder/src/pages/builder/app/[application]/_components/BuilderSidePanel.svelte @@ -37,6 +37,7 @@ import { emailValidator } from "@/helpers/validation" import { fly } from "svelte/transition" import InfoDisplay from "../design/[screenId]/[componentId]/_components/Component/InfoDisplay.svelte" + import BuilderGroupPopover from "./BuilderGroupPopover.svelte" let query = null let loaded = false @@ -197,12 +198,19 @@ return } const update = await users.get(user._id) + const newRoles = { + ...update.roles, + [prodAppId]: role, + } + // make sure no undefined/null roles (during removal) + for (let [appId, role] of Object.entries(newRoles)) { + if (!role) { + delete newRoles[appId] + } + } await users.save({ ...update, - roles: { - ...update.roles, - [prodAppId]: role, - }, + roles: newRoles, }) await searchUsers(query, $builderStore.builderSidePanel, loaded) } @@ -539,6 +547,10 @@ creationAccessType = Constants.Roles.CREATOR } } + + const itemCountText = (word, count) => { + return `${count} ${word}${count !== 1 ? "s" : ""}` + } @@ -701,13 +713,11 @@ >
-
+
{group.name}
- {`${group.users?.length} user${ - group.users?.length != 1 ? "s" : "" - }`} + {itemCountText("user", group.users?.length)}
@@ -741,16 +751,33 @@
Access
{#each allUsers as user} + {@const userGroups = sdk.users.getUserAppGroups( + $appStore.appId, + user, + $groups + )}
-
- {user.email} +
+
+ {user.email} +
+ {#if userGroups.length} +
+
+ {itemCountText("group", userGroups.length)} +
+ +
+ {/if}
diff --git a/packages/builder/src/pages/builder/portal/users/users/[userId].svelte b/packages/builder/src/pages/builder/portal/users/users/[userId].svelte index 6c480d9ef8..f02c2fe058 100644 --- a/packages/builder/src/pages/builder/portal/users/users/[userId].svelte +++ b/packages/builder/src/pages/builder/portal/users/users/[userId].svelte @@ -98,7 +98,9 @@ $: privileged = sdk.users.isAdminOrGlobalBuilder(user) $: nameLabel = getNameLabel(user) $: filteredGroups = getFilteredGroups(internalGroups, searchTerm) - $: availableApps = getAvailableApps($appsStore.apps, privileged, user?.roles) + $: availableApps = user + ? getApps(user, sdk.users.userAppAccessList(user, $groups || [])) + : [] $: userGroups = $groups.filter(x => { return x.users?.find(y => { return y._id === userId @@ -107,23 +109,19 @@ $: globalRole = users.getUserRole(user) $: isTenantOwner = tenantOwner?.email && tenantOwner.email === user?.email - const getAvailableApps = (appList, privileged, roles) => { - let availableApps = appList.slice() - if (!privileged) { - availableApps = availableApps.filter(x => { - let roleKeys = Object.keys(roles || {}) - return roleKeys.concat(user?.builder?.apps).find(y => { - return x.appId === appsStore.extractAppId(y) - }) - }) - } + const getApps = (user, appIds) => { + let availableApps = $appsStore.apps + .slice() + .filter(app => + appIds.find(id => id === appsStore.getProdAppID(app.devId)) + ) return availableApps.map(app => { const prodAppId = appsStore.getProdAppID(app.devId) return { name: app.name, devId: app.devId, icon: app.icon, - role: getRole(prodAppId, roles), + role: getRole(prodAppId, user), } }) } @@ -136,7 +134,7 @@ return groups.filter(group => group.name?.toLowerCase().includes(search)) } - const getRole = (prodAppId, roles) => { + const getRole = (prodAppId, user) => { if (privileged) { return Constants.Roles.ADMIN } @@ -145,7 +143,21 @@ return Constants.Roles.CREATOR } - return roles[prodAppId] + if (user?.roles[prodAppId]) { + return user.roles[prodAppId] + } + + // check if access via group for creator + const foundGroup = $groups?.find( + group => group.roles[prodAppId] || group.builder?.apps[prodAppId] + ) + if (foundGroup.builder?.apps[prodAppId]) { + return Constants.Roles.CREATOR + } + // can't tell how groups will control role + if (foundGroup.roles[prodAppId]) { + return Constants.Roles.GROUP + } } const getNameLabel = user => { diff --git a/packages/builder/src/pages/builder/portal/users/users/_components/AppRoleTableRenderer.svelte b/packages/builder/src/pages/builder/portal/users/users/_components/AppRoleTableRenderer.svelte index 9429cfbc23..5adc38ebc6 100644 --- a/packages/builder/src/pages/builder/portal/users/users/_components/AppRoleTableRenderer.svelte +++ b/packages/builder/src/pages/builder/portal/users/users/_components/AppRoleTableRenderer.svelte @@ -15,7 +15,9 @@ } -{#if value === Constants.Roles.CREATOR} +{#if value === Constants.Roles.GROUP} + Controlled by group +{:else if value === Constants.Roles.CREATOR} Can edit {:else} group.users?.find(u => u._id === user._id)) || [] + ) +} + +export function getUserAppGroups( + appId: string, + user: User, + groups?: UserGroup[] +) { + const prodAppId = getProdAppID(appId) + const userGroups = getUserGroups(user, groups) + return userGroups.filter(group => + Object.keys(group.roles || {}).find(app => app === prodAppId) + ) +} + +export function userAppAccessList(user: User, groups?: UserGroup[]) { + const userGroups = getUserGroups(user, groups) + const userGroupApps = userGroups.flatMap(userGroup => + Object.keys(userGroup.roles || {}) + ) + const fullList = [...Object.keys(user.roles), ...userGroupApps] + return [...new Set(fullList)] +}