From 53ead79de2d48e2fc6393f460e578250cbe9ede0 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 6 Jul 2022 16:09:05 +0100 Subject: [PATCH] Updating user search endpoint to allow searching by app ID with a new view, as well as updating user page to have a search input again. --- packages/backend-core/src/db/constants.ts | 1 + packages/backend-core/src/db/utils.ts | 29 +++++++++++++++-- packages/backend-core/src/db/views.js | 30 ++++++++++++++++- packages/backend-core/src/users.js | 28 +++++++++++++--- .../builder/portal/manage/users/index.svelte | 32 +++++++++++++++---- packages/frontend-core/src/api/user.js | 9 ++++-- packages/worker/src/sdk/users/users.ts | 24 ++++++++------ 7 files changed, 126 insertions(+), 27 deletions(-) diff --git a/packages/backend-core/src/db/constants.ts b/packages/backend-core/src/db/constants.ts index 3d642f63c0..a39b0c6d5e 100644 --- a/packages/backend-core/src/db/constants.ts +++ b/packages/backend-core/src/db/constants.ts @@ -12,6 +12,7 @@ export enum AutomationViewModes { export enum ViewNames { USER_BY_EMAIL = "by_email", + USER_BY_APP = "by_app", BY_API_KEY = "by_api_key", USER_BY_BUILDERS = "by_builders", LINK = "by_link", diff --git a/packages/backend-core/src/db/utils.ts b/packages/backend-core/src/db/utils.ts index 4a99dfee32..780917686f 100644 --- a/packages/backend-core/src/db/utils.ts +++ b/packages/backend-core/src/db/utils.ts @@ -8,7 +8,7 @@ import { doWithDB, allDbs } from "./index" import { getCouchInfo } from "./pouch" import { getAppMetadata } from "../cache/appMetadata" import { checkSlashesInUrl } from "../helpers" -import { isDevApp, isDevAppID } from "./conversions" +import { isDevApp, isDevAppID, getProdAppID } from "./conversions" import { APP_PREFIX } from "./constants" import * as events from "../events" @@ -107,6 +107,15 @@ export function getGlobalUserParams(globalId: any, otherProps: any = {}) { } } +export function getUsersByAppParams(appId: any, otherProps: any = {}) { + const prodAppId = getProdAppID(appId) + return { + ...otherProps, + startkey: prodAppId, + endkey: `${prodAppId}${UNICODE_MAX}`, + } +} + /** * Generates a template ID. * @param ownerId The owner/user of the template, this could be global or a workspace level. @@ -464,15 +473,29 @@ export const getPlatformUrl = async (opts = { tenantAware: true }) => { export function pagination( data: any[], pageSize: number, - { paginate, property } = { paginate: true, property: "_id" } + { + paginate, + property, + getKey, + }: { + paginate: boolean + property: string + getKey?: (doc: any) => string | undefined + } = { + paginate: true, + property: "_id", + } ) { if (!paginate) { return { data, hasNextPage: false } } const hasNextPage = data.length > pageSize let nextPage = undefined + if (!getKey) { + getKey = (doc: any) => (property ? doc?.[property] : doc?._id) + } if (hasNextPage) { - nextPage = property ? data[pageSize]?.[property] : data[pageSize]?._id + nextPage = getKey(data[pageSize]) } return { data: data.slice(0, pageSize), diff --git a/packages/backend-core/src/db/views.js b/packages/backend-core/src/db/views.js index e0281c6584..851ff7c59c 100644 --- a/packages/backend-core/src/db/views.js +++ b/packages/backend-core/src/db/views.js @@ -1,4 +1,4 @@ -const { DocumentTypes, ViewNames } = require("./utils") +const { DocumentTypes, ViewNames, SEPARATOR } = require("./constants") const { getGlobalDB } = require("../tenancy") function DesignDoc() { @@ -34,6 +34,33 @@ exports.createUserEmailView = async () => { await db.put(designDoc) } +exports.createUserAppView = async () => { + const db = getGlobalDB() + let designDoc + try { + designDoc = await db.get("_design/database") + } catch (err) { + // no design doc, make one + designDoc = DesignDoc() + } + const view = { + // if using variables in a map function need to inject them before use + map: `function(doc) { + if (doc._id.startsWith("${DocumentTypes.USER}${SEPARATOR}") && doc.roles) { + for (let prodAppId of Object.keys(doc.roles)) { + let emitted = prodAppId + "${SEPARATOR}" + doc._id + emit(emitted, null) + } + } + }`, + } + designDoc.views = { + ...designDoc.views, + [ViewNames.USER_BY_APP]: view, + } + await db.put(designDoc) +} + exports.createApiKeyView = async () => { const db = getGlobalDB() let designDoc @@ -84,6 +111,7 @@ exports.queryGlobalView = async (viewName, params, db = null) => { [ViewNames.USER_BY_EMAIL]: exports.createUserEmailView, [ViewNames.BY_API_KEY]: exports.createApiKeyView, [ViewNames.USER_BY_BUILDERS]: exports.createUserBuildersView, + [ViewNames.USER_BY_APP]: exports.createUserAppView, } // can pass DB in if working with something specific if (!db) { diff --git a/packages/backend-core/src/users.js b/packages/backend-core/src/users.js index 0c1350a674..ad42443910 100644 --- a/packages/backend-core/src/users.js +++ b/packages/backend-core/src/users.js @@ -1,6 +1,6 @@ -const { ViewNames } = require("./db/utils") +const { ViewNames, getUsersByAppParams, getProdAppID } = require("./db/utils") const { queryGlobalView } = require("./db/views") -const { UNICODE_MAX } = require("./db/constants") +const { UNICODE_MAX, SEPARATOR } = require("./db/constants") /** * Given an email address this will use a view to search through @@ -13,12 +13,32 @@ exports.getGlobalUserByEmail = async email => { throw "Must supply an email address to view" } - const response = await queryGlobalView(ViewNames.USER_BY_EMAIL, { + return await queryGlobalView(ViewNames.USER_BY_EMAIL, { key: email.toLowerCase(), include_docs: true, }) +} - return response +exports.searchGlobalUsersByApp = async (appId, opts) => { + if (typeof appId !== "string") { + throw new Error("Must provide a string based app ID") + } + const params = getUsersByAppParams(appId, { + include_docs: true, + }) + params.startkey = opts && opts.startkey ? opts.startkey : params.startkey + let response = await queryGlobalView(ViewNames.USER_BY_APP, params) + if (!response) { + response = [] + } + return Array.isArray(response) ? response : [response] +} + +exports.getGlobalUserByAppPage = (appId, user) => { + if (!user) { + return + } + return `${getProdAppID(appId)}${SEPARATOR}${user._id}` } /** 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 0f55dd8dcc..14da17517f 100644 --- a/packages/builder/src/pages/builder/portal/manage/users/index.svelte +++ b/packages/builder/src/pages/builder/portal/manage/users/index.svelte @@ -11,6 +11,8 @@ Icon, notifications, Pagination, + Search, + Label, } from "@budibase/bbui" import AddUserModal from "./_components/AddUserModal.svelte" import { users, groups } from "stores/portal" @@ -70,10 +72,10 @@ importUsersModal let pageInfo = createPaginationStore() - let prevSearch = undefined, - search = undefined + let prevEmail = undefined, + searchEmail = undefined $: page = $pageInfo.page - $: fetchUsers(page, search) + $: fetchUsers(page, searchEmail) $: { enrichedUsers = $users.data?.map(user => { @@ -160,19 +162,19 @@ } }) - async function fetchUsers(page, search) { + async function fetchUsers(page, email) { if ($pageInfo.loading) { return } // need to remove the page if they've started searching - if (search && !prevSearch) { + if (email && !prevEmail) { pageInfo.reset() page = undefined } - prevSearch = search + prevEmail = email try { pageInfo.loading() - await users.search({ page, search }) + await users.search({ page, email }) pageInfo.fetched($users.hasNextPage, $users.nextPage) } catch (error) { notifications.error("Error getting user list") @@ -207,6 +209,10 @@ +
+ + +
$goto(`./${detail._id}`)} @@ -273,6 +279,18 @@