From 284f430f3cb3a6ebfac0fb0d68687d775a6fd420 Mon Sep 17 00:00:00 2001 From: Raquel Smith Date: Mon, 28 Oct 2024 13:11:06 -0700 Subject: [PATCH 1/7] record non-onboarding product intents for data warehouse --- .../data-warehouse/new/sourceWizardLogic.tsx | 7 +++++++ frontend/src/scenes/teamLogic.tsx | 3 ++- frontend/src/types.ts | 7 +++++++ posthog/api/team.py | 19 +++++++++++++++---- 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/frontend/src/scenes/data-warehouse/new/sourceWizardLogic.tsx b/frontend/src/scenes/data-warehouse/new/sourceWizardLogic.tsx index 2d8bba2f256bc..95a525987b8a2 100644 --- a/frontend/src/scenes/data-warehouse/new/sourceWizardLogic.tsx +++ b/frontend/src/scenes/data-warehouse/new/sourceWizardLogic.tsx @@ -6,6 +6,7 @@ import api from 'lib/api' import posthog from 'posthog-js' import { preflightLogic } from 'scenes/PreflightCheck/preflightLogic' import { Scene } from 'scenes/sceneTypes' +import { teamLogic } from 'scenes/teamLogic' import { urls } from 'scenes/urls' import { @@ -16,6 +17,7 @@ import { manualLinkSources, ManualLinkSourceType, PipelineTab, + ProductKey, SourceConfig, SourceFieldConfig, } from '~/types' @@ -731,6 +733,8 @@ export const sourceWizardLogic = kea([ ['resetTable', 'createTableSuccess'], dataWarehouseSettingsLogic, ['loadSources'], + teamLogic, + ['addProductIntent'], ], }), reducers({ @@ -1129,6 +1133,9 @@ export const sourceWizardLogic = kea([ setManualLinkingProvider: () => { actions.onNext() }, + selectConnector: () => { + actions.addProductIntent({ product_type: ProductKey.DATA_WAREHOUSE, intent_context: 'selected connector' }) + }, })), urlToAction(({ actions }) => ({ '/data-warehouse/:kind/redirect': ({ kind = '' }, searchParams) => { diff --git a/frontend/src/scenes/teamLogic.tsx b/frontend/src/scenes/teamLogic.tsx index 3f6d102270525..0e6754609ca29 100644 --- a/frontend/src/scenes/teamLogic.tsx +++ b/frontend/src/scenes/teamLogic.tsx @@ -145,13 +145,14 @@ export const teamLogic = kea([ resetToken: async () => await api.update(`api/environments/${values.currentTeamId}/reset_token`, {}), addProductIntent: async ({ product_type, + intent_context, }: { product_type: ProductKey intent_context?: string | null }) => await api.update(`api/environments/${values.currentTeamId}/add_product_intent`, { product_type, - intent_context: null, + intent_context: intent_context ?? undefined, }), recordProductIntentOnboardingComplete: async ({ product_type }: { product_type: ProductKey }) => await api.update(`api/environments/${values.currentTeamId}/complete_product_onboarding`, { diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 7c0a8acedad00..c67475b085a93 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -544,6 +544,13 @@ export interface TeamType extends TeamBasicType { extra_settings?: Record modifiers?: HogQLQueryModifiers default_modifiers?: HogQLQueryModifiers + product_intents?: ProductIntentType[] +} + +export interface ProductIntentType { + product_type: string + created_at: string + onboarding_completed_at?: string } // This type would be more correct without `Partial`, but it's only used in the shared dashboard/insight diff --git a/posthog/api/team.py b/posthog/api/team.py index 3f47a67791f28..b3456022cbf44 100644 --- a/posthog/api/team.py +++ b/posthog/api/team.py @@ -6,17 +6,17 @@ from django.shortcuts import get_object_or_404 from loginas.utils import is_impersonated_session -from posthog.auth import PersonalAPIKeyAuthentication -from posthog.jwt import PosthogJwtAudience, encode_jwt from rest_framework import exceptions, request, response, serializers, viewsets from rest_framework.permissions import BasePermission, IsAuthenticated from posthog.api.routing import TeamAndOrgViewSetMixin from posthog.api.shared import TeamBasicSerializer from posthog.api.utils import action +from posthog.auth import PersonalAPIKeyAuthentication from posthog.constants import AvailableFeature from posthog.event_usage import report_user_action from posthog.geoip import get_geoip_properties +from posthog.jwt import PosthogJwtAudience, encode_jwt from posthog.models import ProductIntent, Team, User from posthog.models.activity_logging.activity_log import ( Detail, @@ -209,7 +209,9 @@ def get_live_events_token(self, team: Team) -> Optional[str]: ) def get_product_intents(self, obj): - return ProductIntent.objects.filter(team=obj).values("product_type", "created_at", "onboarding_completed_at") + return ProductIntent.objects.filter(team=obj).values( + "product_type", "created_at", "onboarding_completed_at", "updated_at" + ) @staticmethod def validate_session_recording_linked_flag(value) -> dict | None: @@ -580,7 +582,7 @@ def add_product_intent(self, request: request.Request, *args, **kwargs): product_intent.updated_at = datetime.now(tz=UTC) product_intent.save() - if created and isinstance(user, User): + if isinstance(user, User): report_user_action( user, "user showed product intent", @@ -590,6 +592,9 @@ def add_product_intent(self, request: request.Request, *args, **kwargs): "$current_url": current_url, "$session_id": session_id, "intent_context": request.data.get("intent_context"), + "is_first_intent_for_product": created, + "intent_created_at": product_intent.created_at, + "intent_updated_at": product_intent.updated_at, }, team=team, ) @@ -619,6 +624,9 @@ def complete_product_onboarding(self, request: request.Request, *args, **kwargs) "$current_url": current_url, "$session_id": session_id, "intent_context": request.data.get("intent_context"), + "is_first_intent_for_product": created, + "intent_created_at": product_intent.created_at, + "intent_updated_at": product_intent.updated_at, }, team=team, ) @@ -633,6 +641,9 @@ def complete_product_onboarding(self, request: request.Request, *args, **kwargs) "product_key": product_type, "$current_url": current_url, "$session_id": session_id, + "intent_context": request.data.get("intent_context"), + "intent_created_at": product_intent.created_at, + "intent_updated_at": product_intent.updated_at, }, team=team, ) From fc759a09413197bb9448b2912ddc7a16a3453b49 Mon Sep 17 00:00:00 2001 From: Raquel Smith Date: Mon, 28 Oct 2024 19:43:49 -0700 Subject: [PATCH 2/7] fix type --- frontend/src/scenes/teamActivityDescriber.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/scenes/teamActivityDescriber.tsx b/frontend/src/scenes/teamActivityDescriber.tsx index 7fd8e6cdfe289..7417634b94aa1 100644 --- a/frontend/src/scenes/teamActivityDescriber.tsx +++ b/frontend/src/scenes/teamActivityDescriber.tsx @@ -336,6 +336,7 @@ const teamActionsMapping: Record< updated_at: () => null, uuid: () => null, live_events_token: () => null, + product_intents: () => null, } function nameAndLink(logItem?: ActivityLogItem): JSX.Element { From c356848908ce2d7bfb296f86e99017ab187f4d06 Mon Sep 17 00:00:00 2001 From: Raquel Smith Date: Mon, 28 Oct 2024 19:47:02 -0700 Subject: [PATCH 3/7] fix tests --- posthog/api/test/test_team.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/posthog/api/test/test_team.py b/posthog/api/test/test_team.py index ba697d073699f..5d47c950b62b5 100644 --- a/posthog/api/test/test_team.py +++ b/posthog/api/test/test_team.py @@ -1042,6 +1042,9 @@ def test_can_add_product_intent( "$session_id": "test_session_id", "intent_context": "onboarding product selected", "$set_once": {"first_onboarding_product_selected": "product_analytics"}, + "is_first_intent_for_product": True, + "intent_created_at": datetime(2024, 1, 1, 0, 0, 0, tzinfo=UTC), + "intent_updated_at": datetime(2024, 1, 1, 0, 0, 0, tzinfo=UTC), }, team=self.team, ) @@ -1073,6 +1076,9 @@ def test_can_complete_product_onboarding( "product_key": "product_analytics", "$current_url": "https://posthogtest.com/my-url", "$session_id": "test_session_id", + "intent_context": None, + "intent_created_at": datetime(2024, 1, 1, 0, 0, 0, tzinfo=UTC), + "intent_updated_at": datetime(2024, 1, 5, 0, 0, 0, tzinfo=UTC), }, team=self.team, ) From 03c3d2bb0267aebd49f5322b3c3e87fafc04618b Mon Sep 17 00:00:00 2001 From: Raquel Smith Date: Tue, 29 Oct 2024 07:40:36 -0700 Subject: [PATCH 4/7] update project also --- posthog/api/project.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/posthog/api/project.py b/posthog/api/project.py index 0c60c7e649a91..2f9d360aade83 100644 --- a/posthog/api/project.py +++ b/posthog/api/project.py @@ -195,7 +195,9 @@ def get_live_events_token(self, project: Project) -> Optional[str]: def get_product_intents(self, obj): project = obj team = project.passthrough_team - return ProductIntent.objects.filter(team=team).values("product_type", "created_at", "onboarding_completed_at") + return ProductIntent.objects.filter(team=team).values( + "product_type", "created_at", "onboarding_completed_at", "updated_at" + ) @staticmethod def validate_session_recording_linked_flag(value) -> dict | None: @@ -572,7 +574,7 @@ def add_product_intent(self, request: request.Request, *args, **kwargs): product_intent.updated_at = datetime.now(tz=UTC) product_intent.save() - if created and isinstance(user, User): + if isinstance(user, User): report_user_action( user, "user showed product intent", @@ -582,6 +584,9 @@ def add_product_intent(self, request: request.Request, *args, **kwargs): "$current_url": current_url, "$session_id": session_id, "intent_context": request.data.get("intent_context"), + "is_first_intent_for_product": created, + "intent_created_at": product_intent.created_at, + "intent_updated_at": product_intent.updated_at, }, team=team, ) @@ -612,6 +617,9 @@ def complete_product_onboarding(self, request: request.Request, *args, **kwargs) "$current_url": current_url, "$session_id": session_id, "intent_context": request.data.get("intent_context"), + "is_first_intent_for_product": created, + "intent_created_at": product_intent.created_at, + "intent_updated_at": product_intent.updated_at, }, team=team, ) @@ -626,6 +634,9 @@ def complete_product_onboarding(self, request: request.Request, *args, **kwargs) "product_key": product_type, "$current_url": current_url, "$session_id": session_id, + "intent_context": request.data.get("intent_context"), + "intent_created_at": product_intent.created_at, + "intent_updated_at": product_intent.updated_at, }, team=team, ) From ca47c7b23e54af316c08cace3df7a873da3d0690 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 29 Oct 2024 14:54:26 +0000 Subject: [PATCH 5/7] Update query snapshots --- posthog/api/test/__snapshots__/test_decide.ambr | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/posthog/api/test/__snapshots__/test_decide.ambr b/posthog/api/test/__snapshots__/test_decide.ambr index 75b3f38264283..5c7ea865a823e 100644 --- a/posthog/api/test/__snapshots__/test_decide.ambr +++ b/posthog/api/test/__snapshots__/test_decide.ambr @@ -521,7 +521,8 @@ ''' SELECT "posthog_productintent"."product_type", "posthog_productintent"."created_at", - "posthog_productintent"."onboarding_completed_at" + "posthog_productintent"."onboarding_completed_at", + "posthog_productintent"."updated_at" FROM "posthog_productintent" WHERE "posthog_productintent"."team_id" = 2 ''' From 39c3f076f2f92a2506e1aaabd9acd1b9cb7b33e6 Mon Sep 17 00:00:00 2001 From: Raquel Smith Date: Tue, 29 Oct 2024 09:10:59 -0700 Subject: [PATCH 6/7] add realm info to events --- posthog/api/project.py | 9 ++++++++- posthog/api/team.py | 9 ++++++++- posthog/api/test/test_team.py | 3 +++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/posthog/api/project.py b/posthog/api/project.py index 2f9d360aade83..8efca11b21968 100644 --- a/posthog/api/project.py +++ b/posthog/api/project.py @@ -44,7 +44,11 @@ TeamMemberStrictManagementPermission, ) from posthog.user_permissions import UserPermissions, UserPermissionsSerializerMixin -from posthog.utils import get_ip_address, get_week_start_for_country_code +from posthog.utils import ( + get_instance_realm, + get_ip_address, + get_week_start_for_country_code, +) class ProjectSerializer(serializers.ModelSerializer): @@ -587,6 +591,7 @@ def add_product_intent(self, request: request.Request, *args, **kwargs): "is_first_intent_for_product": created, "intent_created_at": product_intent.created_at, "intent_updated_at": product_intent.updated_at, + "realm": get_instance_realm(), }, team=team, ) @@ -620,6 +625,7 @@ def complete_product_onboarding(self, request: request.Request, *args, **kwargs) "is_first_intent_for_product": created, "intent_created_at": product_intent.created_at, "intent_updated_at": product_intent.updated_at, + "realm": get_instance_realm(), }, team=team, ) @@ -637,6 +643,7 @@ def complete_product_onboarding(self, request: request.Request, *args, **kwargs) "intent_context": request.data.get("intent_context"), "intent_created_at": product_intent.created_at, "intent_updated_at": product_intent.updated_at, + "realm": get_instance_realm(), }, team=team, ) diff --git a/posthog/api/team.py b/posthog/api/team.py index b3456022cbf44..2e1534a5491fb 100644 --- a/posthog/api/team.py +++ b/posthog/api/team.py @@ -43,7 +43,11 @@ get_organization_from_view, ) from posthog.user_permissions import UserPermissions, UserPermissionsSerializerMixin -from posthog.utils import get_ip_address, get_week_start_for_country_code +from posthog.utils import ( + get_instance_realm, + get_ip_address, + get_week_start_for_country_code, +) class PremiumMultiProjectPermissions(BasePermission): # TODO: Rename to include "Env" in name @@ -595,6 +599,7 @@ def add_product_intent(self, request: request.Request, *args, **kwargs): "is_first_intent_for_product": created, "intent_created_at": product_intent.created_at, "intent_updated_at": product_intent.updated_at, + "realm": get_instance_realm(), }, team=team, ) @@ -627,6 +632,7 @@ def complete_product_onboarding(self, request: request.Request, *args, **kwargs) "is_first_intent_for_product": created, "intent_created_at": product_intent.created_at, "intent_updated_at": product_intent.updated_at, + "realm": get_instance_realm(), }, team=team, ) @@ -644,6 +650,7 @@ def complete_product_onboarding(self, request: request.Request, *args, **kwargs) "intent_context": request.data.get("intent_context"), "intent_created_at": product_intent.created_at, "intent_updated_at": product_intent.updated_at, + "realm": get_instance_realm(), }, team=team, ) diff --git a/posthog/api/test/test_team.py b/posthog/api/test/test_team.py index 5d47c950b62b5..3a881facfea44 100644 --- a/posthog/api/test/test_team.py +++ b/posthog/api/test/test_team.py @@ -26,6 +26,7 @@ from posthog.temporal.common.client import sync_connect from posthog.temporal.common.schedule import describe_schedule from posthog.test.base import APIBaseTest +from posthog.utils import get_instance_realm def team_api_test_factory(): @@ -1045,6 +1046,7 @@ def test_can_add_product_intent( "is_first_intent_for_product": True, "intent_created_at": datetime(2024, 1, 1, 0, 0, 0, tzinfo=UTC), "intent_updated_at": datetime(2024, 1, 1, 0, 0, 0, tzinfo=UTC), + "realm": get_instance_realm(), }, team=self.team, ) @@ -1079,6 +1081,7 @@ def test_can_complete_product_onboarding( "intent_context": None, "intent_created_at": datetime(2024, 1, 1, 0, 0, 0, tzinfo=UTC), "intent_updated_at": datetime(2024, 1, 5, 0, 0, 0, tzinfo=UTC), + "realm": get_instance_realm(), }, team=self.team, ) From a0fa91a2c9849fcc76e20f3fa7c14454da93af86 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 29 Oct 2024 16:15:45 +0000 Subject: [PATCH 7/7] Update query snapshots --- posthog/api/test/__snapshots__/test_api_docs.ambr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/posthog/api/test/__snapshots__/test_api_docs.ambr b/posthog/api/test/__snapshots__/test_api_docs.ambr index a5f9b394809ae..6ef31c6530176 100644 --- a/posthog/api/test/__snapshots__/test_api_docs.ambr +++ b/posthog/api/test/__snapshots__/test_api_docs.ambr @@ -97,8 +97,8 @@ '/home/runner/work/posthog/posthog/posthog/api/survey.py: Warning [SurveyViewSet > SurveySerializer]: unable to resolve type hint for function "get_conditions". Consider using a type hint or @extend_schema_field. Defaulting to string.', '/home/runner/work/posthog/posthog/posthog/api/web_experiment.py: Warning [WebExperimentViewSet]: could not derive type of path parameter "project_id" because model "posthog.models.web_experiment.WebExperiment" contained no such field. Consider annotating parameter with @extend_schema. Defaulting to "string".', 'Warning: encountered multiple names for the same choice set (HrefMatchingEnum). This may be unwanted even though the generated schema is technically correct. Add an entry to ENUM_NAME_OVERRIDES to fix the naming.', - 'Warning: enum naming encountered a non-optimally resolvable collision for fields named "kind". The same name has been used for multiple choice sets in multiple components. The collision was resolved with "KindCfaEnum". add an entry to ENUM_NAME_OVERRIDES to fix the naming.', 'Warning: enum naming encountered a non-optimally resolvable collision for fields named "kind". The same name has been used for multiple choice sets in multiple components. The collision was resolved with "Kind069Enum". add an entry to ENUM_NAME_OVERRIDES to fix the naming.', + 'Warning: enum naming encountered a non-optimally resolvable collision for fields named "kind". The same name has been used for multiple choice sets in multiple components. The collision was resolved with "KindCfaEnum". add an entry to ENUM_NAME_OVERRIDES to fix the naming.', 'Warning: enum naming encountered a non-optimally resolvable collision for fields named "type". The same name has been used for multiple choice sets in multiple components. The collision was resolved with "TypeF73Enum". add an entry to ENUM_NAME_OVERRIDES to fix the naming.', 'Warning: encountered multiple names for the same choice set (EffectivePrivilegeLevelEnum). This may be unwanted even though the generated schema is technically correct. Add an entry to ENUM_NAME_OVERRIDES to fix the naming.', 'Warning: encountered multiple names for the same choice set (MembershipLevelEnum). This may be unwanted even though the generated schema is technically correct. Add an entry to ENUM_NAME_OVERRIDES to fix the naming.',