diff --git a/src/__tests__/surveys.test.ts b/src/__tests__/surveys.test.ts
index 1a465f73d..fc604e874 100644
--- a/src/__tests__/surveys.test.ts
+++ b/src/__tests__/surveys.test.ts
@@ -1,27 +1,26 @@
///
-import { PostHogSurveys } from '../posthog-surveys'
-import {
- SurveyType,
- SurveyQuestionType,
- Survey,
- MultipleSurveyQuestion,
- SurveyQuestionBranchingType,
- SurveyQuestion,
- RatingSurveyQuestion,
-} from '../posthog-surveys-types'
+import { generateSurveys, getNextSurveyStep } from '../extensions/surveys'
import {
canActivateRepeatedly,
getDisplayOrderChoices,
getDisplayOrderQuestions,
} from '../extensions/surveys/surveys-utils'
-import { PostHogPersistence } from '../posthog-persistence'
import { PostHog } from '../posthog-core'
+import { PostHogPersistence } from '../posthog-persistence'
+import { PostHogSurveys } from '../posthog-surveys'
+import {
+ MultipleSurveyQuestion,
+ RatingSurveyQuestion,
+ Survey,
+ SurveyQuestion,
+ SurveyQuestionBranchingType,
+ SurveyQuestionType,
+ SurveyType,
+} from '../posthog-surveys-types'
import { DecideResponse, PostHogConfig, Properties } from '../types'
-import { window } from '../utils/globals'
+import { assignableWindow, window } from '../utils/globals'
import { RequestRouter } from '../utils/request-router'
-import { assignableWindow } from '../utils/globals'
-import { generateSurveys } from '../extensions/surveys'
describe('surveys', () => {
let config: PostHogConfig
@@ -844,6 +843,9 @@ describe('surveys', () => {
})
describe('branching logic', () => {
+ beforeEach(() => {
+ surveys.getNextSurveyStep = getNextSurveyStep
+ })
const survey: Survey = {
name: 'My survey',
description: '',
diff --git a/src/entrypoints/surveys-preview.es.ts b/src/entrypoints/surveys-preview.es.ts
index f716dcbfc..627e129b4 100644
--- a/src/entrypoints/surveys-preview.es.ts
+++ b/src/entrypoints/surveys-preview.es.ts
@@ -1,2 +1 @@
-export { renderFeedbackWidgetPreview, renderSurveysPreview } from '../extensions/surveys'
-export { getNextSurveyStep } from '../posthog-surveys'
+export { getNextSurveyStep, renderFeedbackWidgetPreview, renderSurveysPreview } from '../extensions/surveys'
diff --git a/src/entrypoints/surveys.ts b/src/entrypoints/surveys.ts
index 4baba2ca7..0a0fecc98 100644
--- a/src/entrypoints/surveys.ts
+++ b/src/entrypoints/surveys.ts
@@ -1,11 +1,12 @@
-import { generateSurveys } from '../extensions/surveys'
+import { generateSurveys, getNextSurveyStep } from '../extensions/surveys'
-import { assignableWindow } from '../utils/globals'
import { canActivateRepeatedly } from '../extensions/surveys/surveys-utils'
+import { assignableWindow } from '../utils/globals'
assignableWindow.__PosthogExtensions__ = assignableWindow.__PosthogExtensions__ || {}
assignableWindow.__PosthogExtensions__.canActivateRepeatedly = canActivateRepeatedly
assignableWindow.__PosthogExtensions__.generateSurveys = generateSurveys
+assignableWindow.__PosthogExtensions__.getNextSurveyStep = getNextSurveyStep
// this used to be directly on window, but we moved it to __PosthogExtensions__
// it is still on window for backwards compatibility
diff --git a/src/extensions/surveys.tsx b/src/extensions/surveys.tsx
index 402da1bb4..d848e77d7 100644
--- a/src/extensions/surveys.tsx
+++ b/src/extensions/surveys.tsx
@@ -759,3 +759,106 @@ const getQuestionComponent = ({
return
}
+
+function getRatingBucketForResponseValue(responseValue: number, scale: number) {
+ if (scale === 3) {
+ if (responseValue < 1 || responseValue > 3) {
+ throw new Error('The response must be in range 1-3')
+ }
+
+ return responseValue === 1 ? 'negative' : responseValue === 2 ? 'neutral' : 'positive'
+ } else if (scale === 5) {
+ if (responseValue < 1 || responseValue > 5) {
+ throw new Error('The response must be in range 1-5')
+ }
+
+ return responseValue <= 2 ? 'negative' : responseValue === 3 ? 'neutral' : 'positive'
+ } else if (scale === 7) {
+ if (responseValue < 1 || responseValue > 7) {
+ throw new Error('The response must be in range 1-7')
+ }
+
+ return responseValue <= 3 ? 'negative' : responseValue === 4 ? 'neutral' : 'positive'
+ } else if (scale === 10) {
+ if (responseValue < 0 || responseValue > 10) {
+ throw new Error('The response must be in range 0-10')
+ }
+
+ return responseValue <= 6 ? 'detractors' : responseValue <= 8 ? 'passives' : 'promoters'
+ }
+
+ throw new Error('The scale must be one of: 3, 5, 7, 10')
+}
+
+export function getNextSurveyStep(
+ survey: Survey,
+ currentQuestionIndex: number,
+ response: string | string[] | number | null
+) {
+ const question = survey.questions[currentQuestionIndex]
+ const nextQuestionIndex = currentQuestionIndex + 1
+
+ if (!question.branching?.type) {
+ if (currentQuestionIndex === survey.questions.length - 1) {
+ return SurveyQuestionBranchingType.End
+ }
+
+ return nextQuestionIndex
+ }
+
+ if (question.branching.type === SurveyQuestionBranchingType.End) {
+ return SurveyQuestionBranchingType.End
+ } else if (question.branching.type === SurveyQuestionBranchingType.SpecificQuestion) {
+ if (Number.isInteger(question.branching.index)) {
+ return question.branching.index
+ }
+ } else if (question.branching.type === SurveyQuestionBranchingType.ResponseBased) {
+ // Single choice
+ if (question.type === SurveyQuestionType.SingleChoice) {
+ // :KLUDGE: for now, look up the choiceIndex based on the response
+ // TODO: once QuestionTypes.MultipleChoiceQuestion is refactored, pass the selected choiceIndex into this method
+ const selectedChoiceIndex = question.choices.indexOf(`${response}`)
+
+ if (question.branching?.responseValues?.hasOwnProperty(selectedChoiceIndex)) {
+ const nextStep = question.branching.responseValues[selectedChoiceIndex]
+
+ // Specific question
+ if (Number.isInteger(nextStep)) {
+ return nextStep
+ }
+
+ if (nextStep === SurveyQuestionBranchingType.End) {
+ return SurveyQuestionBranchingType.End
+ }
+
+ return nextQuestionIndex
+ }
+ } else if (question.type === SurveyQuestionType.Rating) {
+ if (typeof response !== 'number' || !Number.isInteger(response)) {
+ throw new Error('The response type must be an integer')
+ }
+
+ const ratingBucket = getRatingBucketForResponseValue(response, question.scale)
+
+ if (question.branching?.responseValues?.hasOwnProperty(ratingBucket)) {
+ const nextStep = question.branching.responseValues[ratingBucket]
+
+ // Specific question
+ if (Number.isInteger(nextStep)) {
+ return nextStep
+ }
+
+ if (nextStep === SurveyQuestionBranchingType.End) {
+ return SurveyQuestionBranchingType.End
+ }
+
+ return nextQuestionIndex
+ }
+ }
+
+ return nextQuestionIndex
+ }
+
+ logger.warn('Falling back to next question index due to unexpected branching type')
+ return nextQuestionIndex
+}
diff --git a/src/posthog-surveys.ts b/src/posthog-surveys.ts
index 6e1b167e7..816553ecc 100644
--- a/src/posthog-surveys.ts
+++ b/src/posthog-surveys.ts
@@ -1,13 +1,7 @@
import { SURVEYS } from './constants'
import { getSurveySeenStorageKeys } from './extensions/surveys/surveys-utils'
import { PostHog } from './posthog-core'
-import {
- Survey,
- SurveyCallback,
- SurveyQuestionBranchingType,
- SurveyQuestionType,
- SurveyUrlMatchType,
-} from './posthog-surveys-types'
+import { Survey, SurveyCallback, SurveyUrlMatchType } from './posthog-surveys-types'
import { RemoteConfig } from './types'
import { assignableWindow, document, window } from './utils/globals'
import { createLogger } from './utils/logger'
@@ -28,109 +22,6 @@ export const surveyUrlValidationMap: Record window?.location.href !== conditionsUrl,
}
-function getRatingBucketForResponseValue(responseValue: number, scale: number) {
- if (scale === 3) {
- if (responseValue < 1 || responseValue > 3) {
- throw new Error('The response must be in range 1-3')
- }
-
- return responseValue === 1 ? 'negative' : responseValue === 2 ? 'neutral' : 'positive'
- } else if (scale === 5) {
- if (responseValue < 1 || responseValue > 5) {
- throw new Error('The response must be in range 1-5')
- }
-
- return responseValue <= 2 ? 'negative' : responseValue === 3 ? 'neutral' : 'positive'
- } else if (scale === 7) {
- if (responseValue < 1 || responseValue > 7) {
- throw new Error('The response must be in range 1-7')
- }
-
- return responseValue <= 3 ? 'negative' : responseValue === 4 ? 'neutral' : 'positive'
- } else if (scale === 10) {
- if (responseValue < 0 || responseValue > 10) {
- throw new Error('The response must be in range 0-10')
- }
-
- return responseValue <= 6 ? 'detractors' : responseValue <= 8 ? 'passives' : 'promoters'
- }
-
- throw new Error('The scale must be one of: 3, 5, 7, 10')
-}
-
-export function getNextSurveyStep(
- survey: Survey,
- currentQuestionIndex: number,
- response: string | string[] | number | null
-) {
- const question = survey.questions[currentQuestionIndex]
- const nextQuestionIndex = currentQuestionIndex + 1
-
- if (!question.branching?.type) {
- if (currentQuestionIndex === survey.questions.length - 1) {
- return SurveyQuestionBranchingType.End
- }
-
- return nextQuestionIndex
- }
-
- if (question.branching.type === SurveyQuestionBranchingType.End) {
- return SurveyQuestionBranchingType.End
- } else if (question.branching.type === SurveyQuestionBranchingType.SpecificQuestion) {
- if (Number.isInteger(question.branching.index)) {
- return question.branching.index
- }
- } else if (question.branching.type === SurveyQuestionBranchingType.ResponseBased) {
- // Single choice
- if (question.type === SurveyQuestionType.SingleChoice) {
- // :KLUDGE: for now, look up the choiceIndex based on the response
- // TODO: once QuestionTypes.MultipleChoiceQuestion is refactored, pass the selected choiceIndex into this method
- const selectedChoiceIndex = question.choices.indexOf(`${response}`)
-
- if (question.branching?.responseValues?.hasOwnProperty(selectedChoiceIndex)) {
- const nextStep = question.branching.responseValues[selectedChoiceIndex]
-
- // Specific question
- if (Number.isInteger(nextStep)) {
- return nextStep
- }
-
- if (nextStep === SurveyQuestionBranchingType.End) {
- return SurveyQuestionBranchingType.End
- }
-
- return nextQuestionIndex
- }
- } else if (question.type === SurveyQuestionType.Rating) {
- if (typeof response !== 'number' || !Number.isInteger(response)) {
- throw new Error('The response type must be an integer')
- }
-
- const ratingBucket = getRatingBucketForResponseValue(response, question.scale)
-
- if (question.branching?.responseValues?.hasOwnProperty(ratingBucket)) {
- const nextStep = question.branching.responseValues[ratingBucket]
-
- // Specific question
- if (Number.isInteger(nextStep)) {
- return nextStep
- }
-
- if (nextStep === SurveyQuestionBranchingType.End) {
- return SurveyQuestionBranchingType.End
- }
-
- return nextQuestionIndex
- }
- }
-
- return nextQuestionIndex
- }
-
- logger.warn('Falling back to next question index due to unexpected branching type')
- return nextQuestionIndex
-}
-
export class PostHogSurveys {
private _decideServerResponse?: boolean
public _surveyEventReceiver: SurveyEventReceiver | null
@@ -302,7 +193,13 @@ export class PostHogSurveys {
return this.instance.featureFlags.isFeatureEnabled(value)
})
}
- getNextSurveyStep = getNextSurveyStep
+ getNextSurveyStep(survey: Survey, currentQuestionIndex: number, response: string | string[] | number | null) {
+ if (isNullish(assignableWindow.__PosthogExtensions__?.getNextSurveyStep)) {
+ logger.warn('init was not called')
+ return 0
+ }
+ return assignableWindow.__PosthogExtensions__.getNextSurveyStep(survey, currentQuestionIndex, response)
+ }
// this method is lazily loaded onto the window to avoid loading preact and other dependencies if surveys is not enabled
private _canActivateRepeatedly(survey: Survey) {
diff --git a/src/utils/globals.ts b/src/utils/globals.ts
index 3daa7bb48..9bcbb9879 100644
--- a/src/utils/globals.ts
+++ b/src/utils/globals.ts
@@ -78,6 +78,7 @@ interface PostHogExtensions {
rrwebPlugins?: { getRecordConsolePlugin: any; getRecordNetworkPlugin?: any }
canActivateRepeatedly?: (survey: any) => boolean
generateSurveys?: (posthog: PostHog) => any | undefined
+ getNextSurveyStep?: (survey: any, currentQuestionIndex: number, response: string | string[] | number | null) => any
postHogWebVitalsCallbacks?: {
onLCP: (metric: any) => void
onCLS: (metric: any) => void