diff --git a/frontend/__snapshots__/scenes-app-experiments--experiment-not-found--dark.png b/frontend/__snapshots__/scenes-app-experiments--experiment-not-found--dark.png index 0d5d3ebcc36d0..98d4a882ab5d5 100644 Binary files a/frontend/__snapshots__/scenes-app-experiments--experiment-not-found--dark.png and b/frontend/__snapshots__/scenes-app-experiments--experiment-not-found--dark.png differ diff --git a/frontend/__snapshots__/scenes-app-experiments--experiment-not-found--light.png b/frontend/__snapshots__/scenes-app-experiments--experiment-not-found--light.png index 7f58cbcc0104b..5336f30852c7d 100644 Binary files a/frontend/__snapshots__/scenes-app-experiments--experiment-not-found--light.png and b/frontend/__snapshots__/scenes-app-experiments--experiment-not-found--light.png differ diff --git a/frontend/__snapshots__/scenes-app-sidepanels--side-panel-docs--light.png b/frontend/__snapshots__/scenes-app-sidepanels--side-panel-docs--light.png index b0d04d6d0bca7..ca494887f274e 100644 Binary files a/frontend/__snapshots__/scenes-app-sidepanels--side-panel-docs--light.png and b/frontend/__snapshots__/scenes-app-sidepanels--side-panel-docs--light.png differ diff --git a/frontend/src/lib/taxonomy.tsx b/frontend/src/lib/taxonomy.tsx index b8a333f2d2fa8..76c60960eec8e 100644 --- a/frontend/src/lib/taxonomy.tsx +++ b/frontend/src/lib/taxonomy.tsx @@ -1212,6 +1212,10 @@ export const CORE_FILTER_DEFINITIONS_BY_GROUP = { description: 'posthog-js adds these to the page leave event, they are used in web analytics calculations', label: 'Previous pageview duration', }, + $surveys_activated: { + label: 'Surveys Activated', + description: 'The surveys that were activated for this event.', + }, }, numerical_event_properties: {}, // Same as event properties, see assignment below person_properties: {}, // Currently person properties are the same as event properties, see assignment below @@ -1432,7 +1436,13 @@ export const NON_DOLLAR_POSTHOG_PROPERTY_KEYS = [ 'current_usage.session_replay', 'current_usage.surveys', 'customer_deactivated', - 'custom_limits_usd.data_warehouse', + 'custom_limits.data_warehouse', + 'custom_limits.feature_flags', + 'custom_limits.integrations', + 'custom_limits.platform_and_support', + 'custom_limits.product_analytics', + 'custom_limits.session_replay', + 'custom_limits.surveys', 'free_allocation.data_warehouse', 'free_allocation.feature_flags', 'free_allocation.integrations', @@ -1474,8 +1484,6 @@ export const NON_DOLLAR_POSTHOG_PROPERTY_KEYS = [ 'email_service_available', 'slack_service_available', 'commit_sha', - 'token', - 'distinct_id', ] /** Return whether a given filter key is part of PostHog's core (marked by the PostHog logo). */ diff --git a/frontend/src/queries/nodes/DataVisualization/Components/Variables/addVariableLogic.ts b/frontend/src/queries/nodes/DataVisualization/Components/Variables/addVariableLogic.ts index 7045f1d2048a5..a8802e6b6b6ea 100644 --- a/frontend/src/queries/nodes/DataVisualization/Components/Variables/addVariableLogic.ts +++ b/frontend/src/queries/nodes/DataVisualization/Components/Variables/addVariableLogic.ts @@ -1,6 +1,6 @@ -import { actions, connect, kea, key, path, props, reducers } from 'kea' -import { loaders } from 'kea-loaders' -import api from 'lib/api' +import { lemonToast } from '@posthog/lemon-ui' +import { actions, connect, kea, key, listeners, path, props, reducers } from 'kea' +import api, { ApiError } from 'lib/api' import { BooleanVariable, ListVariable, NumberVariable, StringVariable, Variable, VariableType } from '../../types' import type { addVariableLogicType } from './addVariableLogicType' @@ -30,6 +30,7 @@ export const addVariableLogic = kea([ openModal: (variableType: VariableType) => ({ variableType }), closeModal: true, updateVariable: (variable: Variable) => ({ variable }), + save: true, }), reducers({ variableType: [ @@ -97,20 +98,18 @@ export const addVariableLogic = kea([ }, ], }), - loaders(({ values, actions }) => ({ - savedVariable: [ - null as null | Variable, - { - save: async () => { - const variable = await api.insightVariables.create(values.variable) - - actions.getVariables() - actions.addVariable({ variableId: variable.id, code_name: variable.code_name }) - actions.closeModal() + listeners(({ values, actions }) => ({ + save: async () => { + try { + const variable = await api.insightVariables.create(values.variable) - return variable - }, - }, - ], + actions.getVariables() + actions.addVariable({ variableId: variable.id, code_name: variable.code_name }) + actions.closeModal() + } catch (e: any) { + const error = e as ApiError + lemonToast.error(error.detail ?? error.message) + } + }, })), ]) diff --git a/frontend/src/scenes/experiments/ExperimentView/components.tsx b/frontend/src/scenes/experiments/ExperimentView/components.tsx index 476ea2fa613fe..71e6c7f35dd25 100644 --- a/frontend/src/scenes/experiments/ExperimentView/components.tsx +++ b/frontend/src/scenes/experiments/ExperimentView/components.tsx @@ -360,7 +360,6 @@ export function PageHeaderCustom(): JSX.Element { launchExperiment, endExperiment, archiveExperiment, - setEditExperiment, loadExperimentResults, loadSecondaryMetricResults, createExposureCohort, @@ -374,9 +373,6 @@ export function PageHeaderCustom(): JSX.Element { <> {experiment && !isExperimentRunning && (
- setEditExperiment(true)}> - Edit - ([ }, { key: [Scene.Experiment, experimentId], - name: experiment?.name || 'New', - path: urls.experiment(experimentId || 'new'), + name: experiment?.name || '', + onRename: async (name: string) => { + // :KLUDGE: work around a type error when using asyncActions accessed via a callback passed to selectors() + const logic = experimentLogic({ experimentId }) + await logic.asyncActions.updateExperiment({ name }) + }, }, ], ], diff --git a/posthog/api/insight_variable.py b/posthog/api/insight_variable.py index 85303b4e58c84..8f53a2ea80ed6 100644 --- a/posthog/api/insight_variable.py +++ b/posthog/api/insight_variable.py @@ -1,5 +1,6 @@ from django_filters.rest_framework import DjangoFilterBackend from rest_framework import serializers, viewsets +from rest_framework.exceptions import ValidationError from posthog.api.routing import TeamAndOrgViewSetMixin from posthog.models.insight_variable import InsightVariable @@ -22,6 +23,13 @@ def create(self, validated_data): "".join(n for n in validated_data["name"] if n.isalnum() or n == " ").replace(" ", "_").lower() ) + count = InsightVariable.objects.filter( + team_id=self.context["team_id"], code_name=validated_data["code_name"] + ).count() + + if count > 0: + raise ValidationError("Variable with name already exists") + return InsightVariable.objects.create(**validated_data) diff --git a/posthog/api/survey.py b/posthog/api/survey.py index b2faf7a419186..ea894a7dd30c0 100644 --- a/posthog/api/survey.py +++ b/posthog/api/survey.py @@ -386,7 +386,7 @@ def update(self, instance: Survey, validated_data): instance.targeting_flag.active = False instance.targeting_flag.save() - iteration_count = validated_data.get("iteration_count") + iteration_count = validated_data.get("iteration_count", None) if ( instance.current_iteration is not None and iteration_count is not None @@ -396,8 +396,9 @@ def update(self, instance: Survey, validated_data): f"Cannot change survey recurrence to {iteration_count}, should be at least {instance.current_iteration}" ) - instance.iteration_count = iteration_count - instance.iteration_frequency_days = validated_data.get("iteration_frequency_days") + if iteration_count is not None: + instance.iteration_count = iteration_count + instance.iteration_frequency_days = validated_data.get("iteration_frequency_days") instance = super().update(instance, validated_data) diff --git a/posthog/api/test/test_insight_variable.py b/posthog/api/test/test_insight_variable.py new file mode 100644 index 0000000000000..2b6f09ef8ed89 --- /dev/null +++ b/posthog/api/test/test_insight_variable.py @@ -0,0 +1,33 @@ +from posthog.models.insight_variable import InsightVariable +from posthog.test.base import APIBaseTest + + +class TestInsightVariable(APIBaseTest): + def test_create_insight_variable(self): + response = self.client.post( + f"/api/projects/{self.team.pk}/insight_variables/", data={"name": "Test 1", "type": "String"} + ) + + assert response.status_code == 201 + + variable = InsightVariable.objects.get(team_id=self.team.pk) + + assert variable is not None + assert variable.created_by is not None + assert variable.created_at is not None + assert variable.name == "Test 1" + assert variable.type == "String" + assert variable.code_name == "test_1" + + def test_no_duplicate_code_names(self): + InsightVariable.objects.create(team=self.team, name="Test 1", code_name="test_1") + + response = self.client.post( + f"/api/projects/{self.team.pk}/insight_variables/", data={"name": "Test 1", "type": "String"} + ) + + assert response.status_code == 400 + + variable_count = InsightVariable.objects.filter(team_id=self.team.pk).count() + + assert variable_count == 1 diff --git a/posthog/api/test/test_survey.py b/posthog/api/test/test_survey.py index 4f171d91b6c14..c6de95a44702e 100644 --- a/posthog/api/test/test_survey.py +++ b/posthog/api/test/test_survey.py @@ -2371,6 +2371,19 @@ def test_can_create_recurring_survey(self): assert len(response_data["iteration_start_dates"]) == 2 assert response_data["current_iteration"] == 1 + def test_can_create_and_launch_recurring_survey(self): + survey = self._create_recurring_survey() + response = self.client.patch( + f"/api/projects/{self.team.id}/surveys/{survey.id}/", + data={ + "start_date": datetime.now() - timedelta(days=1), + }, + ) + response_data = response.json() + assert response_data["iteration_start_dates"] is not None + assert len(response_data["iteration_start_dates"]) == 2 + assert response_data["current_iteration"] == 1 + def test_can_set_internal_targeting_flag(self): survey = self._create_recurring_survey() response = self.client.patch( @@ -2493,7 +2506,7 @@ def test_guards_for_nil_iteration_count(self): ) assert response.status_code == status.HTTP_200_OK survey.refresh_from_db() - self.assertIsNone(survey.current_iteration) + self.assertIsNotNone(survey.current_iteration) response = self.client.patch( f"/api/projects/{self.team.id}/surveys/{survey.id}/", data={