Skip to content

Commit

Permalink
fix: Organisation can't have a new Github integration when had a prio…
Browse files Browse the repository at this point in the history
…r one deleted (#3874)
  • Loading branch information
novakzaballa authored May 3, 2024
1 parent 08d4046 commit 53e728a
Show file tree
Hide file tree
Showing 9 changed files with 221 additions and 138 deletions.
26 changes: 19 additions & 7 deletions api/features/feature_external_resources/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,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 organisations.models import Organisation
from webhooks.webhooks import WebhookEventType

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -47,12 +48,18 @@ class Meta:
]

@hook(AFTER_SAVE)
def exectute_after_save_actions(self):
def execute_after_save_actions(self):
# Add a comment to GitHub Issue/PR when feature is linked to the GH external resource
if hasattr(self.feature.project.organisation, "github_config"):
github_configuration = self.feature.project.organisation.github_config

feature_states = FeatureState.objects.filter(feature_id=self.feature_id)
if (
github_configuration := 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,
self.feature_id,
Expand All @@ -68,8 +75,13 @@ def exectute_after_save_actions(self):
@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 hasattr(self.feature.project.organisation, "github_config"):
github_configuration = self.feature.project.organisation.github_config
if (
github_configuration := 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_id=self.feature_id,
Expand Down
25 changes: 19 additions & 6 deletions api/features/feature_external_resources/views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import re

from django.db.utils import IntegrityError
from django.shortcuts import get_object_or_404
from rest_framework import status, viewsets
Expand All @@ -6,6 +8,7 @@

from features.models import Feature
from features.permissions import FeatureExternalResourcePermissions
from organisations.models import Organisation

from .models import FeatureExternalResource
from .serializers import FeatureExternalResourceSerializer
Expand All @@ -29,9 +32,15 @@ def create(self, request, *args, **kwargs):
),
)

