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/",