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

Send event to DD on merged PR #30627

Merged
merged 11 commits into from
Nov 4, 2024
33 changes: 33 additions & 0 deletions .github/workflows/report-merged-pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Report PR merged event to Datadog

name: Report Merged PR

on:
pull_request:
types: closed

permissions: {}

jobs:
if_merged:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
with:
fetch-depth: 0
pducolin marked this conversation as resolved.
Show resolved Hide resolved

- name: Setup Python3
uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0
with:
python-version: "3.12.6"
cache: "pip"
cache-dependency-path: '**/requirements*.txt'

pducolin marked this conversation as resolved.
Show resolved Hide resolved
- name: Install python dependencies
run: pip3 install -r requirements.txt

- name: Send merge event to Datadog
run: |
invoke -e github.pr-merge-dd-event-sender -p ${{ github.event.pull_request.number }}
72 changes: 71 additions & 1 deletion tasks/github_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
trigger_macos_workflow,
)
from tasks.libs.common.constants import DEFAULT_BRANCH, DEFAULT_INTEGRATIONS_CORE_BRANCH
from tasks.libs.common.datadog_api import create_gauge, send_metrics
from tasks.libs.common.datadog_api import create_gauge, send_event, send_metrics
from tasks.libs.common.junit_upload_core import repack_macos_junit_tar
from tasks.libs.common.utils import get_git_pretty_ref
from tasks.libs.owners.linter import codeowner_has_orphans, directory_has_packages_without_owner
Expand Down Expand Up @@ -409,3 +409,73 @@ def pr_commenter(

if verbose:
print(f"{action} comment on PR #{pr.number} - {pr.title}")


@task
def pr_merge_dd_event_sender(
_,
pr_id: int | None = None,
dry_run: bool = False,
):
"""
Sends a PR merged event to Datadog with the following tags:
- repo:datadog-agent
- pr:<pr_number>
- author:<pr_author>
- qa_label:missing if the PR doesn't have the qa/done or qa/no-code-change label
- qa_description:missing if the PR doesn't have a test/QA description section

- pr_id: If None, will use $CI_COMMIT_BRANCH to identify which PR
"""

from tasks.libs.ciproviders.github_api import GithubAPI

github = GithubAPI()

if pr_id is None:
branch = os.environ["CI_COMMIT_BRANCH"]
prs = list(github.get_pr_for_branch(branch))
assert len(prs) == 1, f"Expected 1 PR for branch {branch}, found {len(prs)} PRs"
pr = prs[0]
else:
pr = github.get_pr(int(pr_id))

if not pr.merged:
raise Exit(f"PR {pr.number} is not merged yet", code=1)

tags = ['repo:datadog-agent', f'pr:{pr.number}', f'author:{pr.user.login}']
labels = set(github.get_pr_labels(pr.number))
if labels.isdisjoint({'qa/done', 'qa/no-code-change'}):
tags.append('qa_label:missing')

qa_description = extract_test_qa_description(pr.body)
if qa_description.strip() == '':
tags.append('qa_description:missing')

tags.extend([f"team:{label.removeprefix('team/')}" for label in labels if label.startswith('team/')])
title = "PR merged"
text = f"PR {pr.number} merged to {pr.base.ref} at {pr.base.repo.full_name}"

if dry_run:
print(f'''I would send the following event to Datadog:

title: {title}
text: {text}
tags: {tags}''')
return

send_event(
title="PR merged",
text=f"PR {pr.number} has been merged to {pr.repository.full_name}",
tags=tags,
)


def extract_test_qa_description(pr_body: str) -> str:
"""
Extract the test/QA description section from the PR body
"""
# capture test/QA description section content from PR body
# see example for this regex https://regexr.com/87mm2
result = re.search(r'^###\ Describe\ how\ to\ test.*\n([^#]*)\n###', pr_body, flags=re.MULTILINE)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ question: Should this take into account such case:

### Describe how to test
Some description

#### Step 1
...

#### Step 2
...

### ...

In this case, only Some description is matched and not Step 1...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Fixing this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I switched to plain string parsing, it makes reading the code more comfortable and easier to implement

return result.group(1) if result else ''
29 changes: 29 additions & 0 deletions tasks/libs/common/datadog_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,32 @@ def send_metrics(series):
raise Exit(code=1)

return response


def send_event(title: str, text: str, tags: list[str] = None):
"""
Post an event returns "OK" response
"""

from datadog_api_client import ApiClient, Configuration
from datadog_api_client.v1.api.events_api import EventsApi
from datadog_api_client.v1.model.event_create_request import EventCreateRequest

body = EventCreateRequest(
title=title,
text=text,
tags=tags or [],
)

configuration = Configuration()
with ApiClient(configuration) as api_client:
api_instance = EventsApi(api_client)
response = api_instance.create_event(body=body)

if response["errors"]:
print(
f"Error(s) while sending pipeline event to the Datadog backend: {response['errors']}", file=sys.stderr
)
raise Exit(code=1)

return response
47 changes: 46 additions & 1 deletion tasks/unit_tests/github_tasks_tests.py
pducolin marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from invoke.context import Context

import tasks
from tasks.github_tasks import assign_team_label
from tasks.github_tasks import assign_team_label, extract_test_qa_description


class GithubAPIMock:
Expand Down Expand Up @@ -112,3 +112,48 @@ def test_invalid_team_label(self):
expected_labels = []

self.make_test(changed_files, expected_labels, possible_labels=['team/team-doc'])


class TestExtractQADescriptionFromPR(unittest.TestCase):
pducolin marked this conversation as resolved.
Show resolved Hide resolved
def test_single_line_description(self):
body = """### What does this PR do?

### Motivation

### Describe how to test/QA your changes
I added one test
### Possible Drawbacks / Trade-offs

### Additional Notes
"""
qa_description = extract_test_qa_description(body)
self.assertEqual(qa_description, "I added one test")

def test_multi_line_description(self):
body = """### What does this PR do?

### Motivation

### Describe how to test/QA your changes
I added one unit test
and one e2e test
### Possible Drawbacks / Trade-offs

### Additional Notes
"""
qa_description = extract_test_qa_description(body)
self.assertEqual(qa_description, "I added one unit test\nand one e2e test")

def test_empty_description(self):
body = """### What does this PR do?

### Motivation

### Describe how to test/QA your changes

### Possible Drawbacks / Trade-offs

### Additional Notes
"""
qa_description = extract_test_qa_description(body)
self.assertEqual(qa_description, "")
Loading