if not hasattr(feature.project.organisation, "github_config") or not hasattr(
feature.project, "github_project"
if not (
(
Organisation.objects.prefetch_related("github_config")
.get(id=feature.project.organisation_id)
.github_config.first()
)
or not hasattr(feature.project, "github_project")
):

return Response(
data={
"detail": "This Project doesn't have a valid GitHub integration configuration"
Expand All @@ -42,10 +51,14 @@ def create(self, request, *args, **kwargs):

try:
return super().create(request, *args, **kwargs)
except IntegrityError:
raise ValidationError(
detail="Duplication error. The feature already has this resource URI"
)

except IntegrityError as e:
if re.search(r"Key \(feature_id, url\)", str(e)) and re.search(
r"already exists.$", str(e)
):
raise ValidationError(
detail="Duplication error. The feature already has this resource URI"
)

def perform_update(self, serializer):
external_resource_id = int(self.kwargs["id"])
Expand Down
12 changes: 8 additions & 4 deletions api/features/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,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 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 @@ -65,8 +66,11 @@ def trigger_feature_state_change_webhooks(
and instance.environment.project.github_project.exists()
and hasattr(instance.environment.project.organisation, "github_config")
):
github_configuration = 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"],
Expand All @@ -78,11 +82,11 @@ def trigger_feature_state_change_webhooks(
github_configuration=github_configuration,
feature_id=history_instance.feature.id,
feature_name=history_instance.feature.name,
type=WebhookEventType.FLAG_UPDATED,
type=WebhookEventType.FLAG_UPDATED.value,
feature_states=feature_states,
)

feature_data["feature_states"].append(feature_state)
feature_data.feature_states.append(feature_state)

call_github_app_webhook_for_feature_state.delay(
args=(asdict(feature_data),),
Expand Down
6 changes: 6 additions & 0 deletions api/integrations/github/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from rest_framework.exceptions import APIException


class DuplicateGitHubIntegration(APIException):
status_code = 400
default_detail = "Duplication error. The GitHub integration already created"
24 changes: 24 additions & 0 deletions api/integrations/github/migrations/0002_auto_20240502_1949.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Generated by Django 3.2.25 on 2024-05-02 19:49

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('organisations', '0052_create_hubspot_organisation'),
('github', '0001_initial'),
]

operations = [
migrations.AlterField(
model_name='githubconfiguration',
name='organisation',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='github_config', to='organisations.organisation'),
),
migrations.AddConstraint(
model_name='githubconfiguration',
constraint=models.UniqueConstraint(condition=models.Q(('deleted_at__isnull', True)), fields=('organisation',), name='githubconf_organisation_id_idx'),
),
]
13 changes: 12 additions & 1 deletion api/integrations/github/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@


class GithubConfiguration(SoftDeleteExportableModel):
organisation = models.OneToOneField(
organisation = models.ForeignKey(
Organisation, on_delete=models.CASCADE, related_name="github_config"
)
installation_id = models.CharField(max_length=100, blank=False, null=False)
Expand All @@ -21,6 +21,17 @@ def has_github_configuration(organisation_id: int) -> bool:
organisation_id=organisation_id
).exists()

class Meta:
constraints = [
models.UniqueConstraint(
fields=[
"organisation",
],
name="githubconf_organisation_id_idx",
condition=models.Q(deleted_at__isnull=True),
)
]


class GithubRepository(LifecycleModelMixin, SoftDeleteExportableModel):
github_configuration = models.ForeignKey(
Expand Down
33 changes: 26 additions & 7 deletions api/integrations/github/views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import re
from functools import wraps

import requests
Expand All @@ -12,6 +13,7 @@

from integrations.github.client import generate_token
from integrations.github.constants import GITHUB_API_URL, GITHUB_API_VERSION
from integrations.github.exceptions import DuplicateGitHubIntegration
from integrations.github.models import GithubConfiguration, GithubRepository
from integrations.github.permissions import HasPermissionToGithubConfiguration
from integrations.github.serializers import (
Expand Down Expand Up @@ -61,6 +63,13 @@ def get_queryset(self):
organisation_id=self.kwargs["organisation_pk"]
)

def create(self, request, *args, **kwargs):
try:
return super().create(request, *args, **kwargs)
except IntegrityError as e:
if re.search(r"Key \(organisation_id\)=\(\d+\) already exists", str(e)):
raise DuplicateGitHubIntegration


class GithubRepositoryViewSet(viewsets.ModelViewSet):
permission_classes = (
Expand All @@ -84,20 +93,27 @@ def create(self, request, *args, **kwargs):

try:
return super().create(request, *args, **kwargs)
except IntegrityError:
raise ValidationError(
detail="Duplication error. The Github repository already linked"
)

except IntegrityError as e:
if re.search(
r"Key \(github_configuration_id, project_id, repository_owner, repository_name\)",
str(e),
) and re.search(r"already exists.$", str(e)):
raise ValidationError(
detail="Duplication error. The GitHub repository already linked"
)


@api_view(["GET"])
@permission_classes([IsAuthenticated, HasPermissionToGithubConfiguration])
@github_auth_required
def fetch_pull_requests(request, organisation_pk):
organisation = Organisation.objects.get(id=organisation_pk)

github_configuration = GithubConfiguration.objects.get(
organisation=organisation, deleted_at__isnull=True
)
token = generate_token(
organisation.github_config.installation_id,
github_configuration.installation_id,
settings.GITHUB_APP_ID,
)

Expand Down Expand Up @@ -130,8 +146,11 @@ def fetch_pull_requests(request, organisation_pk):
@github_auth_required
def fetch_issues(request, organisation_pk):
organisation = Organisation.objects.get(id=organisation_pk)
github_configuration = GithubConfiguration.objects.get(
organisation=organisation, deleted_at__isnull=True
)
token = generate_token(
organisation.github_config.installation_id,
github_configuration.installation_id,
settings.GITHUB_APP_ID,
)

Expand Down
Loading

0 comments on commit 53e728a

Please sign in to comment.