Skip to content
This repository was archived by the owner on Jun 13, 2025. It is now read-only.

Commit ff910f7

Browse files
authored
Merge branch 'main' into oct_11_bam
2 parents 16ad8e0 + 3c2b4ff commit ff910f7

File tree

8 files changed

+141
-26
lines changed

8 files changed

+141
-26
lines changed

.github/workflows/ci.yml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,19 @@ permissions:
1919
jobs:
2020
lint:
2121
name: Run Lint
22-
uses: codecov/gha-workflows/.github/workflows/[email protected].23
22+
uses: codecov/gha-workflows/.github/workflows/[email protected].24
2323

2424
build:
2525
name: Build API
26-
uses: codecov/gha-workflows/.github/workflows/[email protected].23
26+
uses: codecov/gha-workflows/.github/workflows/[email protected].24
2727
secrets: inherit
2828
with:
2929
repo: ${{ vars.CODECOV_IMAGE_V2 || 'codecov/self-hosted-api' }}
3030

3131
codecovstartup:
3232
name: Codecov Startup
3333
needs: build
34-
uses: codecov/gha-workflows/.github/workflows/[email protected].23
34+
uses: codecov/gha-workflows/.github/workflows/[email protected].24
3535
secrets: inherit
3636

3737
# ats:
@@ -47,15 +47,15 @@ jobs:
4747
test:
4848
name: Test
4949
needs: [build]
50-
uses: codecov/gha-workflows/.github/workflows/[email protected].23
50+
uses: codecov/gha-workflows/.github/workflows/[email protected].24
5151
secrets: inherit
5252
with:
5353
repo: ${{ vars.CODECOV_IMAGE_V2 || 'codecov/self-hosted-api' }}
5454

5555
build-self-hosted:
5656
name: Build Self Hosted API
5757
needs: [build, test]
58-
uses: codecov/gha-workflows/.github/workflows/[email protected].23
58+
uses: codecov/gha-workflows/.github/workflows/[email protected].24
5959
secrets: inherit
6060
with:
6161
repo: ${{ vars.CODECOV_IMAGE_V2 || 'codecov/self-hosted-api' }}
@@ -64,7 +64,7 @@ jobs:
6464
name: Push Staging Image
6565
needs: [build, test]
6666
if: ${{ github.event_name == 'push' && github.event.ref == 'refs/heads/staging' && github.repository_owner == 'codecov' }}
67-
uses: codecov/gha-workflows/.github/workflows/[email protected].23
67+
uses: codecov/gha-workflows/.github/workflows/[email protected].24
6868
secrets: inherit
6969
with:
7070
environment: staging
@@ -74,7 +74,7 @@ jobs:
7474
name: Push Production Image
7575
needs: [build, test]
7676
if: ${{ github.event_name == 'push' && github.event.ref == 'refs/heads/main' && github.repository_owner == 'codecov' }}
77-
uses: codecov/gha-workflows/.github/workflows/[email protected].23
77+
uses: codecov/gha-workflows/.github/workflows/[email protected].24
7878
secrets: inherit
7979
with:
8080
environment: production
@@ -85,7 +85,7 @@ jobs:
8585
needs: [build-self-hosted, test]
8686
secrets: inherit
8787
if: ${{ github.event_name == 'push' && github.event.ref == 'refs/heads/main' && github.repository_owner == 'codecov' }}
88-
uses: codecov/gha-workflows/.github/workflows/[email protected].23
88+
uses: codecov/gha-workflows/.github/workflows/[email protected].24
8989
with:
9090
push_rolling: true
9191
repo: ${{ vars.CODECOV_IMAGE_V2 || 'codecov/self-hosted-api' }}

codecov_auth/commands/owner/interactors/save_terms_agreement.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,11 @@ class SaveTermsAgreementInteractor(BaseInteractor):
2121
requires_service = False
2222

2323
def validate(self, input: TermsAgreementInput):
24-
if input.terms_agreement is None:
25-
raise ValidationError("Terms of agreement cannot be null")
26-
if input.customer_intent and input.customer_intent not in [
27-
"Business",
28-
"BUSINESS",
29-
"Personal",
30-
"PERSONAL",
31-
]:
24+
valid_customer_intents = ["Business", "BUSINESS", "Personal", "PERSONAL"]
25+
if (
26+
input.customer_intent
27+
and input.customer_intent not in valid_customer_intents
28+
):
3229
raise ValidationError("Invalid customer intent provided")
3330
if not self.current_user.is_authenticated:
3431
raise Unauthenticated()
@@ -37,13 +34,15 @@ def update_terms_agreement(self, input: TermsAgreementInput):
3734
self.current_user.terms_agreement = input.terms_agreement
3835
self.current_user.terms_agreement_at = timezone.now()
3936
self.current_user.customer_intent = input.customer_intent
37+
self.current_user.email_opt_in = input.marketing_consent
4038
self.current_user.save()
4139

