Skip to content

Commit

Permalink
chore: remove error tracking models
Browse files Browse the repository at this point in the history
  • Loading branch information
daibhin committed Oct 30, 2024
1 parent 84db60b commit 50faca1
Show file tree
Hide file tree
Showing 9 changed files with 305 additions and 335 deletions.
2 changes: 1 addition & 1 deletion latest_migrations.manifest
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ contenttypes: 0002_remove_content_type_name
ee: 0016_rolemembership_organization_member
otp_static: 0002_throttling
otp_totp: 0002_auto_20190420_0723
posthog: 0502_team_session_recording_url_blocklist_config
posthog: 0503_remove_errortrackingissuefingerprint_issue_and_more
sessions: 0001_initial
social_django: 0010_uid_db_index
two_factor: 0007_auto_20201201_1019
12 changes: 6 additions & 6 deletions posthog/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -496,12 +496,12 @@ def register_grandfathered_environment_nested_viewset(
["project_id"],
)

projects_router.register(
r"error_tracking",
error_tracking.ErrorTrackingGroupViewSet,
"project_error_tracking",
["team_id"],
)
# projects_router.register(
# r"error_tracking",
# error_tracking.ErrorTrackingGroupViewSet,
# "project_error_tracking",
# ["team_id"],
# )

projects_router.register(
r"comments",
Expand Down
104 changes: 45 additions & 59 deletions posthog/api/error_tracking.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,5 @@
import json
import structlog

from rest_framework import serializers, viewsets, status
from rest_framework.response import Response
from rest_framework.exceptions import ValidationError

from django.db.models import QuerySet
from django.conf import settings
from django.utils.http import urlsafe_base64_decode

from posthog.api.forbid_destroy_model import ForbidDestroyModel
from posthog.models.error_tracking import ErrorTrackingGroup
from posthog.api.routing import TeamAndOrgViewSetMixin
from posthog.api.utils import action
from posthog.storage import object_storage

FIFTY_MEGABYTES = 50 * 1024 * 1024

Expand All @@ -24,48 +10,48 @@ class ObjectStorageUnavailable(Exception):
pass


class ErrorTrackingGroupSerializer(serializers.ModelSerializer):
class Meta:
model = ErrorTrackingGroup
fields = ["assignee", "status"]


class ErrorTrackingGroupViewSet(TeamAndOrgViewSetMixin, ForbidDestroyModel, viewsets.ModelViewSet):
scope_object = "INTERNAL"
queryset = ErrorTrackingGroup.objects.all()
serializer_class = ErrorTrackingGroupSerializer

def safely_get_object(self, queryset) -> QuerySet:
stringified_fingerprint = self.kwargs["pk"]
fingerprint = json.loads(urlsafe_base64_decode(stringified_fingerprint))
group, _ = queryset.get_or_create(fingerprint=fingerprint, team=self.team)
return group

@action(methods=["POST"], detail=True)
def merge(self, request, **kwargs):
group: ErrorTrackingGroup = self.get_object()
merging_fingerprints: list[list[str]] = request.data.get("merging_fingerprints", [])
group.merge(merging_fingerprints)
return Response({"success": True})

@action(methods=["POST"], detail=False)
def upload_source_maps(self, request, **kwargs):
try:
if settings.OBJECT_STORAGE_ENABLED:
file = request.FILES["source_map"]
if file.size > FIFTY_MEGABYTES:
raise ValidationError(code="file_too_large", detail="Source maps must be less than 50MB")

upload_path = (
f"{settings.OBJECT_STORAGE_ERROR_TRACKING_SOURCE_MAPS_FOLDER}/team-{self.team_id}/{file.name}"
)

object_storage.write(upload_path, file)
return Response({"ok": True}, status=status.HTTP_201_CREATED)
else:
raise ObjectStorageUnavailable()
except ObjectStorageUnavailable:
raise ValidationError(
code="object_storage_required",
detail="Object storage must be available to allow source map uploads.",
)
# class ErrorTrackingGroupSerializer(serializers.ModelSerializer):
# class Meta:
# model = ErrorTrackingGroup
# fields = ["assignee", "status"]


# class ErrorTrackingGroupViewSet(TeamAndOrgViewSetMixin, ForbidDestroyModel, viewsets.ModelViewSet):
# scope_object = "INTERNAL"
# queryset = ErrorTrackingGroup.objects.all()
# serializer_class = ErrorTrackingGroupSerializer

# def safely_get_object(self, queryset) -> QuerySet:
# stringified_fingerprint = self.kwargs["pk"]
# fingerprint = json.loads(urlsafe_base64_decode(stringified_fingerprint))
# group, _ = queryset.get_or_create(fingerprint=fingerprint, team=self.team)
# return group

# @action(methods=["POST"], detail=True)
# def merge(self, request, **kwargs):
# group: ErrorTrackingGroup = self.get_object()
# merging_fingerprints: list[list[str]] = request.data.get("merging_fingerprints", [])
# group.merge(merging_fingerprints)
# return Response({"success": True})

# @action(methods=["POST"], detail=False)
# def upload_source_maps(self, request, **kwargs):
# try:
# if settings.OBJECT_STORAGE_ENABLED:
# file = request.FILES["source_map"]
# if file.size > FIFTY_MEGABYTES:
# raise ValidationError(code="file_too_large", detail="Source maps must be less than 50MB")

# upload_path = (
# f"{settings.OBJECT_STORAGE_ERROR_TRACKING_SOURCE_MAPS_FOLDER}/team-{self.team_id}/{file.name}"
# )

# object_storage.write(upload_path, file)
# return Response({"ok": True}, status=status.HTTP_201_CREATED)
# else:
# raise ObjectStorageUnavailable()
# except ObjectStorageUnavailable:
# raise ValidationError(
# code="object_storage_required",
# detail="Object storage must be available to allow source map uploads.",
# )
228 changes: 106 additions & 122 deletions posthog/api/test/test_error_tracking.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,5 @@
import os
import json
from boto3 import resource
from rest_framework import status

from django.utils.http import urlsafe_base64_encode
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import override_settings

from posthog.test.base import APIBaseTest
from posthog.models import Team, ErrorTrackingGroup
from botocore.config import Config
from posthog.settings import (
OBJECT_STORAGE_ENDPOINT,
OBJECT_STORAGE_ACCESS_KEY_ID,
OBJECT_STORAGE_SECRET_ACCESS_KEY,
OBJECT_STORAGE_BUCKET,
)


TEST_BUCKET = "test_storage_bucket-TestErrorTracking"

Expand All @@ -25,108 +9,108 @@ def get_path_to(fixture_file: str) -> str:
return os.path.join(file_dir, "fixtures", fixture_file)


class TestErrorTracking(APIBaseTest):
def teardown_method(self, method) -> None:
s3 = resource(
"s3",
endpoint_url=OBJECT_STORAGE_ENDPOINT,
aws_access_key_id=OBJECT_STORAGE_ACCESS_KEY_ID,
aws_secret_access_key=OBJECT_STORAGE_SECRET_ACCESS_KEY,
config=Config(signature_version="s3v4"),
region_name="us-east-1",
)
bucket = s3.Bucket(OBJECT_STORAGE_BUCKET)
bucket.objects.filter(Prefix=TEST_BUCKET).delete()

def send_request(self, fingerprint, data, endpoint=""):
base64_fingerprint = urlsafe_base64_encode(json.dumps(fingerprint).encode("utf-8"))
request_method = self.client.patch if endpoint == "" else self.client.post
request_method(
f"/api/projects/{self.team.id}/error_tracking/{base64_fingerprint}/{endpoint}",
data=data,
)

def test_reuses_existing_group_for_team(self):
fingerprint = ["CustomFingerprint"]
ErrorTrackingGroup.objects.create(fingerprint=fingerprint, team=self.team)

self.assertEqual(ErrorTrackingGroup.objects.count(), 1)
self.send_request(fingerprint, {"assignee": self.user.id})
self.assertEqual(ErrorTrackingGroup.objects.count(), 1)

def test_creates_group_if_not_already_existing_for_team(self):
fingerprint = ["CustomFingerprint"]
other_team = Team.objects.create(organization=self.organization)
ErrorTrackingGroup.objects.create(fingerprint=fingerprint, team=other_team)

self.assertEqual(ErrorTrackingGroup.objects.count(), 1)
self.send_request(fingerprint, {"assignee": self.user.id})
self.assertEqual(ErrorTrackingGroup.objects.count(), 2)

def test_can_only_update_allowed_fields(self):
fingerprint = ["CustomFingerprint"]
other_team = Team.objects.create(organization=self.organization)
group = ErrorTrackingGroup.objects.create(fingerprint=fingerprint, team=other_team)

self.send_request(fingerprint, {"fingerprint": ["NewFingerprint"], "assignee": self.user.id})
group.refresh_from_db()
self.assertEqual(group.fingerprint, ["CustomFingerprint"])

def test_merging_of_an_existing_group(self):
fingerprint = ["CustomFingerprint"]
merging_fingerprints = [["NewFingerprint"]]
group = ErrorTrackingGroup.objects.create(fingerprint=fingerprint, team=self.team)

self.send_request(fingerprint, {"merging_fingerprints": merging_fingerprints}, endpoint="merge")

group.refresh_from_db()
self.assertEqual(group.merged_fingerprints, merging_fingerprints)

def test_merging_when_no_group_exists(self):
fingerprint = ["CustomFingerprint"]
merging_fingerprints = [["NewFingerprint"]]

self.assertEqual(ErrorTrackingGroup.objects.count(), 0)
self.send_request(fingerprint, {"merging_fingerprints": merging_fingerprints}, endpoint="merge")
self.assertEqual(ErrorTrackingGroup.objects.count(), 1)
groups = ErrorTrackingGroup.objects.only("merged_fingerprints")
self.assertEqual(groups[0].merged_fingerprints, merging_fingerprints)

def test_can_upload_a_source_map(self) -> None:
with self.settings(OBJECT_STORAGE_ENABLED=True, OBJECT_STORAGE_ERROR_TRACKING_SOURCE_MAPS_FOLDER=TEST_BUCKET):
with open(get_path_to("source.js.map"), "rb") as image:
response = self.client.post(
f"/api/projects/{self.team.id}/error_tracking/upload_source_maps",
{"source_map": image},
format="multipart",
)
self.assertEqual(response.status_code, status.HTTP_201_CREATED, response.json())

def test_rejects_too_large_file_type(self) -> None:
fifty_megabytes_plus_a_little = b"1" * (50 * 1024 * 1024 + 1)
fake_big_file = SimpleUploadedFile(
name="large_source.js.map",
content=fifty_megabytes_plus_a_little,
content_type="text/plain",
)
response = self.client.post(
f"/api/projects/{self.team.id}/error_tracking/upload_source_maps",
{"source_map": fake_big_file},
format="multipart",
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST, response.json())
self.assertEqual(response.json()["detail"], "Source maps must be less than 50MB")

def test_rejects_upload_when_object_storage_is_unavailable(self) -> None:
with override_settings(OBJECT_STORAGE_ENABLED=False):
fake_big_file = SimpleUploadedFile(name="large_source.js.map", content=b"", content_type="text/plain")
response = self.client.post(
f"/api/projects/{self.team.id}/error_tracking/upload_source_maps",
{"source_map": fake_big_file},
format="multipart",
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST, response.json())
self.assertEqual(
response.json()["detail"],
"Object storage must be available to allow source map uploads.",
)
# class TestErrorTracking(APIBaseTest):
# def teardown_method(self, method) -> None:
# s3 = resource(
# "s3",
# endpoint_url=OBJECT_STORAGE_ENDPOINT,
# aws_access_key_id=OBJECT_STORAGE_ACCESS_KEY_ID,
# aws_secret_access_key=OBJECT_STORAGE_SECRET_ACCESS_KEY,
# config=Config(signature_version="s3v4"),
# region_name="us-east-1",
# )
# bucket = s3.Bucket(OBJECT_STORAGE_BUCKET)
# bucket.objects.filter(Prefix=TEST_BUCKET).delete()

# def send_request(self, fingerprint, data, endpoint=""):
# base64_fingerprint = urlsafe_base64_encode(json.dumps(fingerprint).encode("utf-8"))
# request_method = self.client.patch if endpoint == "" else self.client.post
# request_method(
# f"/api/projects/{self.team.id}/error_tracking/{base64_fingerprint}/{endpoint}",
# data=data,
# )

# def test_reuses_existing_group_for_team(self):
# fingerprint = ["CustomFingerprint"]
# ErrorTrackingGroup.objects.create(fingerprint=fingerprint, team=self.team)

# self.assertEqual(ErrorTrackingGroup.objects.count(), 1)
# self.send_request(fingerprint, {"assignee": self.user.id})
# self.assertEqual(ErrorTrackingGroup.objects.count(), 1)

# def test_creates_group_if_not_already_existing_for_team(self):
# fingerprint = ["CustomFingerprint"]
# other_team = Team.objects.create(organization=self.organization)
# ErrorTrackingGroup.objects.create(fingerprint=fingerprint, team=other_team)

# self.assertEqual(ErrorTrackingGroup.objects.count(), 1)
# self.send_request(fingerprint, {"assignee": self.user.id})
# self.assertEqual(ErrorTrackingGroup.objects.count(), 2)

# def test_can_only_update_allowed_fields(self):
# fingerprint = ["CustomFingerprint"]
# other_team = Team.objects.create(organization=self.organization)
# group = ErrorTrackingGroup.objects.create(fingerprint=fingerprint, team=other_team)

# self.send_request(fingerprint, {"fingerprint": ["NewFingerprint"], "assignee": self.user.id})
# group.refresh_from_db()
# self.assertEqual(group.fingerprint, ["CustomFingerprint"])

# def test_merging_of_an_existing_group(self):
# fingerprint = ["CustomFingerprint"]
# merging_fingerprints = [["NewFingerprint"]]
# group = ErrorTrackingGroup.objects.create(fingerprint=fingerprint, team=self.team)

# self.send_request(fingerprint, {"merging_fingerprints": merging_fingerprints}, endpoint="merge")

# group.refresh_from_db()
# self.assertEqual(group.merged_fingerprints, merging_fingerprints)

# def test_merging_when_no_group_exists(self):
# fingerprint = ["CustomFingerprint"]
# merging_fingerprints = [["NewFingerprint"]]

# self.assertEqual(ErrorTrackingGroup.objects.count(), 0)
# self.send_request(fingerprint, {"merging_fingerprints": merging_fingerprints}, endpoint="merge")
# self.assertEqual(ErrorTrackingGroup.objects.count(), 1)
# groups = ErrorTrackingGroup.objects.only("merged_fingerprints")
# self.assertEqual(groups[0].merged_fingerprints, merging_fingerprints)

# def test_can_upload_a_source_map(self) -> None:
# with self.settings(OBJECT_STORAGE_ENABLED=True, OBJECT_STORAGE_ERROR_TRACKING_SOURCE_MAPS_FOLDER=TEST_BUCKET):
# with open(get_path_to("source.js.map"), "rb") as image:
# response = self.client.post(
# f"/api/projects/{self.team.id}/error_tracking/upload_source_maps",
# {"source_map": image},
# format="multipart",
# )
# self.assertEqual(response.status_code, status.HTTP_201_CREATED, response.json())

# def test_rejects_too_large_file_type(self) -> None:
# fifty_megabytes_plus_a_little = b"1" * (50 * 1024 * 1024 + 1)
# fake_big_file = SimpleUploadedFile(
# name="large_source.js.map",
# content=fifty_megabytes_plus_a_little,
# content_type="text/plain",
# )
# response = self.client.post(
# f"/api/projects/{self.team.id}/error_tracking/upload_source_maps",
# {"source_map": fake_big_file},
# format="multipart",
# )
# self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST, response.json())
# self.assertEqual(response.json()["detail"], "Source maps must be less than 50MB")

# def test_rejects_upload_when_object_storage_is_unavailable(self) -> None:
# with override_settings(OBJECT_STORAGE_ENABLED=False):
# fake_big_file = SimpleUploadedFile(name="large_source.js.map", content=b"", content_type="text/plain")
# response = self.client.post(
# f"/api/projects/{self.team.id}/error_tracking/upload_source_maps",
# {"source_map": fake_big_file},
# format="multipart",
# )
# self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST, response.json())
# self.assertEqual(
# response.json()["detail"],
# "Object storage must be available to allow source map uploads.",
# )
Loading

0 comments on commit 50faca1

Please sign in to comment.