diff --git a/packages/backend-core/src/db/constants.js b/packages/backend-core/src/db/constants.js index 10c6e174d7..12626fb90e 100644 --- a/packages/backend-core/src/db/constants.js +++ b/packages/backend-core/src/db/constants.js @@ -1,4 +1,5 @@ exports.SEPARATOR = "_" +exports.UNICODE_MAX = "\ufff0" const PRE_APP = "app" const PRE_DEV = "dev" diff --git a/packages/backend-core/src/db/utils.ts b/packages/backend-core/src/db/utils.ts index fc4094ad9f..54af4fc7a2 100644 --- a/packages/backend-core/src/db/utils.ts +++ b/packages/backend-core/src/db/utils.ts @@ -1,7 +1,7 @@ import { newid } from "../hashing" import { DEFAULT_TENANT_ID, Configs } from "../constants" import env from "../environment" -import { SEPARATOR, DocumentTypes } from "./constants" +import { SEPARATOR, DocumentTypes, UNICODE_MAX } from "./constants" import { getTenantId, getGlobalDBName, getGlobalDB } from "../tenancy" import fetch from "node-fetch" import { doWithDB, allDbs } from "./index" @@ -12,8 +12,6 @@ import { isDevApp, isDevAppID } from "./conversions" import { APP_PREFIX } from "./constants" import * as events from "../events" -const UNICODE_MAX = "\ufff0" - export const ViewNames = { USER_BY_EMAIL: "by_email", BY_API_KEY: "by_api_key", @@ -439,21 +437,22 @@ export const getPlatformUrl = async (opts = { tenantAware: true }) => { } export function pagination( - response: any, + data: any[], pageSize: number, - paginate: boolean = true + { paginate, property } = { paginate: true, property: "_id" } ) { - const data = response.rows.map((row: any) => { - return row.doc ? row.doc : row - }) if (!paginate) { return { data, hasNextPage: false } } const hasNextPage = data.length > pageSize + let nextPage = undefined + if (hasNextPage) { + nextPage = property ? data[pageSize]?.[property] : data[pageSize]?._id + } return { data: data.slice(0, pageSize), hasNextPage, - nextPage: hasNextPage ? data[pageSize]?._id : undefined, + nextPage, } } diff --git a/packages/backend-core/src/users.js b/packages/backend-core/src/users.js index 4acccda2a0..0c1350a674 100644 --- a/packages/backend-core/src/users.js +++ b/packages/backend-core/src/users.js @@ -1,5 +1,6 @@ const { ViewNames } = require("./db/utils") const { queryGlobalView } = require("./db/views") +const { UNICODE_MAX } = require("./db/constants") /** * Given an email address this will use a view to search through @@ -19,3 +20,24 @@ exports.getGlobalUserByEmail = async email => { return response } + +/** + * Performs a starts with search on the global email view. + */ +exports.searchGlobalUsersByEmail = async (email, opts) => { + if (typeof email !== "string") { + throw new Error("Must provide a string to search by") + } + const lcEmail = email.toLowerCase() + // handle if passing up startkey for pagination + const startkey = opts && opts.startkey ? opts.startkey : lcEmail + let response = await queryGlobalView(ViewNames.USER_BY_EMAIL, { + ...opts, + startkey, + endkey: `${lcEmail}${UNICODE_MAX}`, + }) + if (!response) { + response = [] + } + return Array.isArray(response) ? response : [response] +} diff --git a/packages/builder/src/helpers/pagination.js b/packages/builder/src/helpers/pagination.js index 8f524d8d9f..9399d34d12 100644 --- a/packages/builder/src/helpers/pagination.js +++ b/packages/builder/src/helpers/pagination.js @@ -1,31 +1,56 @@ -export class PageInfo { - constructor(fetch) { - this.reset() - this.fetch = fetch - } +import { writable } from "svelte/store" - async goToNextPage() { - this.pageNumber++ - this.prevPage = this.page - this.page = this.nextPage - this.hasPrevPage = this.pageNumber > 1 - await this.fetch(this.page) - } - - async goToPrevPage() { - this.pageNumber-- - this.nextPage = this.page - this.page = this.prevPage - this.hasPrevPage = this.pageNumber > 1 - await this.fetch(this.page) - } - - reset() { - this.prevPage = null - this.nextPage = null - this.page = undefined - this.hasPrevPage = false - this.hasNextPage = false - this.pageNumber = 1 +function defaultValue() { + return { + nextPage: null, + page: undefined, + hasPrevPage: false, + hasNextPage: false, + pageNumber: 1, + pages: [], + } +} + +export function createPaginationStore() { + const { subscribe, set, update } = writable(defaultValue()) + + function prevPage() { + update(state => { + state.pageNumber-- + state.nextPage = state.pages.pop() + state.page = state.pages[state.pages.length - 1] + state.hasPrevPage = state.pageNumber > 1 + return state + }) + } + + function nextPage() { + update(state => { + state.pageNumber++ + state.page = state.nextPage + state.pages.push(state.page) + state.hasPrevPage = state.pageNumber > 1 + return state + }) + } + + function fetched(hasNextPage, nextPage) { + update(state => { + state.hasNextPage = hasNextPage + state.nextPage = nextPage + return state + }) + } + + function reset() { + set(defaultValue()) + } + + return { + subscribe, + prevPage, + nextPage, + fetched, + reset, } } diff --git a/packages/builder/src/pages/builder/portal/manage/users/_components/AddUserModal.svelte b/packages/builder/src/pages/builder/portal/manage/users/_components/AddUserModal.svelte index 5583a48b7d..88a8fb6c5d 100644 --- a/packages/builder/src/pages/builder/portal/manage/users/_components/AddUserModal.svelte +++ b/packages/builder/src/pages/builder/portal/manage/users/_components/AddUserModal.svelte @@ -10,7 +10,9 @@ } from "@budibase/bbui" import { createValidationStore, emailValidator } from "helpers/validation" import { users } from "stores/portal" + import { createEventDispatcher } from "svelte" + const dispatch = createEventDispatcher() const password = Math.random().toString(36).substring(2, 22) const options = ["Email onboarding", "Basic onboarding"] const [email, error, touched] = createValidationStore("", emailValidator) @@ -39,6 +41,7 @@ forceResetPassword: true, }) notifications.success("Successfully created user") + dispatch("created") } catch (error) { notifications.error("Error creating user") } 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 ef1ba2a24a..bc14b7e8d2 100644 --- a/packages/builder/src/pages/builder/portal/manage/users/index.svelte +++ b/packages/builder/src/pages/builder/portal/manage/users/index.svelte @@ -17,8 +17,7 @@ import TagsRenderer from "./_components/TagsTableRenderer.svelte" import AddUserModal from "./_components/AddUserModal.svelte" import { users } from "stores/portal" - import { PageInfo } from "helpers/pagination" - import { onMount } from "svelte" + import { createPaginationStore } from "helpers/pagination" const schema = { email: {}, @@ -27,43 +26,21 @@ group: {}, } - let pageInfo = new PageInfo(fetchUsers) - let search - $: checkRefreshed($users.page) - $: filteredUsers = $users.data - ?.filter(user => user?.email?.includes(search || "")) - .map(user => ({ - ...user, - group: ["All users"], - developmentAccess: !!user.builder?.global, - adminAccess: !!user.admin?.global, - })) + let pageInfo = createPaginationStore() + let search = undefined + $: page = $pageInfo.page + $: fetchUsers(page, search) let createUserModal - async function checkRefreshed(page) { - // the users have been reset, go back to first page - if (!page && pageInfo.page) { - pageInfo.reset() - pageInfo.pageNumber = pageInfo.pageNumber - pageInfo.hasNextPage = $users.hasNextPage - pageInfo.nextPage = $users.nextPage - } - } - - async function fetchUsers(page) { + async function fetchUsers(page, search) { try { - await users.fetch(page) - pageInfo.hasNextPage = $users.hasNextPage - pageInfo.nextPage = $users.nextPage + await users.fetch({ page, search }) + pageInfo.fetched($users.hasNextPage, $users.nextPage) } catch (error) { notifications.error("Error getting user list") } } - - onMount(async () => { - await fetchUsers() - }) @@ -92,7 +69,7 @@ $goto(`./${detail._id}`)} {schema} - data={filteredUsers || $users.data} + data={$users.data} allowEditColumns={false} allowEditRows={false} allowSelectRows={false} @@ -100,18 +77,23 @@ /> - + { + pageInfo.reset() + await fetchUsers() + }} + />