diff --git a/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown--light.png b/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown--light.png
index 4bc0d3a54c2ef..643ed8a4088bc 100644
Binary files a/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown--light.png and b/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown--light.png differ
diff --git a/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown-edit--dark.png b/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown-edit--dark.png
index a3cf660f1ff02..9e15d8e251e65 100644
Binary files a/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown-edit--dark.png and b/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown-edit--dark.png differ
diff --git a/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown-edit--light.png b/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown-edit--light.png
index f65369bb2629d..a00206db77781 100644
Binary files a/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown-edit--light.png and b/frontend/__snapshots__/scenes-app-insights--funnel-top-to-bottom-breakdown-edit--light.png differ
diff --git a/frontend/__snapshots__/scenes-app-surveys--new-survey-customisation-section--dark.png b/frontend/__snapshots__/scenes-app-surveys--new-survey-customisation-section--dark.png
index 4251781ca3742..d03a47e37fc87 100644
Binary files a/frontend/__snapshots__/scenes-app-surveys--new-survey-customisation-section--dark.png and b/frontend/__snapshots__/scenes-app-surveys--new-survey-customisation-section--dark.png differ
diff --git a/frontend/__snapshots__/scenes-app-surveys--new-survey-customisation-section--light.png b/frontend/__snapshots__/scenes-app-surveys--new-survey-customisation-section--light.png
index c6b6932c294ed..87b0a262d2914 100644
Binary files a/frontend/__snapshots__/scenes-app-surveys--new-survey-customisation-section--light.png and b/frontend/__snapshots__/scenes-app-surveys--new-survey-customisation-section--light.png differ
diff --git a/frontend/src/scenes/surveys/SurveyCustomization.tsx b/frontend/src/scenes/surveys/SurveyCustomization.tsx
index 76fb35c4fc6e0..20e29c274c19c 100644
--- a/frontend/src/scenes/surveys/SurveyCustomization.tsx
+++ b/frontend/src/scenes/surveys/SurveyCustomization.tsx
@@ -2,6 +2,7 @@ import { LemonButton, LemonCheckbox, LemonDialog, LemonInput, LemonSelect } from
import { useActions, useValues } from 'kea'
import { PayGateMini } from 'lib/components/PayGateMini/PayGateMini'
import { upgradeModalLogic } from 'lib/components/UpgradeModal/upgradeModalLogic'
+import { LemonField } from 'lib/lemon-ui/LemonField'
import { surveyLogic } from 'scenes/surveys/surveyLogic'
import {
@@ -162,6 +163,40 @@ export function Customization({ appearance, surveyQuestionItem, onAppearanceChan
checked={appearance?.shuffleQuestions}
/>
+
+
+
+ {
+ const surveyPopupDelaySeconds = checked ? 5 : undefined
+ onAppearanceChange({ ...appearance, surveyPopupDelaySeconds })
+ }}
+ />
+ Delay survey popup after page load by at least{' '}
+ {
+ if (newValue && newValue > 0) {
+ onAppearanceChange({ ...appearance, surveyPopupDelaySeconds: newValue })
+ } else {
+ onAppearanceChange({
+ ...appearance,
+ surveyPopupDelaySeconds: undefined,
+ })
+ }
+ }}
+ className="w-12"
+ />{' '}
+ seconds.
+
+
+
>
)
diff --git a/frontend/src/types.ts b/frontend/src/types.ts
index 9b14ea8d07f3a..a171f6daeec28 100644
--- a/frontend/src/types.ts
+++ b/frontend/src/types.ts
@@ -2653,12 +2653,13 @@ export interface SurveyAppearance {
thankYouMessageDescriptionContentType?: SurveyQuestionDescriptionContentType
autoDisappear?: boolean
position?: string
+ shuffleQuestions?: boolean
+ surveyPopupDelaySeconds?: number
// widget only
widgetType?: 'button' | 'tab' | 'selector'
widgetSelector?: string
widgetLabel?: string
widgetColor?: string
- shuffleQuestions?: boolean
}
export interface SurveyQuestionBase {
diff --git a/package.json b/package.json
index 2903873485f24..1f3a0788af5d8 100644
--- a/package.json
+++ b/package.json
@@ -146,7 +146,7 @@
"pmtiles": "^2.11.0",
"postcss": "^8.4.31",
"postcss-preset-env": "^9.3.0",
- "posthog-js": "1.140.1",
+ "posthog-js": "1.141.0",
"posthog-js-lite": "3.0.0",
"prettier": "^2.8.8",
"prop-types": "^15.7.2",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1466d663e04e3..b15a8fad0a46d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -260,8 +260,8 @@ dependencies:
specifier: ^9.3.0
version: 9.3.0(postcss@8.4.31)
posthog-js:
- specifier: 1.140.1
- version: 1.140.1
+ specifier: 1.141.0
+ version: 1.141.0
posthog-js-lite:
specifier: 3.0.0
version: 3.0.0
@@ -17706,8 +17706,8 @@ packages:
resolution: {integrity: sha512-dyajjnfzZD1tht4N7p7iwf7nBnR1MjVaVu+MKr+7gBgA39bn28wizCIJZztZPtHy4PY0YwtSGgwfBCuG/hnHgA==}
dev: false
- /posthog-js@1.140.1:
- resolution: {integrity: sha512-UeKuAtQSvbzmTCzNVaauku8F194EYwAP33WrRrWZlDlMNbMy7GKcZOgKbr7jZqnha7FlVlHrWk+Rpyr1zCFhPQ==}
+ /posthog-js@1.141.0:
+ resolution: {integrity: sha512-EuVCq86izPX7+1eD/o87lF1HalRD6Nk5735w+FKZJ5KAPwoQjr5FCaL2V8Ed36DyQQz4gQj+PEx5i6DFKCiDzA==}
dependencies:
fflate: 0.4.8
preact: 10.22.0
diff --git a/posthog/api/survey.py b/posthog/api/survey.py
index 1203b4ab47292..4065c0874191a 100644
--- a/posthog/api/survey.py
+++ b/posthog/api/survey.py
@@ -5,8 +5,10 @@
import nh3
from django.db.models import Min
from django.http import JsonResponse
+from django.utils.text import slugify
from django.views.decorators.csrf import csrf_exempt
-from rest_framework import status
+from nanoid import generate
+from rest_framework import request, serializers, status, viewsets
from rest_framework.decorators import action
from rest_framework.request import Request
from rest_framework.response import Response
@@ -20,13 +22,10 @@
from posthog.api.shared import UserBasicSerializer
from posthog.api.utils import get_token
from posthog.client import sync_execute
-from posthog.exceptions import generate_exception_response
-from posthog.models.feedback.survey import Survey
-from django.utils.text import slugify
-from nanoid import generate
-from rest_framework import request, serializers, viewsets
from posthog.constants import AvailableFeature
+from posthog.exceptions import generate_exception_response
from posthog.models.feature_flag.feature_flag import FeatureFlag
+from posthog.models.feedback.survey import Survey
from posthog.models.team.team import Team
from posthog.utils_cors import cors_response
@@ -134,6 +133,10 @@ def validate_appearance(self, value):
"You need to upgrade to PostHog Enterprise to use HTML in survey thank you message"
)
+ survey_popup_delay_seconds = value.get("surveyPopupDelaySeconds")
+ if survey_popup_delay_seconds and survey_popup_delay_seconds < 0:
+ raise serializers.ValidationError("Survey popup delay seconds must be a positive integer")
+
return value
def validate_questions(self, value):
diff --git a/posthog/api/test/__snapshots__/test_properties_timeline.ambr b/posthog/api/test/__snapshots__/test_properties_timeline.ambr
index ac8cf04120ea6..670ac0cd349e4 100644
--- a/posthog/api/test/__snapshots__/test_properties_timeline.ambr
+++ b/posthog/api/test/__snapshots__/test_properties_timeline.ambr
@@ -446,7 +446,7 @@
ORDER BY timestamp ASC ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING) AS end_event_number
FROM
(SELECT timestamp, person_properties AS properties,
- array(replaceRegexpAll(JSONExtractRaw(person_properties, 'bar'), '^"|"$', ''), replaceRegexpAll(JSONExtractRaw(person_properties, 'foo'), '^"|"$', '')) AS relevant_property_values,
+ array(replaceRegexpAll(JSONExtractRaw(person_properties, 'foo'), '^"|"$', ''), replaceRegexpAll(JSONExtractRaw(person_properties, 'bar'), '^"|"$', '')) AS relevant_property_values,
lagInFrame(relevant_property_values) OVER (
ORDER BY timestamp ASC ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS previous_relevant_property_values,
row_number() OVER (
@@ -482,7 +482,7 @@
ORDER BY timestamp ASC ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING) AS end_event_number
FROM
(SELECT timestamp, person_properties AS properties,
- array("mat_pp_bar", "mat_pp_foo") AS relevant_property_values,
+ array("mat_pp_foo", "mat_pp_bar") AS relevant_property_values,
lagInFrame(relevant_property_values) OVER (
ORDER BY timestamp ASC ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS previous_relevant_property_values,
row_number() OVER (
@@ -522,7 +522,7 @@
ORDER BY timestamp ASC ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING) AS end_event_number
FROM
(SELECT timestamp, person_properties AS properties,
- array(replaceRegexpAll(JSONExtractRaw(person_properties, 'bar'), '^"|"$', ''), replaceRegexpAll(JSONExtractRaw(person_properties, 'foo'), '^"|"$', '')) AS relevant_property_values,
+ array(replaceRegexpAll(JSONExtractRaw(person_properties, 'foo'), '^"|"$', ''), replaceRegexpAll(JSONExtractRaw(person_properties, 'bar'), '^"|"$', '')) AS relevant_property_values,
lagInFrame(relevant_property_values) OVER (
ORDER BY timestamp ASC ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS previous_relevant_property_values,
row_number() OVER (
@@ -558,7 +558,7 @@
ORDER BY timestamp ASC ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING) AS end_event_number
FROM
(SELECT timestamp, person_properties AS properties,
- array("mat_pp_bar", "mat_pp_foo") AS relevant_property_values,
+ array("mat_pp_foo", "mat_pp_bar") AS relevant_property_values,
lagInFrame(relevant_property_values) OVER (
ORDER BY timestamp ASC ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS previous_relevant_property_values,
row_number() OVER (
diff --git a/posthog/api/test/test_survey.py b/posthog/api/test/test_survey.py
index 67ed3722eb226..363688915daf5 100644
--- a/posthog/api/test/test_survey.py
+++ b/posthog/api/test/test_survey.py
@@ -9,7 +9,6 @@
from posthog.api.survey import nh3_clean_with_allow_list
from posthog.models.cohort.cohort import Cohort
from nanoid import generate
-
from rest_framework import status
from posthog.constants import AvailableFeature
@@ -1249,6 +1248,7 @@ def test_create_basic_survey_question_validation(self):
"thankYouMessageHeader": "Thanks for your feedback!",
"thankYouMessageDescription": "We'll use it to make notebooks better.",
"shuffleQuestions": True,
+ "surveyPopupDelaySeconds": 60,
},
},
format="json",
@@ -1271,6 +1271,7 @@ def test_create_basic_survey_question_validation(self):
"thankYouMessageHeader": "Thanks for your feedback!",
"thankYouMessageDescription": "We'll use it to make notebooks better.",
"shuffleQuestions": True,
+ "surveyPopupDelaySeconds": 60,
}
assert response_data["created_by"]["id"] == self.user.id
@@ -1676,6 +1677,38 @@ def test_validate_thank_you_description_content_type(self):
assert response.status_code == status.HTTP_400_BAD_REQUEST, response_data
assert response_data["detail"] == "thankYouMessageDescriptionContentType must be one of ['text', 'html']"
+ def test_create_survey_with_survey_popup_delay(self):
+ response = self.client.post(
+ f"/api/projects/{self.team.id}/surveys/",
+ data={
+ "name": "Notebooks beta release survey",
+ "type": "popover",
+ "appearance": {
+ "surveyPopupDelaySeconds": 6000,
+ },
+ },
+ format="json",
+ )
+ response_data = response.json()
+ assert response.status_code == status.HTTP_201_CREATED, response_data
+ assert response_data["appearance"]["surveyPopupDelaySeconds"] == 6000
+
+ def test_validate_survey_popup_delay(self):
+ response = self.client.post(
+ f"/api/projects/{self.team.id}/surveys/",
+ data={
+ "name": "Notebooks beta release survey",
+ "type": "popover",
+ "appearance": {
+ "surveyPopupDelaySeconds": -100,
+ },
+ },
+ format="json",
+ )
+ response_data = response.json()
+ assert response.status_code == status.HTTP_400_BAD_REQUEST, response_data
+ assert response_data["detail"] == "Survey popup delay seconds must be a positive integer"
+
def test_create_survey_with_valid_question_description_content_type_html(self):
response = self.client.post(
f"/api/projects/{self.team.id}/surveys/",