Merge pull request #10533 from Budibase/budi-6158/allow_searching_users
Allow searching users on group views
This commit is contained in:
commit
a6455b4b4d
|
@ -21,7 +21,7 @@ export enum ViewName {
|
|||
AUTOMATION_LOGS = "automation_logs",
|
||||
ACCOUNT_BY_EMAIL = "account_by_email",
|
||||
PLATFORM_USERS_LOWERCASE = "platform_users_lowercase",
|
||||
USER_BY_GROUP = "by_group_user",
|
||||
USER_BY_GROUP = "user_by_group",
|
||||
APP_BACKUP_BY_TRIGGER = "by_trigger",
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import EditUserPicker from "./EditUserPicker.svelte"
|
||||
|
||||
import { Heading, Pagination, Table } from "@budibase/bbui"
|
||||
import { Heading, Pagination, Table, Search } from "@budibase/bbui"
|
||||
import { fetchData } from "@budibase/frontend-core"
|
||||
import { goto } from "@roxi/routify"
|
||||
import { API } from "api"
|
||||
|
@ -12,7 +12,9 @@
|
|||
|
||||
export let groupId
|
||||
|
||||
const fetchGroupUsers = fetchData({
|
||||
let emailSearch
|
||||
let fetchGroupUsers
|
||||
$: fetchGroupUsers = fetchData({
|
||||
API,
|
||||
datasource: {
|
||||
type: "groupUser",
|
||||
|
@ -20,6 +22,7 @@
|
|||
options: {
|
||||
query: {
|
||||
groupId,
|
||||
emailSearch,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
@ -59,24 +62,31 @@
|
|||
</script>
|
||||
|
||||
<div class="header">
|
||||
<Heading size="S">Users</Heading>
|
||||
{#if !scimEnabled}
|
||||
<EditUserPicker {groupId} onUsersUpdated={fetchGroupUsers.getInitialData} />
|
||||
{:else}
|
||||
<ScimBanner />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="controls-right">
|
||||
<Search bind:value={emailSearch} placeholder="Search email" />
|
||||
</div>
|
||||
</div>
|
||||
<Table
|
||||
schema={userSchema}
|
||||
data={$fetchGroupUsers?.rows}
|
||||
loading={$fetchGroupUsers.loading}
|
||||
allowEditRows={false}
|
||||
customPlaceholder
|
||||
customRenderers={customUserTableRenderers}
|
||||
on:click={e => $goto(`../users/${e.detail._id}`)}
|
||||
>
|
||||
<div class="placeholder" slot="placeholder">
|
||||
<Heading size="S">This user group doesn't have any users</Heading>
|
||||
<Heading size="S"
|
||||
>{emailSearch
|
||||
? `No users found matching the email "${emailSearch}"`
|
||||
: "This user group doesn't have any users"}</Heading
|
||||
>
|
||||
</div>
|
||||
</Table>
|
||||
|
||||
|
@ -98,7 +108,7 @@
|
|||
.header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: var(--spacing-l);
|
||||
}
|
||||
|
@ -109,4 +119,15 @@
|
|||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.controls-right {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xl);
|
||||
}
|
||||
.controls-right :global(.spectrum-Search) {
|
||||
width: 200px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -55,10 +55,13 @@ export const buildGroupsEndpoints = API => {
|
|||
/**
|
||||
* Gets a group users by the group id
|
||||
*/
|
||||
getGroupUsers: async ({ id, bookmark }) => {
|
||||
getGroupUsers: async ({ id, bookmark, emailSearch }) => {
|
||||
let url = `/api/global/groups/${id}/users?`
|
||||
if (bookmark) {
|
||||
url += `bookmark=${bookmark}`
|
||||
url += `bookmark=${bookmark}&`
|
||||
}
|
||||
if (emailSearch) {
|
||||
url += `emailSearch=${emailSearch}&`
|
||||
}
|
||||
|
||||
return await API.get({
|
||||
|
|
|
@ -31,6 +31,7 @@ export default class GroupUserFetch extends DataFetch {
|
|||
try {
|
||||
const res = await this.API.getGroupUsers({
|
||||
id: query.groupId,
|
||||
emailSearch: query.emailSearch,
|
||||
bookmark: cursor,
|
||||
})
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 14345384f7a6755d1e2de327104741e0f208f55d
|
||||
Subproject commit 64a2025727c25d5813832c92eb360de3947b7aa6
|
|
@ -1,5 +1,9 @@
|
|||
import { events } from "@budibase/backend-core"
|
||||
import { generator } from "@budibase/backend-core/tests"
|
||||
import { structures, TestConfiguration, mocks } from "../../../../tests"
|
||||
import { UserGroup } from "@budibase/types"
|
||||
|
||||
mocks.licenses.useGroups()
|
||||
|
||||
describe("/api/global/groups", () => {
|
||||
const config = new TestConfiguration()
|
||||
|
@ -113,4 +117,118 @@ describe("/api/global/groups", () => {
|
|||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("find users", () => {
|
||||
describe("without users", () => {
|
||||
let group: UserGroup
|
||||
beforeAll(async () => {
|
||||
group = structures.groups.UserGroup()
|
||||
await config.api.groups.saveGroup(group)
|
||||
})
|
||||
|
||||
it("should return empty", async () => {
|
||||
const result = await config.api.groups.searchUsers(group._id!)
|
||||
expect(result.body).toEqual({
|
||||
users: [],
|
||||
bookmark: undefined,
|
||||
hasNextPage: false,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("existing users", () => {
|
||||
let groupId: string
|
||||
let users: { _id: string; email: string }[] = []
|
||||
|
||||
beforeAll(async () => {
|
||||
groupId = (
|
||||
await config.api.groups.saveGroup(structures.groups.UserGroup())
|
||||
).body._id
|
||||
|
||||
await Promise.all(
|
||||
Array.from({ length: 30 }).map(async (_, i) => {
|
||||
const email = `user${i}@${generator.domain()}`
|
||||
const user = await config.api.users.saveUser({
|
||||
...structures.users.user(),
|
||||
email,
|
||||
})
|
||||
users.push({ _id: user.body._id, email })
|
||||
})
|
||||
)
|
||||
users = users.sort((a, b) => a._id.localeCompare(b._id))
|
||||
await config.api.groups.updateGroupUsers(groupId, {
|
||||
add: users.map(u => u._id),
|
||||
remove: [],
|
||||
})
|
||||
})
|
||||
|
||||
describe("pagination", () => {
|
||||
it("should return first page", async () => {
|
||||
const result = await config.api.groups.searchUsers(groupId)
|
||||
expect(result.body).toEqual({
|
||||
users: users.slice(0, 10),
|
||||
bookmark: users[10]._id,
|
||||
hasNextPage: true,
|
||||
})
|
||||
})
|
||||
|
||||
it("given a bookmark, should return skip items", async () => {
|
||||
const result = await config.api.groups.searchUsers(groupId, {
|
||||
bookmark: users[7]._id,
|
||||
})
|
||||
expect(result.body).toEqual({
|
||||
users: users.slice(7, 17),
|
||||
bookmark: users[17]._id,
|
||||
hasNextPage: true,
|
||||
})
|
||||
})
|
||||
|
||||
it("bookmarking the last page, should return last page info", async () => {
|
||||
const result = await config.api.groups.searchUsers(groupId, {
|
||||
bookmark: users[20]._id,
|
||||
})
|
||||
expect(result.body).toEqual({
|
||||
users: users.slice(20),
|
||||
bookmark: undefined,
|
||||
hasNextPage: false,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("search by email", () => {
|
||||
it('should be able to search "starting" by email', async () => {
|
||||
const result = await config.api.groups.searchUsers(groupId, {
|
||||
emailSearch: `user1`,
|
||||
})
|
||||
|
||||
const matchedUsers = users
|
||||
.filter(u => u.email.startsWith("user1"))
|
||||
.sort((a, b) => a.email.localeCompare(b.email))
|
||||
|
||||
expect(result.body).toEqual({
|
||||
users: matchedUsers.slice(0, 10),
|
||||
bookmark: matchedUsers[10].email,
|
||||
hasNextPage: true,
|
||||
})
|
||||
})
|
||||
|
||||
it("should be able to bookmark when searching by email", async () => {
|
||||
const matchedUsers = users
|
||||
.filter(u => u.email.startsWith("user1"))
|
||||
.sort((a, b) => a.email.localeCompare(b.email))
|
||||
|
||||
const result = await config.api.groups.searchUsers(groupId, {
|
||||
emailSearch: `user1`,
|
||||
bookmark: matchedUsers[4].email,
|
||||
})
|
||||
|
||||
expect(result.body).toEqual({
|
||||
users: matchedUsers.slice(4),
|
||||
bookmark: undefined,
|
||||
hasNextPage: false,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -23,4 +23,34 @@ export class GroupsAPI extends TestAPI {
|
|||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
}
|
||||
|
||||
searchUsers = (
|
||||
id: string,
|
||||
params?: { bookmark?: string; emailSearch?: string }
|
||||
) => {
|
||||
let url = `/api/global/groups/${id}/users?`
|
||||
if (params?.bookmark) {
|
||||
url += `bookmark=${params.bookmark}&`
|
||||
}
|
||||
if (params?.emailSearch) {
|
||||
url += `emailSearch=${params.emailSearch}&`
|
||||
}
|
||||
return this.request
|
||||
.get(url)
|
||||
.set(this.config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
}
|
||||
|
||||
updateGroupUsers = (
|
||||
id: string,
|
||||
body: { add: string[]; remove: string[] }
|
||||
) => {
|
||||
return this.request
|
||||
.post(`/api/global/groups/${id}/users`)
|
||||
.send(body)
|
||||
.set(this.config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue