diff --git a/frontend/src/exporter/__mocks__/Exporter.mocks.tsx b/frontend/src/exporter/__mocks__/Exporter.mocks.tsx index 6d2b7e87d1fad..e4edb8f324c09 100644 --- a/frontend/src/exporter/__mocks__/Exporter.mocks.tsx +++ b/frontend/src/exporter/__mocks__/Exporter.mocks.tsx @@ -222,6 +222,7 @@ export const dashboard: DashboardType = { effective_privilege_level: 37, timezone: null, tags: [], + user_access_level: 'editor', }, } as DashboardTile, { @@ -369,6 +370,7 @@ export const dashboard: DashboardType = { effective_privilege_level: 37, timezone: null, tags: [], + user_access_level: 'editor', }, } as DashboardTile, { @@ -547,6 +549,7 @@ export const dashboard: DashboardType = { effective_privilege_level: 37, timezone: null, tags: [], + user_access_level: 'editor', }, } as DashboardTile, { @@ -688,6 +691,7 @@ export const dashboard: DashboardType = { effective_privilege_level: 37, timezone: null, tags: [], + user_access_level: 'editor', }, } as DashboardTile, { @@ -1107,6 +1111,7 @@ export const dashboard: DashboardType = { effective_privilege_level: 37, timezone: null, tags: [], + user_access_level: 'editor', }, } as DashboardTile, { @@ -1245,6 +1250,7 @@ export const dashboard: DashboardType = { effective_privilege_level: 37, timezone: null, tags: [], + user_access_level: 'editor', }, } as DashboardTile, ], @@ -1257,5 +1263,6 @@ export const dashboard: DashboardType = { restriction_level: 37, effective_restriction_level: 37, effective_privilege_level: 37, + user_access_level: 'editor', tags: [], } diff --git a/frontend/src/layout/navigation-3000/sidepanel/panels/access_control/AccessControlObject.tsx b/frontend/src/layout/navigation-3000/sidepanel/panels/access_control/AccessControlObject.tsx index dd8f202933a2b..a5a00f0b50908 100644 --- a/frontend/src/layout/navigation-3000/sidepanel/panels/access_control/AccessControlObject.tsx +++ b/frontend/src/layout/navigation-3000/sidepanel/panels/access_control/AccessControlObject.tsx @@ -39,7 +39,7 @@ export function AccessControlObject(props: AccessControlLogicProps): JSX.Element return (
- {canEditAccessControls === true ? ( + {canEditAccessControls === false ? ( Permission required
diff --git a/frontend/src/models/dashboardsModel.test.ts b/frontend/src/models/dashboardsModel.test.ts index c3d42a78b83b7..d0e6517278ef5 100644 --- a/frontend/src/models/dashboardsModel.test.ts +++ b/frontend/src/models/dashboardsModel.test.ts @@ -54,6 +54,7 @@ const basicDashboard: DashboardBasicType = { restriction_level: DashboardRestrictionLevel.EveryoneInProjectCanEdit, effective_restriction_level: DashboardRestrictionLevel.EveryoneInProjectCanEdit, effective_privilege_level: DashboardPrivilegeLevel.CanEdit, + user_access_level: 'editor', } describe('the dashboards model', () => { diff --git a/frontend/src/scenes/insights/insightLogic.test.ts b/frontend/src/scenes/insights/insightLogic.test.ts index 5c01255da1463..500ee64cfa958 100644 --- a/frontend/src/scenes/insights/insightLogic.test.ts +++ b/frontend/src/scenes/insights/insightLogic.test.ts @@ -99,6 +99,7 @@ function insightModelWith(properties: Record): QueryBasedInsightMod effective_restriction_level: DashboardRestrictionLevel.EveryoneInProjectCanEdit, layouts: {}, color: null, + user_access_level: 'editor', ...properties, } as QueryBasedInsightModel } diff --git a/frontend/src/scenes/saved-insights/activityDescriptions.tsx b/frontend/src/scenes/saved-insights/activityDescriptions.tsx index cd7668905e5ad..8f6f9f10dc2a6 100644 --- a/frontend/src/scenes/saved-insights/activityDescriptions.tsx +++ b/frontend/src/scenes/saved-insights/activityDescriptions.tsx @@ -231,6 +231,7 @@ const insightActionsMapping: Record< disable_baseline: () => null, dashboard_tiles: () => null, query_status: () => null, + user_access_level: () => null, } function summarizeChanges(filtersAfter: Partial): ChangeMapping | null { diff --git a/frontend/src/types.ts b/frontend/src/types.ts index dc5e08c322f8e..19998559d505d 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -1822,7 +1822,7 @@ export interface TextModel { last_modified_at: string } -export interface InsightModel extends Cacheable { +export interface InsightModel extends Cacheable, WithAccessControl { /** The unique key we use when communicating with the user, e.g. in URLs */ short_id: InsightShortId /** The primary key in the database, used as well in API endpoints */ @@ -1861,7 +1861,7 @@ export interface QueryBasedInsightModel extends Omit { query: Node | null } -export interface DashboardBasicType { +export interface DashboardBasicType extends WithAccessControl { id: number name: string description: string diff --git a/posthog/api/dashboards/dashboard.py b/posthog/api/dashboards/dashboard.py index 393df01a2d554..6c8046c3743cd 100644 --- a/posthog/api/dashboards/dashboard.py +++ b/posthog/api/dashboards/dashboard.py @@ -3,7 +3,6 @@ import structlog from django.db.models import Prefetch -from django.shortcuts import get_object_or_404 from django.utils.timezone import now from rest_framework import exceptions, serializers, viewsets from rest_framework.permissions import SAFE_METHODS, BasePermission @@ -516,13 +515,14 @@ def dangerously_get_queryset(self): ), ) + # Add access level filtering for list actions + queryset = self._filter_queryset_by_access_level(queryset) + return queryset @monitor(feature=Feature.DASHBOARD, endpoint="dashboard", method="GET") def retrieve(self, request: Request, *args: Any, **kwargs: Any) -> Response: - pk = kwargs["pk"] - queryset = self.get_queryset() - dashboard = get_object_or_404(queryset, pk=pk) + dashboard = self.get_object() dashboard.last_accessed_at = now() dashboard.save(update_fields=["last_accessed_at"]) serializer = DashboardSerializer(dashboard, context=self.get_serializer_context()) diff --git a/posthog/api/insight.py b/posthog/api/insight.py index 06a8d13bf3f08..4d85414de8ec6 100644 --- a/posthog/api/insight.py +++ b/posthog/api/insight.py @@ -211,7 +211,7 @@ class Meta: fields = ["id", "dashboard_id", "deleted"] -class InsightBasicSerializer(TaggedItemSerializerMixin, serializers.ModelSerializer): +class InsightBasicSerializer(TaggedItemSerializerMixin, serializers.ModelSerializer, UserAccessControlSerializerMixin): """ Simplified serializer to speed response times when loading large amounts of objects. """ @@ -267,7 +267,7 @@ def _dashboard_tiles(self, instance): return [tile.dashboard_id for tile in instance.dashboard_tiles.all()] -class InsightSerializer(InsightBasicSerializer, UserPermissionsSerializerMixin, UserAccessControlSerializerMixin): +class InsightSerializer(InsightBasicSerializer, UserPermissionsSerializerMixin): result = serializers.SerializerMethodField() hasMore = serializers.SerializerMethodField() columns = serializers.SerializerMethodField() @@ -792,6 +792,10 @@ def dangerously_get_queryset(self): ), ) + # Add access level filtering for list actions if not sharing access token + if not isinstance(self.request.successful_authenticator, SharingAccessTokenAuthentication): + queryset = self._filter_queryset_by_access_level(queryset) + queryset = queryset.select_related("created_by", "last_modified_by", "team") if self.action == "list": queryset = queryset.prefetch_related("tagged_items__tag") @@ -1149,7 +1153,7 @@ def calculate_funnel_hogql(self, request: request.Request) -> dict[str, Any]: # /projects/:id/insights/:short_id/viewed # Creates or updates an InsightViewed object for the user/insight combo # ****************************************** - @action(methods=["POST"], detail=True) + @action(methods=["POST"], detail=True, required_scopes=["insight:read"]) def viewed(self, request: request.Request, *args: Any, **kwargs: Any) -> Response: InsightViewed.objects.update_or_create( team=self.team, diff --git a/posthog/api/notebook.py b/posthog/api/notebook.py index f68d2ddb0b26a..dc902b145384d 100644 --- a/posthog/api/notebook.py +++ b/posthog/api/notebook.py @@ -78,7 +78,7 @@ def log_notebook_activity( ) -class NotebookMinimalSerializer(serializers.ModelSerializer): +class NotebookMinimalSerializer(serializers.ModelSerializer, UserAccessControlSerializerMixin): created_by = UserBasicSerializer(read_only=True) last_modified_by = UserBasicSerializer(read_only=True) @@ -97,7 +97,7 @@ class Meta: read_only_fields = fields -class NotebookSerializer(NotebookMinimalSerializer, UserAccessControlSerializerMixin): +class NotebookSerializer(NotebookMinimalSerializer): class Meta: model = Notebook fields = [ diff --git a/posthog/api/routing.py b/posthog/api/routing.py index 7c59d73de662a..dfd627e8667a0 100644 --- a/posthog/api/routing.py +++ b/posthog/api/routing.py @@ -168,27 +168,29 @@ def get_queryset(self) -> QuerySet: queryset = self._filter_queryset_by_parents_lookups(queryset) - if self.action != "list": - # NOTE: If we are getting an individual object then we don't filter it out here - this is handled by the permission logic - # The reason being, that if we filter out here already, we can't load the object which is required for checking access controls for it - return queryset - - # NOTE: Half implemented - for admins, they may want to include listing of results that are not accessible (like private resources) - include_all_if_admin = self.request.GET.get("admin_include_all") == "true" - - # Additionally "projects" is a special one where we always want to include all projects if you're an org admin - if self.scope_object == "project": - include_all_if_admin = True - - # Only apply access control filter if we're not already in a recursive call - queryset = self.user_access_control.filter_queryset_by_access_level( - queryset, include_all_if_admin=include_all_if_admin - ) + queryset = self._filter_queryset_by_access_level(queryset) return queryset finally: self._in_get_queryset = False + def _filter_queryset_by_access_level(self, queryset: QuerySet) -> QuerySet: + if self.action != "list": + # NOTE: If we are getting an individual object then we don't filter it out here - this is handled by the permission logic + # The reason being, that if we filter out here already, we can't load the object which is required for checking access controls for it + return queryset + + # NOTE: Half implemented - for admins, they may want to include listing of results that are not accessible (like private resources) + include_all_if_admin = self.request.GET.get("admin_include_all") == "true" + + # Additionally "projects" is a special one where we always want to include all projects if you're an org admin + if self.scope_object == "project": + include_all_if_admin = True + + return self.user_access_control.filter_queryset_by_access_level( + queryset, include_all_if_admin=include_all_if_admin + ) + def dangerously_get_object(self) -> Any: """ WARNING: This should be used very carefully. It bypasses common security access control checks. @@ -307,7 +309,7 @@ def organization_id(self) -> str: current_organization_id = self.team.organization_id if self._is_project_view: current_organization_id = self.project.organization_id - else: + elif user: current_organization_id = user.current_organization_id if not current_organization_id: diff --git a/posthog/api/test/dashboards/__snapshots__/test_dashboard.ambr b/posthog/api/test/dashboards/__snapshots__/test_dashboard.ambr index 6567ed2ba50fe..3a3372182c533 100644 --- a/posthog/api/test/dashboards/__snapshots__/test_dashboard.ambr +++ b/posthog/api/test/dashboards/__snapshots__/test_dashboard.ambr @@ -439,6 +439,32 @@ ''' # --- # name: TestDashboard.test_adding_insights_is_not_nplus1_for_gets.20 + ''' + SELECT "posthog_organization"."id", + "posthog_organization"."name", + "posthog_organization"."slug", + "posthog_organization"."logo_media_id", + "posthog_organization"."created_at", + "posthog_organization"."updated_at", + "posthog_organization"."plugins_access_level", + "posthog_organization"."for_internal_metrics", + "posthog_organization"."is_member_join_email_enabled", + "posthog_organization"."enforce_2fa", + "posthog_organization"."is_hipaa", + "posthog_organization"."customer_id", + "posthog_organization"."available_product_features", + "posthog_organization"."usage", + "posthog_organization"."never_drop_data", + "posthog_organization"."customer_trust_scores", + "posthog_organization"."setup_section_2_completed", + "posthog_organization"."personalization", + "posthog_organization"."domain_whitelist" + FROM "posthog_organization" + WHERE "posthog_organization"."id" = '00000000-0000-0000-0000-000000000000'::uuid + LIMIT 21 + ''' +# --- +# name: TestDashboard.test_adding_insights_is_not_nplus1_for_gets.21 ''' SELECT "posthog_dashboard"."id", "posthog_dashboard"."name", @@ -493,7 +519,7 @@ LIMIT 21 ''' # --- -# name: TestDashboard.test_adding_insights_is_not_nplus1_for_gets.21 +# name: TestDashboard.test_adding_insights_is_not_nplus1_for_gets.22 ''' SELECT "posthog_sharingconfiguration"."id", "posthog_sharingconfiguration"."team_id", @@ -511,7 +537,7 @@ 5 /* ... */) ''' # --- -# name: TestDashboard.test_adding_insights_is_not_nplus1_for_gets.22 +# name: TestDashboard.test_adding_insights_is_not_nplus1_for_gets.23 ''' SELECT "posthog_dashboardtile"."id", "posthog_dashboardtile"."dashboard_id", @@ -703,7 +729,7 @@ ORDER BY "posthog_dashboarditem"."order" ASC ''' # --- -# name: TestDashboard.test_adding_insights_is_not_nplus1_for_gets.23 +# name: TestDashboard.test_adding_insights_is_not_nplus1_for_gets.24 ''' SELECT "posthog_insightcachingstate"."id", "posthog_insightcachingstate"."team_id", @@ -724,7 +750,7 @@ 5 /* ... */) ''' # --- -# name: TestDashboard.test_adding_insights_is_not_nplus1_for_gets.24 +# name: TestDashboard.test_adding_insights_is_not_nplus1_for_gets.25 ''' SELECT ("posthog_dashboardtile"."insight_id") AS "_prefetch_related_val_insight_id", "posthog_dashboard"."id", @@ -761,7 +787,7 @@ 5 /* ... */)) ''' # --- -# name: TestDashboard.test_adding_insights_is_not_nplus1_for_gets.25 +# name: TestDashboard.test_adding_insights_is_not_nplus1_for_gets.26 ''' SELECT "posthog_dashboardtile"."id", "posthog_dashboardtile"."dashboard_id", @@ -786,7 +812,7 @@ 5 /* ... */)) ''' # --- -# name: TestDashboard.test_adding_insights_is_not_nplus1_for_gets.26 +# name: TestDashboard.test_adding_insights_is_not_nplus1_for_gets.27 ''' SELECT "posthog_dashboard"."id", "posthog_dashboard"."name", @@ -813,7 +839,29 @@ 5 /* ... */) ''' # --- -# name: TestDashboard.test_adding_insights_is_not_nplus1_for_gets.27 +# name: TestDashboard.test_adding_insights_is_not_nplus1_for_gets.28 + ''' + SELECT "posthog_taggeditem"."id", + "posthog_taggeditem"."tag_id", + "posthog_taggeditem"."dashboard_id", + "posthog_taggeditem"."insight_id", + "posthog_taggeditem"."event_definition_id", + "posthog_taggeditem"."property_definition_id", + "posthog_taggeditem"."action_id", + "posthog_taggeditem"."feature_flag_id", + "posthog_tag"."id", + "posthog_tag"."name", + "posthog_tag"."team_id" + FROM "posthog_taggeditem" + INNER JOIN "posthog_tag" ON ("posthog_taggeditem"."tag_id" = "posthog_tag"."id") + WHERE "posthog_taggeditem"."dashboard_id" IN (1, + 2, + 3, + 4, + 5 /* ... */) + ''' +# --- +# name: TestDashboard.test_adding_insights_is_not_nplus1_for_gets.29 ''' SELECT "posthog_dashboardtile"."id", "posthog_dashboardtile"."dashboard_id", @@ -1006,7 +1054,53 @@ ORDER BY "posthog_dashboarditem"."order" ASC ''' # --- -# name: TestDashboard.test_adding_insights_is_not_nplus1_for_gets.28 +# name: TestDashboard.test_adding_insights_is_not_nplus1_for_gets.3 + ''' + SELECT "ee_accesscontrol"."id", + "ee_accesscontrol"."team_id", + "ee_accesscontrol"."access_level", + "ee_accesscontrol"."resource", + "ee_accesscontrol"."resource_id", + "ee_accesscontrol"."organization_member_id", + "ee_accesscontrol"."role_id", + "ee_accesscontrol"."created_by_id", + "ee_accesscontrol"."created_at", + "ee_accesscontrol"."updated_at" + FROM "ee_accesscontrol" + LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") + WHERE (("ee_accesscontrol"."organization_member_id" IS NULL + AND "ee_accesscontrol"."resource" = 'project' + AND "ee_accesscontrol"."resource_id" = '99999' + AND "ee_accesscontrol"."role_id" IS NULL + AND "ee_accesscontrol"."team_id" = 99999) + OR ("posthog_organizationmembership"."user_id" = 99999 + AND "ee_accesscontrol"."resource" = 'project' + AND "ee_accesscontrol"."resource_id" = '99999' + AND "ee_accesscontrol"."role_id" IS NULL + AND "ee_accesscontrol"."team_id" = 99999) + OR ("ee_accesscontrol"."organization_member_id" IS NULL + AND "ee_accesscontrol"."resource" = 'insight' + AND "ee_accesscontrol"."resource_id" IS NULL + AND "ee_accesscontrol"."role_id" IS NULL + AND "ee_accesscontrol"."team_id" = 99999) + OR ("posthog_organizationmembership"."user_id" = 99999 + AND "ee_accesscontrol"."resource" = 'insight' + AND "ee_accesscontrol"."resource_id" IS NULL + AND "ee_accesscontrol"."role_id" IS NULL + AND "ee_accesscontrol"."team_id" = 99999) + OR ("ee_accesscontrol"."organization_member_id" IS NULL + AND "ee_accesscontrol"."resource" = 'insight' + AND "ee_accesscontrol"."resource_id" IS NOT NULL + AND "ee_accesscontrol"."role_id" IS NULL + AND "ee_accesscontrol"."team_id" = 99999) + OR ("posthog_organizationmembership"."user_id" = 99999 + AND "ee_accesscontrol"."resource" = 'insight' + AND "ee_accesscontrol"."resource_id" IS NOT NULL + AND "ee_accesscontrol"."role_id" IS NULL + AND "ee_accesscontrol"."team_id" = 99999)) + ''' +# --- +# name: TestDashboard.test_adding_insights_is_not_nplus1_for_gets.30 ''' SELECT "posthog_insightcachingstate"."id", "posthog_insightcachingstate"."team_id", @@ -1027,7 +1121,7 @@ 5 /* ... */) ''' # --- -# name: TestDashboard.test_adding_insights_is_not_nplus1_for_gets.29 +# name: TestDashboard.test_adding_insights_is_not_nplus1_for_gets.31 ''' SELECT ("posthog_dashboardtile"."insight_id") AS "_prefetch_related_val_insight_id", "posthog_dashboard"."id", @@ -1064,53 +1158,7 @@ 5 /* ... */)) ''' # --- -# name: TestDashboard.test_adding_insights_is_not_nplus1_for_gets.3 - ''' - SELECT "ee_accesscontrol"."id", - "ee_accesscontrol"."team_id", - "ee_accesscontrol"."access_level", - "ee_accesscontrol"."resource", - "ee_accesscontrol"."resource_id", - "ee_accesscontrol"."organization_member_id", - "ee_accesscontrol"."role_id", - "ee_accesscontrol"."created_by_id", - "ee_accesscontrol"."created_at", - "ee_accesscontrol"."updated_at" - FROM "ee_accesscontrol" - LEFT OUTER JOIN "posthog_organizationmembership" ON ("ee_accesscontrol"."organization_member_id" = "posthog_organizationmembership"."id") - WHERE (("ee_accesscontrol"."organization_member_id" IS NULL - AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '99999' - AND "ee_accesscontrol"."role_id" IS NULL - AND "ee_accesscontrol"."team_id" = 99999) - OR ("posthog_organizationmembership"."user_id" = 99999 - AND "ee_accesscontrol"."resource" = 'project' - AND "ee_accesscontrol"."resource_id" = '99999' - AND "ee_accesscontrol"."role_id" IS NULL - AND "ee_accesscontrol"."team_id" = 99999) - OR ("ee_accesscontrol"."organization_member_id" IS NULL - AND "ee_accesscontrol"."resource" = 'insight' - AND "ee_accesscontrol"."resource_id" IS NULL - AND "ee_accesscontrol"."role_id" IS NULL - AND "ee_accesscontrol"."team_id" = 99999) - OR ("posthog_organizationmembership"."user_id" = 99999 - AND "ee_accesscontrol"."resource" = 'insight' - AND "ee_accesscontrol"."resource_id" IS NULL - AND "ee_accesscontrol"."role_id" IS NULL - AND "ee_accesscontrol"."team_id" = 99999) - OR ("ee_accesscontrol"."organization_member_id" IS NULL - AND "ee_accesscontrol"."resource" = 'insight' - AND "ee_accesscontrol"."resource_id" IS NOT NULL - AND "ee_accesscontrol"."role_id" IS NULL - AND "ee_accesscontrol"."team_id" = 99999) - OR ("posthog_organizationmembership"."user_id" = 99999 - AND "ee_accesscontrol"."resource" = 'insight' - AND "ee_accesscontrol"."resource_id" IS NOT NULL - AND "ee_accesscontrol"."role_id" IS NULL - AND "ee_accesscontrol"."team_id" = 99999)) - ''' -# --- -# name: TestDashboard.test_adding_insights_is_not_nplus1_for_gets.30 +# name: TestDashboard.test_adding_insights_is_not_nplus1_for_gets.32 ''' SELECT "posthog_dashboardtile"."id", "posthog_dashboardtile"."dashboard_id", @@ -1135,7 +1183,7 @@ 5 /* ... */)) ''' # --- -# name: TestDashboard.test_adding_insights_is_not_nplus1_for_gets.31 +# name: TestDashboard.test_adding_insights_is_not_nplus1_for_gets.33 ''' SELECT "posthog_dashboard"."id", "posthog_dashboard"."name", @@ -1162,7 +1210,7 @@ 5 /* ... */) ''' # --- -# name: TestDashboard.test_adding_insights_is_not_nplus1_for_gets.32 +# name: TestDashboard.test_adding_insights_is_not_nplus1_for_gets.34 ''' SELECT "posthog_taggeditem"."id", "posthog_taggeditem"."tag_id", @@ -1184,7 +1232,7 @@ 5 /* ... */) ''' # --- -# name: TestDashboard.test_adding_insights_is_not_nplus1_for_gets.33 +# name: TestDashboard.test_adding_insights_is_not_nplus1_for_gets.35 ''' SELECT "posthog_dashboard"."id", "posthog_dashboard"."name", @@ -1212,40 +1260,6 @@ 5 /* ... */)) ''' # --- -# name: TestDashboard.test_adding_insights_is_not_nplus1_for_gets.34 - ''' - SELECT "posthog_organization"."id", - "posthog_organization"."name", - "posthog_organization"."slug", - "posthog_organization"."logo_media_id", - "posthog_organization"."created_at", - "posthog_organization"."updated_at", - "posthog_organization"."plugins_access_level", - "posthog_organization"."for_internal_metrics", - "posthog_organization"."is_member_join_email_enabled", - "posthog_organization"."enforce_2fa", - "posthog_organization"."is_hipaa", - "posthog_organization"."customer_id", - "posthog_organization"."available_product_features", - "posthog_organization"."usage", - "posthog_organization"."never_drop_data", - "posthog_organization"."customer_trust_scores", - "posthog_organization"."setup_section_2_completed", - "posthog_organization"."personalization", - "posthog_organization"."domain_whitelist" - FROM "posthog_organization" - WHERE "posthog_organization"."id" = '00000000-0000-0000-0000-000000000000'::uuid - LIMIT 21 - ''' -# --- -# name: TestDashboard.test_adding_insights_is_not_nplus1_for_gets.35 - ''' - SELECT "posthog_tag"."name" - FROM "posthog_taggeditem" - INNER JOIN "posthog_tag" ON ("posthog_taggeditem"."tag_id" = "posthog_tag"."id") - WHERE "posthog_taggeditem"."dashboard_id" = 99999 - ''' -# --- # name: TestDashboard.test_adding_insights_is_not_nplus1_for_gets.4 ''' SELECT "posthog_organizationmembership"."id", @@ -4861,6 +4875,32 @@ ''' # --- # name: TestDashboard.test_loading_individual_dashboard_does_not_prefetch_all_possible_tiles.140 + ''' + SELECT "posthog_organization"."id", + "posthog_organization"."name", + "posthog_organization"."slug", + "posthog_organization"."logo_media_id", + "posthog_organization"."created_at", + "posthog_organization"."updated_at", + "posthog_organization"."plugins_access_level", + "posthog_organization"."for_internal_metrics", + "posthog_organization"."is_member_join_email_enabled", + "posthog_organization"."enforce_2fa", + "posthog_organization"."is_hipaa", + "posthog_organization"."customer_id", + "posthog_organization"."available_product_features", + "posthog_organization"."usage", + "posthog_organization"."never_drop_data", + "posthog_organization"."customer_trust_scores", + "posthog_organization"."setup_section_2_completed", + "posthog_organization"."personalization", + "posthog_organization"."domain_whitelist" + FROM "posthog_organization" + WHERE "posthog_organization"."id" = '00000000-0000-0000-0000-000000000000'::uuid + LIMIT 21 + ''' +# --- +# name: TestDashboard.test_loading_individual_dashboard_does_not_prefetch_all_possible_tiles.141 ''' SELECT "posthog_dashboard"."id", "posthog_dashboard"."name", @@ -4915,7 +4955,7 @@ LIMIT 21 ''' # --- -# name: TestDashboard.test_loading_individual_dashboard_does_not_prefetch_all_possible_tiles.141 +# name: TestDashboard.test_loading_individual_dashboard_does_not_prefetch_all_possible_tiles.142 ''' SELECT "posthog_sharingconfiguration"."id", "posthog_sharingconfiguration"."team_id", @@ -4933,7 +4973,7 @@ 5 /* ... */) ''' # --- -# name: TestDashboard.test_loading_individual_dashboard_does_not_prefetch_all_possible_tiles.142 +# name: TestDashboard.test_loading_individual_dashboard_does_not_prefetch_all_possible_tiles.143 ''' SELECT "posthog_dashboardtile"."id", "posthog_dashboardtile"."dashboard_id", @@ -5125,7 +5165,7 @@ ORDER BY "posthog_dashboarditem"."order" ASC ''' # --- -# name: TestDashboard.test_loading_individual_dashboard_does_not_prefetch_all_possible_tiles.143 +# name: TestDashboard.test_loading_individual_dashboard_does_not_prefetch_all_possible_tiles.144 ''' SELECT "posthog_insightcachingstate"."id", "posthog_insightcachingstate"."team_id", @@ -5146,7 +5186,7 @@ 5 /* ... */) ''' # --- -# name: TestDashboard.test_loading_individual_dashboard_does_not_prefetch_all_possible_tiles.144 +# name: TestDashboard.test_loading_individual_dashboard_does_not_prefetch_all_possible_tiles.145 ''' SELECT ("posthog_dashboardtile"."insight_id") AS "_prefetch_related_val_insight_id", "posthog_dashboard"."id", @@ -5183,7 +5223,7 @@ 5 /* ... */)) ''' # --- -# name: TestDashboard.test_loading_individual_dashboard_does_not_prefetch_all_possible_tiles.145 +# name: TestDashboard.test_loading_individual_dashboard_does_not_prefetch_all_possible_tiles.146 ''' SELECT "posthog_dashboardtile"."id", "posthog_dashboardtile"."dashboard_id", @@ -5208,7 +5248,7 @@ 5 /* ... */)) ''' # --- -# name: TestDashboard.test_loading_individual_dashboard_does_not_prefetch_all_possible_tiles.146 +# name: TestDashboard.test_loading_individual_dashboard_does_not_prefetch_all_possible_tiles.147 ''' SELECT "posthog_dashboard"."id", "posthog_dashboard"."name", @@ -5235,24 +5275,46 @@ 5 /* ... */) ''' # --- -# name: TestDashboard.test_loading_individual_dashboard_does_not_prefetch_all_possible_tiles.147 +# name: TestDashboard.test_loading_individual_dashboard_does_not_prefetch_all_possible_tiles.148 ''' - SELECT "posthog_dashboardtile"."id", - "posthog_dashboardtile"."dashboard_id", - "posthog_dashboardtile"."insight_id", - "posthog_dashboardtile"."text_id", - "posthog_dashboardtile"."layouts", - "posthog_dashboardtile"."color", - "posthog_dashboardtile"."filters_hash", - "posthog_dashboardtile"."last_refresh", - "posthog_dashboardtile"."refreshing", - "posthog_dashboardtile"."refresh_attempt", - "posthog_dashboardtile"."deleted", - "posthog_dashboarditem"."id", - "posthog_dashboarditem"."name", - "posthog_dashboarditem"."derived_name", - "posthog_dashboarditem"."description", - "posthog_dashboarditem"."team_id", + SELECT "posthog_taggeditem"."id", + "posthog_taggeditem"."tag_id", + "posthog_taggeditem"."dashboard_id", + "posthog_taggeditem"."insight_id", + "posthog_taggeditem"."event_definition_id", + "posthog_taggeditem"."property_definition_id", + "posthog_taggeditem"."action_id", + "posthog_taggeditem"."feature_flag_id", + "posthog_tag"."id", + "posthog_tag"."name", + "posthog_tag"."team_id" + FROM "posthog_taggeditem" + INNER JOIN "posthog_tag" ON ("posthog_taggeditem"."tag_id" = "posthog_tag"."id") + WHERE "posthog_taggeditem"."dashboard_id" IN (1, + 2, + 3, + 4, + 5 /* ... */) + ''' +# --- +# name: TestDashboard.test_loading_individual_dashboard_does_not_prefetch_all_possible_tiles.149 + ''' + SELECT "posthog_dashboardtile"."id", + "posthog_dashboardtile"."dashboard_id", + "posthog_dashboardtile"."insight_id", + "posthog_dashboardtile"."text_id", + "posthog_dashboardtile"."layouts", + "posthog_dashboardtile"."color", + "posthog_dashboardtile"."filters_hash", + "posthog_dashboardtile"."last_refresh", + "posthog_dashboardtile"."refreshing", + "posthog_dashboardtile"."refresh_attempt", + "posthog_dashboardtile"."deleted", + "posthog_dashboarditem"."id", + "posthog_dashboarditem"."name", + "posthog_dashboarditem"."derived_name", + "posthog_dashboarditem"."description", + "posthog_dashboarditem"."team_id", "posthog_dashboarditem"."filters", "posthog_dashboarditem"."filters_hash", "posthog_dashboarditem"."query", @@ -5428,64 +5490,6 @@ ORDER BY "posthog_dashboarditem"."order" ASC ''' # --- -# name: TestDashboard.test_loading_individual_dashboard_does_not_prefetch_all_possible_tiles.148 - ''' - SELECT "posthog_insightcachingstate"."id", - "posthog_insightcachingstate"."team_id", - "posthog_insightcachingstate"."insight_id", - "posthog_insightcachingstate"."dashboard_tile_id", - "posthog_insightcachingstate"."cache_key", - "posthog_insightcachingstate"."target_cache_age_seconds", - "posthog_insightcachingstate"."last_refresh", - "posthog_insightcachingstate"."last_refresh_queued_at", - "posthog_insightcachingstate"."refresh_attempt", - "posthog_insightcachingstate"."created_at", - "posthog_insightcachingstate"."updated_at" - FROM "posthog_insightcachingstate" - WHERE "posthog_insightcachingstate"."dashboard_tile_id" IN (1, - 2, - 3, - 4, - 5 /* ... */) - ''' -# --- -# name: TestDashboard.test_loading_individual_dashboard_does_not_prefetch_all_possible_tiles.149 - ''' - SELECT ("posthog_dashboardtile"."insight_id") AS "_prefetch_related_val_insight_id", - "posthog_dashboard"."id", - "posthog_dashboard"."name", - "posthog_dashboard"."description", - "posthog_dashboard"."team_id", - "posthog_dashboard"."pinned", - "posthog_dashboard"."created_at", - "posthog_dashboard"."created_by_id", - "posthog_dashboard"."deleted", - "posthog_dashboard"."last_accessed_at", - "posthog_dashboard"."filters", - "posthog_dashboard"."variables", - "posthog_dashboard"."creation_mode", - "posthog_dashboard"."restriction_level", - "posthog_dashboard"."deprecated_tags", - "posthog_dashboard"."tags", - "posthog_dashboard"."share_token", - "posthog_dashboard"."is_shared" - FROM "posthog_dashboard" - INNER JOIN "posthog_dashboardtile" ON ("posthog_dashboard"."id" = "posthog_dashboardtile"."dashboard_id") - WHERE (NOT ("posthog_dashboard"."deleted") - AND "posthog_dashboard"."id" IN - (SELECT U0."dashboard_id" - FROM "posthog_dashboardtile" U0 - INNER JOIN "posthog_dashboard" U1 ON (U0."dashboard_id" = U1."id") - WHERE (NOT (U0."deleted" - AND U0."deleted" IS NOT NULL) - AND NOT (U1."deleted"))) - AND "posthog_dashboardtile"."insight_id" IN (1, - 2, - 3, - 4, - 5 /* ... */)) - ''' -# --- # name: TestDashboard.test_loading_individual_dashboard_does_not_prefetch_all_possible_tiles.15 ''' SELECT "posthog_team"."id", @@ -5554,6 +5558,64 @@ ''' # --- # name: TestDashboard.test_loading_individual_dashboard_does_not_prefetch_all_possible_tiles.150 + ''' + SELECT "posthog_insightcachingstate"."id", + "posthog_insightcachingstate"."team_id", + "posthog_insightcachingstate"."insight_id", + "posthog_insightcachingstate"."dashboard_tile_id", + "posthog_insightcachingstate"."cache_key", + "posthog_insightcachingstate"."target_cache_age_seconds", + "posthog_insightcachingstate"."last_refresh", + "posthog_insightcachingstate"."last_refresh_queued_at", + "posthog_insightcachingstate"."refresh_attempt", + "posthog_insightcachingstate"."created_at", + "posthog_insightcachingstate"."updated_at" + FROM "posthog_insightcachingstate" + WHERE "posthog_insightcachingstate"."dashboard_tile_id" IN (1, + 2, + 3, + 4, + 5 /* ... */) + ''' +# --- +# name: TestDashboard.test_loading_individual_dashboard_does_not_prefetch_all_possible_tiles.151 + ''' + SELECT ("posthog_dashboardtile"."insight_id") AS "_prefetch_related_val_insight_id", + "posthog_dashboard"."id", + "posthog_dashboard"."name", + "posthog_dashboard"."description", + "posthog_dashboard"."team_id", + "posthog_dashboard"."pinned", + "posthog_dashboard"."created_at", + "posthog_dashboard"."created_by_id", + "posthog_dashboard"."deleted", + "posthog_dashboard"."last_accessed_at", + "posthog_dashboard"."filters", + "posthog_dashboard"."variables", + "posthog_dashboard"."creation_mode", + "posthog_dashboard"."restriction_level", + "posthog_dashboard"."deprecated_tags", + "posthog_dashboard"."tags", + "posthog_dashboard"."share_token", + "posthog_dashboard"."is_shared" + FROM "posthog_dashboard" + INNER JOIN "posthog_dashboardtile" ON ("posthog_dashboard"."id" = "posthog_dashboardtile"."dashboard_id") + WHERE (NOT ("posthog_dashboard"."deleted") + AND "posthog_dashboard"."id" IN + (SELECT U0."dashboard_id" + FROM "posthog_dashboardtile" U0 + INNER JOIN "posthog_dashboard" U1 ON (U0."dashboard_id" = U1."id") + WHERE (NOT (U0."deleted" + AND U0."deleted" IS NOT NULL) + AND NOT (U1."deleted"))) + AND "posthog_dashboardtile"."insight_id" IN (1, + 2, + 3, + 4, + 5 /* ... */)) + ''' +# --- +# name: TestDashboard.test_loading_individual_dashboard_does_not_prefetch_all_possible_tiles.152 ''' SELECT "posthog_dashboardtile"."id", "posthog_dashboardtile"."dashboard_id", @@ -5578,7 +5640,7 @@ 5 /* ... */)) ''' # --- -# name: TestDashboard.test_loading_individual_dashboard_does_not_prefetch_all_possible_tiles.151 +# name: TestDashboard.test_loading_individual_dashboard_does_not_prefetch_all_possible_tiles.153 ''' SELECT "posthog_dashboard"."id", "posthog_dashboard"."name", @@ -5605,7 +5667,7 @@ 5 /* ... */) ''' # --- -# name: TestDashboard.test_loading_individual_dashboard_does_not_prefetch_all_possible_tiles.152 +# name: TestDashboard.test_loading_individual_dashboard_does_not_prefetch_all_possible_tiles.154 ''' SELECT "posthog_taggeditem"."id", "posthog_taggeditem"."tag_id", @@ -5627,7 +5689,7 @@ 5 /* ... */) ''' # --- -# name: TestDashboard.test_loading_individual_dashboard_does_not_prefetch_all_possible_tiles.153 +# name: TestDashboard.test_loading_individual_dashboard_does_not_prefetch_all_possible_tiles.155 ''' SELECT "posthog_dashboard"."id", "posthog_dashboard"."name", @@ -5655,40 +5717,6 @@ 5 /* ... */)) ''' # --- -# name: TestDashboard.test_loading_individual_dashboard_does_not_prefetch_all_possible_tiles.154 - ''' - SELECT "posthog_organization"."id", - "posthog_organization"."name", - "posthog_organization"."slug", - "posthog_organization"."logo_media_id", - "posthog_organization"."created_at", - "posthog_organization"."updated_at", - "posthog_organization"."plugins_access_level", - "posthog_organization"."for_internal_metrics", - "posthog_organization"."is_member_join_email_enabled", - "posthog_organization"."enforce_2fa", - "posthog_organization"."is_hipaa", - "posthog_organization"."customer_id", - "posthog_organization"."available_product_features", - "posthog_organization"."usage", - "posthog_organization"."never_drop_data", - "posthog_organization"."customer_trust_scores", - "posthog_organization"."setup_section_2_completed", - "posthog_organization"."personalization", - "posthog_organization"."domain_whitelist" - FROM "posthog_organization" - WHERE "posthog_organization"."id" = '00000000-0000-0000-0000-000000000000'::uuid - LIMIT 21 - ''' -# --- -# name: TestDashboard.test_loading_individual_dashboard_does_not_prefetch_all_possible_tiles.155 - ''' - SELECT "posthog_tag"."name" - FROM "posthog_taggeditem" - INNER JOIN "posthog_tag" ON ("posthog_taggeditem"."tag_id" = "posthog_tag"."id") - WHERE "posthog_taggeditem"."dashboard_id" = 99999 - ''' -# --- # name: TestDashboard.test_loading_individual_dashboard_does_not_prefetch_all_possible_tiles.16 ''' SELECT "posthog_organizationmembership"."id", @@ -9446,56 +9474,237 @@ # --- # name: TestDashboard.test_retrieve_dashboard.10 ''' - SELECT "posthog_organization"."id", - "posthog_organization"."name", - "posthog_organization"."slug", - "posthog_organization"."logo_media_id", - "posthog_organization"."created_at", - "posthog_organization"."updated_at", - "posthog_organization"."plugins_access_level", - "posthog_organization"."for_internal_metrics", - "posthog_organization"."is_member_join_email_enabled", - "posthog_organization"."enforce_2fa", - "posthog_organization"."is_hipaa", - "posthog_organization"."customer_id", - "posthog_organization"."available_product_features", - "posthog_organization"."usage", - "posthog_organization"."never_drop_data", - "posthog_organization"."customer_trust_scores", - "posthog_organization"."setup_section_2_completed", - "posthog_organization"."personalization", - "posthog_organization"."domain_whitelist" - FROM "posthog_organization" - WHERE "posthog_organization"."id" = '00000000-0000-0000-0000-000000000000'::uuid - LIMIT 21 - ''' -# --- -# name: TestDashboard.test_retrieve_dashboard.11 - ''' - SELECT "posthog_tag"."name" + SELECT "posthog_taggeditem"."id", + "posthog_taggeditem"."tag_id", + "posthog_taggeditem"."dashboard_id", + "posthog_taggeditem"."insight_id", + "posthog_taggeditem"."event_definition_id", + "posthog_taggeditem"."property_definition_id", + "posthog_taggeditem"."action_id", + "posthog_taggeditem"."feature_flag_id", + "posthog_tag"."id", + "posthog_tag"."name", + "posthog_tag"."team_id" FROM "posthog_taggeditem" INNER JOIN "posthog_tag" ON ("posthog_taggeditem"."tag_id" = "posthog_tag"."id") - WHERE "posthog_taggeditem"."dashboard_id" = 99999 + WHERE "posthog_taggeditem"."dashboard_id" IN (1, + 2, + 3, + 4, + 5 /* ... */) ''' # --- -# name: TestDashboard.test_retrieve_dashboard.2 +# name: TestDashboard.test_retrieve_dashboard.11 ''' - SELECT "posthog_team"."id", - "posthog_team"."uuid", - "posthog_team"."organization_id", - "posthog_team"."project_id", - "posthog_team"."api_token", - "posthog_team"."app_urls", - "posthog_team"."name", - "posthog_team"."slack_incoming_webhook", - "posthog_team"."created_at", - "posthog_team"."updated_at", - "posthog_team"."anonymize_ips", - "posthog_team"."completed_snippet_onboarding", - "posthog_team"."has_completed_onboarding_for", - "posthog_team"."ingested_event", - "posthog_team"."autocapture_opt_out", - "posthog_team"."autocapture_web_vitals_opt_in", + SELECT "posthog_dashboardtile"."id", + "posthog_dashboardtile"."dashboard_id", + "posthog_dashboardtile"."insight_id", + "posthog_dashboardtile"."text_id", + "posthog_dashboardtile"."layouts", + "posthog_dashboardtile"."color", + "posthog_dashboardtile"."filters_hash", + "posthog_dashboardtile"."last_refresh", + "posthog_dashboardtile"."refreshing", + "posthog_dashboardtile"."refresh_attempt", + "posthog_dashboardtile"."deleted", + "posthog_dashboarditem"."id", + "posthog_dashboarditem"."name", + "posthog_dashboarditem"."derived_name", + "posthog_dashboarditem"."description", + "posthog_dashboarditem"."team_id", + "posthog_dashboarditem"."filters", + "posthog_dashboarditem"."filters_hash", + "posthog_dashboarditem"."query", + "posthog_dashboarditem"."order", + "posthog_dashboarditem"."deleted", + "posthog_dashboarditem"."saved", + "posthog_dashboarditem"."created_at", + "posthog_dashboarditem"."refreshing", + "posthog_dashboarditem"."created_by_id", + "posthog_dashboarditem"."is_sample", + "posthog_dashboarditem"."short_id", + "posthog_dashboarditem"."favorited", + "posthog_dashboarditem"."refresh_attempt", + "posthog_dashboarditem"."last_modified_at", + "posthog_dashboarditem"."last_modified_by_id", + "posthog_dashboarditem"."dashboard_id", + "posthog_dashboarditem"."last_refresh", + "posthog_dashboarditem"."layouts", + "posthog_dashboarditem"."color", + "posthog_dashboarditem"."dive_dashboard_id", + "posthog_dashboarditem"."updated_at", + "posthog_dashboarditem"."deprecated_tags", + "posthog_dashboarditem"."tags", + "posthog_team"."id", + "posthog_team"."uuid", + "posthog_team"."organization_id", + "posthog_team"."project_id", + "posthog_team"."api_token", + "posthog_team"."app_urls", + "posthog_team"."name", + "posthog_team"."slack_incoming_webhook", + "posthog_team"."created_at", + "posthog_team"."updated_at", + "posthog_team"."anonymize_ips", + "posthog_team"."completed_snippet_onboarding", + "posthog_team"."has_completed_onboarding_for", + "posthog_team"."ingested_event", + "posthog_team"."autocapture_opt_out", + "posthog_team"."autocapture_web_vitals_opt_in", + "posthog_team"."autocapture_web_vitals_allowed_metrics", + "posthog_team"."autocapture_exceptions_opt_in", + "posthog_team"."autocapture_exceptions_errors_to_ignore", + "posthog_team"."person_processing_opt_out", + "posthog_team"."session_recording_opt_in", + "posthog_team"."session_recording_sample_rate", + "posthog_team"."session_recording_minimum_duration_milliseconds", + "posthog_team"."session_recording_linked_flag", + "posthog_team"."session_recording_network_payload_capture_config", + "posthog_team"."session_recording_url_trigger_config", + "posthog_team"."session_recording_url_blocklist_config", + "posthog_team"."session_recording_event_trigger_config", + "posthog_team"."session_replay_config", + "posthog_team"."survey_config", + "posthog_team"."capture_console_log_opt_in", + "posthog_team"."capture_performance_opt_in", + "posthog_team"."capture_dead_clicks", + "posthog_team"."surveys_opt_in", + "posthog_team"."heatmaps_opt_in", + "posthog_team"."flags_persistence_default", + "posthog_team"."session_recording_version", + "posthog_team"."signup_token", + "posthog_team"."is_demo", + "posthog_team"."access_control", + "posthog_team"."week_start_day", + "posthog_team"."inject_web_apps", + "posthog_team"."test_account_filters", + "posthog_team"."test_account_filters_default_checked", + "posthog_team"."path_cleaning_filters", + "posthog_team"."timezone", + "posthog_team"."data_attributes", + "posthog_team"."person_display_name_properties", + "posthog_team"."live_events_columns", + "posthog_team"."recording_domains", + "posthog_team"."human_friendly_comparison_periods", + "posthog_team"."cookieless_server_hash_mode", + "posthog_team"."primary_dashboard_id", + "posthog_team"."default_data_theme", + "posthog_team"."extra_settings", + "posthog_team"."modifiers", + "posthog_team"."correlation_config", + "posthog_team"."session_recording_retention_period_days", + "posthog_team"."plugins_opt_in", + "posthog_team"."opt_out_capture", + "posthog_team"."event_names", + "posthog_team"."event_names_with_usage", + "posthog_team"."event_properties", + "posthog_team"."event_properties_with_usage", + "posthog_team"."event_properties_numerical", + "posthog_team"."external_data_workspace_id", + "posthog_team"."external_data_workspace_last_synced_at", + "posthog_user"."id", + "posthog_user"."password", + "posthog_user"."last_login", + "posthog_user"."first_name", + "posthog_user"."last_name", + "posthog_user"."is_staff", + "posthog_user"."date_joined", + "posthog_user"."uuid", + "posthog_user"."current_organization_id", + "posthog_user"."current_team_id", + "posthog_user"."email", + "posthog_user"."pending_email", + "posthog_user"."temporary_token", + "posthog_user"."distinct_id", + "posthog_user"."is_email_verified", + "posthog_user"."requested_password_reset_at", + "posthog_user"."has_seen_product_intro_for", + "posthog_user"."strapi_id", + "posthog_user"."is_active", + "posthog_user"."role_at_organization", + "posthog_user"."theme_mode", + "posthog_user"."partial_notification_settings", + "posthog_user"."anonymize_data", + "posthog_user"."toolbar_mode", + "posthog_user"."hedgehog_config", + "posthog_user"."events_column_config", + "posthog_user"."email_opt_in", + T6."id", + T6."password", + T6."last_login", + T6."first_name", + T6."last_name", + T6."is_staff", + T6."date_joined", + T6."uuid", + T6."current_organization_id", + T6."current_team_id", + T6."email", + T6."pending_email", + T6."temporary_token", + T6."distinct_id", + T6."is_email_verified", + T6."requested_password_reset_at", + T6."has_seen_product_intro_for", + T6."strapi_id", + T6."is_active", + T6."role_at_organization", + T6."theme_mode", + T6."partial_notification_settings", + T6."anonymize_data", + T6."toolbar_mode", + T6."hedgehog_config", + T6."events_column_config", + T6."email_opt_in", + "posthog_text"."id", + "posthog_text"."body", + "posthog_text"."created_by_id", + "posthog_text"."last_modified_at", + "posthog_text"."last_modified_by_id", + "posthog_text"."team_id" + FROM "posthog_dashboardtile" + INNER JOIN "posthog_dashboard" ON ("posthog_dashboardtile"."dashboard_id" = "posthog_dashboard"."id") + LEFT OUTER JOIN "posthog_dashboarditem" ON ("posthog_dashboardtile"."insight_id" = "posthog_dashboarditem"."id") + LEFT OUTER JOIN "posthog_team" ON ("posthog_dashboarditem"."team_id" = "posthog_team"."id") + LEFT OUTER JOIN "posthog_user" ON ("posthog_dashboarditem"."created_by_id" = "posthog_user"."id") + LEFT OUTER JOIN "posthog_user" T6 ON ("posthog_dashboarditem"."last_modified_by_id" = T6."id") + LEFT OUTER JOIN "posthog_text" ON ("posthog_dashboardtile"."text_id" = "posthog_text"."id") + WHERE (NOT ("posthog_dashboardtile"."deleted" + AND "posthog_dashboardtile"."deleted" IS NOT NULL) + AND NOT ("posthog_dashboard"."deleted") + AND NOT ("posthog_dashboard"."deleted" + AND "posthog_dashboardtile"."deleted" + AND "posthog_dashboardtile"."deleted" IS NOT NULL) + AND (NOT "posthog_dashboarditem"."deleted" + OR "posthog_dashboardtile"."insight_id" IS NULL) + AND "posthog_dashboardtile"."dashboard_id" = 99999 + AND "posthog_dashboardtile"."dashboard_id" = 99999 + AND NOT ("posthog_dashboard"."deleted" + AND "posthog_dashboardtile"."deleted" + AND "posthog_dashboardtile"."deleted" IS NOT NULL) + AND (NOT "posthog_dashboarditem"."deleted" + OR "posthog_dashboardtile"."insight_id" IS NULL)) + ORDER BY "posthog_dashboarditem"."order" ASC + ''' +# --- +# name: TestDashboard.test_retrieve_dashboard.2 + ''' + SELECT "posthog_team"."id", + "posthog_team"."uuid", + "posthog_team"."organization_id", + "posthog_team"."project_id", + "posthog_team"."api_token", + "posthog_team"."app_urls", + "posthog_team"."name", + "posthog_team"."slack_incoming_webhook", + "posthog_team"."created_at", + "posthog_team"."updated_at", + "posthog_team"."anonymize_ips", + "posthog_team"."completed_snippet_onboarding", + "posthog_team"."has_completed_onboarding_for", + "posthog_team"."ingested_event", + "posthog_team"."autocapture_opt_out", + "posthog_team"."autocapture_web_vitals_opt_in", "posthog_team"."autocapture_web_vitals_allowed_metrics", "posthog_team"."autocapture_exceptions_opt_in", "posthog_team"."autocapture_exceptions_errors_to_ignore", @@ -9658,6 +9867,32 @@ ''' # --- # name: TestDashboard.test_retrieve_dashboard.6 + ''' + SELECT "posthog_organization"."id", + "posthog_organization"."name", + "posthog_organization"."slug", + "posthog_organization"."logo_media_id", + "posthog_organization"."created_at", + "posthog_organization"."updated_at", + "posthog_organization"."plugins_access_level", + "posthog_organization"."for_internal_metrics", + "posthog_organization"."is_member_join_email_enabled", + "posthog_organization"."enforce_2fa", + "posthog_organization"."is_hipaa", + "posthog_organization"."customer_id", + "posthog_organization"."available_product_features", + "posthog_organization"."usage", + "posthog_organization"."never_drop_data", + "posthog_organization"."customer_trust_scores", + "posthog_organization"."setup_section_2_completed", + "posthog_organization"."personalization", + "posthog_organization"."domain_whitelist" + FROM "posthog_organization" + WHERE "posthog_organization"."id" = '00000000-0000-0000-0000-000000000000'::uuid + LIMIT 21 + ''' +# --- +# name: TestDashboard.test_retrieve_dashboard.7 ''' SELECT "posthog_dashboard"."id", "posthog_dashboard"."name", @@ -9712,7 +9947,7 @@ LIMIT 21 ''' # --- -# name: TestDashboard.test_retrieve_dashboard.7 +# name: TestDashboard.test_retrieve_dashboard.8 ''' SELECT "posthog_sharingconfiguration"."id", "posthog_sharingconfiguration"."team_id", @@ -9730,7 +9965,7 @@ 5 /* ... */) ''' # --- -# name: TestDashboard.test_retrieve_dashboard.8 +# name: TestDashboard.test_retrieve_dashboard.9 ''' SELECT "posthog_dashboardtile"."id", "posthog_dashboardtile"."dashboard_id", @@ -9922,199 +10157,6 @@ ORDER BY "posthog_dashboarditem"."order" ASC ''' # --- -# name: TestDashboard.test_retrieve_dashboard.9 - ''' - SELECT "posthog_dashboardtile"."id", - "posthog_dashboardtile"."dashboard_id", - "posthog_dashboardtile"."insight_id", - "posthog_dashboardtile"."text_id", - "posthog_dashboardtile"."layouts", - "posthog_dashboardtile"."color", - "posthog_dashboardtile"."filters_hash", - "posthog_dashboardtile"."last_refresh", - "posthog_dashboardtile"."refreshing", - "posthog_dashboardtile"."refresh_attempt", - "posthog_dashboardtile"."deleted", - "posthog_dashboarditem"."id", - "posthog_dashboarditem"."name", - "posthog_dashboarditem"."derived_name", - "posthog_dashboarditem"."description", - "posthog_dashboarditem"."team_id", - "posthog_dashboarditem"."filters", - "posthog_dashboarditem"."filters_hash", - "posthog_dashboarditem"."query", - "posthog_dashboarditem"."order", - "posthog_dashboarditem"."deleted", - "posthog_dashboarditem"."saved", - "posthog_dashboarditem"."created_at", - "posthog_dashboarditem"."refreshing", - "posthog_dashboarditem"."created_by_id", - "posthog_dashboarditem"."is_sample", - "posthog_dashboarditem"."short_id", - "posthog_dashboarditem"."favorited", - "posthog_dashboarditem"."refresh_attempt", - "posthog_dashboarditem"."last_modified_at", - "posthog_dashboarditem"."last_modified_by_id", - "posthog_dashboarditem"."dashboard_id", - "posthog_dashboarditem"."last_refresh", - "posthog_dashboarditem"."layouts", - "posthog_dashboarditem"."color", - "posthog_dashboarditem"."dive_dashboard_id", - "posthog_dashboarditem"."updated_at", - "posthog_dashboarditem"."deprecated_tags", - "posthog_dashboarditem"."tags", - "posthog_team"."id", - "posthog_team"."uuid", - "posthog_team"."organization_id", - "posthog_team"."project_id", - "posthog_team"."api_token", - "posthog_team"."app_urls", - "posthog_team"."name", - "posthog_team"."slack_incoming_webhook", - "posthog_team"."created_at", - "posthog_team"."updated_at", - "posthog_team"."anonymize_ips", - "posthog_team"."completed_snippet_onboarding", - "posthog_team"."has_completed_onboarding_for", - "posthog_team"."ingested_event", - "posthog_team"."autocapture_opt_out", - "posthog_team"."autocapture_web_vitals_opt_in", - "posthog_team"."autocapture_web_vitals_allowed_metrics", - "posthog_team"."autocapture_exceptions_opt_in", - "posthog_team"."autocapture_exceptions_errors_to_ignore", - "posthog_team"."person_processing_opt_out", - "posthog_team"."session_recording_opt_in", - "posthog_team"."session_recording_sample_rate", - "posthog_team"."session_recording_minimum_duration_milliseconds", - "posthog_team"."session_recording_linked_flag", - "posthog_team"."session_recording_network_payload_capture_config", - "posthog_team"."session_recording_url_trigger_config", - "posthog_team"."session_recording_url_blocklist_config", - "posthog_team"."session_recording_event_trigger_config", - "posthog_team"."session_replay_config", - "posthog_team"."survey_config", - "posthog_team"."capture_console_log_opt_in", - "posthog_team"."capture_performance_opt_in", - "posthog_team"."capture_dead_clicks", - "posthog_team"."surveys_opt_in", - "posthog_team"."heatmaps_opt_in", - "posthog_team"."flags_persistence_default", - "posthog_team"."session_recording_version", - "posthog_team"."signup_token", - "posthog_team"."is_demo", - "posthog_team"."access_control", - "posthog_team"."week_start_day", - "posthog_team"."inject_web_apps", - "posthog_team"."test_account_filters", - "posthog_team"."test_account_filters_default_checked", - "posthog_team"."path_cleaning_filters", - "posthog_team"."timezone", - "posthog_team"."data_attributes", - "posthog_team"."person_display_name_properties", - "posthog_team"."live_events_columns", - "posthog_team"."recording_domains", - "posthog_team"."human_friendly_comparison_periods", - "posthog_team"."cookieless_server_hash_mode", - "posthog_team"."primary_dashboard_id", - "posthog_team"."default_data_theme", - "posthog_team"."extra_settings", - "posthog_team"."modifiers", - "posthog_team"."correlation_config", - "posthog_team"."session_recording_retention_period_days", - "posthog_team"."plugins_opt_in", - "posthog_team"."opt_out_capture", - "posthog_team"."event_names", - "posthog_team"."event_names_with_usage", - "posthog_team"."event_properties", - "posthog_team"."event_properties_with_usage", - "posthog_team"."event_properties_numerical", - "posthog_team"."external_data_workspace_id", - "posthog_team"."external_data_workspace_last_synced_at", - "posthog_user"."id", - "posthog_user"."password", - "posthog_user"."last_login", - "posthog_user"."first_name", - "posthog_user"."last_name", - "posthog_user"."is_staff", - "posthog_user"."date_joined", - "posthog_user"."uuid", - "posthog_user"."current_organization_id", - "posthog_user"."current_team_id", - "posthog_user"."email", - "posthog_user"."pending_email", - "posthog_user"."temporary_token", - "posthog_user"."distinct_id", - "posthog_user"."is_email_verified", - "posthog_user"."requested_password_reset_at", - "posthog_user"."has_seen_product_intro_for", - "posthog_user"."strapi_id", - "posthog_user"."is_active", - "posthog_user"."role_at_organization", - "posthog_user"."theme_mode", - "posthog_user"."partial_notification_settings", - "posthog_user"."anonymize_data", - "posthog_user"."toolbar_mode", - "posthog_user"."hedgehog_config", - "posthog_user"."events_column_config", - "posthog_user"."email_opt_in", - T6."id", - T6."password", - T6."last_login", - T6."first_name", - T6."last_name", - T6."is_staff", - T6."date_joined", - T6."uuid", - T6."current_organization_id", - T6."current_team_id", - T6."email", - T6."pending_email", - T6."temporary_token", - T6."distinct_id", - T6."is_email_verified", - T6."requested_password_reset_at", - T6."has_seen_product_intro_for", - T6."strapi_id", - T6."is_active", - T6."role_at_organization", - T6."theme_mode", - T6."partial_notification_settings", - T6."anonymize_data", - T6."toolbar_mode", - T6."hedgehog_config", - T6."events_column_config", - T6."email_opt_in", - "posthog_text"."id", - "posthog_text"."body", - "posthog_text"."created_by_id", - "posthog_text"."last_modified_at", - "posthog_text"."last_modified_by_id", - "posthog_text"."team_id" - FROM "posthog_dashboardtile" - INNER JOIN "posthog_dashboard" ON ("posthog_dashboardtile"."dashboard_id" = "posthog_dashboard"."id") - LEFT OUTER JOIN "posthog_dashboarditem" ON ("posthog_dashboardtile"."insight_id" = "posthog_dashboarditem"."id") - LEFT OUTER JOIN "posthog_team" ON ("posthog_dashboarditem"."team_id" = "posthog_team"."id") - LEFT OUTER JOIN "posthog_user" ON ("posthog_dashboarditem"."created_by_id" = "posthog_user"."id") - LEFT OUTER JOIN "posthog_user" T6 ON ("posthog_dashboarditem"."last_modified_by_id" = T6."id") - LEFT OUTER JOIN "posthog_text" ON ("posthog_dashboardtile"."text_id" = "posthog_text"."id") - WHERE (NOT ("posthog_dashboardtile"."deleted" - AND "posthog_dashboardtile"."deleted" IS NOT NULL) - AND NOT ("posthog_dashboard"."deleted") - AND NOT ("posthog_dashboard"."deleted" - AND "posthog_dashboardtile"."deleted" - AND "posthog_dashboardtile"."deleted" IS NOT NULL) - AND (NOT "posthog_dashboarditem"."deleted" - OR "posthog_dashboardtile"."insight_id" IS NULL) - AND "posthog_dashboardtile"."dashboard_id" = 99999 - AND "posthog_dashboardtile"."dashboard_id" = 99999 - AND NOT ("posthog_dashboard"."deleted" - AND "posthog_dashboardtile"."deleted" - AND "posthog_dashboardtile"."deleted" IS NOT NULL) - AND (NOT "posthog_dashboarditem"."deleted" - OR "posthog_dashboardtile"."insight_id" IS NULL)) - ORDER BY "posthog_dashboarditem"."order" ASC - ''' -# --- # name: TestDashboard.test_retrieve_dashboard_list ''' SELECT "posthog_user"."id", diff --git a/posthog/api/test/dashboards/test_dashboard.py b/posthog/api/test/dashboards/test_dashboard.py index 63f40588999d2..d7b8b118ceb04 100644 --- a/posthog/api/test/dashboards/test_dashboard.py +++ b/posthog/api/test/dashboards/test_dashboard.py @@ -24,6 +24,7 @@ QueryMatchingTest, snapshot_postgres_queries, ) +from ee.models.rbac.access_control import AccessControl valid_template: dict = { "template_name": "Sign up conversion template with variables", @@ -1444,3 +1445,37 @@ def test_dashboard_variables(self): assert value["code_name"] == variable.code_name assert value["variableId"] == str(variable.id) assert value["value"] == "some override value" + + def test_dashboard_access_control_filtering(self) -> None: + """Test that dashboards are properly filtered based on access control.""" + + user2 = User.objects.create_and_join(self.organization, "test2@posthog.com", None) + + visible_dashboard = Dashboard.objects.create( + team=self.team, + name="Public Dashboard", + created_by=self.user, + ) + hidden_dashboard = Dashboard.objects.create( + team=self.team, + name="Hidden Dashboard", + created_by=self.user, + ) + AccessControl.objects.create( + resource="dashboard", resource_id=hidden_dashboard.id, team=self.team, access_level="none" + ) + + # Verify we can access visible dashboards + self.client.force_login(user2) + response = self.client.get(f"/api/projects/{self.team.pk}/dashboards/") + self.assertEqual(response.status_code, status.HTTP_200_OK) + dashboard_ids = [dashboard["id"] for dashboard in response.json()["results"]] + self.assertIn(visible_dashboard.id, dashboard_ids) + self.assertNotIn(hidden_dashboard.id, dashboard_ids) + + # Verify we can access all dashboards as creator + self.client.force_login(self.user) + response = self.client.get(f"/api/projects/{self.team.pk}/dashboards/") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIn(visible_dashboard.id, [dashboard["id"] for dashboard in response.json()["results"]]) + self.assertIn(hidden_dashboard.id, [dashboard["id"] for dashboard in response.json()["results"]]) diff --git a/posthog/api/test/test_insight.py b/posthog/api/test/test_insight.py index 13b5a1774ba92..12f61d9e5eed0 100644 --- a/posthog/api/test/test_insight.py +++ b/posthog/api/test/test_insight.py @@ -32,6 +32,7 @@ Text, User, ) +from ee.models.rbac.access_control import AccessControl from posthog.models.insight_caching_state import InsightCachingState from posthog.models.insight_variable import InsightVariable from posthog.models.project import Project @@ -1258,7 +1259,7 @@ def test_insight_refreshing_legacy_conversion(self) -> None: self.assertEqual(response["last_refresh"], None) self.assertEqual(response["last_modified_at"], "2012-01-15T04:01:34Z") # did not change - #  Test property filter + # Test property filter dashboard = Dashboard.objects.get(pk=dashboard_id) dashboard.filters = { @@ -1421,7 +1422,7 @@ def test_insight_refreshing_query(self, properties_filter, spy_execute_hogql_que self.assertEqual(response["last_modified_at"], "2012-01-15T04:01:34Z") # did not change self.assertFalse(response["is_cached"]) - #  Test property filter + # Test property filter Dashboard.objects.update( id=dashboard_id, @@ -2669,7 +2670,7 @@ def test_soft_delete_cannot_be_reversed_for_another_team(self) -> None: def test_cancel_running_query(self) -> None: # There is no good way of writing a test that tests this without it being very slow - #  Just verify it doesn't throw an error + # Just verify it doesn't throw an error response = self.client.post( f"/api/projects/{self.team.id}/insights/cancel", {"client_query_id": f"testid"}, @@ -3430,3 +3431,39 @@ def test_insight_variables_overrides(self): assert value["code_name"] == variable.code_name assert value["variableId"] == str(variable.id) assert value["value"] == "override value!" + + def test_insight_access_control_filtering(self) -> None: + """Test that insights are properly filtered based on access control.""" + + user2 = self._create_user("test2@posthog.com") + + visible_insight = Insight.objects.create( + team=self.team, + name="Public Insight", + created_by=self.user, + filters={"events": [{"id": "$pageview"}]}, + ) + hidden_insight = Insight.objects.create( + team=self.team, + name="Hidden Insight", + created_by=self.user, + filters={"events": [{"id": "$pageview"}]}, + ) + AccessControl.objects.create( + resource="insight", resource_id=hidden_insight.id, team=self.team, access_level="none" + ) + + # Verify we can access visible insights + self.client.force_login(user2) + response = self.client.get(f"/api/projects/{self.team.pk}/insights/") + self.assertEqual(response.status_code, status.HTTP_200_OK) + insight_ids = [insight["id"] for insight in response.json()["results"]] + self.assertIn(visible_insight.id, insight_ids) + self.assertNotIn(hidden_insight.id, insight_ids) + + # Verify we can access all insights as creator + self.client.force_login(self.user) + response = self.client.get(f"/api/projects/{self.team.pk}/insights/") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIn(visible_insight.id, [insight["id"] for insight in response.json()["results"]]) + self.assertIn(hidden_insight.id, [insight["id"] for insight in response.json()["results"]]) diff --git a/posthog/rbac/user_access_control.py b/posthog/rbac/user_access_control.py index f3b5f5b2b0d9c..64ac759bb3efe 100644 --- a/posthog/rbac/user_access_control.py +++ b/posthog/rbac/user_access_control.py @@ -474,7 +474,7 @@ def user_access_control(self) -> Optional[UserAccessControl]: elif hasattr(self.context.get("view", None), "user_access_control"): # Otherwise from the view (the default case) return self.context["view"].user_access_control - else: + elif "request" in self.context: user = cast(User | AnonymousUser, self.context["request"].user) # The user could be anonymous - if so there is no access control to be used @@ -485,6 +485,8 @@ def user_access_control(self) -> Optional[UserAccessControl]: return UserAccessControl(user, organization_id=str(user.current_organization_id)) + return None + def get_user_access_level(self, obj: Model) -> Optional[str]: if not self.user_access_control: return None