Skip to content

Commit

Permalink
[feat] Add GQL mutation to store event metrics (#620)
Browse files Browse the repository at this point in the history
  • Loading branch information
rohitvinnakota-codecov committed Jun 19, 2024
1 parent 6efefaf commit 1787236
Show file tree
Hide file tree
Showing 12 changed files with 196 additions and 3 deletions.
1 change: 1 addition & 0 deletions codecov/settings_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
# New Shared Models
"shared.django_apps.rollouts",
"shared.django_apps.user_measurements",
"shared.django_apps.codecov_metrics",
]

MIDDLEWARE = [
Expand Down
31 changes: 31 additions & 0 deletions codecov_auth/commands/owner/interactors/store_codecov_metric.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import json

from shared.django_apps.codecov_metrics.service.codecov_metrics import (
UserOnboardingMetricsService,
)

from codecov.commands.base import BaseInteractor
from codecov.commands.exceptions import Unauthenticated, ValidationError
from codecov.db import sync_to_async
from codecov_auth.models import Owner


class StoreCodecovMetricInteractor(BaseInteractor):
@sync_to_async
def execute(self, org_username: str, event: str, json_string: str) -> None:
current_org = Owner.objects.filter(
username=org_username, service=self.service
).first()
if not current_org:
raise ValidationError("Cannot find owner record in the database")

try:
payload = json.loads(json_string)
except json.JSONDecodeError:
raise ValidationError("Invalid JSON string")

UserOnboardingMetricsService.create_user_onboarding_metric(
org_id=current_org.pk,
event=event,
payload=payload,
)
8 changes: 8 additions & 0 deletions codecov_auth/commands/owner/owner.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from .interactors.save_terms_agreement import SaveTermsAgreementInteractor
from .interactors.set_yaml_on_owner import SetYamlOnOwnerInteractor
from .interactors.start_trial import StartTrialInteractor
from .interactors.store_codecov_metric import StoreCodecovMetricInteractor
from .interactors.trigger_sync import TriggerSyncInteractor
from .interactors.update_default_organization import UpdateDefaultOrganizationInteractor
from .interactors.update_profile import UpdateProfileInteractor
Expand Down Expand Up @@ -86,3 +87,10 @@ def cancel_trial(self, org_username: str) -> None:

def update_self_hosted_settings(self, input) -> None:
return self.get_interactor(UpdateSelfHostedSettingsInteractor).execute(input)

def store_codecov_metric(
self, org_username: str, event: str, json_string: str
) -> None:
return self.get_interactor(StoreCodecovMetricInteractor).execute(
org_username, event, json_string
)
108 changes: 108 additions & 0 deletions graphql_api/tests/mutation/test_store_codecov_metrics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
from django.test import TransactionTestCase
from shared.django_apps.codecov_metrics.models import UserOnboardingLifeCycleMetrics

from codecov_auth.tests.factories import OwnerFactory
from graphql_api.tests.helper import GraphQLTestHelper

query = """
mutation($input: StoreEventMetricsInput!) {
storeEventMetric(input: $input) {
error {
__typename
... on ResolverError {
message
}
}
}
}
"""


class StoreEventMetricMutationTest(GraphQLTestHelper, TransactionTestCase):
def _request(self, org_username: str, event: str, json_payload: str, owner=None):
return self.gql_request(
query,
variables={
"input": {
"orgUsername": org_username,
"eventName": event,
"jsonPayload": json_payload,
}
},
owner=owner,
)

def setUp(self):
self.owner = OwnerFactory(username="codecov-user")

def test_unauthenticated(self):
response = self._request(
org_username="codecov-user",
event="VISITED_PAGE",
json_payload='{"key": "value"}',
)
assert response == {
"storeEventMetric": {
"error": {
"__typename": "UnauthenticatedError",
"message": "You are not authenticated",
}
}
}

def test_authenticated_inserts_into_db(self):
response = self._request(
org_username="codecov-user",
event="VISITED_PAGE",
json_payload='{"some-key": "some-value"}',
owner=self.owner,
)
metric = UserOnboardingLifeCycleMetrics.objects.filter(
event="VISITED_PAGE"
).first()
self.assertIsNotNone(metric)
self.assertEqual(metric.additional_data, {"some-key": "some-value"})

def test_invalid_org(self):
response = self._request(
org_username="invalid_org",
event="VISITED_PAGE",
json_payload='{"key": "value"}',
owner=self.owner,
)
assert response == {
"storeEventMetric": {
"error": {
"__typename": "ValidationError",
"message": "Cannot find owner record in the database",
}
}
}

def test_invalid_event(self):
self._request(
org_username="codecov-user",
event="INVALID_EVENT",
json_payload='{"key": "value"}',
owner=self.owner,
)
metric = UserOnboardingLifeCycleMetrics.objects.filter(
event="INVALID_EVENT"
).first()
self.assertIsNone(metric)

def test_invalid_json_string(self):
response = self._request(
org_username="codecov-user",
event="VISITED_PAGE",
json_payload="invalid-json",
owner=self.owner,
)
assert response == {
"storeEventMetric": {
"error": {
"__typename": "ValidationError",
"message": "Invalid JSON string",
}
}
}
2 changes: 2 additions & 0 deletions graphql_api/types/mutation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from .save_terms_agreement import gql_save_terms_agreement
from .set_yaml_on_owner import gql_set_yaml_on_owner
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_default_organization import gql_update_default_organization
from .update_profile import gql_update_profile
Expand Down Expand Up @@ -47,3 +48,4 @@
mutation = mutation + gql_update_repository
mutation = mutation + gql_update_self_hosted_settings
mutation = mutation + gql_regenerate_repository_upload_token
mutation = mutation + gql_store_event_metrics
1 change: 1 addition & 0 deletions graphql_api/types/mutation/mutation.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@ type Mutation {
updateRepository(input: UpdateRepositoryInput!): UpdateRepositoryPayload
updateSelfHostedSettings(input: UpdateSelfHostedSettingsInput!): UpdateSelfHostedSettingsPayload
regenerateRepositoryUploadToken(input: RegenerateRepositoryUploadTokenInput!): RegenerateRepositoryUploadTokenPayload
storeEventMetric(input: StoreEventMetricsInput!): StoreEventMetricsPayload
}
4 changes: 3 additions & 1 deletion graphql_api/types/mutation/mutation.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
)
from .set_yaml_on_owner import error_set_yaml_error, resolve_set_yaml_on_owner
from .start_trial import error_start_trial, resolve_start_trial
from .store_event_metrics import error_store_event_metrics, resolve_store_event_metrics
from .sync_with_git_provider import (
error_sync_with_git_provider,
resolve_sync_with_git_provider,
Expand Down Expand Up @@ -83,7 +84,7 @@
mutation_bindable.field("regenerateRepositoryUploadToken")(
resolve_regenerate_repository_upload_token
)

mutation_bindable.field("storeEventMetric")(resolve_store_event_metrics)

mutation_resolvers = [
mutation_bindable,
Expand All @@ -109,4 +110,5 @@
error_update_repository,
error_update_self_hosted_settings,
error_regenerate_repository_upload_token,
error_store_event_metrics,
]
7 changes: 7 additions & 0 deletions graphql_api/types/mutation/store_event_metrics/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from graphql_api.helpers.ariadne import ariadne_load_local_graphql

from .store_event_metrics import error_store_event_metrics, resolve_store_event_metrics

gql_store_event_metrics = ariadne_load_local_graphql(
__file__, "store_event_metrics.graphql"
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
union StoreEventMetricsError = UnauthenticatedError | ValidationError

type StoreEventMetricsPayload {
error: StoreEventMetricsError
}

input StoreEventMetricsInput {
orgUsername: String!
eventName: String!
jsonPayload: String # The input expects a serialized json string
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from ariadne import UnionType

from codecov_auth.commands.owner import OwnerCommands
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_store_event_metrics(_, info, input) -> None:
command: OwnerCommands = info.context["executor"].get_command("owner")
await command.store_codecov_metric(
input.get("orgUsername"), input.get("eventName"), input.get("jsonPayload")
)
return None


error_store_event_metrics = UnionType("StoreEventMetricsError")
error_store_event_metrics.type_resolver(resolve_union_error_type)
2 changes: 1 addition & 1 deletion requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ factory-boy
fakeredis
freezegun
https://github.com/codecov/opentelem-python/archive/refs/tags/v0.0.4a1.tar.gz#egg=codecovopentelem
https://github.com/codecov/shared/archive/850b28bfb3900a3d34fc347f81f7584d54e6dc24.tar.gz#egg=shared
https://github.com/codecov/shared/archive/ab7b235c720c9a018271768e5bb9745ed18b01f8.tar.gz#egg=shared
google-cloud-pubsub
gunicorn>=22.0.0
https://github.com/photocrowd/django-cursor-pagination/archive/f560902696b0c8509e4d95c10ba0d62700181d84.tar.gz
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ sentry-sdk[celery]==1.44.1
# shared
setproctitle==1.1.10
# via -r requirements.in
shared @ https://github.com/codecov/shared/archive/850b28bfb3900a3d34fc347f81f7584d54e6dc24.tar.gz
shared @ https://github.com/codecov/shared/archive/ab7b235c720c9a018271768e5bb9745ed18b01f8.tar.gz
# via -r requirements.in
simplejson==3.17.2
# via -r requirements.in
Expand Down

0 comments on commit 1787236

Please sign in to comment.