1
0
Fork 0
mirror of synced 2024-10-01 09:38:55 +13:00

Merge pull request #11186 from Budibase/app-list-improvements

Show all users editing an app in the app list
This commit is contained in:
Andrew Kingston 2023-07-18 08:12:50 +01:00 committed by GitHub
commit d3ab2a31d0
10 changed files with 67 additions and 10 deletions

View file

@ -2,7 +2,7 @@
import { Icon } from "@budibase/bbui" import { Icon } from "@budibase/bbui"
import { createEventDispatcher, getContext } from "svelte" import { createEventDispatcher, getContext } from "svelte"
import { helpers } from "@budibase/shared-core" 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 icon
export let withArrow = false export let withArrow = false

View file

@ -2,12 +2,12 @@
import { Heading, Body, Button, Icon } from "@budibase/bbui" import { Heading, Body, Button, Icon } from "@budibase/bbui"
import { processStringSync } from "@budibase/string-templates" import { processStringSync } from "@budibase/string-templates"
import { goto } from "@roxi/routify" import { goto } from "@roxi/routify"
import { UserAvatar } from "@budibase/frontend-core" import { UserAvatars } from "@budibase/frontend-core"
export let app export let app
export let lockedAction export let lockedAction
$: editing = app?.lockedBy != null $: editing = app.sessions?.length
const handleDefaultClick = () => { const handleDefaultClick = () => {
if (window.innerWidth < 640) { if (window.innerWidth < 640) {
@ -41,7 +41,7 @@
<div class="updated"> <div class="updated">
{#if editing} {#if editing}
Currently editing Currently editing
<UserAvatar user={app.lockedBy} /> <UserAvatars users={app.sessions} />
{:else if app.updatedAt} {:else if app.updatedAt}
{processStringSync("Updated {{ duration time 'millisecond' }} ago", { {processStringSync("Updated {{ duration time 'millisecond' }} ago", {
time: new Date().getTime() - new Date(app.updatedAt).getTime(), time: new Date().getTime() - new Date(app.updatedAt).getTime(),

View file

@ -26,7 +26,7 @@
import TourWrap from "components/portal/onboarding/TourWrap.svelte" import TourWrap from "components/portal/onboarding/TourWrap.svelte"
import TourPopover from "components/portal/onboarding/TourPopover.svelte" import TourPopover from "components/portal/onboarding/TourPopover.svelte"
import BuilderSidePanel from "./_components/BuilderSidePanel.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 { TOUR_KEYS, TOURS } from "components/portal/onboarding/tours.js"
import PreviewOverlay from "./_components/PreviewOverlay.svelte" import PreviewOverlay from "./_components/PreviewOverlay.svelte"

View file

@ -2,4 +2,5 @@ export { default as SplitPage } from "./SplitPage.svelte"
export { default as TestimonialPage } from "./TestimonialPage.svelte" export { default as TestimonialPage } from "./TestimonialPage.svelte"
export { default as Testimonial } from "./Testimonial.svelte" export { default as Testimonial } from "./Testimonial.svelte"
export { default as UserAvatar } from "./UserAvatar.svelte" export { default as UserAvatar } from "./UserAvatar.svelte"
export { default as UserAvatars } from "./UserAvatars.svelte"
export { Grid } from "./grid" export { Grid } from "./grid"

View file

@ -49,6 +49,7 @@ import {
MigrationType, MigrationType,
PlanType, PlanType,
Screen, Screen,
SocketSession,
UserCtx, UserCtx,
} from "@budibase/types" } from "@budibase/types"
import { BASE_LAYOUT_PROP_IDS } from "../../constants/layouts" import { BASE_LAYOUT_PROP_IDS } from "../../constants/layouts"
@ -183,6 +184,7 @@ export async function fetch(ctx: UserCtx) {
const appIds = apps const appIds = apps
.filter((app: any) => app.status === "development") .filter((app: any) => app.status === "development")
.map((app: any) => app.appId) .map((app: any) => app.appId)
// get the locks for all the dev apps // get the locks for all the dev apps
if (dev || all) { if (dev || all) {
const locks = await getLocksById(appIds) const locks = await getLocksById(appIds)
@ -197,7 +199,10 @@ export async function fetch(ctx: UserCtx) {
} }
} }
ctx.body = await checkAppMetadata(apps) // Enrich apps with all builder user sessions
const enrichedApps = await sdk.users.sessions.enrichApps(apps)
ctx.body = await checkAppMetadata(enrichedApps)
} }
export async function fetchAppDefinition(ctx: UserCtx) { export async function fetchAppDefinition(ctx: UserCtx) {

View file

@ -1,5 +1,7 @@
import * as utils from "./utils" import * as utils from "./utils"
import * as sessions from "./sessions"
export default { export default {
...utils, ...utils,
sessions,
} }

View file

@ -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<string, SocketSession[]> = {}
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
}
}

View file

@ -125,9 +125,18 @@ export class BaseSocket {
} }
// Gets an array of all redis keys of users inside a certain room // Gets an array of all redis keys of users inside a certain room
async getRoomSessionIds(room: string): Promise<string[]> { async getRoomSessionIds(room: string | string[]): Promise<string[]> {
const keys = await this.redisClient?.get(this.getRoomKey(room)) if (Array.isArray(room)) {
return keys || [] 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. // 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 // Gets a list of all users inside a certain room
async getRoomSessions(room?: string): Promise<SocketSession[]> { async getRoomSessions(room?: string | string[]): Promise<SocketSession[]> {
if (room) { if (room) {
const sessionIds = await this.getRoomSessionIds(room) const sessionIds = await this.getRoomSessionIds(room)
const keys = sessionIds.map(this.getSessionKey.bind(this)) const keys = sessionIds.map(this.getSessionKey.bind(this))

View file

@ -1,4 +1,5 @@
import { User, Document } from "../" import { User, Document } from "../"
import { SocketSession } from "../../sdk"
export type AppMetadataErrors = { [key: string]: string[] } export type AppMetadataErrors = { [key: string]: string[] }
@ -17,6 +18,7 @@ export interface App extends Document {
customTheme?: AppCustomTheme customTheme?: AppCustomTheme
revertableVersion?: string revertableVersion?: string
lockedBy?: User lockedBy?: User
sessions?: SocketSession[]
navigation?: AppNavigation navigation?: AppNavigation
automationErrors?: AppMetadataErrors automationErrors?: AppMetadataErrors
icon?: AppIcon icon?: AppIcon