-
Notifications
You must be signed in to change notification settings - Fork 429
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
feat: Add automatic tagging for github integration #4028
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎ 3 Skipped Deployments
|
Uffizzi Preview |
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #4028 +/- ##
==========================================
+ Coverage 96.88% 96.91% +0.02%
==========================================
Files 1172 1176 +4
Lines 39035 39345 +310
==========================================
+ Hits 37820 38131 +311
+ Misses 1215 1214 -1 ☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A lot of comments but nothing I don't trust that you'll handle.
api/conftest.py
Outdated
from tests.unit.features.test_unit_feature_external_resources_views import ( | ||
mocked_requests_post, | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think mocked_requests_post
should be converted into a fixture and added to a conftest.py
instead of importing it like this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Converted into a fixture in conftest.py
api/conftest.py
Outdated
) | ||
mock_generate_token.return_value = "mocked_token" | ||
|
||
mocker.patch("requests.post", side_effect=mocked_requests_post) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where is mocked_requests_post
coming from?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Converted into a fixture in conftest
tag_by_event_type = { | ||
"pull_request": { | ||
"closed": GitHubTag.PR_CLOSED.value, | ||
"converted_to_draft": GitHubTag.PR_DRAFT.value, | ||
"opened": GitHubTag.PR_OPEN.value, | ||
"reopened": GitHubTag.PR_OPEN.value, | ||
"ready_for_review": GitHubTag.PR_OPEN.value, | ||
"merged": GitHubTag.PR_MERGED.value, | ||
}, | ||
"issues": { | ||
"closed": GitHubTag.ISSUE_CLOSED.value, | ||
"opened": GitHubTag.ISSUE_OPEN.value, | ||
"reopened": GitHubTag.ISSUE_OPEN.value, | ||
}, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This section should probably live in the constants.py
module.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was tempted to put it in constants, however, it would lose context and require extra comments since it's used only in the context of the github.py file. i.e. the event_type in its name refers to a webhook request event type.
api/integrations/github/github.py
Outdated
def handle_installation_deleted(payload: dict[str, typing.Any]) -> None: | ||
installation_id = payload.get("installation", {}).get("id") | ||
if installation_id: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really want to fail without an exception if installation_id
is None
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! At least initially we want an unhandled exception in case of these unexpected cases. GitHub should never send an "installation.delete" event without an installation ID, if that happens we want the alerts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
if feature: | ||
if ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would return early here.
if not feature:
return
if ( #....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case, it's the same, right? I will keep this since it is a positive condition, it is returning early anyway, and the code is readable in this way.
from django.db import migrations | ||
|
||
|
||
class Migration(migrations.Migration): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should be able to combine this migration with the other one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right. Done.
from integrations.github.constants import ( | ||
GitHubTag, | ||
github_tag_description, | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure you need to inline import these constants? Surely they would be topline imports since there should be no cyclical import issues.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. I have a circular import issue otherwise.
|
||
print(feature_external_resource) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you want to remove this print statement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed
url = reverse("api-v1:github-webhook") | ||
|
||
# When | ||
client = APIClient() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not use the api_client
fixture instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
# def test_handle_installation_deleted_with_non_existing_installation(mocker: MockerFixture): | ||
# payload = {"installation": {"id": 456}} | ||
# handle_installation_deleted(payload) | ||
# mock_logger.error.assert_called_once_with( | ||
# "Github Configuration with installation_id 456 does not exist" | ||
# ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extra commented out code. Should be deleted, yes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops. Thanks! Deleted
918207d
to
856e670
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approved with some comments that may be helpful
feature.tags.remove( | ||
*feature.tags.filter( | ||
Q(type=TagType.GITHUB.value) & Q(label__startswith=tag_label_pattern) | ||
) | ||
) | ||
|
||
feature.tags.add(github_tag) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to call save()
when removing or adding the tags?
assert ( | ||
response.json()["non_field_errors"][0] | ||
== "The fields feature, url must make a unique set." | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand why the fields feature and url do not make a unique set. Does the test set these as duplicates elsewhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is because already exists in this fixture:
this:
Line 1044 in e9e1e59
def feature_external_resource_gh_pr( |
feature_external_resource: FeatureExternalResource, | ||
) -> None: | ||
# Given | ||
settings.GITHUB_WEBHOOK_SECRET = WEBHOOK_SECRET |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If settings
is being set like this it is better pulling it into the test as a fixture.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added
mocker_logger.error.assert_called_once_with( | ||
"GitHub Configuration with installation_id 765432 does not exist" | ||
"The installation_id is not present in the payload: {'installation': {'test': 765432}, 'action': 'deleted'}" | ||
) | ||
assert response.status_code == status.HTTP_200_OK |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this error really return an HTTP 200 response?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case, 200 is enough.
mocker_logger.error.assert_called_once_with( | ||
"The installation_id is not present in the payload: {'installation': {'test': 765432}, 'action': 'deleted'}" | ||
"GitHub Configuration with installation_id 765432 does not exist" | ||
) | ||
assert response.status_code == status.HTTP_200_OK |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same idea with this error, should these really be 200 responses?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case, 200 is enough.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added
07292e4
to
42d2ade
Compare
42d2ade
to
e9e1e59
Compare
Thanks for submitting a PR! Please check the boxes below:
pre-commit
to check lintingdocs/
if required so people know about the feature!Changes
When a project is lilnked to a GitHub repository, the following tags are added to the project:
The tags added are:
PR Open
PR Merged
PR Closed
PR Draft
Issue Open
Issue Closed
Add automatic tagging of features linked to GitHub resources. The tags are added on response to events in Flagsmith, like linking/unlinking PR/Issues, and also in response to events in GitHub like closing/reopening/marking as draft/merging PR/Issues. Each feature can have only one active PR tag and one active Issue tag at any time.
How did you test this code?
Unit tests added.
For local manual testing, the corresponding FE PR is #4035
It would be necessary to configure a local dev environment with these two branches (BE/FE) for testing until this BE PR is merged.
NOTE
For the case of multiple PRs/Issues linked to the same feature, we should consider tagging the feature as closed/merged only when all the linked Issues/PRs are closed/merged correspondingly.