From b335e492d79aa6cd5027253db3eb75cd2e668469 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 10 Jul 2023 17:42:27 +0100 Subject: [PATCH 1/4] Show all users editing an app in the app list --- .../src/components/common/NavItem.svelte | 2 +- .../src/components/start/AppRow.svelte | 6 ++-- .../builder/app/[application]/_layout.svelte | 2 +- .../src/components}/UserAvatars.svelte | 0 .../frontend-core/src/components/index.js | 1 + .../server/src/api/controllers/application.ts | 28 ++++++++++++++++++- packages/server/src/websockets/websocket.ts | 17 ++++++++--- packages/types/src/documents/app/app.ts | 2 ++ 8 files changed, 48 insertions(+), 10 deletions(-) rename packages/{builder/src/pages/builder/app/[application]/_components => frontend-core/src/components}/UserAvatars.svelte (100%) diff --git a/packages/builder/src/components/common/NavItem.svelte b/packages/builder/src/components/common/NavItem.svelte index 6f87c8c362..7d61751364 100644 --- a/packages/builder/src/components/common/NavItem.svelte +++ b/packages/builder/src/components/common/NavItem.svelte @@ -2,7 +2,7 @@ import { Icon } from "@budibase/bbui" import { createEventDispatcher, getContext } from "svelte" import { helpers } from "@budibase/shared-core" - import UserAvatars from "../../pages/builder/app/[application]/_components/UserAvatars.svelte" + import { UserAvatars } from "@budibase/frontend-core" export let icon export let withArrow = false diff --git a/packages/builder/src/components/start/AppRow.svelte b/packages/builder/src/components/start/AppRow.svelte index 50e6b8466a..2e7719987d 100644 --- a/packages/builder/src/components/start/AppRow.svelte +++ b/packages/builder/src/components/start/AppRow.svelte @@ -2,12 +2,12 @@ import { Heading, Body, Button, Icon } from "@budibase/bbui" import { processStringSync } from "@budibase/string-templates" import { goto } from "@roxi/routify" - import { UserAvatar } from "@budibase/frontend-core" + import { UserAvatars } from "@budibase/frontend-core" export let app export let lockedAction - $: editing = app?.lockedBy != null + $: editing = app.sessions?.length const handleDefaultClick = () => { if (window.innerWidth < 640) { @@ -41,7 +41,7 @@
{#if editing} Currently editing - + {:else if app.updatedAt} {processStringSync("Updated {{ duration time 'millisecond' }} ago", { time: new Date().getTime() - new Date(app.updatedAt).getTime(), diff --git a/packages/builder/src/pages/builder/app/[application]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/_layout.svelte index 8e637d6fa2..7eb7b35c7a 100644 --- a/packages/builder/src/pages/builder/app/[application]/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/_layout.svelte @@ -26,7 +26,7 @@ import TourWrap from "components/portal/onboarding/TourWrap.svelte" import TourPopover from "components/portal/onboarding/TourPopover.svelte" import BuilderSidePanel from "./_components/BuilderSidePanel.svelte" - import UserAvatars from "./_components/UserAvatars.svelte" + import { UserAvatars } from "@budibase/frontend-core" import { TOUR_KEYS, TOURS } from "components/portal/onboarding/tours.js" import PreviewOverlay from "./_components/PreviewOverlay.svelte" diff --git a/packages/builder/src/pages/builder/app/[application]/_components/UserAvatars.svelte b/packages/frontend-core/src/components/UserAvatars.svelte similarity index 100% rename from packages/builder/src/pages/builder/app/[application]/_components/UserAvatars.svelte rename to packages/frontend-core/src/components/UserAvatars.svelte diff --git a/packages/frontend-core/src/components/index.js b/packages/frontend-core/src/components/index.js index 3005c85d01..01a7c78cb8 100644 --- a/packages/frontend-core/src/components/index.js +++ b/packages/frontend-core/src/components/index.js @@ -2,4 +2,5 @@ export { default as SplitPage } from "./SplitPage.svelte" export { default as TestimonialPage } from "./TestimonialPage.svelte" export { default as Testimonial } from "./Testimonial.svelte" export { default as UserAvatar } from "./UserAvatar.svelte" +export { default as UserAvatars } from "./UserAvatars.svelte" export { Grid } from "./grid" diff --git a/packages/server/src/api/controllers/application.ts b/packages/server/src/api/controllers/application.ts index c068a422b0..5e12dd4d76 100644 --- a/packages/server/src/api/controllers/application.ts +++ b/packages/server/src/api/controllers/application.ts @@ -49,6 +49,7 @@ import { MigrationType, PlanType, Screen, + SocketSession, UserCtx, } from "@budibase/types" import { BASE_LAYOUT_PROP_IDS } from "../../constants/layouts" @@ -178,11 +179,12 @@ export const addSampleData = async (ctx: UserCtx) => { export async function fetch(ctx: UserCtx) { const dev = ctx.query && ctx.query.status === AppStatus.DEV const all = ctx.query && ctx.query.status === AppStatus.ALL - const apps = (await dbCore.getAllApps({ dev, all })) as App[] + let apps = (await dbCore.getAllApps({ dev, all })) as App[] const appIds = apps .filter((app: any) => app.status === "development") .map((app: any) => app.appId) + // get the locks for all the dev apps if (dev || all) { const locks = await getLocksById(appIds) @@ -197,6 +199,30 @@ export async function fetch(ctx: UserCtx) { } } + // Get all builder sessions in each app + const sessions = await builderSocket?.getRoomSessions(appIds) + if (sessions?.length) { + let appSessionMap: { [key: string]: SocketSession[] } = {} + sessions.forEach(session => { + const room = session.room + if (!room) { + return + } + if (!appSessionMap[room]) { + appSessionMap[room] = [] + } + appSessionMap[room].push(session) + }) + apps.forEach(app => { + const sessions = appSessionMap[app.appId] + if (sessions?.length) { + app.sessions = sessions + } else { + delete app.sessions + } + }) + } + ctx.body = await checkAppMetadata(apps) } diff --git a/packages/server/src/websockets/websocket.ts b/packages/server/src/websockets/websocket.ts index 24cac3c37d..9dea67ef5f 100644 --- a/packages/server/src/websockets/websocket.ts +++ b/packages/server/src/websockets/websocket.ts @@ -125,9 +125,18 @@ export class BaseSocket { } // Gets an array of all redis keys of users inside a certain room - async getRoomSessionIds(room: string): Promise { - const keys = await this.redisClient?.get(this.getRoomKey(room)) - return keys || [] + async getRoomSessionIds(room: string | string[]): Promise { + if (Array.isArray(room)) { + const roomKeys = room.map(this.getRoomKey.bind(this)) + const roomSessionIdMap = await this.redisClient?.bulkGet(roomKeys) + let sessionIds: any[] = [] + Object.values(roomSessionIdMap || {}).forEach(roomSessionIds => { + sessionIds = sessionIds.concat(roomSessionIds) + }) + return sessionIds + } else { + return (await this.redisClient?.get(this.getRoomKey(room))) || [] + } } // Sets the list of redis keys for users inside a certain room. @@ -137,7 +146,7 @@ export class BaseSocket { } // Gets a list of all users inside a certain room - async getRoomSessions(room?: string): Promise { + async getRoomSessions(room?: string | string[]): Promise { if (room) { const sessionIds = await this.getRoomSessionIds(room) const keys = sessionIds.map(this.getSessionKey.bind(this)) diff --git a/packages/types/src/documents/app/app.ts b/packages/types/src/documents/app/app.ts index 258eaef297..f42422b557 100644 --- a/packages/types/src/documents/app/app.ts +++ b/packages/types/src/documents/app/app.ts @@ -1,4 +1,5 @@ import { User, Document } from "../" +import { SocketSession } from "../../sdk" export type AppMetadataErrors = { [key: string]: string[] } @@ -17,6 +18,7 @@ export interface App extends Document { customTheme?: AppCustomTheme revertableVersion?: string lockedBy?: User + sessions?: SocketSession[] navigation?: AppNavigation automationErrors?: AppMetadataErrors icon?: AppIcon From 1584ee4c9554b62aa39da747a779969bfa8f805c Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 17 Jul 2023 14:45:13 +0100 Subject: [PATCH 2/4] Use shorthand typing --- packages/server/src/api/controllers/application.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/api/controllers/application.ts b/packages/server/src/api/controllers/application.ts index 5e12dd4d76..666dacc28c 100644 --- a/packages/server/src/api/controllers/application.ts +++ b/packages/server/src/api/controllers/application.ts @@ -202,7 +202,7 @@ export async function fetch(ctx: UserCtx) { // Get all builder sessions in each app const sessions = await builderSocket?.getRoomSessions(appIds) if (sessions?.length) { - let appSessionMap: { [key: string]: SocketSession[] } = {} + let appSessionMap: Record = {} sessions.forEach(session => { const room = session.room if (!room) { From 1a8da1c0afba51d1dcfbda92da67d026d70cbae2 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 17 Jul 2023 14:56:09 +0100 Subject: [PATCH 3/4] Move app enrichment of user sessions to users SDK --- .../server/src/api/controllers/application.ts | 27 ++----------- packages/server/src/sdk/users/index.ts | 2 + packages/server/src/sdk/users/sessions.ts | 38 +++++++++++++++++++ 3 files changed, 43 insertions(+), 24 deletions(-) create mode 100644 packages/server/src/sdk/users/sessions.ts diff --git a/packages/server/src/api/controllers/application.ts b/packages/server/src/api/controllers/application.ts index 666dacc28c..2c53d3ad35 100644 --- a/packages/server/src/api/controllers/application.ts +++ b/packages/server/src/api/controllers/application.ts @@ -199,31 +199,10 @@ export async function fetch(ctx: UserCtx) { } } - // Get all builder sessions in each app - const sessions = await builderSocket?.getRoomSessions(appIds) - if (sessions?.length) { - let appSessionMap: Record = {} - sessions.forEach(session => { - const room = session.room - if (!room) { - return - } - if (!appSessionMap[room]) { - appSessionMap[room] = [] - } - appSessionMap[room].push(session) - }) - apps.forEach(app => { - const sessions = appSessionMap[app.appId] - if (sessions?.length) { - app.sessions = sessions - } else { - delete app.sessions - } - }) - } + // Enrich apps with all builder user sessions + const enrichedApps = await sdk.users.sessions.enrichApps(apps) - ctx.body = await checkAppMetadata(apps) + ctx.body = await checkAppMetadata(enrichedApps) } export async function fetchAppDefinition(ctx: UserCtx) { diff --git a/packages/server/src/sdk/users/index.ts b/packages/server/src/sdk/users/index.ts index c8431f7e61..c03eab7c2a 100644 --- a/packages/server/src/sdk/users/index.ts +++ b/packages/server/src/sdk/users/index.ts @@ -1,5 +1,7 @@ import * as utils from "./utils" +import * as sessions from "./sessions" export default { ...utils, + sessions, } diff --git a/packages/server/src/sdk/users/sessions.ts b/packages/server/src/sdk/users/sessions.ts new file mode 100644 index 0000000000..c413242277 --- /dev/null +++ b/packages/server/src/sdk/users/sessions.ts @@ -0,0 +1,38 @@ +import { builderSocket } from "../../websockets" +import { App, SocketSession } from "@budibase/types" + +export const enrichApps = async (apps: App[]) => { + // Sessions can only exist for dev app IDs + const devAppIds = apps + .filter((app: any) => app.status === "development") + .map((app: any) => app.appId) + + // Get all sessions for all apps and enrich app list + const sessions = await builderSocket?.getRoomSessions(devAppIds) + if (sessions?.length) { + let appSessionMap: Record = {} + sessions.forEach(session => { + const room = session.room + if (!room) { + return + } + if (!appSessionMap[room]) { + appSessionMap[room] = [] + } + appSessionMap[room].push(session) + }) + return apps.map(app => { + // Shallow clone to avoid mutating original reference + let enriched = { ...app } + const sessions = appSessionMap[app.appId] + if (sessions?.length) { + enriched.sessions = sessions + } else { + delete enriched.sessions + } + return enriched + }) + } else { + return apps + } +} From 9271210b48e1455e28b24c417a8dd94c6e78abd9 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 17 Jul 2023 16:41:48 +0100 Subject: [PATCH 4/4] Update application.ts --- packages/server/src/api/controllers/application.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/api/controllers/application.ts b/packages/server/src/api/controllers/application.ts index 2c53d3ad35..a2448a0384 100644 --- a/packages/server/src/api/controllers/application.ts +++ b/packages/server/src/api/controllers/application.ts @@ -179,7 +179,7 @@ export const addSampleData = async (ctx: UserCtx) => { export async function fetch(ctx: UserCtx) { const dev = ctx.query && ctx.query.status === AppStatus.DEV const all = ctx.query && ctx.query.status === AppStatus.ALL - let apps = (await dbCore.getAllApps({ dev, all })) as App[] + const apps = (await dbCore.getAllApps({ dev, all })) as App[] const appIds = apps .filter((app: any) => app.status === "development")