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

fix: Move call to GitHub integration tasks out from trigger_feature_state_change_webhooks #3905

36 changes: 36 additions & 0 deletions api/features/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import typing
import uuid
from copy import deepcopy
from dataclasses import asdict

from core.models import (
AbstractBaseExportableModel,
Expand All @@ -22,6 +23,7 @@
from django.utils.translation import ugettext_lazy as _
from django_lifecycle import (
AFTER_CREATE,
AFTER_SAVE,
BEFORE_CREATE,
BEFORE_SAVE,
LifecycleModelMixin,
Expand Down Expand Up @@ -72,6 +74,7 @@
STRING,
)
from features.versioning.models import EnvironmentFeatureVersion
from integrations.github.models import GithubConfiguration
from projects.models import Project
from projects.tags.models import Tag

Expand Down Expand Up @@ -989,6 +992,39 @@ def copy_identity_feature_states(
# Save changes to target feature_state
target_feature_state.save()

@hook(AFTER_SAVE)
def create_github_comment(self) -> None:
from integrations.github.github import GithubData, generate_data
from integrations.github.tasks import (
call_github_app_webhook_for_feature_state,
)
from webhooks.webhooks import WebhookEventType

if (
not self.identity_id
and not self.feature_segment
and self.feature.external_resources.exists()
and self.environment.project.github_project.exists()
and self.environment.project.organisation.github_config.exists()
):
github_configuration = GithubConfiguration.objects.get(
organisation_id=self.environment.project.organisation_id
)
feature_states = []
feature_states.append(self)

feature_data: GithubData = generate_data(
github_configuration=github_configuration,
feature_id=self.feature.id,
feature_name=self.feature.name,
type=WebhookEventType.FLAG_UPDATED.value,
feature_states=feature_states,
)

call_github_app_webhook_for_feature_state.delay(
args=(asdict(feature_data),),
)


class FeatureStateValue(
AbstractBaseFeatureValueModel,
Expand Down
37 changes: 0 additions & 37 deletions api/features/tasks.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import logging
from dataclasses import asdict

from environments.models import Webhook
from features.models import Feature, FeatureState
from integrations.github.github import GithubData, generate_data
from integrations.github.tasks import call_github_app_webhook_for_feature_state
from organisations.models import Organisation
from task_processor.decorators import register_task_handler
from webhooks.constants import WEBHOOK_DATETIME_FORMAT
from webhooks.webhooks import (
Expand Down Expand Up @@ -59,39 +55,6 @@ def trigger_feature_state_change_webhooks(
)
)

if (
not instance.identity_id
and not instance.feature_segment
and instance.feature.external_resources.exists()
and instance.environment.project.github_project.exists()
and hasattr(instance.environment.project.organisation, "github_config")
):
github_configuration = (
Organisation.objects.prefetch_related("github_config")
.get(id=instance.environment.project.organisationn_id)
.github_config.first()
)
feature_state = {
"environment_name": new_state["environment"]["name"],
"feature_value": new_state["enabled"],
}
feature_states = []
feature_states.append(instance)

feature_data: GithubData = generate_data(
github_configuration=github_configuration,
feature_id=history_instance.feature.id,
feature_name=history_instance.feature.name,
type=WebhookEventType.FLAG_UPDATED.value,
feature_states=feature_states,
)

feature_data.feature_states.append(feature_state)

call_github_app_webhook_for_feature_state.delay(
args=(asdict(feature_data),),
)


def _get_previous_state(
instance: FeatureState,
Expand Down
2 changes: 1 addition & 1 deletion api/integrations/github/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def post_comment_to_github(
url, json=payload, headers=headers, timeout=10
)

return response.json() if response.status_code == 200 else None
return response.json() if response.status_code == 201 else None
except requests.RequestException as e:
logger.error(f" {e}")
return None
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import datetime
from unittest.mock import ANY

import simplejson as json
from django.core.serializers.json import DjangoJSONEncoder
Expand All @@ -7,10 +8,16 @@
from rest_framework import status
from rest_framework.test import APIClient

from environments.models import Environment
from environments.permissions.constants import UPDATE_FEATURE_STATE
from features.feature_external_resources.models import FeatureExternalResource
from features.models import Feature
from features.models import Feature, FeatureState
from features.serializers import FeatureStateSerializerBasic
from integrations.github.github import GithubData
from integrations.github.models import GithubConfiguration, GithubRepository
from projects.models import Project
from tests.types import WithEnvironmentPermissionsCallable
from webhooks.webhooks import WebhookEventType

_django_json_encoder_default = DjangoJSONEncoder().default

Expand Down Expand Up @@ -66,6 +73,7 @@ def test_create_feature_external_resource(
url, data=feature_external_resource_data, format="json"
)

# Then
github_request_mock.assert_called_with(
"https://api.github.com/repos/repoowner/repo-name/issues/35/comments",
json={
Expand All @@ -77,15 +85,13 @@ def test_create_feature_external_resource(
},
timeout=10,
)
# Then
assert response.status_code == status.HTTP_201_CREATED
# assert that the payload has been save to the database
feature_external_resources = FeatureExternalResource.objects.filter(
feature=feature_with_value,
type=feature_external_resource_data["type"],
url=feature_external_resource_data["url"],
).all()

assert len(feature_external_resources) == 1
assert feature_external_resources[0].metadata == json.dumps(
feature_external_resource_data["metadata"], default=_django_json_encoder_default
Expand Down Expand Up @@ -300,3 +306,79 @@ def test_get_feature_external_resource(
assert response.data["id"] == feature_external_resource.id
assert response.data["type"] == feature_external_resource.type
assert response.data["url"] == feature_external_resource.url


def test_create_github_comment_on_feature_state_updated(
staff_client: APIClient,
with_environment_permissions: WithEnvironmentPermissionsCallable,
feature_external_resource: FeatureExternalResource,
feature: Feature,
project: Project,
github_configuration: GithubConfiguration,
github_repository: GithubRepository,
mocker,
environment: Environment,
) -> None:
# Given
with_environment_permissions([UPDATE_FEATURE_STATE])
feature_state = FeatureState.objects.get(
feature=feature, environment=environment.id
)
mock_generate_token = mocker.patch(
"integrations.github.github.generate_token",
)
mock_generate_token.return_value = "mocked_token"
github_request_mock = mocker.patch(
"requests.post", side_effect=mocked_requests_post
)

feature_state_value = feature_state.get_feature_state_value()
feature_env_data = {}
feature_env_data["feature_state_value"] = feature_state_value
feature_env_data["feature_state_value_type"] = (
feature_state.get_feature_state_value_type(feature_state_value)
)
feature_env_data["environment_name"] = environment.name
feature_env_data["feature_value"] = feature_state.enabled

mock_generate_data = mocker.patch(
"integrations.github.github.generate_data",
return_value=GithubData(
installation_id=github_configuration.installation_id,
feature_id=feature.id,
feature_name=feature.name,
type=feature_external_resource.type,
feature_states=[feature_env_data],
url=feature_external_resource.url,
),
)

payload = dict(FeatureStateSerializerBasic(instance=feature_state).data)

payload["enabled"] = not feature_state.enabled
url = reverse(
viewname="api-v1:environments:environment-featurestates-detail",
kwargs={"environment_api_key": environment.api_key, "pk": feature_state.id},
)

# When
response = staff_client.put(path=url, data=payload, format="json")

# Then
assert response.status_code == status.HTTP_200_OK
github_request_mock.assert_called_with(
"https://api.github.com/repos/userexample/example-project-repo/issues/11/comments",
json=ANY,
headers={
"Accept": "application/vnd.github.v3+json",
"Authorization": "Bearer mocked_token",
},
timeout=10,
)
mock_generate_data.assert_called_with(
github_configuration=github_configuration,
feature_id=feature.id,
feature_name=feature.name,
type=WebhookEventType.FLAG_UPDATED.value,
feature_states=[feature_state],
)