Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Survey actions type #1589

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/__tests__/extensions/surveys/action-matcher.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/// <reference lib="dom" />

import { ActionType, ActionStepStringMatching } from '../../../posthog-surveys-types'
import { SurveyActionType, ActionStepStringMatching } from '../../../posthog-surveys-types'
import { PostHogPersistence } from '../../../posthog-persistence'
import { PostHog } from '../../../posthog-core'
import { CaptureResult, PostHogConfig } from '../../../types'
Expand Down Expand Up @@ -45,7 +45,7 @@ describe('action-matcher', () => {
eventName: string,
currentUrl?: string,
urlMatch?: ActionStepStringMatching
): ActionType => {
): SurveyActionType => {
return {
id: id,
name: `${eventName || 'user defined '} action`,
Expand All @@ -68,7 +68,7 @@ describe('action-matcher', () => {
}

it('can match action on event name', () => {
const pageViewAction = createAction(3, '$mypageview') as unknown as ActionType
const pageViewAction = createAction(3, '$mypageview') as unknown as SurveyActionType
const actionMatcher = new ActionMatcher(instance)
actionMatcher.register([pageViewAction])
let pageViewActionMatched = false
Expand Down
10 changes: 5 additions & 5 deletions src/__tests__/utils/survey-event-receiver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
SurveyType,
SurveyQuestionType,
Survey,
ActionType,
SurveyActionType,
ActionStepStringMatching,
} from '../../posthog-surveys-types'
import { PostHogPersistence } from '../../posthog-persistence'
Expand Down Expand Up @@ -209,7 +209,7 @@ describe('survey-event-receiver', () => {
eventName: string,
currentUrl?: string,
urlMatch?: ActionStepStringMatching
): ActionType => {
): SurveyActionType => {
return {
id: id,
name: `${eventName || 'user defined '} action`,
Expand Down Expand Up @@ -238,7 +238,7 @@ describe('survey-event-receiver', () => {
type: SurveyType.Popover,
questions: [{ type: SurveyQuestionType.Open, question: 'what is a bokoblin?' }],
conditions: {
actions: [createAction(2, '$autocapture') as unknown as ActionType],
actions: [createAction(2, '$autocapture') as unknown as SurveyActionType],
},
} as unknown as Survey

Expand All @@ -249,7 +249,7 @@ describe('survey-event-receiver', () => {
type: SurveyType.Popover,
questions: [{ type: SurveyQuestionType.Open, question: 'what is a bokoblin?' }],
conditions: {
actions: [createAction(3, '$pageview') as unknown as ActionType],
actions: [createAction(3, '$pageview') as unknown as SurveyActionType],
},
} as unknown as Survey

Expand All @@ -262,7 +262,7 @@ describe('survey-event-receiver', () => {
questions: [{ type: SurveyQuestionType.Open, question: 'what is a bokoblin?' }],
conditions: {
actions: {
values: [createAction(3, '$mypageview') as unknown as ActionType],
values: [createAction(3, '$mypageview') as unknown as SurveyActionType],
},
},
} as unknown as Survey
Expand Down
8 changes: 4 additions & 4 deletions src/extensions/surveys.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ export class SurveyManager {
)
}

public callSurveysAndEvaluateDisplayLogic = (forceReload: boolean = false): void => {
public callSurveysAndEvaluateDisplayLogic = (): void => {
this.posthog?.getActiveMatchingSurveys((surveys) => {
const nonAPISurveys = surveys.filter((survey) => survey.type !== 'api')

Expand Down Expand Up @@ -240,7 +240,7 @@ export class SurveyManager {
this.handlePopoverSurvey(survey)
}
})
}, forceReload)
})
}

private addSurveyToFocus = (id: string): void => {
Expand Down Expand Up @@ -350,11 +350,11 @@ export function generateSurveys(posthog: PostHog) {
}

const surveyManager = new SurveyManager(posthog)
surveyManager.callSurveysAndEvaluateDisplayLogic(true)
surveyManager.callSurveysAndEvaluateDisplayLogic()

// recalculate surveys every second to check if URL or selectors have changed
setInterval(() => {
surveyManager.callSurveysAndEvaluateDisplayLogic(false)
surveyManager.callSurveysAndEvaluateDisplayLogic()
}, 1000)
return surveyManager
}
Expand Down
10 changes: 5 additions & 5 deletions src/extensions/surveys/action-matcher.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import { PostHog } from '../../posthog-core'
import { ActionStepStringMatching, ActionStepType, ActionType, SurveyElement } from '../../posthog-surveys-types'
import { ActionStepStringMatching, ActionStepType, SurveyActionType, SurveyElement } from '../../posthog-surveys-types'
import { SimpleEventEmitter } from '../../utils/simple-event-emitter'
import { CaptureResult } from '../../types'
import { isUndefined } from '../../utils/type-utils'
import { window } from '../../utils/globals'
import { isUrlMatchingRegex } from '../../utils/request-utils'

export class ActionMatcher {
private readonly actionRegistry?: Set<ActionType>
private readonly actionRegistry?: Set<SurveyActionType>
private readonly instance?: PostHog
private readonly actionEvents: Set<string>
private _debugEventEmitter = new SimpleEventEmitter()

constructor(instance?: PostHog) {
this.instance = instance
this.actionEvents = new Set<string>()
this.actionRegistry = new Set<ActionType>()
this.actionRegistry = new Set<SurveyActionType>()
}

init() {
Expand All @@ -27,7 +27,7 @@ export class ActionMatcher {
}
}

register(actions: ActionType[]): void {
register(actions: SurveyActionType[]): void {
if (isUndefined(this.instance?._addCaptureHook)) {
return
}
Expand Down Expand Up @@ -74,7 +74,7 @@ export class ActionMatcher {
this.onAction('actionCaptured', (data) => callback(data))
}

private checkAction(event?: CaptureResult, action?: ActionType): boolean {
private checkAction(event?: CaptureResult, action?: SurveyActionType): boolean {
if (action?.steps == null) {
return false
}
Expand Down
11 changes: 2 additions & 9 deletions src/posthog-surveys-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,6 @@ export interface Survey {
// Sync this with the backend's SurveyAPISerializer!
id: string
name: string
description: string
type: SurveyType
feature_flag_keys:
| {
Expand All @@ -172,7 +171,7 @@ export interface Survey {
}[]
} | null
actions: {
values: ActionType[]
values: SurveyActionType[]
} | null
} | null
start_date: string | null
Expand All @@ -181,16 +180,10 @@ export interface Survey {
current_iteration_start_date: string | null
}

export interface ActionType {
count?: number
created_at: string
deleted?: boolean
export interface SurveyActionType {
id: number
name: string | null
steps?: ActionStepType[]
tags?: string[]
is_action?: true
action_id?: number // alias of id to make it compatible with event definitions uuid
}

/** Sync with plugin-server/src/types.ts */
Expand Down
86 changes: 50 additions & 36 deletions src/posthog-surveys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { SurveyEventReceiver } from './utils/survey-event-receiver'
import { assignableWindow, document, window } from './utils/globals'
import { RemoteConfig } from './types'
import { createLogger } from './utils/logger'
import { isNullish } from './utils/type-utils'
import { isArray, isNullish } from './utils/type-utils'
import { getSurveySeenStorageKeys } from './extensions/surveys/surveys-utils'

const logger = createLogger('[Surveys]')
Expand Down Expand Up @@ -59,18 +59,26 @@ function getRatingBucketForResponseValue(responseValue: number, scale: number) {
}

export class PostHogSurveys {
private _decideServerResponse?: boolean
private _surveysRemoteEnabled?: boolean
public _surveyEventReceiver: SurveyEventReceiver | null
private _surveyManager: any

private surveys?: Survey[]

constructor(private readonly instance: PostHog) {
// we set this to undefined here because we need the persistence storage for this type
// but that's not initialized until loadIfEnabled is called.
this._surveyEventReceiver = null
}

onRemoteConfig(response: RemoteConfig) {
this._decideServerResponse = !!response['surveys']
if (isArray(response['surveys'])) {
this.surveys = response['surveys']
this._surveysRemoteEnabled = this.surveys.length > 0
} else {
this._surveysRemoteEnabled = !!response['surveys']
}

this.loadIfEnabled()
}

Expand All @@ -83,7 +91,7 @@ export class PostHogSurveys {
loadIfEnabled() {
const surveysGenerator = assignableWindow?.__PosthogExtensions__?.generateSurveys

if (!this.instance.config.disable_surveys && this._decideServerResponse && !surveysGenerator) {
if (!this.instance.config.disable_surveys && this._surveysRemoteEnabled && !surveysGenerator) {
if (this._surveyEventReceiver == null) {
this._surveyEventReceiver = new SurveyEventReceiver(this.instance)
}
Expand All @@ -98,6 +106,38 @@ export class PostHogSurveys {
}
}

reloadSurveys(callback: (surveys: Survey[]) => void) {
// TODO: If we are using RemoteConfig - load it from there instead of API
this.instance._send_request({
url: this.instance.requestRouter.endpointFor('api', `/api/surveys/?token=${this.instance.config.token}`),
method: 'GET',
transport: 'XHR',
callback: (response) => {
if (response.statusCode !== 200 || !response.json) {
return callback([])
}
const surveys = response.json.surveys || []

const eventOrActionBasedSurveys = surveys.filter(
(survey: Survey) =>
(survey.conditions?.events &&
survey.conditions?.events?.values &&
survey.conditions?.events?.values?.length > 0) ||
(survey.conditions?.actions &&
survey.conditions?.actions?.values &&
survey.conditions?.actions?.values?.length > 0)
)

if (eventOrActionBasedSurveys.length > 0) {
this._surveyEventReceiver?.register(eventOrActionBasedSurveys)
}

this.instance.persistence?.register({ [SURVEYS]: surveys })
return callback(surveys)
},
})
}

getSurveys(callback: SurveyCallback, forceReload = false) {
// In case we manage to load the surveys script, but config says not to load surveys
// then we shouldn't return survey data
Expand All @@ -109,40 +149,14 @@ export class PostHogSurveys {
this._surveyEventReceiver = new SurveyEventReceiver(this.instance)
}

const existingSurveys = this.instance.get_property(SURVEYS)
const existingSurveys = this.surveys ?? this.instance.get_property(SURVEYS)

if (!existingSurveys || forceReload) {
this.instance._send_request({
url: this.instance.requestRouter.endpointFor(
'api',
`/api/surveys/?token=${this.instance.config.token}`
),
method: 'GET',
transport: 'XHR',
callback: (response) => {
if (response.statusCode !== 200 || !response.json) {
return callback([])
}
const surveys = response.json.surveys || []

const eventOrActionBasedSurveys = surveys.filter(
(survey: Survey) =>
(survey.conditions?.events &&
survey.conditions?.events?.values &&
survey.conditions?.events?.values?.length > 0) ||
(survey.conditions?.actions &&
survey.conditions?.actions?.values &&
survey.conditions?.actions?.values?.length > 0)
)

if (eventOrActionBasedSurveys.length > 0) {
this._surveyEventReceiver?.register(eventOrActionBasedSurveys)
}
if (forceReload) {
throw new Error('forceReload')
}

this.instance.persistence?.register({ [SURVEYS]: surveys })
return callback(surveys)
},
})
if (!existingSurveys || forceReload) {
this.reloadSurveys(callback)
} else {
return callback(existingSurveys)
}
Expand Down
4 changes: 3 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { PostHog } from './posthog-core'
import type { SegmentAnalytics } from './extensions/segment-integration'
import { recordOptions } from './extensions/replay/sessionrecording-utils'
import { Survey, SurveyType } from './posthog-surveys-types'

Check failure on line 4 in src/types.ts

View workflow job for this annotation

GitHub Actions / Lint

'SurveyType' is defined but never used

export type Property = any
export type Properties = Record<string, Property>
Expand Down Expand Up @@ -522,7 +523,8 @@
urlBlocklist?: SessionRecordingUrlTrigger[]
eventTriggers?: string[]
}
surveys?: boolean
surveys?: boolean | Survey[]
survey_config?: Record<string, any> // TODO: Type this better
toolbarParams: ToolbarParams
editorParams?: ToolbarParams /** @deprecated, renamed to toolbarParams, still present on older API responses */
toolbarVersion: 'toolbar' /** @deprecated, moved to toolbarParams */
Expand Down
Loading