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:
commit
e7bbd786d0
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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",
|
||||||
}
|
}
|
||||||
|
|
|
@ -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])
|
||||||
|
})
|
|
@ -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 => {
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 => {
|
||||||
|
|
|
@ -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"]
|
||||||
|
|
|
@ -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 => {
|
||||||
|
|
Loading…
Reference in New Issue