42-
if input.business_email is not None and input.business_email != "":
40+
if input.business_email and input.business_email != "":
4341
self.current_user.email = input.business_email
4442
self.current_user.save()
4543

46-
self.send_data_to_marketo()
44+
if input.marketing_consent:
45+
self.send_data_to_marketo()
4746

4847
def send_data_to_marketo(self):
4948
event_data = {

codecov_auth/commands/owner/interactors/tests/test_save_terms_agreement.py

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from unittest.mock import patch
2+
13
import pytest
24
from asgiref.sync import async_to_sync
35
from django.contrib.auth.models import AnonymousUser
@@ -24,6 +26,7 @@ def execute(
2426
input={
2527
"business_email": None,
2628
"terms_agreement": False,
29+
"marketing_consent": False,
2730
},
2831
):
2932
return SaveTermsAgreementInteractor(None, "github", current_user).execute(
@@ -75,13 +78,6 @@ def test_update_owner_and_user_when_email_is_not_empty(self):
7578
self.current_user.refresh_from_db()
7679
assert self.current_user.email == "[email protected]"
7780

78-
def test_validation_error_when_terms_is_none(self):
79-
with pytest.raises(ValidationError):
80-
self.execute(
81-
current_user=self.current_user,
82-
input={"terms_agreement": None, "customer_intent": "Business"},
83-
)
84-
8581
def test_validation_error_when_customer_intent_invalid(self):
8682
with pytest.raises(ValidationError):
8783
self.execute(
@@ -99,3 +95,45 @@ def test_user_is_not_authenticated(self):
9995
"customer_intent": "Business",
10096
},
10197
)
98+
99+
def test_email_opt_in_saved_in_db(self):
100+
self.execute(
101+
current_user=self.current_user,
102+
input={
103+
"terms_agreement": True,
104+
"marketing_consent": True,
105+
"customer_intent": "Business",
106+
},
107+
)
108+
self.current_user.refresh_from_db()
109+
assert self.current_user.email_opt_in == True
110+
111+
@patch(
112+
"codecov_auth.commands.owner.interactors.save_terms_agreement.SaveTermsAgreementInteractor.send_data_to_marketo"
113+
)
114+
def test_marketo_called_only_with_consent(self, mock_send_data_to_marketo):
115+
self.execute(
116+
current_user=self.current_user,
117+
input={
118+
"terms_agreement": True,
119+
"marketing_consent": True,
120+
"customer_intent": "Business",
121+
},
122+
)
123+
124+
mock_send_data_to_marketo.assert_called_once()
125+
126+
@patch(
127+
"codecov_auth.commands.owner.interactors.save_terms_agreement.SaveTermsAgreementInteractor.send_data_to_marketo"
128+
)
129+
def test_marketo_not_called_without_consent(self, mock_send_data_to_marketo):
130+
self.execute(
131+
current_user=self.current_user,
132+
input={
133+
"terms_agreement": True,
134+
"marketing_consent": False,
135+
"customer_intent": "Business",
136+
},
137+
)
138+
139+
mock_send_data_to_marketo.assert_not_called()

graphql_api/tests/test_test_result.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,15 @@ def setUp(self):
3131
date=date.today() - timedelta(days=2),
3232
avg_duration_seconds=0.6,
3333
latest_run=datetime.now() - timedelta(days=2),
34+
flaky_fail_count=0,
3435
)
3536
_ = DailyTestRollupFactory(
3637
test=self.test,
3738
commits_where_fail=["123", "456"],
3839
date=datetime.now() - timedelta(days=1),
3940
avg_duration_seconds=2,
4041
latest_run=datetime.now() - timedelta(days=1),
42+
flaky_fail_count=1,
4143
)
4244
_ = DailyTestRollupFactory(
4345
test=self.test,
@@ -46,6 +48,7 @@ def setUp(self):
4648
last_duration_seconds=5.0,
4749
avg_duration_seconds=3,
4850
latest_run=datetime.now(),
51+
flaky_fail_count=1,
4952
)
5053

