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

ref: Remove PlanData references for Plan model properties #1150

Merged
merged 7 commits into from
Feb 12, 2025

Conversation

ajay-sentry
Copy link
Contributor

Purpose/Motivation

This PR updates the types on the GQL api and some relevant object accesses to use the Plan model directly (and its new associated properties) instead of the PlanData data-class, allowing us to remove that concept alltogether.

Depends on https://github.com/codecov/shared/pull/509/files

Links to relevant tickets

Closes codecov/engineering-team#3343

What does this PR do?

Include a brief description of the changes in this PR. Bullet points are your friend.

Notes to Reviewer

Anything to note to the team? Any tips on how to review, or where to start?

Legal Boilerplate

Look, I get it. The entity doing business as "Sentry" was incorporated in the State of Delaware in 2015 as Functional Software, Inc. In 2022 this entity acquired Codecov and as result Sentry is going to need some rights from me in order to utilize my contributions in this PR. So here's the deal: I retain all rights, title and interest in and to my contributions, and by keeping this boilerplate intact I confirm that Sentry can use, modify, copy, and redistribute my contributions, under Sentry's choice of terms.

@ajay-sentry ajay-sentry requested a review from a team as a code owner February 11, 2025 00:43
Copy link

codecov bot commented Feb 11, 2025

Codecov Report

Attention: Patch coverage is 97.36842% with 1 line in your changes missing coverage. Please review.

Project coverage is 96.06%. Comparing base (659b4ec) to head (98da4db).
Report is 2 commits behind head on main.

Changes have been made to critical files, which contain lines commonly executed in production. Learn more

✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...i/types/plan_representation/plan_representation.py 96.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1150      +/-   ##
==========================================
+ Coverage   96.04%   96.06%   +0.01%     
==========================================
  Files         838      838              
  Lines       19806    20020     +214     
==========================================
+ Hits        19023    19232     +209     
- Misses        783      788       +5     
Flag Coverage Δ
unit 95.92% <97.36%> (-0.01%) ⬇️
unit-latest-uploader 95.92% <97.36%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@codecov-staging
Copy link

codecov-staging bot commented Feb 11, 2025

Codecov Report

Attention: Patch coverage is 97.36842% with 1 line in your changes missing coverage. Please review.

✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...i/types/plan_representation/plan_representation.py 96.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@codecov-qa
Copy link

codecov-qa bot commented Feb 11, 2025

Codecov Report

Attention: Patch coverage is 97.36842% with 1 line in your changes missing coverage. Please review.

Project coverage is 95.92%. Comparing base (659b4ec) to head (98da4db).
Report is 2 commits behind head on main.

✅ All tests successful. No failed tests found.

Copy link

codecov-public-qa bot commented Feb 11, 2025

❌ 3 Tests Failed:

Tests completed Failed Passed Skipped
2744 3 2741 6
View the top 3 failed tests by shortest run time
upload/tests/views/test_upload_coverage.py::::test_upload_coverage_post
Stack Traces | 0.077s run time
db = None, mocker = <pytest_mock.plugin.MockerFixture object at 0x7fa623f11c70>

    def test_upload_coverage_post(db, mocker):
        mocker.patch.object(
            CanDoCoverageUploadsPermission, "has_permission", return_value=True
        )
        presigned_put_mock = mocker.patch(
            "shared.api_archive.archive.StorageService.create_presigned_put",
            return_value="presigned put",
        )
        upload_task_mock = mocker.patch(
            "upload.views.uploads.trigger_upload_task", return_value=True
        )
    
        repository = RepositoryFactory(
            name="the_repo1", author__username="codecov", author__service="github"
        )
        commit = CommitFactory(repository=repository)
        repository.save()
        commit.save()
    
        owner = repository.author
        client = APIClient()
        client.force_authenticate(user=owner)
        repo_slug = f"{repository.author.username}::::{repository.name}"
        url = reverse(
            "new_upload.upload_coverage",
            args=[repository.author.service, repo_slug],
        )
