Skip to content

Commit

Permalink
Add message for users to install Codecov app if they're using public …
Browse files Browse the repository at this point in the history
…commenter (#516)
  • Loading branch information
michelletran-codecov authored Jun 28, 2024
1 parent 646d815 commit aceebad
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 14 deletions.
5 changes: 3 additions & 2 deletions services/bots/public_bots.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,14 @@ def get_token_type_mapping(

if admin_bot_token is None:
log.warning(
"No admin_bog_token provided, but still continuing operations in case it is not doing an admin call anyway",
"No admin_bot_token provided, but still continuing operations in case it is not doing an admin call anyway",
extra=dict(repoid=repo.repoid),
)

mapping = {
TokenType.admin: admin_bot_token,
# [GitHub] Only legacy Personal Access Tokens (PAT) can post statuses and comment to all public repos, so there can't be a dedicated_app for this
# [GitHub] Only legacy Personal Access Tokens (PAT) can post statuses and comment to all public repos,
# so there can't be a dedicated_app for this
TokenType.comment: get_config(repo.service, "bots", "comment"),
TokenType.status: admin_bot_token or get_config(repo.service, "bots", "status"),
}
Expand Down
1 change: 1 addition & 0 deletions services/comparison/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class ComparisonContext(object):
all_tests_passed: bool | None = None
test_results_error: TestResultsProcessingError | None = None
gh_app_installation_name: str | None = None
gh_is_using_codecov_commenter: bool = False


class ComparisonProxy(object):
Expand Down
41 changes: 30 additions & 11 deletions services/notification/notifiers/mixins/message/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import logging
from typing import List
from typing import Callable, List

from shared.reports.resources import Report, ReportTotals
from shared.django_apps.core.models import Repository
from shared.reports.resources import ReportTotals
from shared.validation.helpers import LayoutStructure

from database.models.core import Owner
from helpers.environment import is_enterprise
from helpers.metrics import metrics
from services.billing import BillingPlan
from services.comparison import ComparisonProxy
Expand Down Expand Up @@ -69,9 +71,14 @@ async def create_message(
or (head_report.totals if head_report else ReportTotals()).complexity
)

message = [
f'## [Codecov]({links["pull"]}?dropdown=coverage&src=pr&el=h1) Report',
]
message = []
# note: since we're using append, calling write("") will add a newline to the message
write = message.append

await self._possibly_write_install_app(comparison, write)

# Write Header
write(f'## [Codecov]({links["pull"]}?dropdown=coverage&src=pr&el=h1) Report')

repo = comparison.head.commit.repository
owner: Owner = repo.owner
Expand All @@ -90,9 +97,6 @@ async def create_message(
current_yaml=current_yaml,
)

write = message.append
# note: since we're using append, calling write("") will add a newline to the message

upper_section_names = self.get_upper_section_names(settings)
# We write the header and then the messages_to_user section
upper_section_names.append("messages_to_user")
Expand All @@ -114,9 +118,6 @@ async def create_message(

is_compact_message = should_message_be_compact(comparison, settings)

if base_report is None:
base_report = Report()

if head_report:
if is_compact_message:
write(
Expand Down Expand Up @@ -180,6 +181,24 @@ async def create_message(

return [m for m in message if m is not None]

async def _possibly_write_install_app(
self, comparison: ComparisonProxy, write: Callable
) -> None:
"""Write a message if the user does not have any GH installations
and will be writing with a Codecov Commenter Account.
"""
repo: Repository = comparison.head.commit.repository
repo_owner: Owner = repo.owner
if (
repo_owner.service == "github"
and not is_enterprise()
and repo_owner.github_app_installations == []
and comparison.context.gh_is_using_codecov_commenter
):
message_to_display = ":warning: Please install the !['codecov app svg image'](https://github.com/codecov/engineering-team/assets/152432831/e90313f4-9d3a-4b63-8b54-cfe14e7ec20d) to ensure uploads and comments are reliably processed by Codecov."
write(message_to_display)
write("")

def _team_plan_notification(
self,
comparison: ComparisonProxy,
Expand Down
26 changes: 26 additions & 0 deletions services/notification/notifiers/tests/unit/test_comment.py
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,32 @@ async def test_create_message_files_section_with_critical_files(
)
assert mocked_search_files_for_critical_changes.call_count == 2

@pytest.mark.asyncio
async def test_create_message_with_github_app_comment(
self,
dbsession,
mock_configuration,
mock_repo_provider,
sample_comparison,
mocker,
):
comparison = sample_comparison
comparison.context = ComparisonContext(gh_is_using_codecov_commenter=True)
notifier = CommentNotifier(
repository=sample_comparison.head.commit.repository,
title="title",
notifier_yaml_settings={
"layout": "files,betaprofiling",
},
notifier_site_settings=True,
current_yaml={},
)
res = await notifier.build_message(comparison)
assert (
res[0]
== ":warning: Please install the !['codecov app svg image'](https://github.com/codecov/engineering-team/assets/152432831/e90313f4-9d3a-4b63-8b54-cfe14e7ec20d) to ensure uploads and comments are reliably processed by Codecov."
)

@pytest.mark.asyncio
async def test_build_message(
self, dbsession, mock_configuration, mock_repo_provider, sample_comparison
Expand Down
24 changes: 23 additions & 1 deletion tasks/notify.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
notify_task_name,
status_set_error_task_name,
)
from shared.config import get_config
from shared.reports.readonly import ReadOnlyReport
from shared.torngit.base import TorngitBaseAdapter
from shared.torngit.base import TokenType, TorngitBaseAdapter
from shared.torngit.exceptions import TorngitClientError, TorngitServerFailureError
from shared.yaml import UserYaml
from sqlalchemy.orm.session import Session
Expand Down Expand Up @@ -377,6 +378,9 @@ def run_impl_within_lock(
and test_result_commit_report.test_result_totals.error
),
installation_name_to_use=installation_name_to_use,
gh_is_using_codecov_commenter=self.is_using_codecov_commenter(
repository_service
),
)
self.log_checkpoint(kwargs, UploadFlow.NOTIFIED)
log.info(
Expand All @@ -400,6 +404,22 @@ def run_impl_within_lock(
)
return {"notified": False, "notifications": None}

def is_using_codecov_commenter(
self, repository_service: TorngitBaseAdapter
) -> bool:
"""Returns a boolean indicating if the message will be sent by codecov-commenter.
If the user doesn't have an installation, and if the token type for the repo is codecov-commenter,
then it's likely that they're using the commenter bot.
"""
commenter_bot_token = get_config(repository_service.service, "bots", "comment")
return (
repository_service.service == "github"
and repository_service.data.get("installation") is None
and commenter_bot_token is not None
and repository_service.get_token_by_type(TokenType.comment)
== commenter_bot_token
)

def _possibly_refresh_previous_selection(self, commit: Commit) -> bool:
installation_cached: str = get_github_app_for_commit(commit)
app_id_used_in_successful_comment: int = next(
Expand Down Expand Up @@ -484,6 +504,7 @@ def submit_third_party_notifications(
all_tests_passed: bool = False,
test_results_error: bool = False,
installation_name_to_use: str = GITHUB_APP_INSTALLATION_DEFAULT_NAME,
gh_is_using_codecov_commenter: bool = False,
):
# base_commit is an "adjusted" base commit; for project coverage, we
# compare a PR head's report against its base's report, or if the base
Expand Down Expand Up @@ -516,6 +537,7 @@ def submit_third_party_notifications(
all_tests_passed=all_tests_passed,
test_results_error=test_results_error,
gh_app_installation_name=installation_name_to_use,
gh_is_using_codecov_commenter=gh_is_using_codecov_commenter,
),
)

Expand Down
60 changes: 60 additions & 0 deletions tasks/tests/unit/test_notify_task.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import json
from typing import Any
from unittest.mock import MagicMock, call

import pytest
from celery.exceptions import MaxRetriesExceededError, Retry
from freezegun import freeze_time
from shared.celery_config import new_user_activated_task_name
from shared.reports.resources import Report
from shared.torngit.base import TorngitBaseAdapter
from shared.torngit.exceptions import (
TorngitClientGeneralError,
TorngitServer5xxCodeError,
)
from shared.typings.oauth_token_types import Token
from shared.typings.torngit import GithubInstallationInfo, TorngitInstanceData
from shared.yaml import UserYaml

Expand Down Expand Up @@ -1084,3 +1087,60 @@ def test_checkpoints_not_logged_outside_upload_flow(
current_yaml={"coverage": {"status": {"patch": True}}},
)
assert not mock_checkpoint_submit.called

@pytest.mark.parametrize(
"service,data,bot_token,expected_response",
[
pytest.param(
"github",
{},
Token(username="test-codecov-commenter-bot"),
True,
id="expected_to_be_true",
),
pytest.param(
"bitbucket",
{},
Token(username="test-codecov-commenter-bot"),
False,
id="not_github",
),
pytest.param(
"github",
{"installation": GithubInstallationInfo(installation_id="some_id")},
Token(username="test-codecov-commenter-bot"),
False,
id="has_installation",
),
pytest.param(
"github",
{"installation": GithubInstallationInfo(installation_id="some_id")},
None,
False,
id="not_using_commenter_bot",
),
],
)
def test_is_using_codecov_commenter(
self,
mocker,
mock_configuration: dict[str, Any],
service: str,
data: dict[str, Any],
bot_token: Token | None,
expected_response,
) -> None:
mock_repository_service: Any = mocker.MagicMock(spec=TorngitBaseAdapter)
mock_repository_service.data = data
mock_repository_service.service = service
mock_repository_service.get_token_by_type.return_value = bot_token

mock_configuration.params["github"] = {
"bots": {"comment": {"username": "test-codecov-commenter-bot"}}
}

task = NotifyTask()
assert (
task.is_using_codecov_commenter(mock_repository_service)
== expected_response
)

0 comments on commit aceebad

Please sign in to comment.