Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: remove error tracking models #25911

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading