Skip to content

Commit

Permalink
Merge branch 'master' into filter-groups-simplify
Browse files Browse the repository at this point in the history
* master:
  hobby: Wait for ClickHouse and for Postgres before starting (#8686)
  Part 2: Deprecate old tags and upgrade to new tags Backend (#8529)
  Remove flake8-commas (#8695)
  Update Breakdown props to use filter groups (#8679)
  Automatically switch to the right project if possible (#8681)
  Super Lazy VMs (#8609)
  .github/workflows/ci-backend.yml: fix flake8 config (#8676)
  Fix recording page refresh loop (#8685)
  Instance status configuration (#8096)
  • Loading branch information
EDsCODE committed Feb 18, 2022
2 parents c1cf0ae + 24eb266 commit 68c538b
Show file tree
Hide file tree
Showing 93 changed files with 3,653 additions and 1,634 deletions.
5 changes: 0 additions & 5 deletions .flake8
Expand Up @@ -27,11 +27,6 @@ ignore=
C413, # Unnecessary list call around sorted()
C414, # Unnecessary list call within sorted().
C416, # Unnecessary list comprehension - rewrite using list().
C812, # missing trailing comma
C813, # missing trailing comma in Python 3
C815, # missing trailing comma in Python 3.5+
C816, # missing trailing comma in Python 3.6+
C819, # trailing comma prohibited
C901, # function complexity
E203, # whitespace before ‘:’
E231, # missing whitespace after ‘,’, ‘;’, or ‘:’
Expand Down
9 changes: 3 additions & 6 deletions .github/workflows/ci-backend.yml
Expand Up @@ -95,14 +95,11 @@ jobs:
black --check .
isort --check-only .
- name: Lint with flake8
- name: Check for errors and code style violations
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=120 --statistics
flake8 .
- name: Typecheck
- name: Check static typing
run: |
mypy .
Expand Down
28 changes: 27 additions & 1 deletion bin/deploy-hobby
Expand Up @@ -61,18 +61,42 @@ SENTRY_DSN=$SENTRY_DSN
DOMAIN=$DOMAIN
EOF


# write entrypoint
rm -rf compose
mkdir -p compose
cat > compose/start <<EOF
#!/bin/bash
/compose/wait
./bin/migrate
./bin/docker-server
./bin/docker-frontend
EOF
chmod +x compose/start

# write wait script
cat > compose/wait <<EOF
#!/usr/bin/env python3
import socket
import time
def loop():
print("Waiting for ClickHouse and Postgres to be ready")
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect(('clickhouse', 9000))
print("Clickhouse is ready")
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect(('db', 5432))
print("Postgres is ready")
except ConnectionRefusedError as e:
time.sleep(5)
loop()
loop()
EOF
chmod +x compose/wait

# setup docker
echo "Setting up Docker"
sudo apt install -y apt-transport-https ca-certificates curl software-properties-common
Expand Down Expand Up @@ -100,10 +124,12 @@ curl -L --header "Content-Type: application/json" -d '{
}' https://app.posthog.com/batch/

# start up the stack
echo "Configuring Docker Compose...."
rm -f docker-compose.yml
cp posthog/docker-compose.hobby.yml docker-compose.yml.tmpl
envsubst < docker-compose.yml.tmpl > docker-compose.yml
rm docker-compose.yml.tmpl
echo "Starting the stack!"
sudo -E docker-compose -f docker-compose.yml up -d

echo "We will need to wait ~5-10 minutes for things to settle down, migrations to finish, and TLS certs to be issued"
Expand Down
6 changes: 4 additions & 2 deletions cypress/integration/dashboardPremium.js
Expand Up @@ -4,7 +4,9 @@ describe('Dashboards Premium Features', () => {
cy.location('pathname').should('include', '/dashboard')
})

it('Tag dashboard', () => {
// Taggables are an enterprise feature. Cypress isn't setup with a scale license so these
// tests should fail now that we make that license check in the backend and return a 402.
xit('Tag dashboard', () => {
const newTag = `test-${Math.floor(Math.random() * 10000)}`
cy.get('[data-attr=dashboard-name]').contains('App Analytics').click()
cy.get('[data-attr=button-add-tag]').click()
Expand All @@ -19,7 +21,7 @@ describe('Dashboards Premium Features', () => {
cy.get('.ant-tag').should('contain', newTag) // Tag is shown in dashboard list too
})

it('Cannot add duplicate tags', () => {
xit('Cannot add duplicate tags', () => {
const newTag = `test2-${Math.floor(Math.random() * 10000)}`
cy.get('[data-attr=dashboard-name]').contains('App Analytics').click()
cy.get('[data-attr=button-add-tag]').click()
Expand Down
2 changes: 1 addition & 1 deletion cypress/integration/systemStatus.js
Expand Up @@ -6,7 +6,7 @@ describe('System Status', () => {
cy.wait(500)
cy.get('[data-attr=top-menu-toggle]').click()
cy.get('[data-attr=system-status-badge]').click()
cy.get('h1').should('contain', 'System Status')
cy.get('h1').should('contain', 'Instance status')
cy.get('table').should('contain', 'Events in ClickHouse')
})
})
3 changes: 2 additions & 1 deletion ee/api/ee_event_definition.py
Expand Up @@ -3,9 +3,10 @@

from ee.models.event_definition import EnterpriseEventDefinition
from posthog.api.shared import UserBasicSerializer
from posthog.api.tagged_item import TaggedItemSerializerMixin


class EnterpriseEventDefinitionSerializer(serializers.ModelSerializer):
class EnterpriseEventDefinitionSerializer(TaggedItemSerializerMixin, serializers.ModelSerializer):
updated_by = UserBasicSerializer(read_only=True)
verified_by = UserBasicSerializer(read_only=True)

Expand Down
3 changes: 2 additions & 1 deletion ee/api/ee_property_definition.py
Expand Up @@ -2,9 +2,10 @@

from ee.models.property_definition import EnterprisePropertyDefinition
from posthog.api.shared import UserBasicSerializer
from posthog.api.tagged_item import TaggedItemSerializerMixin


class EnterprisePropertyDefinitionSerializer(serializers.ModelSerializer):
class EnterprisePropertyDefinitionSerializer(TaggedItemSerializerMixin, serializers.ModelSerializer):
updated_by = UserBasicSerializer(read_only=True)

class Meta:
Expand Down
12 changes: 3 additions & 9 deletions ee/api/test/test_dashboard.py
Expand Up @@ -14,9 +14,7 @@ def test_retrieve_dashboard_forbidden_for_project_outsider(self):
self.team.save()
self.organization_membership.level = OrganizationMembership.Level.MEMBER
self.organization_membership.save()
dashboard = Dashboard.objects.create(
team=self.team, name="private dashboard", created_by=self.user, tags=["deprecated"]
)
dashboard = Dashboard.objects.create(team=self.team, name="private dashboard", created_by=self.user)
response = self.client.get(f"/api/projects/{self.team.id}/dashboards/{dashboard.id}")
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

Expand All @@ -25,9 +23,7 @@ def test_retrieve_dashboard_forbidden_for_org_admin(self):
self.team.save()
self.organization_membership.level = OrganizationMembership.Level.ADMIN
self.organization_membership.save()
dashboard = Dashboard.objects.create(
team=self.team, name="private dashboard", created_by=self.user, tags=["deprecated"]
)
dashboard = Dashboard.objects.create(team=self.team, name="private dashboard", created_by=self.user)
response = self.client.get(f"/api/projects/{self.team.id}/dashboards/{dashboard.id}")
self.assertEqual(response.status_code, status.HTTP_200_OK)

Expand All @@ -37,9 +33,7 @@ def test_retrieve_dashboard_allowed_for_project_member(self):
self.organization_membership.level = OrganizationMembership.Level.MEMBER
self.organization_membership.save()
ExplicitTeamMembership.objects.create(team=self.team, parent_membership=self.organization_membership)
dashboard = Dashboard.objects.create(
team=self.team, name="private dashboard", created_by=self.user, tags=["deprecated"]
)
dashboard = Dashboard.objects.create(team=self.team, name="private dashboard", created_by=self.user)
response = self.client.get(f"/api/projects/{self.team.id}/dashboards/{dashboard.id}")
self.assertEqual(response.status_code, status.HTTP_200_OK)

Expand Down
22 changes: 12 additions & 10 deletions ee/api/test/test_event_definition.py
Expand Up @@ -7,6 +7,7 @@

from ee.models.event_definition import EnterpriseEventDefinition
from ee.models.license import License, LicenseManager
from posthog.models import Tag
from posthog.models.event_definition import EventDefinition
from posthog.test.base import APIBaseTest

Expand All @@ -16,9 +17,9 @@ def test_retrieve_existing_event_definition(self):
super(LicenseManager, cast(LicenseManager, License.objects)).create(
plan="enterprise", valid_until=timezone.datetime(2500, 1, 19, 3, 14, 7)
)
event = EnterpriseEventDefinition.objects.create(
team=self.team, name="enterprise event", owner=self.user, tags=["deprecated"]
)
event = EnterpriseEventDefinition.objects.create(team=self.team, name="enterprise event", owner=self.user)
tag = Tag.objects.create(name="deprecated", team_id=self.team.id)
event.tagged_items.create(tag_id=tag.id)
response = self.client.get(f"/api/projects/@current/event_definitions/{event.id}")
self.assertEqual(response.status_code, status.HTTP_200_OK)
response_data = response.json()
Expand Down Expand Up @@ -49,12 +50,13 @@ def test_search_event_definition(self):
super(LicenseManager, cast(LicenseManager, License.objects)).create(
plan="enterprise", valid_until=timezone.datetime(2500, 1, 19, 3, 14, 7)
)
EnterpriseEventDefinition.objects.create(
team=self.team, name="enterprise event", owner=self.user, tags=["deprecated"]
)
EnterpriseEventDefinition.objects.create(
team=self.team, name="regular event", owner=self.user, tags=["deprecated"]
enterprise_property = EnterpriseEventDefinition.objects.create(
team=self.team, name="enterprise event", owner=self.user
)
tag = Tag.objects.create(name="deprecated", team_id=self.team.id)
enterprise_property.tagged_items.create(tag_id=tag.id)
regular_event = EnterpriseEventDefinition.objects.create(team=self.team, name="regular event", owner=self.user)
regular_event.tagged_items.create(tag_id=tag.id)

response = self.client.get(f"/api/projects/@current/event_definitions/?search=enter")
self.assertEqual(response.status_code, status.HTTP_200_OK)
Expand Down Expand Up @@ -93,11 +95,11 @@ def test_update_event_definition(self):
response_data = response.json()
self.assertEqual(response_data["description"], "This is a description.")
self.assertEqual(response_data["updated_by"]["first_name"], self.user.first_name)
self.assertEqual(response_data["tags"], ["official", "internal"])
self.assertEqual(set(response_data["tags"]), {"official", "internal"})

event.refresh_from_db()
self.assertEqual(event.description, "This is a description.")
self.assertEqual(event.tags, ["official", "internal"])
self.assertEqual(set(event.tagged_items.values_list("tag__name", flat=True)), {"official", "internal"})

def test_update_event_without_license(self):
event = EnterpriseEventDefinition.objects.create(team=self.team, name="enterprise event")
Expand Down
29 changes: 16 additions & 13 deletions ee/api/test/test_property_definition.py
Expand Up @@ -7,7 +7,7 @@

from ee.models.license import License, LicenseManager
from ee.models.property_definition import EnterprisePropertyDefinition
from posthog.models import EventProperty
from posthog.models import EventProperty, Tag
from posthog.models.property_definition import PropertyDefinition
from posthog.test.base import APIBaseTest

Expand All @@ -23,6 +23,7 @@ def test_can_set_and_query_property_type_and_format(self):
assert response.json()["property_type"] == "DateTime"

query_list_response = self.client.get(f"/api/projects/@current/property_definitions")
self.assertEqual(query_list_response.status_code, status.HTTP_200_OK)
matches = [p["name"] for p in query_list_response.json()["results"] if p["name"] == "a timestamp"]
assert len(matches) == 1

Expand All @@ -36,9 +37,9 @@ def test_retrieve_existing_property_definition(self):
super(LicenseManager, cast(LicenseManager, License.objects)).create(
plan="enterprise", valid_until=timezone.datetime(2500, 1, 19, 3, 14, 7)
)
property = EnterprisePropertyDefinition.objects.create(
team=self.team, name="enterprise property", tags=["deprecated"]
)
property = EnterprisePropertyDefinition.objects.create(team=self.team, name="enterprise property")
tag = Tag.objects.create(name="deprecated", team_id=self.team.id)
property.tagged_items.create(tag_id=tag.id)
response = self.client.get(f"/api/projects/@current/property_definitions/{property.id}")
self.assertEqual(response.status_code, status.HTTP_200_OK)
response_data = response.json()
Expand All @@ -63,16 +64,18 @@ def test_search_property_definition(self):
super(LicenseManager, cast(LicenseManager, License.objects)).create(
plan="enterprise", valid_until=timezone.datetime(2500, 1, 19, 3, 14, 7)
)
tag = Tag.objects.create(name="deprecated", team_id=self.team.id)
EventProperty.objects.create(team=self.team, event="$pageview", property="enterprise property")
EnterprisePropertyDefinition.objects.create(
team=self.team, name="enterprise property", description="", tags=["deprecated"]
)
EnterprisePropertyDefinition.objects.create(
team=self.team, name="other property", description="", tags=["deprecated"]
enterprise_property = EnterprisePropertyDefinition.objects.create(
team=self.team, name="enterprise property", description=""
)
EnterprisePropertyDefinition.objects.create(
team=self.team, name="$set", description="", tags=["hidden-system-property"]
enterprise_property.tagged_items.create(tag_id=tag.id)
other_property = EnterprisePropertyDefinition.objects.create(
team=self.team, name="other property", description=""
)
other_property.tagged_items.create(tag_id=tag.id)
set_property = EnterprisePropertyDefinition.objects.create(team=self.team, name="$set", description="")
set_property.tagged_items.create(tag_id=tag.id)

response = self.client.get(f"/api/projects/@current/property_definitions/?search=enter")
self.assertEqual(response.status_code, status.HTTP_200_OK)
Expand Down Expand Up @@ -134,10 +137,10 @@ def test_update_property_definition(self):
response_data = response.json()
self.assertEqual(response_data["description"], "This is a description.")
self.assertEqual(response_data["updated_by"]["first_name"], self.user.first_name)
self.assertEqual(response_data["tags"], ["official", "internal"])
self.assertEqual(set(response_data["tags"]), {"official", "internal"})

property.refresh_from_db()
self.assertEqual(property.tags, ["official", "internal"])
self.assertEqual(set(property.tagged_items.values_list("tag__name", flat=True)), {"official", "internal"})

def test_update_property_without_license(self):
property = EnterprisePropertyDefinition.objects.create(team=self.team, name="enterprise property")
Expand Down
91 changes: 91 additions & 0 deletions ee/api/test/test_tagged_item.py
@@ -0,0 +1,91 @@
from typing import cast

import pytest
from django.utils import timezone
from rest_framework import status

from posthog.models import Dashboard, Tag
from posthog.models.tagged_item import TaggedItem
from posthog.test.base import APIBaseTest

# This serializer only tests the business logic of getting and setting of ee descriptions. It uses the dashboard model
# as an example, since model specific functionality is already tested in their models' respective serializer tests.


class TestEnterpriseTaggedItemSerializerMixin(APIBaseTest):
@pytest.mark.ee
def test_get_tags(self):
from ee.models.license import License, LicenseManager

super(LicenseManager, cast(LicenseManager, License.objects)).create(
key="key_123", plan="enterprise", valid_until=timezone.datetime(2038, 1, 19, 3, 14, 7), max_users=3,
)

dashboard = Dashboard.objects.create(team_id=self.team.id, name="private dashboard")
tag = Tag.objects.create(name="random", team_id=self.team.id)
dashboard.tagged_items.create(tag_id=tag.id)

response = self.client.get(f"/api/projects/{self.team.id}/dashboards/{dashboard.id}")

self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.json()["tags"], ["random"])

@pytest.mark.ee
def test_resolve_overlapping_tags_on_update(self):
from ee.models.license import License, LicenseManager

super(LicenseManager, cast(LicenseManager, License.objects)).create(
key="key_123", plan="enterprise", valid_until=timezone.datetime(2038, 1, 19, 3, 14, 7), max_users=3,
)

dashboard = Dashboard.objects.create(team_id=self.team.id, name="private dashboard")
tag_a = Tag.objects.create(name="a", team_id=self.team.id)
tag_b = Tag.objects.create(name="b", team_id=self.team.id)
dashboard.tagged_items.create(tag_id=tag_a.id)
dashboard.tagged_items.create(tag_id=tag_b.id)

self.assertEqual(TaggedItem.objects.all().count(), 2)

response = self.client.patch(
f"/api/projects/{self.team.id}/dashboards/{dashboard.id}",
{"name": "Default", "pinned": "true", "tags": ["b", "c", "d", "e"]},
)

self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(sorted(response.json()["tags"]), ["b", "c", "d", "e"])
self.assertEqual(TaggedItem.objects.all().count(), 4)

@pytest.mark.ee
def test_create_and_update_object_with_tags(self):
from ee.models.license import License, LicenseManager

super(LicenseManager, cast(LicenseManager, License.objects)).create(
key="key_123", plan="enterprise", valid_until=timezone.datetime(2038, 1, 19, 3, 14, 7), max_users=3,
)

response = self.client.post(f"/api/projects/{self.team.id}/dashboards/", {"name": "Default", "pinned": "true"})

self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.json()["tags"], [])
self.assertEqual(TaggedItem.objects.all().count(), 0)

id = response.json()["id"]
response = self.client.patch(f"/api/projects/{self.team.id}/dashboards/{id}", {"tags": ["b", "c", "d", "e"]})
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(sorted(response.json()["tags"]), ["b", "c", "d", "e"])
self.assertEqual(TaggedItem.objects.all().count(), 4)

def test_create_with_tags(self):
from ee.models.license import License, LicenseManager

super(LicenseManager, cast(LicenseManager, License.objects)).create(
key="key_123", plan="enterprise", valid_until=timezone.datetime(2038, 1, 19, 3, 14, 7), max_users=3,
)

response = self.client.post(
f"/api/projects/{self.team.id}/dashboards/", {"name": "Default", "pinned": "true", "tags": ["nightly"]}
)

self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.json()["tags"], ["nightly"])
self.assertEqual(TaggedItem.objects.all().count(), 1)

0 comments on commit 68c538b

Please sign in to comment.