From 39dc0afdc9d60b41d6a35fa01b0bc79468755f46 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 5 Aug 2022 15:57:21 +0100 Subject: [PATCH] Add new UserFetch for searching users table and use in users page. Add debounce utility --- .../builder/portal/manage/users/index.svelte | 199 +++++++++--------- packages/frontend-core/src/fetch/DataFetch.js | 2 + packages/frontend-core/src/fetch/UserFetch.js | 46 ++++ packages/frontend-core/src/fetch/fetchData.js | 2 + packages/frontend-core/src/utils/utils.js | 22 ++ 5 files changed, 176 insertions(+), 95 deletions(-) create mode 100644 packages/frontend-core/src/fetch/UserFetch.js 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 a474ca708d..9b9898aeb3 100644 --- a/packages/builder/src/pages/builder/portal/manage/users/index.svelte +++ b/packages/builder/src/pages/builder/portal/manage/users/index.svelte @@ -24,41 +24,53 @@ import OnboardingTypeModal from "./_components/OnboardingTypeModal.svelte" import PasswordModal from "./_components/PasswordModal.svelte" import ImportUsersModal from "./_components/ImportUsersModal.svelte" - import { createPaginationStore } from "helpers/pagination" import { get } from "svelte/store" - import { Constants } from "@budibase/frontend-core" + import { Constants, Utils, fetchData } from "@budibase/frontend-core" + import { API } from "api" + const fetch = fetchData({ + API, + datasource: { + type: "user", + }, + }) + + let loaded = false let enrichedUsers = [] let createUserModal, inviteConfirmationModal, onboardingTypeModal, passwordModal, importUsersModal - let pageInfo = createPaginationStore() - let prevEmail = undefined, - searchEmail = undefined + let searchEmail = undefined let selectedRows = [] let customRenderers = [ { column: "userGroups", component: GroupsTableRenderer }, { column: "apps", component: AppsTableRenderer }, { column: "role", component: RoleTableRenderer }, ] + let userData = [] + $: debouncedUpdateFetch(searchEmail) $: schema = { - email: {}, + email: { + sortable: false, + }, role: { sortable: false, }, ...($auth.groupsEnabled && { - userGroups: { sortable: false, displayName: "User groups" }, + userGroups: { + sortable: false, + displayName: "User groups", + }, }), - apps: {}, + apps: { + sortable: false, + }, } - $: userData = [] - $: page = $pageInfo.page - $: fetchUsers(page, searchEmail) $: { - enrichedUsers = $users.data?.map(user => { + enrichedUsers = $fetch.rows?.map(user => { let userGroups = [] $groups.forEach(group => { if (group.users) { @@ -78,6 +90,18 @@ }) } + const updateFetch = email => { + if (email == null && $fetch.query.email == null) { + return + } + fetch.update({ + query: { + email, + }, + }) + } + const debouncedUpdateFetch = Utils.debounce(updateFetch, 250) + const showOnboardingTypeModal = async addUsersData => { userData = await removingDuplicities(addUsersData) if (!userData?.users?.length) return @@ -161,14 +185,6 @@ } } - onMount(async () => { - try { - await groups.actions.init() - } catch (error) { - notifications.error("Error fetching User Group data") - } - }) - const deleteRows = async () => { try { let ids = selectedRows.map(user => user._id) @@ -179,88 +195,81 @@ await users.bulkDelete(ids) notifications.success(`Successfully deleted ${selectedRows.length} rows`) selectedRows = [] - await fetchUsers(page, searchEmail) + await fetch.refresh() } catch (error) { notifications.error("Error deleting rows") } } - async function fetchUsers(page, email) { - if ($pageInfo.loading) { - return - } - // need to remove the page if they've started searching - if (email && !prevEmail) { - pageInfo.reset() - page = undefined - } - prevEmail = email + onMount(async () => { try { - pageInfo.loading() - await users.search({ page, email }) - pageInfo.fetched($users.hasNextPage, $users.nextPage) + loaded = false + await groups.actions.init() + loaded = true } catch (error) { - notifications.error("Error getting user list") + notifications.error("Error fetching User Group data") } - } + }) - - - Users - Add users and control who gets access to your published apps - - -
- - - - -
- - {#if selectedRows.length > 0} - - {/if} +{#if loaded && $fetch.loaded} + + + Users + Add users and control who gets access to your published apps + + +
+ + + + +
+ + {#if selectedRows.length > 0} + + {/if} +
-
- $goto(`./${detail._id}`)} - {schema} - bind:selectedRows - data={enrichedUsers} - allowEditColumns={false} - allowEditRows={false} - allowSelectRows={true} - showHeaderBorder={false} - {customRenderers} - /> - - + + +{/if} @@ -272,11 +281,11 @@ title="Invites sent!" confirmText="Done" > - Your users should now recieve an email invite to get access to their - Budibase account + + Your users should now recieve an email invite to get access to their + Budibase account + + diff --git a/packages/frontend-core/src/fetch/DataFetch.js b/packages/frontend-core/src/fetch/DataFetch.js index 338e6e0405..e875219e88 100644 --- a/packages/frontend-core/src/fetch/DataFetch.js +++ b/packages/frontend-core/src/fetch/DataFetch.js @@ -158,6 +158,8 @@ export default class DataFetch { schema, query, loading: true, + cursors: [], + cursor: null, })) // Actually fetch data diff --git a/packages/frontend-core/src/fetch/UserFetch.js b/packages/frontend-core/src/fetch/UserFetch.js new file mode 100644 index 0000000000..3bd0cdbaa4 --- /dev/null +++ b/packages/frontend-core/src/fetch/UserFetch.js @@ -0,0 +1,46 @@ +import { get } from "svelte/store" +import DataFetch from "./DataFetch.js" +import { TableNames } from "../constants" + +export default class UserFetch extends DataFetch { + constructor(opts) { + super({ + ...opts, + datasource: { + tableId: TableNames.USERS, + }, + }) + } + + determineFeatureFlags() { + return { + supportsSearch: true, + supportsSort: false, + supportsPagination: true, + } + } + + async getData() { + const { cursor, query } = get(this.store) + try { + // "query" normally contains a lucene query, but users uses a non-standard + // search endpoint so we use query uniquely here + const res = await this.API.searchUsers({ + page: cursor, + email: query.email, + appId: query.appId, + }) + return { + rows: res?.data || [], + hasNextPage: res?.hasNextPage || false, + cursor: res?.nextPage || null, + } + } catch (error) { + return { + rows: [], + hasNextPage: false, + error, + } + } + } +} diff --git a/packages/frontend-core/src/fetch/fetchData.js b/packages/frontend-core/src/fetch/fetchData.js index e914ff863f..4974816496 100644 --- a/packages/frontend-core/src/fetch/fetchData.js +++ b/packages/frontend-core/src/fetch/fetchData.js @@ -5,12 +5,14 @@ import RelationshipFetch from "./RelationshipFetch.js" import NestedProviderFetch from "./NestedProviderFetch.js" import FieldFetch from "./FieldFetch.js" import JSONArrayFetch from "./JSONArrayFetch.js" +import UserFetch from "./UserFetch.js" const DataFetchMap = { table: TableFetch, view: ViewFetch, query: QueryFetch, link: RelationshipFetch, + user: UserFetch, // Client specific datasource types provider: NestedProviderFetch, diff --git a/packages/frontend-core/src/utils/utils.js b/packages/frontend-core/src/utils/utils.js index 71688981a9..c31b3d5111 100644 --- a/packages/frontend-core/src/utils/utils.js +++ b/packages/frontend-core/src/utils/utils.js @@ -19,3 +19,25 @@ export const sequential = fn => { } } } + +/** + * Utility to debounce an async function and ensure a minimum delay between + * invocations is enforced. + * @param callback an async function to run + * @param minDelay the minimum delay between invocations + * @returns {Promise} a debounced version of the callback + */ +export const debounce = (callback, minDelay = 1000) => { + let timeout + return async (...params) => { + return new Promise(resolve => { + if (timeout) { + clearTimeout(timeout) + } + timeout = setTimeout(async () => { + console.log("timeout reached!") + resolve(await callback(...params)) + }, minDelay) + }) + } +}