Fix assignment modal, use userFetch for searching users, fix data mutations
This commit is contained in:
parent
e18e941449
commit
8d13928d67
2 changed files with 154 additions and 155 deletions
|
@ -16,54 +16,34 @@
|
||||||
import RoleSelect from "components/common/RoleSelect.svelte"
|
import RoleSelect from "components/common/RoleSelect.svelte"
|
||||||
import { users, groups, apps, auth } from "stores/portal"
|
import { users, groups, apps, auth } from "stores/portal"
|
||||||
import AssignmentModal from "./AssignmentModal.svelte"
|
import AssignmentModal from "./AssignmentModal.svelte"
|
||||||
import { createPaginationStore } from "helpers/pagination"
|
|
||||||
import { roles } from "stores/backend"
|
import { roles } from "stores/backend"
|
||||||
|
import { API } from "api"
|
||||||
|
import { fetchData } from "@budibase/frontend-core"
|
||||||
|
|
||||||
export let app
|
export let app
|
||||||
|
|
||||||
|
const usersFetch = fetchData({
|
||||||
|
API,
|
||||||
|
datasource: {
|
||||||
|
type: "user",
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
query: {
|
||||||
|
appId: apps.getProdAppID(app.devId),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
let assignmentModal
|
let assignmentModal
|
||||||
let appGroups = []
|
let appGroups = []
|
||||||
let appUsers = []
|
let appUsers = []
|
||||||
let prevSearch = undefined,
|
|
||||||
search = undefined
|
|
||||||
let pageInfo = createPaginationStore()
|
|
||||||
let fixedAppId
|
|
||||||
|
|
||||||
$: page = $pageInfo.page
|
|
||||||
$: fixedAppId = apps.getProdAppID(app.devId)
|
$: fixedAppId = apps.getProdAppID(app.devId)
|
||||||
|
$: appUsers = $usersFetch.rows
|
||||||
$: appGroups = $groups.filter(x => {
|
$: appGroups = $groups.filter(x => {
|
||||||
return x.apps.includes(app.appId)
|
return x.apps.includes(app.appId)
|
||||||
})
|
})
|
||||||
|
|
||||||
async function addData(appData) {
|
|
||||||
let gr_prefix = "gr"
|
|
||||||
let us_prefix = "us"
|
|
||||||
appData.forEach(async data => {
|
|
||||||
if (data.id.startsWith(gr_prefix)) {
|
|
||||||
let matchedGroup = $groups.find(group => {
|
|
||||||
return group._id === data.id
|
|
||||||
})
|
|
||||||
matchedGroup.apps.push(app.appId)
|
|
||||||
matchedGroup.roles[fixedAppId] = data.role
|
|
||||||
|
|
||||||
groups.actions.save(matchedGroup)
|
|
||||||
} else if (data.id.startsWith(us_prefix)) {
|
|
||||||
let matchedUser = $users.data.find(user => {
|
|
||||||
return user._id === data.id
|
|
||||||
})
|
|
||||||
|
|
||||||
let newUser = {
|
|
||||||
...matchedUser,
|
|
||||||
roles: { [fixedAppId]: data.role, ...matchedUser.roles },
|
|
||||||
}
|
|
||||||
|
|
||||||
await users.save(newUser, { opts: { appId: fixedAppId } })
|
|
||||||
await fetchUsers(page, search)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
await groups.actions.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
async function removeUser(user) {
|
async function removeUser(user) {
|
||||||
// Remove the user role
|
// Remove the user role
|
||||||
const filteredRoles = { ...user.roles }
|
const filteredRoles = { ...user.roles }
|
||||||
|
@ -74,65 +54,35 @@
|
||||||
...filteredRoles,
|
...filteredRoles,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
await fetchUsers(page, search)
|
await usersFetch.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function removeGroup(group) {
|
async function removeGroup(group) {
|
||||||
// Remove the user role
|
const filteredApps = group.apps.filter(
|
||||||
let filteredApps = group.apps.filter(
|
|
||||||
x => apps.extractAppId(x) !== app.appId
|
x => apps.extractAppId(x) !== app.appId
|
||||||
)
|
)
|
||||||
const filteredRoles = { ...group.roles }
|
const filteredRoles = { ...group.roles }
|
||||||
delete filteredRoles[fixedAppId]
|
delete filteredRoles[fixedAppId]
|
||||||
|
|
||||||
await groups.actions.save({
|
await groups.actions.save({
|
||||||
...group,
|
...group,
|
||||||
apps: filteredApps,
|
apps: filteredApps,
|
||||||
roles: { ...filteredRoles },
|
roles: { ...filteredRoles },
|
||||||
})
|
})
|
||||||
|
await usersFetch.refresh()
|
||||||
await fetchUsers(page, search)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateUserRole(role, user) {
|
async function updateUserRole(role, user) {
|
||||||
user.roles[fixedAppId] = role
|
user.roles[fixedAppId] = role
|
||||||
users.save(user)
|
await users.save(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateGroupRole(role, group) {
|
async function updateGroupRole(role, group) {
|
||||||
group.roles[fixedAppId] = role
|
group.roles[fixedAppId] = role
|
||||||
groups.actions.save(group)
|
await groups.actions.save(group)
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchUsers(page, search) {
|
|
||||||
if ($pageInfo.loading) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// need to remove the page if they've started searching
|
|
||||||
if (search && !prevSearch) {
|
|
||||||
pageInfo.reset()
|
|
||||||
page = undefined
|
|
||||||
}
|
|
||||||
prevSearch = search
|
|
||||||
try {
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
try {
|
try {
|
||||||
await fetchUsers(page, search)
|
|
||||||
|
|
||||||
await groups.actions.init()
|
await groups.actions.init()
|
||||||
await apps.load()
|
await apps.load()
|
||||||
await roles.fetch()
|
await roles.fetch()
|
||||||
|
@ -149,11 +99,11 @@
|
||||||
<Heading>Access</Heading>
|
<Heading>Access</Heading>
|
||||||
<div class="subtitle">
|
<div class="subtitle">
|
||||||
<Body size="S">
|
<Body size="S">
|
||||||
Assign users to your app and define their access here</Body
|
Assign users to your app and define their access here
|
||||||
>
|
</Body>
|
||||||
<Button on:click={assignmentModal.show} icon="User" cta
|
<Button on:click={assignmentModal.show} icon="User" cta>
|
||||||
>Assign users</Button
|
Assign users
|
||||||
>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#if $auth.groupsEnabled && appGroups.length}
|
{#if $auth.groupsEnabled && appGroups.length}
|
||||||
|
@ -184,41 +134,37 @@
|
||||||
</List>
|
</List>
|
||||||
{/if}
|
{/if}
|
||||||
{#if appUsers.length}
|
{#if appUsers.length}
|
||||||
<List title="Users">
|
<div>
|
||||||
{#each appUsers as user}
|
<List title="Users">
|
||||||
<ListItem title={user.email} avatar>
|
{#each appUsers as user}
|
||||||
<RoleSelect
|
<ListItem title={user.email} avatar>
|
||||||
on:change={e => updateUserRole(e.detail, user)}
|
<RoleSelect
|
||||||
autoWidth
|
on:change={e => updateUserRole(e.detail, user)}
|
||||||
quiet
|
autoWidth
|
||||||
value={user.roles[
|
quiet
|
||||||
Object.keys(user.roles).find(x => x === fixedAppId)
|
value={user.roles[
|
||||||
]}
|
Object.keys(user.roles).find(x => x === fixedAppId)
|
||||||
allowPublic={false}
|
]}
|
||||||
/>
|
allowPublic={false}
|
||||||
<Icon
|
/>
|
||||||
on:click={() => removeUser(user)}
|
<Icon
|
||||||
hoverable
|
on:click={() => removeUser(user)}
|
||||||
size="S"
|
hoverable
|
||||||
name="Close"
|
size="S"
|
||||||
/>
|
name="Close"
|
||||||
</ListItem>
|
/>
|
||||||
{/each}
|
</ListItem>
|
||||||
</List>
|
{/each}
|
||||||
<div class="pagination">
|
</List>
|
||||||
<Pagination
|
<div class="pagination">
|
||||||
page={$pageInfo.pageNumber}
|
<Pagination
|
||||||
hasPrevPage={$pageInfo.loading ? false : $pageInfo.hasPrevPage}
|
page={$usersFetch.pageNumber + 1}
|
||||||
hasNextPage={$pageInfo.loading ? false : $pageInfo.hasNextPage}
|
hasPrevPage={$usersFetch.hasPrevPage}
|
||||||
goToPrevPage={async () => {
|
hasNextPage={$usersFetch.hasNextPage}
|
||||||
await pageInfo.prevPage()
|
goToPrevPage={$usersFetch.loading ? null : fetch.prevPage}
|
||||||
fetchUsers(page, search)
|
goToNextPage={$usersFetch.loading ? null : fetch.nextPage}
|
||||||
}}
|
/>
|
||||||
goToNextPage={async () => {
|
</div>
|
||||||
await pageInfo.nextPage()
|
|
||||||
fetchUsers(page, search)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
|
@ -246,7 +192,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Modal bind:this={assignmentModal}>
|
<Modal bind:this={assignmentModal}>
|
||||||
<AssignmentModal {app} {appUsers} {addData} />
|
<AssignmentModal {app} {appUsers} on:update={usersFetch.refresh} />
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -5,37 +5,46 @@
|
||||||
ActionButton,
|
ActionButton,
|
||||||
Layout,
|
Layout,
|
||||||
Icon,
|
Icon,
|
||||||
notifications,
|
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { roles } from "stores/backend"
|
import { roles } from "stores/backend"
|
||||||
import { groups, users, auth } from "stores/portal"
|
import { groups, users, auth, apps } from "stores/portal"
|
||||||
import { Constants, RoleUtils } from "@budibase/frontend-core"
|
import { Constants, RoleUtils, fetchData } from "@budibase/frontend-core"
|
||||||
import { createPaginationStore } from "helpers/pagination"
|
import { API } from "api"
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
export let app
|
export let app
|
||||||
export let addData
|
|
||||||
export let appUsers = []
|
export let appUsers = []
|
||||||
|
|
||||||
let prevSearch = undefined,
|
const dispatch = createEventDispatcher()
|
||||||
search = undefined
|
const usersFetch = fetchData({
|
||||||
let pageInfo = createPaginationStore()
|
API,
|
||||||
let appData = [{ id: "", role: "" }]
|
datasource: {
|
||||||
|
type: "user",
|
||||||
$: page = $pageInfo.page
|
},
|
||||||
$: fetchUsers(page, search)
|
options: {
|
||||||
$: availableUsers = getAvailableUsers($users, appUsers, appData)
|
query: {
|
||||||
$: filteredGroups = $groups.filter(group => {
|
email: "",
|
||||||
return !group.apps.find(appId => {
|
},
|
||||||
return appId === app.appId
|
},
|
||||||
})
|
|
||||||
})
|
})
|
||||||
$: valid =
|
|
||||||
appData?.length && !appData?.some(x => !x.id?.length || !x.role?.length)
|
let search = ""
|
||||||
|
let data = [{ id: "", role: "" }]
|
||||||
|
|
||||||
|
$: usersFetch.update({
|
||||||
|
query: {
|
||||||
|
email: search,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
$: fixedAppId = apps.getProdAppID(app.devId)
|
||||||
|
$: availableUsers = getAvailableUsers($usersFetch.rows, appUsers, data)
|
||||||
|
$: availableGroups = getAvailableGroups($groups, app.appId, search, data)
|
||||||
|
$: valid = data?.length && !data?.some(x => !x.id?.length || !x.role?.length)
|
||||||
$: optionSections = {
|
$: optionSections = {
|
||||||
...($auth.groupsEnabled &&
|
...($auth.groupsEnabled &&
|
||||||
filteredGroups.length && {
|
availableGroups.length && {
|
||||||
["User groups"]: {
|
["User groups"]: {
|
||||||
data: filteredGroups,
|
data: availableGroups,
|
||||||
getLabel: group => group.name,
|
getLabel: group => group.name,
|
||||||
getValue: group => group._id,
|
getValue: group => group._id,
|
||||||
getIcon: group => group.icon,
|
getIcon: group => group.icon,
|
||||||
|
@ -51,8 +60,53 @@
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const addData = async appData => {
|
||||||
|
const gr_prefix = "gr"
|
||||||
|
const us_prefix = "us"
|
||||||
|
for (let data of appData) {
|
||||||
|
// Assign group
|
||||||
|
if (data.id.startsWith(gr_prefix)) {
|
||||||
|
const group = $groups.find(group => {
|
||||||
|
return group._id === data.id
|
||||||
|
})
|
||||||
|
if (!group) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
await groups.actions.save({
|
||||||
|
...group,
|
||||||
|
apps: [...group.apps, app.appId],
|
||||||
|
roles: {
|
||||||
|
...group.roles,
|
||||||
|
[fixedAppId]: data.role,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// Assign user
|
||||||
|
else if (data.id.startsWith(us_prefix)) {
|
||||||
|
const user = await users.get(data.id)
|
||||||
|
await users.save({
|
||||||
|
...user,
|
||||||
|
roles: {
|
||||||
|
...user.roles,
|
||||||
|
[fixedAppId]: data.role,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh data when completed
|
||||||
|
await usersFetch.refresh()
|
||||||
|
await groups.actions.init()
|
||||||
|
dispatch("update")
|
||||||
|
}
|
||||||
|
|
||||||
const getAvailableUsers = (allUsers, appUsers, newUsers) => {
|
const getAvailableUsers = (allUsers, appUsers, newUsers) => {
|
||||||
return (allUsers.data || []).filter(user => {
|
return (allUsers || []).filter(user => {
|
||||||
|
// Filter out admin users
|
||||||
|
if (user?.admin?.global || user?.builder?.global) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Filter out assigned users
|
// Filter out assigned users
|
||||||
if (appUsers.find(x => x._id === user._id)) {
|
if (appUsers.find(x => x._id === user._id)) {
|
||||||
return false
|
return false
|
||||||
|
@ -63,31 +117,30 @@
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchUsers(page, search) {
|
const getAvailableGroups = (allGroups, appId, search, newGroups) => {
|
||||||
if ($pageInfo.loading) {
|
search = search?.toLowerCase()
|
||||||
return
|
return (allGroups || []).filter(group => {
|
||||||
}
|
// Filter out assigned groups
|
||||||
// need to remove the page if they've started searching
|
if (group.apps.includes(appId)) {
|
||||||
if (search && !prevSearch) {
|
return false
|
||||||
pageInfo.reset()
|
}
|
||||||
page = undefined
|
|
||||||
}
|
// Filter out new groups which are going to be assigned
|
||||||
prevSearch = search
|
if (newGroups.find(x => x.id === group._id)) {
|
||||||
try {
|
return false
|
||||||
pageInfo.loading()
|
}
|
||||||
await users.search({ page, email: search })
|
|
||||||
pageInfo.fetched($users.hasNextPage, $users.nextPage)
|
// Match search string
|
||||||
} catch (error) {
|
return !search || group.name.toLowerCase().includes(search)
|
||||||
notifications.error("Error getting user list")
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function addNewInput() {
|
function addNewInput() {
|
||||||
appData = [...appData, { id: "", role: "" }]
|
data = [...data, { id: "", role: "" }]
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeItem = index => {
|
const removeItem = index => {
|
||||||
appData = appData.filter((x, idx) => idx !== index)
|
data = data.filter((x, idx) => idx !== index)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -96,13 +149,13 @@
|
||||||
title="Assign users to your app"
|
title="Assign users to your app"
|
||||||
confirmText="Done"
|
confirmText="Done"
|
||||||
cancelText="Cancel"
|
cancelText="Cancel"
|
||||||
onConfirm={() => addData(appData)}
|
onConfirm={() => addData(data)}
|
||||||
showCloseIcon={false}
|
showCloseIcon={false}
|
||||||
disabled={!valid}
|
disabled={!valid}
|
||||||
>
|
>
|
||||||
{#if appData?.length}
|
{#if data.length}
|
||||||
<Layout noPadding gap="XS">
|
<Layout noPadding gap="XS">
|
||||||
{#each appData as input, index}
|
{#each data as input, index}
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<div class="picker">
|
<div class="picker">
|
||||||
<PickerDropdown
|
<PickerDropdown
|
||||||
|
|
Loading…
Reference in a new issue