5154
def test_fetch_test_result_name(self) -> None:
@@ -76,6 +79,40 @@ def test_fetch_test_result_name(self) -> None:
7679
0
7780
]["node"]["name"] == self.test.name.replace("\x1f", " ")
7881

82+
def test_fetch_test_result_name_with_computed_name(self) -> None:
83+
self.test.computed_name = "Computed Name"
84+
self.test.save()
85+
86+
query = """
87+
query {
88+
owner(username: "%s") {
89+
repository(name: "%s") {
90+
... on Repository {
91+
testAnalytics {
92+
testResults {
93+
edges {
94+
node {
95+
name
96+
}
97+
}
98+
}
99+
}
100+
}
101+
}
102+
}
103+
}
104+
""" % (self.owner.username, self.repository.name)
105+
106+
result = self.gql_request(query, owner=self.owner)
107+
108+
assert "errors" not in result
109+
assert (
110+
result["owner"]["repository"]["testAnalytics"]["testResults"]["edges"][0][
111+
"node"
112+
]["name"]
113+
== self.test.computed_name
114+
)
115+
79116
def test_fetch_test_result_updated_at(self) -> None:
80117
query = """
81118
query {
@@ -320,3 +357,34 @@ def test_fetch_test_result_total_pass_count(self) -> None:
320357
]["totalPassCount"]
321358
== 3
322359
)
360+
361+
def test_fetch_test_result_total_flaky_fail_count(self) -> None:
362+
query = """
363+
query {
364+
owner(username: "%s") {
365+
repository(name: "%s") {
366+
... on Repository {
367+
testAnalytics {
368+
testResults {
369+
edges {
370+
node {
371+
totalFlakyFailCount
372+
}
373+
}
374+
}
375+
}
376+
}
377+
}
378+
}
379+
}
380+
""" % (self.owner.username, self.repository.name)
381+
382+
result = self.gql_request(query, owner=self.owner)
383+
384+
assert "errors" not in result
385+
assert (
386+
result["owner"]["repository"]["testAnalytics"]["testResults"]["edges"][0][
387+
"node"
388+
]["totalFlakyFailCount"]
389+
== 2
390+
)

graphql_api/types/test_results/test_results.graphql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ type TestResult {
77
avgDuration: Float!
88
lastDuration: Float!
99
totalFailCount: Int!
10+
totalFlakyFailCount: Int!
1011
totalSkipCount: Int!
1112
totalPassCount: Int!
1213
}

graphql_api/types/test_results/test_results.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,18 @@ class TestDict(TypedDict):
1414
last_duration: float
1515
flake_rate: float
1616
total_fail_count: int
17+
total_flaky_fail_count: int
1718
total_skip_count: int
1819
total_pass_count: int
20+
computed_name: str | None
1921

2022

2123
test_result_bindable = ObjectType("TestResult")
2224

2325

2426
@test_result_bindable.field("name")
2527
def resolve_name(test: TestDict, _: GraphQLResolveInfo) -> str:
26-
return test["name"].replace("\x1f", " ")
28+
return test.get("computed_name") or test["name"].replace("\x1f", " ")
2729

2830

2931
@test_result_bindable.field("updatedAt")
@@ -61,6 +63,11 @@ def resolve_total_fail_count(test: TestDict, _: GraphQLResolveInfo) -> int:
6163
return test["total_fail_count"]
6264

6365

66+
@test_result_bindable.field("totalFlakyFailCount")
67+
def resolve_total_flaky_fail_count(test: TestDict, _: GraphQLResolveInfo) -> int:
68+
return test["total_flaky_fail_count"]
69+
70+
6471
@test_result_bindable.field("totalSkipCount")
6572
def resolve_total_skip_count(test: TestDict, _: GraphQLResolveInfo) -> int:
6673
return test["total_skip_count"]

reports/tests/factories.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ class Meta:
107107
name = factory.Sequence(lambda n: f"{n}")
108108
repository = factory.SubFactory(RepositoryFactory)
109109
commits_where_fail = []
110+
computed_name = None
110111

111112

112113
class TestInstanceFactory(factory.django.DjangoModelFactory):

utils/test_results.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ def generate_test_results(
197197
last_duration=Value(0.0),
198198
avg_duration=Avg("avg_duration_seconds"),
199199
name=F("test__name"),
200+
computed_name=F("test__computed_name"),
200201
)
201202

202203
return aggregation_of_test_results

0 commit comments

Comments
 (0)