make cloud onboarding survey disableable via runtime feature flag (#7407)

## Summary

The survey is causing some friction. It incurs about 5-10% dropoff.
Although that number is actually somewhat slow, the information has
mostly served its purpose for now. We can toggle it freely once this PR
is merged.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7407-make-cloud-onboarding-survey-disableable-via-runtime-feature-flag-2c76d73d365081648195f322cb0d7a64)
by [Unito](https://www.unito.io)
This commit is contained in:
Christian Byrne
2025-12-12 13:44:53 -08:00
committed by GitHub
parent 7d326cbc14
commit b5ab45673a
5 changed files with 40 additions and 6 deletions

View File

@@ -13,7 +13,8 @@ export enum ServerFeatureFlag {
MODEL_UPLOAD_BUTTON_ENABLED = 'model_upload_button_enabled',
ASSET_UPDATE_OPTIONS_ENABLED = 'asset_update_options_enabled',
PRIVATE_MODELS_ENABLED = 'private_models_enabled',
SUBSCRIPTION_TIERS_ENABLED = 'subscription_tiers_enabled'
SUBSCRIPTION_TIERS_ENABLED = 'subscription_tiers_enabled',
ONBOARDING_SURVEY_ENABLED = 'onboarding_survey_enabled'
}
/**
@@ -66,6 +67,12 @@ export function useFeatureFlags() {
true // Default to true (new design)
)
)
},
get onboardingSurveyEnabled() {
return (
remoteConfig.value.onboarding_survey_enabled ??
api.getServerFeature(ServerFeatureFlag.ONBOARDING_SURVEY_ENABLED, true)
)
}
})

View File

@@ -225,6 +225,7 @@ import { computed, onMounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { useFeatureFlags } from '@/composables/useFeatureFlags'
import {
getSurveyCompletedStatus,
submitSurvey
@@ -234,14 +235,20 @@ import { useTelemetry } from '@/platform/telemetry'
const { t } = useI18n()
const router = useRouter()
const { flags } = useFeatureFlags()
const onboardingSurveyEnabled = computed(() => flags.onboardingSurveyEnabled)
// Check if survey is already completed on mount
onMounted(async () => {
if (!onboardingSurveyEnabled.value) {
await router.replace({ name: 'cloud-user-check' })
return
}
try {
const surveyCompleted = await getSurveyCompletedStatus()
if (surveyCompleted) {
// User already completed survey, redirect to waitlist
await router.replace({ name: 'cloud-waitlist' })
// User already completed survey, return to onboarding flow
await router.replace({ name: 'cloud-user-check' })
} else {
// Track survey opened event
if (isCloud) {
@@ -342,6 +349,10 @@ const goTo = (step: number, activate: (val: string | number) => void) => {
// Submit
const onSubmitSurvey = async () => {
try {
if (!onboardingSurveyEnabled.value) {
await router.replace({ name: 'cloud-user-check' })
return
}
isSubmitting.value = true
// prepare payload with consistent structure
const payload = {

View File

@@ -30,6 +30,7 @@ import { computed, nextTick, ref } from 'vue'
import { useRouter } from 'vue-router'
import { useErrorHandling } from '@/composables/useErrorHandling'
import { useFeatureFlags } from '@/composables/useFeatureFlags'
import {
getSurveyCompletedStatus,
getUserCloudStatus
@@ -40,6 +41,10 @@ import CloudSurveyViewSkeleton from './skeletons/CloudSurveyViewSkeleton.vue'
const router = useRouter()
const { wrapWithErrorHandlingAsync } = useErrorHandling()
const { flags } = useFeatureFlags()
const onboardingSurveyEnabled = computed(
() => flags.onboardingSurveyEnabled ?? true
)
const skeletonType = ref<'login' | 'survey' | 'waitlist' | 'loading'>('loading')
@@ -51,6 +56,11 @@ const {
wrapWithErrorHandlingAsync(async () => {
await nextTick()
if (!onboardingSurveyEnabled.value) {
await router.replace({ path: '/' })
return
}
const [cloudUserStats, surveyStatus] = await Promise.all([
getUserCloudStatus(),
getSurveyCompletedStatus()
@@ -63,7 +73,7 @@ const {
return
}
// Survey is required for all users
// Survey is required for all users when feature flag is enabled
if (!surveyStatus) {
skeletonType.value = 'survey'
await router.replace({ name: 'cloud-survey' })

View File

@@ -38,6 +38,7 @@ export type RemoteConfig = {
asset_update_options_enabled?: boolean
private_models_enabled?: boolean
subscription_tiers_enabled?: boolean
onboarding_survey_enabled?: boolean
stripe_publishable_key?: string
stripe_pricing_table_id?: string
}

View File

@@ -7,6 +7,7 @@ import {
} from 'vue-router'
import type { RouteLocationNormalized } from 'vue-router'
import { useFeatureFlags } from '@/composables/useFeatureFlags'
import { isCloud } from '@/platform/distribution/types'
import { useDialogService } from '@/services/dialogService'
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
@@ -89,6 +90,7 @@ installPreservedQueryTracker(router, [
])
if (isCloud) {
const { flags } = useFeatureFlags()
const PUBLIC_ROUTE_NAMES = new Set([
'cloud-login',
'cloud-signup',
@@ -165,9 +167,12 @@ if (isCloud) {
return next({ name: 'cloud-login' })
}
// User is logged in - check if they need onboarding
// User is logged in - check if they need onboarding (when enabled)
// For root path, check actual user status to handle waitlisted users
if (!isElectron() && isLoggedIn && to.path === '/') {
if (!flags.onboardingSurveyEnabled) {
return next()
}
// Import auth functions dynamically to avoid circular dependency
const { getSurveyCompletedStatus } =
await import('@/platform/cloud/onboarding/auth')
@@ -175,7 +180,7 @@ if (isCloud) {
// Check user's actual status
const surveyCompleted = await getSurveyCompletedStatus()
// Survey is required for all users regardless of whitelist status
// Survey is required for all users (when feature flag enabled)
if (!surveyCompleted) {
return next({ name: 'cloud-survey' })
}