Skip to content

Commit

Permalink
Bundle Analysis: Add mutation for toggling bundle caching configurati…
Browse files Browse the repository at this point in the history
…on (#1084)
  • Loading branch information
JerrySentry authored Jan 10, 2025
1 parent a0c8267 commit e3a2467
Show file tree
Hide file tree
Showing 11 changed files with 328 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import pytest
from asgiref.sync import async_to_sync
from django.test import TransactionTestCase
from shared.django_apps.bundle_analysis.models import CacheConfig
from shared.django_apps.core.tests.factories import (
OwnerFactory,
RepositoryFactory,
)

from codecov.commands.exceptions import ValidationError

from ..update_bundle_cache_config import UpdateBundleCacheConfigInteractor


class UpdateBundleCacheConfigInteractorTest(TransactionTestCase):
databases = {"default"}

def setUp(self):
self.org = OwnerFactory(username="test-org")
self.repo = RepositoryFactory(author=self.org, name="test-repo", active=True)
self.user = OwnerFactory(permission=[self.repo.pk])

@async_to_sync
def execute(self, owner, repo_name=None, cache_config=[]):
return UpdateBundleCacheConfigInteractor(owner, "github").execute(
repo_name=repo_name,
owner_username="test-org",
cache_config=cache_config,
)

def test_repo_not_found(self):
with pytest.raises(ValidationError):
self.execute(owner=self.user, repo_name="wrong")

def test_bundle_not_found(self):
with pytest.raises(
ValidationError, match="The following bundle names do not exist: wrong"
):
self.execute(
owner=self.user,
repo_name="test-repo",
cache_config=[{"bundle_name": "wrong", "toggle_caching": True}],
)

def test_some_bundles_not_found(self):
CacheConfig.objects.create(
repo_id=self.repo.pk, bundle_name="bundle1", is_caching=True
)
with pytest.raises(
ValidationError, match="The following bundle names do not exist: bundle2"
):
self.execute(
owner=self.user,
repo_name="test-repo",
cache_config=[
{"bundle_name": "bundle1", "toggle_caching": False},
{"bundle_name": "bundle2", "toggle_caching": True},
],
)

def test_update_bundles_successfully(self):
CacheConfig.objects.create(
repo_id=self.repo.pk, bundle_name="bundle1", is_caching=True
)
CacheConfig.objects.create(
repo_id=self.repo.pk, bundle_name="bundle2", is_caching=True
)

res = self.execute(
owner=self.user,
repo_name="test-repo",
cache_config=[
{"bundle_name": "bundle1", "toggle_caching": False},
{"bundle_name": "bundle2", "toggle_caching": True},
],
)

assert res == [
{"bundle_name": "bundle1", "is_cached": False},
{"bundle_name": "bundle2", "is_cached": True},
]

assert len(CacheConfig.objects.all()) == 2

query = CacheConfig.objects.filter(repo_id=self.repo.pk, bundle_name="bundle1")
assert len(query) == 1
assert query[0].is_caching == False

query = CacheConfig.objects.filter(repo_id=self.repo.pk, bundle_name="bundle2")
assert len(query) == 1
assert query[0].is_caching == True
71 changes: 71 additions & 0 deletions core/commands/repository/interactors/update_bundle_cache_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from typing import Dict, List

from shared.django_apps.bundle_analysis.models import CacheConfig
from shared.django_apps.bundle_analysis.service.bundle_analysis import (
BundleAnalysisCacheConfigService,
)

from codecov.commands.base import BaseInteractor
from codecov.commands.exceptions import ValidationError
from codecov.db import sync_to_async
from codecov_auth.models import Owner
from core.models import Repository


class UpdateBundleCacheConfigInteractor(BaseInteractor):
def validate(
self, repo: Repository, cache_config: List[Dict[str, str | bool]]
) -> None:
if not repo:
raise ValidationError("Repo not found")

# Find any missing bundle names
bundle_names = [
bundle["bundle_name"]
for bundle in cache_config
# the value of bundle_name is always a string, just do this check to appease mypy
if isinstance(bundle["bundle_name"], str)
]
existing_bundle_names = set(
CacheConfig.objects.filter(
repo_id=repo.pk, bundle_name__in=bundle_names
).values_list("bundle_name", flat=True)
)
missing_bundles = set(bundle_names) - existing_bundle_names
if missing_bundles:
raise ValidationError(
f"The following bundle names do not exist: {', '.join(missing_bundles)}"
)

@sync_to_async
def execute(
self,
owner_username: str,
repo_name: str,
cache_config: List[Dict[str, str | bool]],
) -> List[Dict[str, str | bool]]:
author = Owner.objects.filter(
username=owner_username, service=self.service
).first()
repo = (
Repository.objects.viewable_repos(self.current_owner)
.filter(author=author, name=repo_name)
.first()
)

self.validate(repo, cache_config)

results = []
for bundle in cache_config:
bundle_name = bundle["bundle_name"]
is_caching = bundle["toggle_caching"]
BundleAnalysisCacheConfigService.update_cache_option(
repo.pk, bundle_name, is_caching
)
results.append(
{
"bundle_name": bundle_name,
"is_cached": is_caching,
}
)
return results
13 changes: 12 additions & 1 deletion core/commands/repository/repository.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import uuid
from typing import Awaitable, Optional
from typing import Awaitable, Dict, List, Optional

from codecov.commands.base import BaseCommand
from codecov_auth.models import Owner, RepositoryToken
Expand All @@ -16,6 +16,7 @@
from .interactors.regenerate_repository_upload_token import (
RegenerateRepositoryUploadTokenInteractor,
)
from .interactors.update_bundle_cache_config import UpdateBundleCacheConfigInteractor
from .interactors.update_repository import UpdateRepositoryInteractor


Expand Down Expand Up @@ -85,3 +86,13 @@ def encode_secret_string(self, owner: Owner, repo_name: str, value: str) -> str:
return self.get_interactor(EncodeSecretStringInteractor).execute(
owner, repo_name, value
)

def update_bundle_cache_config(
self,
owner_username: str,
repo_name: str,
cache_config: List[Dict[str, str | bool]],
) -> Awaitable[List[Dict[str, str | bool]]]:
return self.get_interactor(UpdateBundleCacheConfigInteractor).execute(
owner_username, repo_name, cache_config
)
79 changes: 79 additions & 0 deletions graphql_api/tests/mutation/test_update_bundle_cache_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
from unittest.mock import patch

from django.test import TransactionTestCase
from shared.django_apps.core.tests.factories import OwnerFactory

from graphql_api.tests.helper import GraphQLTestHelper

query = """
mutation($input: ActivateMeasurementsInput!) {
activateMeasurements(input: $input) {
error {
__typename
}
}
}
"""


query = """
mutation UpdateBundleCacheConfig(
$owner: String!
$repoName: String!
$bundles: [BundleCacheConfigInput!]!
) {
updateBundleCacheConfig(input: {
owner: $owner,
repoName: $repoName,
bundles: $bundles
}) {
results {
bundleName
isCached
}
error {
__typename
... on UnauthenticatedError {
message
}
... on ValidationError {
message
}
}
}
}
"""


class UpdateBundleCacheConfigTestCase(GraphQLTestHelper, TransactionTestCase):
def setUp(self):
self.owner = OwnerFactory()

def test_when_unauthenticated(self):
data = self.gql_request(
query,
variables={
"owner": "codecov",
"repoName": "test-repo",
"bundles": [{"bundleName": "pr_bundle1", "toggleCaching": True}],
},
)
assert (
data["updateBundleCacheConfig"]["error"]["__typename"]
== "UnauthenticatedError"
)

@patch(
"core.commands.repository.interactors.update_bundle_cache_config.UpdateBundleCacheConfigInteractor.execute"
)
def test_when_authenticated(self, execute):
data = self.gql_request(
query,
owner=self.owner,
variables={
"owner": "codecov",
"repoName": "test-repo",
"bundles": [{"bundleName": "pr_bundle1", "toggleCaching": True}],
},
)
assert data == {"updateBundleCacheConfig": {"results": [], "error": None}}
10 changes: 10 additions & 0 deletions graphql_api/types/inputs/bundle_analysis_cache_config.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
input BundleCacheConfigInput {
bundleName: String!
toggleCaching: Boolean!
}

input UpdateBundleCacheConfigInput {
owner: String!
repoName: String!
bundles: [BundleCacheConfigInput!]!
}
2 changes: 2 additions & 0 deletions graphql_api/types/mutation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from .start_trial import gql_start_trial
from .store_event_metrics import gql_store_event_metrics
from .sync_with_git_provider import gql_sync_with_git_provider
from .update_bundle_cache_config import gql_update_bundle_cache_config
from .update_default_organization import gql_update_default_organization
from .update_profile import gql_update_profile
from .update_repository import gql_update_repository
Expand Down Expand Up @@ -55,3 +56,4 @@
mutation = mutation + gql_store_event_metrics
mutation = mutation + gql_save_okta_config
mutation = mutation + gql_set_upload_token_required
mutation = mutation + gql_update_bundle_cache_config
1 change: 1 addition & 0 deletions graphql_api/types/mutation/mutation.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,5 @@ type Mutation {
storeEventMetric(input: StoreEventMetricsInput!): StoreEventMetricsPayload
saveOktaConfig(input: SaveOktaConfigInput!): SaveOktaConfigPayload
setUploadTokenRequired(input: SetUploadTokenRequiredInput!): SetUploadTokenRequiredPayload
updateBundleCacheConfig(input: UpdateBundleCacheConfigInput!): UpdateBundleCacheConfigPayload
}
6 changes: 6 additions & 0 deletions graphql_api/types/mutation/mutation.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@
error_sync_with_git_provider,
resolve_sync_with_git_provider,
)
from .update_bundle_cache_config import (
error_update_bundle_cache_config,
resolve_update_bundle_cache_config,
)
from .update_default_organization import (
error_update_default_organization,
resolve_update_default_organization,
Expand Down Expand Up @@ -99,6 +103,7 @@

mutation_bindable.field("saveOktaConfig")(resolve_save_okta_config)
mutation_bindable.field("setUploadTokenRequired")(resolve_set_upload_token_required)
mutation_bindable.field("updateBundleCacheConfig")(resolve_update_bundle_cache_config)

mutation_resolvers = [
mutation_bindable,
Expand Down Expand Up @@ -128,4 +133,5 @@
error_store_event_metrics,
error_save_okta_config,
error_set_upload_token_required,
error_update_bundle_cache_config,
]
15 changes: 15 additions & 0 deletions graphql_api/types/mutation/update_bundle_cache_config/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from graphql_api.helpers.ariadne import ariadne_load_local_graphql

from .update_bundle_cache_config import (
error_update_bundle_cache_config,
resolve_update_bundle_cache_config,
)

gql_update_bundle_cache_config = ariadne_load_local_graphql(
__file__, "update_bundle_cache_config.graphql"
)

__all__ = [
"error_update_bundle_cache_config",
"resolve_update_bundle_cache_config",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
union UpdateBundleCacheConfigError = UnauthenticatedError | ValidationError

type UpdateBundleCacheConfigResult {
bundleName: String
isCached: Boolean
}

type UpdateBundleCacheConfigPayload {
results: [UpdateBundleCacheConfigResult!]
error: UpdateBundleCacheConfigError
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from typing import Any, Dict, List

from ariadne import UnionType
from graphql import GraphQLResolveInfo

from core.commands.repository.repository import RepositoryCommands
from graphql_api.helpers.mutation import (
require_authenticated,
resolve_union_error_type,
wrap_error_handling_mutation,
)


@wrap_error_handling_mutation
@require_authenticated
async def resolve_update_bundle_cache_config(
_: Any, info: GraphQLResolveInfo, input: Dict[str, Any]
) -> Dict[str, List[Dict[str, str | bool]]]:
command: RepositoryCommands = info.context["executor"].get_command("repository")

results = await command.update_bundle_cache_config(
repo_name=input.get("repo_name", ""),
owner_username=input.get("owner", ""),
cache_config=input.get("bundles", []),
)
return {"results": results}


error_update_bundle_cache_config = UnionType("UpdateBundleCacheConfigError")
error_update_bundle_cache_config.type_resolver(resolve_union_error_type)

0 comments on commit e3a2467

Please sign in to comment.