Skip to content

Commit

Permalink
Merge branch 'master' into feat/actions-webhooks-v2
Browse files Browse the repository at this point in the history
# Conflicts:
#	latest_migrations.manifest
  • Loading branch information
benjackwhite committed Apr 29, 2024
2 parents f8796dd + 23e80d0 commit 1eb825c
Show file tree
Hide file tree
Showing 166 changed files with 2,699 additions and 1,854 deletions.
75 changes: 72 additions & 3 deletions cypress/e2e/dashboard.cy.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { randomString } from '../support/random'
import { insight, dashboards, dashboard } from '../productAnalytics'
import { urls } from 'scenes/urls'

describe('Dashboard', () => {
beforeEach(() => {
Expand All @@ -20,25 +21,93 @@ describe('Dashboard', () => {
})

it('Adding new insight to dashboard works', () => {
const dashboardName = randomString('Dashboard with insight A')
const dashboardName = randomString('Dashboard with matching filter')
const insightName = randomString('insight to add to dashboard')

// create and visit a dashboard to get it into turbomode cache
// Create and visit a dashboard to get it into turbo mode cache
dashboards.createAndGoToEmptyDashboard(dashboardName)

insight.create(insightName)

insight.addInsightToDashboard(dashboardName, { visitAfterAdding: true })

cy.get('.CardMeta h4').should('have.text', insightName)

dashboard.addPropertyFilter()
cy.get('main').contains('There are no matching events for this query').should('not.exist')

cy.clickNavMenu('dashboards')
const dashboardNonMatching = randomString('Dashboard with non-matching filter')
dashboards.createAndGoToEmptyDashboard(dashboardNonMatching)

insight.visitInsight(insightName)
insight.addInsightToDashboard(dashboardNonMatching, { visitAfterAdding: true })

dashboard.addPropertyFilter('Browser', 'Hogbrowser')
cy.get('main').contains('There are no matching events for this query').should('exist')

// Go back and forth to make sure the filters are correctly applied
for (let i = 0; i < 3; i++) {
cy.clickNavMenu('dashboards')
dashboards.visitDashboard(dashboardName)
cy.get('.CardMeta h4').should('have.text', insightName)
cy.get('h4').contains('Refreshing').should('not.exist')
cy.get('main').contains('There are no matching events for this query').should('not.exist')

cy.clickNavMenu('dashboards')
dashboards.visitDashboard(dashboardNonMatching)
cy.get('.CardMeta h4').should('have.text', insightName)
cy.get('h4').contains('Refreshing').should('not.exist')
cy.get('main').contains('There are no matching events for this query').should('exist')
}
})

it('Dashboard filter updates are correctly isolated for one insight on multiple dashboards', () => {
const dashboardAName = randomString('Dashboard with insight A')
const dashboardBName = randomString('Dashboard with insight B')
const insightName = randomString('insight to add to dashboard')

// Create and visit two dashboards to get them into turbo mode cache
dashboards.createAndGoToEmptyDashboard(dashboardAName)
cy.clickNavMenu('dashboards')
dashboards.createAndGoToEmptyDashboard(dashboardBName)

insight.create(insightName)

// Add that one insight to both dashboards
insight.addInsightToDashboard(dashboardAName, { visitAfterAdding: false })
cy.get('[aria-label="close"]').click()
insight.addInsightToDashboard(dashboardBName, { visitAfterAdding: false })
cy.get('[aria-label="close"]').click()

// Let's get dashboard A mounted
cy.clickNavMenu('dashboards')
dashboards.visitDashboard(dashboardAName)
cy.get('[data-attr=date-filter]').contains('No date range override')
cy.get('.InsightCard h5').should('have.length', 1).contains('Last 7 days')
// Now let's see dashboard B
cy.clickNavMenu('dashboards')
dashboards.visitDashboard(dashboardBName)
cy.get('[data-attr=date-filter]').contains('No date range override')
cy.get('.InsightCard h5').should('have.length', 1).contains('Last 7 days')
// Override the time range on dashboard B
cy.get('[data-attr=date-filter]').contains('No date range override').click()
cy.get('div').contains('Yesterday').should('exist').click()
cy.get('[data-attr=date-filter]').contains('Yesterday')
cy.get('.InsightCard h5').should('have.length', 1).contains('Yesterday')
// Cool, now back to A and make sure the insight is still using the original range there, not the one from B
cy.clickNavMenu('dashboards')
dashboards.visitDashboard(dashboardAName)
cy.get('[data-attr=date-filter]').contains('No date range override')
cy.get('.InsightCard h5').should('have.length', 1).contains('Last 7 days') // This must not be "Yesterday"!
})

it('Adding new insight to dashboard does not clear filters', () => {
const dashboardName = randomString('to add an insight to')
const firstInsight = randomString('insight to add to dashboard')
const secondInsight = randomString('another insight to add to dashboard')

// create and visit a dashboard to get it into turbomode cache
// Create and visit a dashboard to get it into turbo mode cache
dashboards.createAndGoToEmptyDashboard(dashboardName)
dashboard.addInsightToEmptyDashboard(firstInsight)

Expand Down
19 changes: 19 additions & 0 deletions cypress/e2e/surveys.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,4 +281,23 @@ describe('Surveys', () => {
.contains('DRAFT')
.should('exist')
})

it.only('can set responses limit', () => {
cy.get('h1').should('contain', 'Surveys')
cy.get('[data-attr=new-survey]').click()
cy.get('[data-attr=new-blank-survey]').click()

cy.get('[data-attr=survey-name]').focus().type(name)

// Set responses limit
cy.get('.LemonCollapsePanel').contains('Completion conditions').click()
cy.get('[data-attr=survey-responses-limit-input]').focus().type('228').click()

// Save the survey
cy.get('[data-attr=save-survey]').first().click()
cy.get('button[data-attr="launch-survey"]').should('have.text', 'Launch')

cy.reload()
cy.contains('The survey will be stopped once 228 responses are received.').should('be.visible')
})
})
10 changes: 9 additions & 1 deletion cypress/productAnalytics/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export const insight = {
},
visitInsight: (insightName: string): void => {
cy.clickNavMenu('savedinsights')
cy.contains('.row-name > .Link', insightName).click()
cy.contains('.Link', insightName).click()
},
create: (insightName: string, insightType: string = 'TRENDS'): void => {
cy.clickNavMenu('savedinsights')
Expand Down Expand Up @@ -169,6 +169,14 @@ export const dashboard = {
cy.get('[data-attr=insight-save-button]').contains('Save & add to dashboard').click()
cy.wait('@postInsight')
},
addPropertyFilter(type: string = 'Browser', value: string = 'Chrome'): void {
cy.get('.PropertyFilterButton').should('have.length', 0)
cy.get('[data-attr="property-filter-0"]').click()
cy.get('[data-attr="taxonomic-filter-searchfield"]').click().type('Browser').wait(1000)
cy.get('[data-attr="prop-filter-event_properties-0"]').click({ force: true })
cy.get('.LemonInput').type(value)
cy.contains('.LemonButton__content', value).click({ force: true })
},
addAnyFilter(): void {
cy.get('.PropertyFilterButton').should('have.length', 0)
cy.get('[data-attr="property-filter-0"]').click()
Expand Down
13 changes: 7 additions & 6 deletions ee/api/explicit_team_member.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,22 +113,23 @@ class ExplicitTeamMemberViewSet(TeamAndOrgViewSetMixin, viewsets.ModelViewSet):

permission_classes = [IsAuthenticated, TeamMemberStrictManagementPermission]

def get_permissions(self):
def dangerously_get_permissions(self):
permissions_classes = self.permission_classes

if (
self.action == "destroy"
and self.request.user.is_authenticated
and self.kwargs.get("parent_membership__user__uuid") == str(self.request.user.uuid)
):
# Special case: allow already authenticated users to leave projects
return []
return [permission() for permission in self.permission_classes]
permissions_classes = [IsAuthenticated]

return [permission() for permission in permissions_classes]

def get_object(self) -> ExplicitTeamMembership:
queryset = self.filter_queryset(self.get_queryset())
def safely_get_object(self, queryset) -> ExplicitTeamMembership:
lookup_value = self.kwargs[self.lookup_field]
if lookup_value == "@me":
return queryset.get(user=self.request.user)
filter_kwargs = {self.lookup_field: lookup_value}
obj = get_object_or_404(queryset, **filter_kwargs)
self.check_object_permissions(self.request, obj)
return obj
4 changes: 2 additions & 2 deletions ee/api/feature_flag_role_access.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,6 @@ class FeatureFlagRoleAccessViewSet(
queryset = FeatureFlagRoleAccess.objects.select_related("feature_flag")
filter_rewrite_rules = {"team_id": "feature_flag__team_id"}

def get_queryset(self):
def safely_get_queryset(self, queryset):
filters = self.request.GET.dict()
return super().get_queryset().filter(**filters)
return queryset.filter(**filters)
5 changes: 2 additions & 3 deletions ee/api/role.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,8 @@ class RoleViewSet(
serializer_class = RoleSerializer
queryset = Role.objects.all()

def get_queryset(self):
filters = self.request.GET.dict()
return super().get_queryset().filter(**filters)
def safely_get_queryset(self, queryset):
return queryset.filter(**self.request.GET.dict())


class RoleMembershipSerializer(serializers.ModelSerializer):
Expand Down
3 changes: 1 addition & 2 deletions ee/api/subscription.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,7 @@ class SubscriptionViewSet(TeamAndOrgViewSetMixin, ForbidDestroyModel, viewsets.M
permission_classes = [PremiumFeaturePermission]
premium_feature = AvailableFeature.SUBSCRIPTIONS

def get_queryset(self) -> QuerySet:
queryset = super().get_queryset()
def safely_get_queryset(self, queryset) -> QuerySet:
filters = self.request.GET.dict()

if self.action == "list" and "deleted" not in filters:
Expand Down
8 changes: 3 additions & 5 deletions ee/api/test/test_capture.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import hashlib
import json
from typing import Any
from unittest.mock import patch

from django.test.client import Client
from kafka.errors import NoBrokersAvailable
from rest_framework import status
from typing import Any
from unittest.mock import patch

from posthog.settings.data_stores import KAFKA_EVENTS_PLUGIN_INGESTION
from posthog.test.base import APIBaseTest
Expand Down Expand Up @@ -176,7 +174,7 @@ def test_partition_key_override(self, kafka_produce):
kafka_produce_call = kafka_produce.call_args_list[0].kwargs
self.assertEqual(
kafka_produce_call["key"],
hashlib.sha256(default_partition_key.encode()).hexdigest(),
default_partition_key,
)

# Setting up an override via EVENT_PARTITION_KEYS_TO_OVERRIDE should cause us to pass None
Expand Down
7 changes: 6 additions & 1 deletion ee/billing/billing_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import requests
import structlog
from django.utils import timezone
from requests import JSONDecodeError # type: ignore[attr-defined]
from rest_framework.exceptions import NotAuthenticated
from sentry_sdk import capture_exception

Expand Down Expand Up @@ -44,7 +45,11 @@ def build_billing_token(license: License, organization: Organization):
def handle_billing_service_error(res: requests.Response, valid_codes=(200, 404, 401)) -> None:
if res.status_code not in valid_codes:
logger.error(f"Billing service returned bad status code: {res.status_code}, body: {res.text}")
raise Exception(f"Billing service returned bad status code: {res.status_code}", f"body:", res.json())
try:
response = res.json()
raise Exception(f"Billing service returned bad status code: {res.status_code}", f"body:", response)
except JSONDecodeError:
raise Exception(f"Billing service returned bad status code: {res.status_code}", f"body:", res.text)


class BillingManager:
Expand Down
6 changes: 6 additions & 0 deletions ee/clickhouse/test/test_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@
"Query exceeds memory limits. Try reducing its scope by changing the time range.",
241,
),
(
ServerException("Too many simultaneous queries. Maximum: 100.", code=202),
"CHQueryErrorTooManySimultaneousQueries",
"Code: 202.\nToo many simultaneous queries. Try again later.",
202,
),
],
)
def test_wrap_query_error(error, expected_type, expected_message, expected_code):
Expand Down
5 changes: 1 addition & 4 deletions ee/clickhouse/views/experiments.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,12 +286,9 @@ def update(self, instance: Experiment, validated_data: dict, *args: Any, **kwarg
class ClickhouseExperimentsViewSet(TeamAndOrgViewSetMixin, viewsets.ModelViewSet):
scope_object = "experiment"
serializer_class = ExperimentSerializer
queryset = Experiment.objects.all()
queryset = Experiment.objects.prefetch_related("feature_flag", "created_by").all()
ordering = "-created_at"

def get_queryset(self):
return super().get_queryset().prefetch_related("feature_flag", "created_by")

# ******************************************
# /projects/:id/experiments/:experiment_id/results
#
Expand Down
12 changes: 4 additions & 8 deletions ee/clickhouse/views/groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,10 @@ class ClickhouseGroupsView(TeamAndOrgViewSetMixin, mixins.ListModelMixin, viewse
queryset = Group.objects.all()
pagination_class = GroupCursorPagination

def get_queryset(self):
return (
super()
.get_queryset()
.filter(
group_type_index=self.request.GET["group_type_index"],
group_key__icontains=self.request.GET.get("group_key", ""),
)
def safely_get_queryset(self, queryset):
return queryset.filter(
group_type_index=self.request.GET["group_type_index"],
group_key__icontains=self.request.GET.get("group_key", ""),
)

@extend_schema(
Expand Down
4 changes: 1 addition & 3 deletions ee/session_recordings/session_recording_playlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,9 +171,7 @@ class SessionRecordingPlaylistViewSet(TeamAndOrgViewSetMixin, ForbidDestroyModel
filterset_fields = ["short_id", "created_by"]
lookup_field = "short_id"

def get_queryset(self) -> QuerySet:
queryset = super().get_queryset()

def safely_get_queryset(self, queryset) -> QuerySet:
if not self.action.endswith("update"):
# Soft-deleted insights can be brought back with a PATCH request
queryset = queryset.filter(deleted=False)
Expand Down
7 changes: 3 additions & 4 deletions ee/tasks/test/subscriptions/test_subscriptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,9 @@ def test_subscription_delivery_scheduling(

schedule_all_subscriptions()

assert mock_deliver_task.delay.mock_calls == [
call(subscriptions[0].id),
call(subscriptions[1].id),
]
self.assertCountEqual(
mock_deliver_task.delay.call_args_list, [call(subscriptions[0].id), call(subscriptions[1].id)]
)

@patch("ee.tasks.subscriptions.deliver_subscription_report")
def test_does_not_schedule_subscription_if_item_is_deleted(
Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/__snapshots__/scenes-app-surveys--new-survey--dark.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/__snapshots__/scenes-app-surveys--new-survey--light.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/__snapshots__/scenes-other-toolbar--heatmap--dark.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/__snapshots__/scenes-other-toolbar--heatmap--light.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/public/google-cloud-storage-logo.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/public/s3-logo.png

0 comments on commit 1eb825c

Please sign in to comment.