Merge pull request #15174 from Budibase/ts-portal-apps-store

Convert portal apps store to typescript
This commit is contained in:
Andrew Kingston 2024-12-17 10:32:53 +00:00 committed by GitHub
commit e3d2041d73
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 77 additions and 39 deletions

View File

@ -1,8 +1,17 @@
import { writable } from "svelte/store" import { writable, Writable } from "svelte/store"
export default class BudiStore { interface BudiStoreOpts {
constructor(init, opts) { debug?: boolean
const store = writable({ ...init }) }
export default class BudiStore<T> implements Writable<T> {
store: Writable<T>
subscribe: Writable<T>["subscribe"]
update: Writable<T>["update"]
set: Writable<T>["set"]
constructor(init: T, opts?: BudiStoreOpts) {
const store = writable<T>({ ...init })
/** /**
* Internal Svelte store * Internal Svelte store

View File

@ -1,19 +1,39 @@
import { derived } from "svelte/store" import { derived } from "svelte/store"
// @ts-ignore
import { AppStatus } from "constants" import { AppStatus } from "constants"
import { API } from "api" import { API } from "api"
import { auth } from "./auth" import { auth } from "./auth"
import BudiStore from "../BudiStore" // move this import BudiStore from "../BudiStore"
import { App, UpdateAppRequest, User } from "@budibase/types"
// properties that should always come from the dev app, not the deployed interface AppIdentifierMetadata {
const DEV_PROPS = ["updatedBy", "updatedAt"] devId?: string
devRev?: string
export const INITIAL_APPS_STATE = { prodId?: string
apps: [], prodRev?: string
} }
export class AppsStore extends BudiStore { interface AppUIMetadata {
deployed: boolean
lockedYou: boolean
lockedOther: boolean
favourite: boolean
}
interface StoreApp extends App, AppIdentifierMetadata {}
interface EnrichedApp extends StoreApp, AppUIMetadata {}
interface PortalAppsStore {
apps: StoreApp[]
sortBy?: string
}
export class AppsStore extends BudiStore<PortalAppsStore> {
constructor() { constructor() {
super({ ...INITIAL_APPS_STATE }) super({
apps: [],
})
this.extractAppId = this.extractAppId.bind(this) this.extractAppId = this.extractAppId.bind(this)
this.getProdAppID = this.getProdAppID.bind(this) this.getProdAppID = this.getProdAppID.bind(this)
@ -22,12 +42,12 @@ export class AppsStore extends BudiStore {
this.save = this.save.bind(this) this.save = this.save.bind(this)
} }
extractAppId(id) { extractAppId(appId?: string) {
const split = id?.split("_") || [] const split = appId?.split("_") || []
return split.length ? split[split.length - 1] : null return split.length ? split[split.length - 1] : null
} }
getProdAppID(appId) { getProdAppID(appId: string) {
if (!appId) { if (!appId) {
return appId return appId
} }
@ -47,15 +67,15 @@ export class AppsStore extends BudiStore {
return `app${separator}${rest}` return `app${separator}${rest}`
} }
updateSort(sortBy) { async updateSort(sortBy: string) {
this.update(state => ({ this.update(state => ({
...state, ...state,
sortBy, sortBy,
})) }))
this.updateUserSort(sortBy) await this.updateUserSort(sortBy)
} }
async updateUserSort(sortBy) { async updateUserSort(sortBy: string) {
try { try {
await auth.updateSelf({ appSort: sortBy }) await auth.updateSelf({ appSort: sortBy })
} catch (err) { } catch (err) {
@ -64,16 +84,19 @@ export class AppsStore extends BudiStore {
} }
async load() { async load() {
const json = await API.getApps() const json = (await API.getApps()) as App[]
if (Array.isArray(json)) { if (Array.isArray(json)) {
// Merge apps into one sensible list // Merge apps into one sensible list
let appMap = {} let appMap: Record<string, StoreApp> = {}
let devApps = json.filter(app => app.status === AppStatus.DEV) let devApps = json.filter(app => app.status === AppStatus.DEV)
let deployedApps = json.filter(app => app.status === AppStatus.DEPLOYED) let deployedApps = json.filter(app => app.status === AppStatus.DEPLOYED)
// First append all dev app version // First append all dev app version
devApps.forEach(app => { devApps.forEach(app => {
const id = this.extractAppId(app.appId) const id = this.extractAppId(app.appId)
if (!id) {
return
}
appMap[id] = { appMap[id] = {
...app, ...app,
devId: app.appId, devId: app.appId,
@ -84,20 +107,22 @@ export class AppsStore extends BudiStore {
// Then merge with all prod app versions // Then merge with all prod app versions
deployedApps.forEach(app => { deployedApps.forEach(app => {
const id = this.extractAppId(app.appId) const id = this.extractAppId(app.appId)
if (!id) {
return
}
// Skip any deployed apps which don't have a dev counterpart // Skip any deployed apps which don't have a dev counterpart
if (!appMap[id]) { if (!appMap[id]) {
return return
} }
let devProps = {} // Extract certain properties from the dev app to override the prod app
let devProps: Pick<App, "updatedBy" | "updatedAt"> = {}
if (appMap[id]) { if (appMap[id]) {
const entries = Object.entries(appMap[id]).filter( devProps = {
([key]) => DEV_PROPS.indexOf(key) !== -1 updatedBy: appMap[id].updatedBy,
) updatedAt: appMap[id].updatedAt,
entries.forEach(entry => { }
devProps[entry[0]] = entry[1]
})
} }
appMap[id] = { appMap[id] = {
...appMap[id], ...appMap[id],
@ -111,7 +136,10 @@ export class AppsStore extends BudiStore {
// Transform into an array and clean up // Transform into an array and clean up
const apps = Object.values(appMap) const apps = Object.values(appMap)
apps.forEach(app => { apps.forEach(app => {
app.appId = this.extractAppId(app.devId) const appId = this.extractAppId(app.devId)
if (appId) {
app.appId = appId
}
delete app._id delete app._id
delete app._rev delete app._rev
}) })
@ -127,7 +155,7 @@ export class AppsStore extends BudiStore {
} }
} }
async save(appId, value) { async save(appId: string, value: UpdateAppRequest) {
await API.saveAppMetadata(appId, value) await API.saveAppMetadata(appId, value)
this.update(state => { this.update(state => {
const updatedAppIndex = state.apps.findIndex( const updatedAppIndex = state.apps.findIndex(
@ -146,22 +174,23 @@ export class AppsStore extends BudiStore {
export const appsStore = new AppsStore() export const appsStore = new AppsStore()
export const sortBy = derived([appsStore, auth], ([$store, $auth]) => { export const sortBy = derived([appsStore, auth], ([$store, $auth]) => {
return $store.sortBy || $auth.user?.appSort || "name" return $store.sortBy || ($auth.user as User | null)?.appSort || "name"
}) })
// Centralise any logic that enriches the apps list // Centralise any logic that enriches the apps list
export const enrichedApps = derived( export const enrichedApps = derived(
[appsStore, auth, sortBy], [appsStore, auth, sortBy],
([$store, $auth, $sortBy]) => { ([$store, $auth, $sortBy]) => {
const enrichedApps = $store.apps const enrichedApps: EnrichedApp[] = $store.apps.map(app => {
? $store.apps.map(app => ({ const user = $auth.user as User | null
...app, return {
deployed: app.status === AppStatus.DEPLOYED, ...app,
lockedYou: app.lockedBy && app.lockedBy.email === $auth.user?.email, deployed: app.status === AppStatus.DEPLOYED,
lockedOther: app.lockedBy && app.lockedBy.email !== $auth.user?.email, lockedYou: app.lockedBy != null && app.lockedBy.email === user?.email,
favourite: $auth.user?.appFavourites?.includes(app.appId), lockedOther: app.lockedBy != null && app.lockedBy.email !== user?.email,
})) favourite: !!user?.appFavourites?.includes(app.appId),
: [] }
})
if ($sortBy === "status") { if ($sortBy === "status") {
return enrichedApps.sort((a, b) => { return enrichedApps.sort((a, b) => {