Merge pull request #9500 from Budibase/fix/add-onboarding-tour-tenant-flag

Added a tenant feature flag for the onboarding tour
This commit is contained in:
deanhannigan 2023-02-03 09:05:21 +00:00 committed by GitHub
commit e7bbd786d0
10 changed files with 122 additions and 15 deletions

View File

@ -76,7 +76,7 @@ affinity: {}
globals: globals:
appVersion: "latest" appVersion: "latest"
budibaseEnv: PRODUCTION budibaseEnv: PRODUCTION
tenantFeatureFlags: "*:LICENSING,*:USER_GROUPS" tenantFeatureFlags: "*:LICENSING,*:USER_GROUPS,*:ONBOARDING_TOUR"
enableAnalytics: "1" enableAnalytics: "1"
sentryDSN: "" sentryDSN: ""
posthogToken: "phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU" posthogToken: "phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU"

View File

@ -10,7 +10,7 @@ declare -a DOCKER_VARS=("APP_PORT" "APPS_URL" "ARCHITECTURE" "BUDIBASE_ENVIRONME
[[ -z "${MINIO_URL}" ]] && export MINIO_URL=http://localhost:9000 [[ -z "${MINIO_URL}" ]] && export MINIO_URL=http://localhost:9000
[[ -z "${NODE_ENV}" ]] && export NODE_ENV=production [[ -z "${NODE_ENV}" ]] && export NODE_ENV=production
[[ -z "${POSTHOG_TOKEN}" ]] && export POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU [[ -z "${POSTHOG_TOKEN}" ]] && export POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU
[[ -z "${TENANT_FEATURE_FLAGS}" ]] && export TENANT_FEATURE_FLAGS="*:LICENSING,*:USER_GROUPS" [[ -z "${TENANT_FEATURE_FLAGS}" ]] && export TENANT_FEATURE_FLAGS="*:LICENSING,*:USER_GROUPS,*:ONBOARDING_TOUR"
[[ -z "${ACCOUNT_PORTAL_URL}" ]] && export ACCOUNT_PORTAL_URL=https://account.budibase.app [[ -z "${ACCOUNT_PORTAL_URL}" ]] && export ACCOUNT_PORTAL_URL=https://account.budibase.app
[[ -z "${REDIS_URL}" ]] && export REDIS_URL=localhost:6379 [[ -z "${REDIS_URL}" ]] && export REDIS_URL=localhost:6379
[[ -z "${SELF_HOSTED}" ]] && export SELF_HOSTED=1 [[ -z "${SELF_HOSTED}" ]] && export SELF_HOSTED=1

View File

@ -6,7 +6,7 @@ import * as tenancy from "../tenancy"
* The env var is formatted as: * The env var is formatted as:
* tenant1:feature1:feature2,tenant2:feature1 * tenant1:feature1:feature2,tenant2:feature1
*/ */
function getFeatureFlags() { export function buildFeatureFlags() {
if (!env.TENANT_FEATURE_FLAGS) { if (!env.TENANT_FEATURE_FLAGS) {
return return
} }
@ -27,8 +27,6 @@ function getFeatureFlags() {
return tenantFeatureFlags return tenantFeatureFlags
} }
const TENANT_FEATURE_FLAGS = getFeatureFlags()
export function isEnabled(featureFlag: string) { export function isEnabled(featureFlag: string) {
const tenantId = tenancy.getTenantId() const tenantId = tenancy.getTenantId()
const flags = getTenantFeatureFlags(tenantId) const flags = getTenantFeatureFlags(tenantId)
@ -36,18 +34,36 @@ export function isEnabled(featureFlag: string) {
} }
export function getTenantFeatureFlags(tenantId: string) { export function getTenantFeatureFlags(tenantId: string) {
const flags = [] let flags: string[] = []
const envFlags = buildFeatureFlags()
if (envFlags) {
const globalFlags = envFlags["*"]
const tenantFlags = envFlags[tenantId] || []
if (TENANT_FEATURE_FLAGS) { // Explicitly exclude tenants from global features if required.
const globalFlags = TENANT_FEATURE_FLAGS["*"] // Prefix the tenant flag with '!'
const tenantFlags = TENANT_FEATURE_FLAGS[tenantId] const tenantOverrides = tenantFlags.reduce(
(acc: string[], flag: string) => {
if (flag.startsWith("!")) {
let stripped = flag.substring(1)
acc.push(stripped)
}
return acc
},
[]
)
if (globalFlags) { if (globalFlags) {
flags.push(...globalFlags) flags.push(...globalFlags)
} }
if (tenantFlags) { if (tenantFlags.length) {
flags.push(...tenantFlags) flags.push(...tenantFlags)
} }
// Purge any tenant specific overrides
flags = flags.filter(flag => {
return tenantOverrides.indexOf(flag) == -1 && !flag.startsWith("!")
})
} }
return flags return flags
@ -57,4 +73,5 @@ export enum TenantFeatureFlag {
LICENSING = "LICENSING", LICENSING = "LICENSING",
GOOGLE_SHEETS = "GOOGLE_SHEETS", GOOGLE_SHEETS = "GOOGLE_SHEETS",
USER_GROUPS = "USER_GROUPS", USER_GROUPS = "USER_GROUPS",
ONBOARDING_TOUR = "ONBOARDING_TOUR",
} }

View File

@ -0,0 +1,85 @@
import {
TenantFeatureFlag,
buildFeatureFlags,
getTenantFeatureFlags,
} from "../"
import env from "../../environment"
const { ONBOARDING_TOUR, LICENSING, USER_GROUPS } = TenantFeatureFlag
describe("featureFlags", () => {
beforeEach(() => {
env._set("TENANT_FEATURE_FLAGS", "")
})
it("Should return no flags when the TENANT_FEATURE_FLAG is empty", async () => {
let features = buildFeatureFlags()
expect(features).toBeUndefined()
})
it("Should generate a map of global and named tenant feature flags from the env value", async () => {
env._set(
"TENANT_FEATURE_FLAGS",
`*:${ONBOARDING_TOUR},tenant1:!${ONBOARDING_TOUR},tenant2:${USER_GROUPS},tenant1:${LICENSING}`
)
const parsedFlags: Record<string, string[]> = {
"*": [ONBOARDING_TOUR],
tenant1: [`!${ONBOARDING_TOUR}`, LICENSING],
tenant2: [USER_GROUPS],
}
let features = buildFeatureFlags()
expect(features).toBeDefined()
expect(features).toEqual(parsedFlags)
})
it("Should add feature flag flag only to explicitly configured tenant", async () => {
env._set(
"TENANT_FEATURE_FLAGS",
`*:${LICENSING},*:${USER_GROUPS},tenant1:${ONBOARDING_TOUR}`
)
let tenant1Flags = getTenantFeatureFlags("tenant1")
let tenant2Flags = getTenantFeatureFlags("tenant2")
expect(tenant1Flags).toBeDefined()
expect(tenant1Flags).toEqual([LICENSING, USER_GROUPS, ONBOARDING_TOUR])
expect(tenant2Flags).toBeDefined()
expect(tenant2Flags).toEqual([LICENSING, USER_GROUPS])
})
})
it("Should exclude tenant1 from global feature flag", async () => {
env._set(
"TENANT_FEATURE_FLAGS",
`*:${LICENSING},*:${ONBOARDING_TOUR},tenant1:!${ONBOARDING_TOUR}`
)
let tenant1Flags = getTenantFeatureFlags("tenant1")
let tenant2Flags = getTenantFeatureFlags("tenant2")
expect(tenant1Flags).toBeDefined()
expect(tenant1Flags).toEqual([LICENSING])
expect(tenant2Flags).toBeDefined()
expect(tenant2Flags).toEqual([LICENSING, ONBOARDING_TOUR])
})
it("Should explicitly add flags to configured tenants only", async () => {
env._set(
"TENANT_FEATURE_FLAGS",
`tenant1:${ONBOARDING_TOUR},tenant1:${LICENSING},tenant2:${LICENSING}`
)
let tenant1Flags = getTenantFeatureFlags("tenant1")
let tenant2Flags = getTenantFeatureFlags("tenant2")
expect(tenant1Flags).toBeDefined()
expect(tenant1Flags).toEqual([ONBOARDING_TOUR, LICENSING])
expect(tenant2Flags).toBeDefined()
expect(tenant2Flags).toEqual([LICENSING])
})

View File

@ -4,6 +4,7 @@ import { get } from "svelte/store"
export const TENANT_FEATURE_FLAGS = { export const TENANT_FEATURE_FLAGS = {
LICENSING: "LICENSING", LICENSING: "LICENSING",
USER_GROUPS: "USER_GROUPS", USER_GROUPS: "USER_GROUPS",
ONBOARDING_TOUR: "ONBOARDING_TOUR",
} }
export const isEnabled = featureFlag => { export const isEnabled = featureFlag => {

View File

@ -2,6 +2,7 @@
import { store, automationStore } from "builderStore" import { store, automationStore } from "builderStore"
import { roles, flags } from "stores/backend" import { roles, flags } from "stores/backend"
import { auth } from "stores/portal" import { auth } from "stores/portal"
import { TENANT_FEATURE_FLAGS, isEnabled } from "helpers/featureFlags"
import { import {
ActionMenu, ActionMenu,
MenuItem, MenuItem,
@ -68,7 +69,10 @@
} }
const initTour = async () => { const initTour = async () => {
if (!$auth.user?.onboardedAt) { if (
!$auth.user?.onboardedAt &&
isEnabled(TENANT_FEATURE_FLAGS.ONBOARDING_TOUR)
) {
// Determine the correct step // Determine the correct step
const activeNav = $layout.children.find(c => $isActive(c.path)) const activeNav = $layout.children.find(c => $isActive(c.path))
const onboardingTour = TOURS[TOUR_KEYS.TOUR_BUILDER_ONBOARDING] const onboardingTour = TOURS[TOUR_KEYS.TOUR_BUILDER_ONBOARDING]

View File

@ -12,7 +12,7 @@ ENV COUCH_DB_URL=https://couchdb.budi.live:5984
ENV BUDIBASE_ENVIRONMENT=PRODUCTION ENV BUDIBASE_ENVIRONMENT=PRODUCTION
ENV SERVICE=app-service ENV SERVICE=app-service
ENV POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU ENV POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU
ENV TENANT_FEATURE_FLAGS=*:LICENSING,*:USER_GROUPS ENV TENANT_FEATURE_FLAGS=*:LICENSING,*:USER_GROUPS,*:ONBOARDING_TOUR
ENV ACCOUNT_PORTAL_URL=https://account.budibase.app ENV ACCOUNT_PORTAL_URL=https://account.budibase.app
# copy files and install dependencies # copy files and install dependencies

View File

@ -44,7 +44,7 @@ async function init() {
BB_ADMIN_USER_EMAIL: "", BB_ADMIN_USER_EMAIL: "",
BB_ADMIN_USER_PASSWORD: "", BB_ADMIN_USER_PASSWORD: "",
PLUGINS_DIR: "", PLUGINS_DIR: "",
TENANT_FEATURE_FLAGS: "*:LICENSING,*:USER_GROUPS", TENANT_FEATURE_FLAGS: "*:LICENSING,*:USER_GROUPS,*:ONBOARDING_TOUR",
} }
let envFile = "" let envFile = ""
Object.keys(envFileJson).forEach(key => { Object.keys(envFileJson).forEach(key => {

View File

@ -23,7 +23,7 @@ ENV NODE_ENV=production
ENV CLUSTER_MODE=${CLUSTER_MODE} ENV CLUSTER_MODE=${CLUSTER_MODE}
ENV SERVICE=worker-service ENV SERVICE=worker-service
ENV POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU ENV POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU
ENV TENANT_FEATURE_FLAGS=*:LICENSING,*:USER_GROUPS ENV TENANT_FEATURE_FLAGS=*:LICENSING,*:USER_GROUPS,*:ONBOARDING_TOUR
ENV ACCOUNT_PORTAL_URL=https://account.budibase.app ENV ACCOUNT_PORTAL_URL=https://account.budibase.app
CMD ["./docker_run.sh"] CMD ["./docker_run.sh"]

View File

@ -28,7 +28,7 @@ async function init() {
APPS_URL: "http://localhost:4001", APPS_URL: "http://localhost:4001",
SERVICE: "worker-service", SERVICE: "worker-service",
DEPLOYMENT_ENVIRONMENT: "development", DEPLOYMENT_ENVIRONMENT: "development",
TENANT_FEATURE_FLAGS: "*:LICENSING,*:USER_GROUPS", TENANT_FEATURE_FLAGS: "*:LICENSING,*:USER_GROUPS,*:ONBOARDING_TOUR",
} }
let envFile = "" let envFile = ""
Object.keys(envFileJson).forEach(key => { Object.keys(envFileJson).forEach(key => {