>       response = client.post(
            url,
            {
                "branch": "branch",
                "ci_service": "ci_service",
                "ci_url": "ci_url",
                "code": "code",
                "commitid": commit.commitid,
                "flags": ["flag1", "flag2"],
                "job_code": "job_code",
                "version": "version",
            },
            format="json",
        )

.../tests/views/test_upload_coverage.py:120: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../local/lib/python3.12.............../site-packages/rest_framework/test.py:295: in post
    response = super().post(
.../local/lib/python3.12.............../site-packages/rest_framework/test.py:209: in post
    return self.generic('POST', path, data, content_type, **extra)
.../local/lib/python3.12.............../site-packages/rest_framework/test.py:233: in generic
    return super().generic(
.../local/lib/python3.12.../django/test/client.py:609: in generic
    return self.request(**r)
.../local/lib/python3.12.............../site-packages/rest_framework/test.py:285: in request
    return super().request(**kwargs)
.../local/lib/python3.12.............../site-packages/rest_framework/test.py:237: in request
    request = super().request(**kwargs)
.../local/lib/python3.12.../django/test/client.py:891: in request
    self.check_exception(response)
.../local/lib/python3.12.../django/test/client.py:738: in check_exception
    raise exc_value
.../local/lib/python3.12.../core/handlers/exception.py:55: in inner
    response = get_response(request)
.../local/lib/python3.12.../core/handlers/base.py:197: in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
.../local/lib/python3.12.../integrations/django/views.py:89: in sentry_wrapped_callback
    return callback(request, *args, **kwargs)
.../local/lib/python3.12.../views/decorators/csrf.py:56: in wrapper_view
    return view_func(*args, **kwargs)
.../local/lib/python3.12.../views/generic/base.py:104: in view
    return self.dispatch(request, *args, **kwargs)
.../local/lib/python3.12............/site-packages/rest_framework/views.py:509: in dispatch
    response = self.handle_exception(exc)
.../local/lib/python3.12............/site-packages/rest_framework/views.py:469: in handle_exception
    self.raise_uncaught_exception(exc)
.../local/lib/python3.12............/site-packages/rest_framework/views.py:480: in raise_uncaught_exception
    raise exc
.../local/lib/python3.12............/site-packages/rest_framework/views.py:506: in dispatch
    response = handler(request, *args, **kwargs)
upload/views/upload_coverage.py:106: in post
    report = create_report(commit_report_serializer, repository, commit)
upload/views/reports.py:45: in create_report
    TaskService().preprocess_upload(
services/task/task.py:388: in preprocess_upload
    self._create_signature(
services/task/task.py:39: in _create_signature
    queue_and_config = route_task(name, args=args, kwargs=kwargs)
services/task/task_router.py:132: in route_task
    return route_tasks_based_on_user_plan(name, user_plan)
.../local/lib/python3.12.../site-packages/shared/celery_router.py:51: in route_tasks_based_on_user_plan
    plan = Plan.objects.get(name=user_plan)
.../local/lib/python3.12.../db/models/manager.py:87: in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <QuerySet []>, args = (), kwargs = {'name': 'users-developer'}
clone = <QuerySet []>, limit = 21, num = 0

    def get(self, *args, **kwargs):
        """
        Perform the query and return a single object matching the given
        keyword arguments.
        """
        if self.query.combinator and (args or kwargs):
            raise NotSupportedError(
                "Calling QuerySet.get(...) with filters after %s() is not "
                "supported." % self.query.combinator
            )
        clone = self._chain() if self.query.combinator else self.filter(*args, **kwargs)
        if self.query.can_filter() and not self.query.distinct_fields:
            clone = clone.order_by()
        limit = None
        if (
            not clone.query.select_for_update
            or connections[clone.db].features.supports_select_for_update_with_limit
        ):
            limit = MAX_GET_RESULTS
            clone.query.set_limits(high=limit)
        num = len(clone)
        if num == 1:
            return clone._result_cache[0]
        if not num:
>           raise self.model.DoesNotExist(
                "%s matching query does not exist." % self.model._meta.object_name
            )
E           shared.django_apps.codecov_auth.models.Plan.DoesNotExist: Plan matching query does not exist.

.../local/lib/python3.12.../db/models/query.py:637: DoesNotExist
upload/tests/views/test_upload_coverage.py::::test_upload_coverage_post_shelter
Stack Traces | 0.081s run time
db = None, mocker = <pytest_mock.plugin.MockerFixture object at 0x7fa6205ffd40>

    @override_settings(SHELTER_SHARED_SECRET="shelter-shared-secret")
    def test_upload_coverage_post_shelter(db, mocker):
        mocker.patch.object(
            CanDoCoverageUploadsPermission, "has_permission", return_value=True
        )
        presigned_put_mock = mocker.patch(
            "shared.api_archive.archive.StorageService.create_presigned_put",
            return_value="presigned put",
        )
        upload_task_mock = mocker.patch(
            "upload.views.uploads.trigger_upload_task", return_value=True
        )
    
        repository = RepositoryFactory(
            name="the_repo", author__username="codecov", author__service="github"
        )
        commit = CommitFactory(repository=repository)
        repository.save()
        commit.save()
    
        owner = repository.author
        client = APIClient()
        client.force_authenticate(user=owner)
        repo_slug = f"{repository.author.username}::::{repository.name}"
        url = reverse(
            "new_upload.upload_coverage",
            args=[repository.author.service, repo_slug],
        )
>       response = client.post(
            url,
            {
                "branch": "branch",
                "ci_service": "ci_service",
                "ci_url": "ci_url",
                "code": "code",
                "commitid": commit.commitid,
                "flags": ["flag1", "flag2"],
                "job_code": "job_code",
                "storage_path": "shelter/test/path.txt",
                "version": "version",
            },
            headers={
                "X-Shelter-Token": "shelter-shared-secret",
                "User-Agent": "codecov-cli/0.4.7",
            },
            format="json",
        )

.../tests/views/test_upload_coverage.py:218: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../local/lib/python3.12.............../site-packages/rest_framework/test.py:295: in post
    response = super().post(
.../local/lib/python3.12.............../site-packages/rest_framework/test.py:209: in post
    return self.generic('POST', path, data, content_type, **extra)
.../local/lib/python3.12.............../site-packages/rest_framework/test.py:233: in generic
    return super().generic(
.../local/lib/python3.12.../django/test/client.py:609: in generic
    return self.request(**r)
.../local/lib/python3.12.............../site-packages/rest_framework/test.py:285: in request
    return super().request(**kwargs)
.../local/lib/python3.12.............../site-packages/rest_framework/test.py:237: in request
    request = super().request(**kwargs)
.../local/lib/python3.12.../django/test/client.py:891: in request
    self.check_exception(response)
.../local/lib/python3.12.../django/test/client.py:738: in check_exception
    raise exc_value
.../local/lib/python3.12.../core/handlers/exception.py:55: in inner
    response = get_response(request)
.../local/lib/python3.12.../core/handlers/base.py:197: in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
.../local/lib/python3.12.../integrations/django/views.py:89: in sentry_wrapped_callback
    return callback(request, *args, **kwargs)
.../local/lib/python3.12.../views/decorators/csrf.py:56: in wrapper_view
    return view_func(*args, **kwargs)
.../local/lib/python3.12.../views/generic/base.py:104: in view
    return self.dispatch(request, *args, **kwargs)
.../local/lib/python3.12............/site-packages/rest_framework/views.py:509: in dispatch
    response = self.handle_exception(exc)
.../local/lib/python3.12............/site-packages/rest_framework/views.py:469: in handle_exception
    self.raise_uncaught_exception(exc)
.../local/lib/python3.12............/site-packages/rest_framework/views.py:480: in raise_uncaught_exception
    raise exc
.../local/lib/python3.12............/site-packages/rest_framework/views.py:506: in dispatch
    response = handler(request, *args, **kwargs)
upload/views/upload_coverage.py:106: in post
    report = create_report(commit_report_serializer, repository, commit)
upload/views/reports.py:45: in create_report
    TaskService().preprocess_upload(
services/task/task.py:388: in preprocess_upload
    self._create_signature(
services/task/task.py:39: in _create_signature
    queue_and_config = route_task(name, args=args, kwargs=kwargs)
services/task/task_router.py:132: in route_task
    return route_tasks_based_on_user_plan(name, user_plan)
.../local/lib/python3.12.../site-packages/shared/celery_router.py:51: in route_tasks_based_on_user_plan
    plan = Plan.objects.get(name=user_plan)
.../local/lib/python3.12.../db/models/manager.py:87: in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <QuerySet []>, args = (), kwargs = {'name': 'users-developer'}
clone = <QuerySet []>, limit = 21, num = 0

    def get(self, *args, **kwargs):
        """
        Perform the query and return a single object matching the given
        keyword arguments.
        """
        if self.query.combinator and (args or kwargs):
            raise NotSupportedError(
                "Calling QuerySet.get(...) with filters after %s() is not "
                "supported." % self.query.combinator
            )
        clone = self._chain() if self.query.combinator else self.filter(*args, **kwargs)
        if self.query.can_filter() and not self.query.distinct_fields:
            clone = clone.order_by()
        limit = None
        if (
            not clone.query.select_for_update
            or connections[clone.db].features.supports_select_for_update_with_limit
        ):
            limit = MAX_GET_RESULTS
            clone.query.set_limits(high=limit)
        num = len(clone)
        if num == 1:
            return clone._result_cache[0]
        if not num:
>           raise self.model.DoesNotExist(
                "%s matching query does not exist." % self.model._meta.object_name
            )
E           shared.django_apps.codecov_auth.models.Plan.DoesNotExist: Plan matching query does not exist.

.../local/lib/python3.12.../db/models/query.py:637: DoesNotExist
upload/tests/views/test_upload_coverage.py::::test_upload_coverage_with_errors
Stack Traces | 0.122s run time
db = None

    def test_upload_coverage_with_errors(db):
        repository = RepositoryFactory()
        repo_slug = f"{repository.author.username}::::{repository.name}"
        url = reverse(
            "new_upload.upload_coverage",
            args=[repository.author.service, repo_slug],
        )
    
        client = APIClient()
        client.credentials(HTTP_AUTHORIZATION="token " + repository.upload_token)
    
        # Missing required fields
        response = client.post(url, {}, format="json")
        assert response.status_code == 400
        assert "commitid" in response.json()
    
        # Invalid flag format
>       response = client.post(
            url, {"commitid": "abc123", "flags": "not-a-list"}, format="json"
        )

.../tests/views/test_upload_coverage.py:86: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../local/lib/python3.12.............../site-packages/rest_framework/test.py:295: in post
    response = super().post(
.../local/lib/python3.12.............../site-packages/rest_framework/test.py:209: in post
    return self.generic('POST', path, data, content_type, **extra)
.../local/lib/python3.12.............../site-packages/rest_framework/test.py:233: in generic
    return super().generic(
.../local/lib/python3.12.../django/test/client.py:609: in generic
    return self.request(**r)
.../local/lib/python3.12.............../site-packages/rest_framework/test.py:285: in request
    return super().request(**kwargs)
.../local/lib/python3.12.............../site-packages/rest_framework/test.py:237: in request
    request = super().request(**kwargs)
.../local/lib/python3.12.../django/test/client.py:891: in request
    self.check_exception(response)
.../local/lib/python3.12.../django/test/client.py:738: in check_exception
    raise exc_value
.../local/lib/python3.12.../core/handlers/exception.py:55: in inner
    response = get_response(request)
.../local/lib/python3.12.../core/handlers/base.py:197: in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
.../local/lib/python3.12.../integrations/django/views.py:89: in sentry_wrapped_callback
    return callback(request, *args, **kwargs)
.../local/lib/python3.12.../views/decorators/csrf.py:56: in wrapper_view
    return view_func(*args, **kwargs)
.../local/lib/python3.12.../views/generic/base.py:104: in view
    return self.dispatch(request, *args, **kwargs)
.../local/lib/python3.12............/site-packages/rest_framework/views.py:509: in dispatch
    response = self.handle_exception(exc)
.../local/lib/python3.12............/site-packages/rest_framework/views.py:469: in handle_exception
    self.raise_uncaught_exception(exc)
.../local/lib/python3.12............/site-packages/rest_framework/views.py:480: in raise_uncaught_exception
    raise exc
.../local/lib/python3.12............/site-packages/rest_framework/views.py:506: in dispatch
    response = handler(request, *args, **kwargs)
upload/views/upload_coverage.py:85: in post
    commit = create_commit(commit_serializer, repository)
upload/views/commits.py:38: in create_commit
    commit = serializer.save(repository=repository)
.../local/lib/python3.12.../site-packages/rest_framework/serializers.py:208: in save
    self.instance = self.create(validated_data)
upload/serializers.py:148: in create
    TaskService().update_commit(
services/task/task.py:340: in update_commit
    self._create_signature(
services/task/task.py:39: in _create_signature
    queue_and_config = route_task(name, args=args, kwargs=kwargs)
services/task/task_router.py:132: in route_task
    return route_tasks_based_on_user_plan(name, user_plan)
.../local/lib/python3.12.../site-packages/shared/celery_router.py:51: in route_tasks_based_on_user_plan
    plan = Plan.objects.get(name=user_plan)
.../local/lib/python3.12.../db/models/manager.py:87: in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <QuerySet []>, args = (), kwargs = {'name': 'users-developer'}
clone = <QuerySet []>, limit = 21, num = 0

    def get(self, *args, **kwargs):
        """
        Perform the query and return a single object matching the given
        keyword arguments.
        """
        if self.query.combinator and (args or kwargs):
            raise NotSupportedError(
                "Calling QuerySet.get(...) with filters after %s() is not "
                "supported." % self.query.combinator
            )
        clone = self._chain() if self.query.combinator else self.filter(*args, **kwargs)
        if self.query.can_filter() and not self.query.distinct_fields:
            clone = clone.order_by()
        limit = None
        if (
            not clone.query.select_for_update
            or connections[clone.db].features.supports_select_for_update_with_limit
        ):
            limit = MAX_GET_RESULTS
            clone.query.set_limits(high=limit)
        num = len(clone)
        if num == 1:
            return clone._result_cache[0]
        if not num:
>           raise self.model.DoesNotExist(
                "%s matching query does not exist." % self.model._meta.object_name
            )
E           shared.django_apps.codecov_auth.models.Plan.DoesNotExist: Plan matching query does not exist.

.../local/lib/python3.12.../db/models/query.py:637: DoesNotExist

To view more test analytics, go to the Test Analytics Dashboard
📢 Thoughts on this report? Let us know!

Copy link
Contributor

github-actions bot commented Feb 11, 2025

✅ All tests successful. No failed tests were found.

📣 Thoughts on this report? Let Codecov know! | Powered by Codecov

Copy link
Contributor

github-actions bot commented Feb 11, 2025

This PR includes changes to shared. Please review them here: codecov/shared@69b7368...016a756

@ajay-sentry ajay-sentry requested a review from a team as a code owner February 11, 2025 23:24
@ajay-sentry ajay-sentry added this pull request to the merge queue Feb 11, 2025
Merged via the queue into main with commit 06167fb Feb 12, 2025
17 of 19 checks passed
@ajay-sentry ajay-sentry deleted the Ajay/remove-plan-data-types branch February 12, 2025 00:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Update PlanData to Plan Properties in API
2 participants