1
0
Fork 0
mirror of synced 2024-09-20 11:27:56 +12:00

Merge pull request #14379 from Budibase/feature-flags-cache

Cache feature flags per-request, set default values flag to false by default.
This commit is contained in:
Sam Rose 2024-08-14 16:03:10 +01:00 committed by GitHub
commit 5f9ee60c4c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 46 additions and 8 deletions

View file

@ -375,3 +375,22 @@ export function getCurrentContext(): ContextMap | undefined {
return undefined
}
}
export function getFeatureFlags<T extends Record<string, any>>(
key: string
): T | undefined {
const context = getCurrentContext()
if (!context) {
return undefined
}
return context.featureFlagCache?.[key] as T
}
export function setFeatureFlags(key: string, value: Record<string, any>) {
const context = getCurrentContext()
if (!context) {
return
}
context.featureFlagCache ??= {}
context.featureFlagCache[key] = value
}

View file

@ -18,4 +18,7 @@ export type ContextMap = {
oauthClient: OAuth2Client
clients: Record<string, GoogleSpreadsheet>
}
featureFlagCache?: {
[key: string]: Record<string, any>
}
}

View file

@ -18,6 +18,10 @@ export function init(opts?: PostHogOptions) {
}
}
export function shutdown() {
posthog?.shutdown()
}
export abstract class Flag<T> {
static boolean(defaultValue: boolean): Flag<boolean> {
return new BooleanFlag(defaultValue)
@ -87,7 +91,14 @@ class NumberFlag extends Flag<number> {
}
export class FlagSet<V extends Flag<any>, T extends { [key: string]: V }> {
constructor(private readonly flagSchema: T) {}
// This is used to safely cache flags sets in the current request context.
// Because multiple sets could theoretically exist, we don't want the cache of
// one to leak into another.
private readonly setId: string
constructor(private readonly flagSchema: T) {
this.setId = crypto.randomUUID()
}
defaults(): FlagValues<T> {
return Object.keys(this.flagSchema).reduce((acc, key) => {
@ -119,6 +130,12 @@ export class FlagSet<V extends Flag<any>, T extends { [key: string]: V }> {
async fetch(ctx?: UserCtx): Promise<FlagValues<T>> {
return await tracer.trace("features.fetch", async span => {
const cachedFlags = context.getFeatureFlags<FlagValues<T>>(this.setId)
if (cachedFlags) {
span?.addTags({ fromCache: true })
return cachedFlags
}
const tags: Record<string, any> = {}
const flagValues = this.defaults()
const currentTenantId = context.getTenantId()
@ -187,10 +204,7 @@ export class FlagSet<V extends Flag<any>, T extends { [key: string]: V }> {
tags[`identity.tenantId`] = identity?.tenantId
tags[`identity._id`] = identity?._id
// Until we're confident this performs well, we're only enabling it in QA
// and test environments.
const usePosthog = env.isTest() || env.isQA()
if (usePosthog && posthog && identity?.type === IdentityType.USER) {
if (posthog && identity?.type === IdentityType.USER) {
tags[`readFromPostHog`] = true
const personProperties: Record<string, string> = {}
@ -204,7 +218,6 @@ export class FlagSet<V extends Flag<any>, T extends { [key: string]: V }> {
personProperties,
}
)
console.log("posthog flags", JSON.stringify(posthogFlags))
for (const [name, value] of Object.entries(posthogFlags.featureFlags)) {
if (!this.isFlagName(name)) {
@ -236,6 +249,7 @@ export class FlagSet<V extends Flag<any>, T extends { [key: string]: V }> {
}
}
context.setFeatureFlags(this.setId, flagValues)
for (const [key, value] of Object.entries(flagValues)) {
tags[`flags.${key}.value`] = value
}
@ -255,5 +269,5 @@ export const flags = new FlagSet({
GOOGLE_SHEETS: Flag.boolean(false),
USER_GROUPS: Flag.boolean(false),
ONBOARDING_TOUR: Flag.boolean(false),
DEFAULT_VALUES: Flag.boolean(true),
DEFAULT_VALUES: Flag.boolean(false),
})

View file

@ -1,5 +1,5 @@
import { IdentityContext, IdentityType, UserCtx } from "@budibase/types"
import { Flag, FlagSet, FlagValues, init } from "../"
import { Flag, FlagSet, FlagValues, init, shutdown } from "../"
import { context } from "../.."
import environment, { withEnv } from "../../environment"
import nodeFetch from "node-fetch"
@ -197,6 +197,8 @@ describe("feature flags", () => {
throw new Error("No expected value")
}
})
shutdown()
})
}
)