Skip to content

Commit

Permalink
feat: Improvements in the GitHub integration BE (#3962)
Browse files Browse the repository at this point in the history
  • Loading branch information
novakzaballa authored May 22, 2024
1 parent 2fb465f commit 59ddfba
Show file tree
Hide file tree
Showing 18 changed files with 1,043 additions and 322 deletions.
1 change: 1 addition & 0 deletions api/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -971,6 +971,7 @@ def feature_external_resource(feature: Feature) -> FeatureExternalResource:
url="https://github.com/userexample/example-project-repo/issues/11",
type="GITHUB_ISSUE",
feature=feature,
metadata='{"status": "open"}',
)


Expand Down
37 changes: 14 additions & 23 deletions api/features/feature_external_resources/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import logging
from dataclasses import asdict

from django.db import models
from django_lifecycle import (
Expand All @@ -10,8 +9,7 @@
)

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 integrations.github.github import call_github_task
from organisations.models import Organisation
from webhooks.webhooks import WebhookEventType

Expand Down Expand Up @@ -50,43 +48,36 @@ class Meta:
def execute_after_save_actions(self):
# Add a comment to GitHub Issue/PR when feature is linked to the GH external resource
if (
github_configuration := Organisation.objects.prefetch_related(
"github_config"
)
Organisation.objects.prefetch_related("github_config")
.get(id=self.feature.project.organisation_id)
.github_config.first()
):
feature_states = FeatureState.objects.filter(
feature_id=self.feature_id, identity_id__isnull=True
)
feature_data: GithubData = generate_data(
github_configuration=github_configuration,
feature=self.feature,
call_github_task(
organisation_id=self.feature.project.organisation_id,
type=WebhookEventType.FEATURE_EXTERNAL_RESOURCE_ADDED.value,
feature=self.feature,
segment_name=None,
url=None,
feature_states=feature_states,
)

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

@hook(BEFORE_DELETE)
def execute_before_save_actions(self) -> None:
# Add a comment to GitHub Issue/PR when feature is unlinked to the GH external resource
if (
github_configuration := Organisation.objects.prefetch_related(
"github_config"
)
Organisation.objects.prefetch_related("github_config")
.get(id=self.feature.project.organisation_id)
.github_config.first()
):
feature_data: GithubData = generate_data(
github_configuration=github_configuration,
feature=self.feature,

call_github_task(
organisation_id=self.feature.project.organisation_id,
type=WebhookEventType.FEATURE_EXTERNAL_RESOURCE_REMOVED.value,
feature=self.feature,
segment_name=None,
url=self.url,
)

call_github_app_webhook_for_feature_state.delay(
args=(asdict(feature_data),),
feature_states=None,
)
20 changes: 20 additions & 0 deletions api/features/feature_external_resources/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from features.models import Feature
from features.permissions import FeatureExternalResourcePermissions
from integrations.github.client import get_github_issue_pr_title_and_state
from organisations.models import Organisation

from .models import FeatureExternalResource
Expand All @@ -25,6 +26,25 @@ def get_queryset(self):
features_pk = self.kwargs["feature_pk"]
return FeatureExternalResource.objects.filter(feature=features_pk)

# Override get list view to add github issue/pr name to each linked external resource
def list(self, request, *args, **kwargs) -> Response:
queryset = self.get_queryset()
serializer = self.get_serializer(queryset, many=True)
data = serializer.data

# get organisation id from feature and get feature from validated data
organisation_id = get_object_or_404(
Feature.objects.filter(id=self.kwargs["feature_pk"]),
).project.organisation_id

for resource in data if isinstance(data, list) else []:
if resource_url := resource.get("url"):
resource["metadata"] = get_github_issue_pr_title_and_state(
organisation_id=organisation_id, resource_url=resource_url
)

return Response(data={"results": data})

def create(self, request, *args, **kwargs):
feature = get_object_or_404(
Feature.objects.filter(
Expand Down
46 changes: 29 additions & 17 deletions api/features/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import typing
import uuid
from copy import deepcopy
from dataclasses import asdict

from core.models import (
AbstractBaseExportableModel,
Expand All @@ -23,6 +22,7 @@
from django.utils import timezone
from django_lifecycle import (
AFTER_CREATE,
AFTER_DELETE,
AFTER_SAVE,
BEFORE_CREATE,
BEFORE_SAVE,
Expand Down Expand Up @@ -74,7 +74,6 @@
STRING,
)
from features.versioning.models import EnvironmentFeatureVersion
from integrations.github.models import GithubConfiguration
from metadata.models import Metadata
from projects.models import Project
from projects.tags.models import Tag
Expand Down Expand Up @@ -139,10 +138,7 @@ class Meta:

@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 integrations.github.github import call_github_task
from webhooks.webhooks import WebhookEventType

if (
Expand All @@ -151,19 +147,14 @@ def create_github_comment(self) -> None:
and self.project.organisation.github_config.exists()
and self.deleted_at
):
github_configuration = GithubConfiguration.objects.get(
organisation_id=self.project.organisation_id
)

feature_data: GithubData = generate_data(
github_configuration=github_configuration,
feature=self,
call_github_task(
organisation_id=self.project.organisation_id,
type=WebhookEventType.FLAG_DELETED.value,
feature_states=[],
)

call_github_app_webhook_for_feature_state.delay(
args=(asdict(feature_data),),
feature=self,
segment_name=None,
url=None,
feature_states=None,
)

@hook(AFTER_CREATE)
Expand Down Expand Up @@ -219,6 +210,7 @@ def get_next_segment_priority(feature):


class FeatureSegment(
LifecycleModelMixin,
AbstractBaseExportableModel,
OrderedModelBase,
abstract_base_auditable_model_factory(["uuid"]),
Expand Down Expand Up @@ -406,6 +398,26 @@ def get_delete_log_message(self, history_instance) -> typing.Optional[str]:
def _get_environment(self) -> "Environment":
return self.environment

@hook(AFTER_DELETE)
def create_github_comment(self) -> None:
from integrations.github.github import call_github_task
from webhooks.webhooks import WebhookEventType

if (
self.feature.external_resources.exists()
and self.feature.project.github_project.exists()
and self.feature.project.organisation.github_config.exists()
):

call_github_task(
self.feature.project.organisation_id,
WebhookEventType.SEGMENT_OVERRIDE_DELETED.value,
self.feature,
self.segment.name,
None,
None,
)


class FeatureState(
SoftDeleteExportableModel,
Expand Down
26 changes: 9 additions & 17 deletions api/features/serializers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import typing
from dataclasses import asdict
from datetime import datetime

import django.core.exceptions
Expand All @@ -13,9 +12,7 @@
from environments.sdk.serializers_mixins import (
HideSensitiveFieldsSerializerMixin,
)
from integrations.github.github import GithubData, generate_data
from integrations.github.models import GithubConfiguration
from integrations.github.tasks import call_github_app_webhook_for_feature_state
from integrations.github.github import call_github_task
from metadata.serializers import MetadataSerializer, SerializerWithMetadata
from projects.models import Project
from users.serializers import (
Expand Down Expand Up @@ -474,23 +471,18 @@ def save(self, **kwargs):
and feature_state.environment.project.github_project.exists()
and feature_state.environment.project.organisation.github_config.exists()
):
github_configuration = GithubConfiguration.objects.get(
organisation_id=feature_state.environment.project.organisation_id
)
feature_states = []
feature_states.append(feature_state)
feature_data: GithubData = generate_data(
github_configuration=github_configuration,
feature=feature_state.feature,
type=WebhookEventType.FLAG_UPDATED.value,
feature_states=feature_states,
)

call_github_app_webhook_for_feature_state.delay(
args=(asdict(feature_data),),
call_github_task(
organisation_id=feature_state.feature.project.organisation_id,
type=WebhookEventType.FLAG_UPDATED.value,
feature=feature_state.feature,
segment_name=None,
url=None,
feature_states=[feature_state],
)

return response

except django.core.exceptions.ValidationError as e:
raise serializers.ValidationError(str(e))

Expand Down
24 changes: 24 additions & 0 deletions api/features/versioning/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

from features.serializers import CreateSegmentOverrideFeatureStateSerializer
from features.versioning.models import EnvironmentFeatureVersion
from integrations.github.github import call_github_task
from users.models import FFAdminUser
from webhooks.webhooks import WebhookEventType


class EnvironmentFeatureVersionFeatureStateSerializer(
Expand All @@ -14,6 +16,28 @@ class Meta(CreateSegmentOverrideFeatureStateSerializer.Meta):
+ ("feature",)
)

def save(self, **kwargs):
response = super().save(**kwargs)

feature_state = self.instance
if (
not feature_state.identity_id
and feature_state.feature.external_resources.exists()
and feature_state.environment.project.github_project.exists()
and feature_state.environment.project.organisation.github_config.exists()
):

call_github_task(
organisation_id=feature_state.environment.project.organisation_id,
type=WebhookEventType.FLAG_UPDATED.value,
feature=feature_state.feature,
segment_name=None,
url=None,
feature_states=[feature_state],
)

return response


class EnvironmentFeatureVersionSerializer(serializers.ModelSerializer):
class Meta:
Expand Down
Loading

0 comments on commit 59ddfba

Please sign in to comment.