Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/app/settings/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@
"integrations.github",
"integrations.gitlab",
"integrations.grafana",
"integrations.vcs",
# Rate limiting admin endpoints
"axes",
"telemetry",
Expand Down
27 changes: 0 additions & 27 deletions api/features/feature_external_resources/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from django.db import models
from django.db.models import Q
from django_lifecycle import ( # type: ignore[import-untyped]
AFTER_DELETE,
AFTER_SAVE,
BEFORE_DELETE,
LifecycleModelMixin,
Expand Down Expand Up @@ -142,32 +141,6 @@ def notify_github_on_link(self): # type: ignore[no-untyped-def]
feature_states=feature_states,
)

@hook(AFTER_SAVE, when="type", is_now="GITLAB_ISSUE") # type: ignore[misc]
@hook(AFTER_SAVE, when="type", is_now="GITLAB_MR") # type: ignore[misc]
def apply_gitlab_tag(self) -> None:
from integrations.gitlab.services import apply_initial_tag

apply_initial_tag(self)

@hook(AFTER_DELETE, when="type", is_now="GITLAB_ISSUE") # type: ignore[misc]
@hook(AFTER_DELETE, when="type", is_now="GITLAB_MR") # type: ignore[misc]
def deregister_gitlab_webhook(self) -> None:
from integrations.gitlab.models import GitLabConfiguration
from integrations.gitlab.services import parse_project_path
from integrations.gitlab.tasks import (
deregister_gitlab_webhook as deregister_task,
)

project_path = parse_project_path(self.url)
if project_path is None:
return
config = GitLabConfiguration.objects.filter(
project=self.feature.project,
).first()
if config is None:
return
deregister_task.delay(args=(config.id, project_path))

@hook(BEFORE_DELETE, when="type", is_now="GITHUB_ISSUE") # type: ignore[misc]
@hook(BEFORE_DELETE, when="type", is_now="GITHUB_PR") # type: ignore[misc]
def notify_github_on_unlink(self) -> None:
Expand Down
52 changes: 17 additions & 35 deletions api/features/feature_external_resources/views.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import re

import structlog
from django.shortcuts import get_object_or_404
from django.utils.decorators import method_decorator
from drf_spectacular.utils import extend_schema
Expand All @@ -14,12 +13,13 @@
label_github_issue_pr,
)
from integrations.github.models import GitHubRepository
from integrations.gitlab.models import GitLabConfiguration
from integrations.gitlab.services import parse_project_path
from integrations.gitlab.tasks import register_gitlab_webhook
from integrations.vcs.services import (
dispatch_vcs_on_resource_create,
dispatch_vcs_on_resource_destroy,
)
from organisations.models import Organisation

from .models import GITLAB_RESOURCE_TYPES, FeatureExternalResource, ResourceType
from .models import FeatureExternalResource, ResourceType
from .serializers import FeatureExternalResourceSerializer


Expand Down Expand Up @@ -77,14 +77,13 @@ def list(self, request, *args, **kwargs) -> Response: # type: ignore[no-untyped
def create(self, request, *args, **kwargs): # type: ignore[no-untyped-def]
resource_type = request.data.get("type")

# GitLab side effects run in ``perform_create`` below.
if resource_type in GITLAB_RESOURCE_TYPES:
return super().create(request, *args, **kwargs)

if resource_type not in (
ResourceType.GITHUB_ISSUE,
ResourceType.GITHUB_PR,
):
# TODO(#7315): route link/unlink/state-change/delete through a
# provider-agnostic dispatcher so adding a new VCS is contained
# to one integration app rather than spread across view, model,
# and serializer hooks.
if resource_type not in (ResourceType.GITHUB_ISSUE, ResourceType.GITHUB_PR):
# GitLab side effects run in ``perform_create`` below; other
# types fall through to the default create.
return super().create(request, *args, **kwargs)

feature = get_object_or_404(
Expand Down Expand Up @@ -146,28 +145,11 @@ def create(self, request, *args, **kwargs): # type: ignore[no-untyped-def]

def perform_create(self, serializer: FeatureExternalResourceSerializer) -> None: # type: ignore[override]
resource = serializer.save()
resource_type = ResourceType(resource.type)

if resource_type in GITLAB_RESOURCE_TYPES:
project_path = parse_project_path(resource.url)
config = GitLabConfiguration.objects.filter(
project=resource.feature.project,
).first()
if config is not None and project_path is not None:
register_gitlab_webhook.delay(args=(config.id, project_path))

log_event_names: dict[ResourceType, tuple[str, str]] = {
ResourceType.GITLAB_ISSUE: ("gitlab", "issue.linked"),
ResourceType.GITLAB_MR: ("gitlab", "merge_request.linked"),
}
if resource_type in log_event_names:
logger_name, event_name = log_event_names[resource_type]
structlog.get_logger(logger_name).info(
event_name,
organisation__id=resource.feature.project.organisation_id,
project__id=resource.feature.project_id,
feature__id=resource.feature.id,
)
dispatch_vcs_on_resource_create(resource)

def perform_destroy(self, instance: FeatureExternalResource) -> None:
super().perform_destroy(instance)
dispatch_vcs_on_resource_destroy(instance)

def perform_update(self, serializer): # type: ignore[no-untyped-def]
external_resource_id = int(self.kwargs["pk"])
Expand Down
19 changes: 19 additions & 0 deletions api/features/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,25 @@ def create_github_comment(self) -> None:
feature_states=None,
)

@hook(AFTER_SAVE) # type: ignore[misc]
def create_gitlab_comment(self) -> None:
from features.feature_external_resources.models import (
GITLAB_RESOURCE_TYPES,
)
from integrations.gitlab.tasks import (
post_gitlab_feature_deleted_comment,
)

if (
self.deleted_at
and self.external_resources.filter(
type__in=GITLAB_RESOURCE_TYPES,
).exists()
):
post_gitlab_feature_deleted_comment.delay(
args=(self.name, self.id, self.project_id),
)

@hook(AFTER_CREATE)
def create_feature_states(self): # type: ignore[no-untyped-def]
FeatureState.create_initial_feature_states_for_feature(feature=self)
Expand Down
6 changes: 6 additions & 0 deletions api/features/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
)
from integrations.github.constants import GitHubEventType
from integrations.github.github import call_github_task
from integrations.gitlab.services import (
post_gitlab_state_change_comment_for_feature_state,
)
from metadata.serializers import MetadataSerializer, MetadataSerializerMixin
from projects.code_references.serializers import (
FeatureFlagCodeReferencesRepositoryCountSerializer,
Expand Down Expand Up @@ -653,6 +656,9 @@ def save(self, **kwargs): # type: ignore[no-untyped-def]
feature_states=[feature_state],
)

if isinstance(feature_state, FeatureState):
Comment thread
Zaimwa9 marked this conversation as resolved.
post_gitlab_state_change_comment_for_feature_state(feature_state)

return response

except django.core.exceptions.ValidationError as e:
Expand Down
5 changes: 5 additions & 0 deletions api/features/versioning/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
from features.versioning.models import EnvironmentFeatureVersion
from integrations.github.constants import GitHubEventType
from integrations.github.github import call_github_task
from integrations.gitlab.services import (
post_gitlab_state_change_comment_for_feature_state,
)
from segments.models import Segment
from users.models import FFAdminUser

Expand Down Expand Up @@ -54,6 +57,8 @@ def save(self, **kwargs): # type: ignore[no-untyped-def]
feature_states=[feature_state],
)

post_gitlab_state_change_comment_for_feature_state(feature_state)

return response


Expand Down
4 changes: 4 additions & 0 deletions api/integrations/gitlab/client/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from integrations.gitlab.client.api import (
create_issue_note,
create_merge_request_note,
create_project_hook,
delete_project_hook,
fetch_gitlab_projects,
Expand All @@ -19,6 +21,8 @@
"GitLabPage",
"GitLabProject",
"GitLabProjectHook",
"create_issue_note",
"create_merge_request_note",
"create_project_hook",
"delete_project_hook",
"fetch_gitlab_projects",
Expand Down
37 changes: 37 additions & 0 deletions api/integrations/gitlab/client/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
GitLabProjectHook,
T,
)
from integrations.gitlab.constants import GITLAB_CLIENT_TIMEOUT_SECONDS


def _get_from_gitlab_api(
Expand Down Expand Up @@ -190,3 +191,39 @@ def delete_project_hook(
if response.status_code == 404:
return
response.raise_for_status()


def create_issue_note(
Comment thread
Zaimwa9 marked this conversation as resolved.
instance_url: str,
access_token: str,
*,
project_path: str,
issue_iid: int,
body: str,
) -> None:
encoded_path = quote(project_path, safe="")
response = requests.post(
f"{instance_url}/api/v4/projects/{encoded_path}/issues/{issue_iid}/notes",
headers={"PRIVATE-TOKEN": access_token},
json={"body": body},
timeout=GITLAB_CLIENT_TIMEOUT_SECONDS,
)
response.raise_for_status()


def create_merge_request_note(
instance_url: str,
access_token: str,
*,
project_path: str,
merge_request_iid: int,
body: str,
) -> None:
encoded_path = quote(project_path, safe="")
response = requests.post(
f"{instance_url}/api/v4/projects/{encoded_path}/merge_requests/{merge_request_iid}/notes",
headers={"PRIVATE-TOKEN": access_token},
json={"body": body},
timeout=GITLAB_CLIENT_TIMEOUT_SECONDS,
)
response.raise_for_status()
2 changes: 1 addition & 1 deletion api/integrations/gitlab/constants.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from enum import Enum

GITLAB_TAG_COLOR = "#FC6D26"
GITLAB_WEBHOOK_TIMEOUT = 10
GITLAB_CLIENT_TIMEOUT_SECONDS = 10


class GitLabTagLabel(Enum):
Expand Down
41 changes: 41 additions & 0 deletions api/integrations/gitlab/services/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from integrations.gitlab.services.comments import (
post_feature_deleted_comment,
post_gitlab_state_change_comment_for_feature_state,
post_linked_comment,
post_state_change_comment,
post_unlinked_comment,
)
from integrations.gitlab.services.tagging import (
apply_initial_tag,
apply_tag_for_event,
set_gitlab_tag,
)
from integrations.gitlab.services.url_parsing import (
parse_project_path,
parse_resource_iid,
)
from integrations.gitlab.services.webhooks import (
deregister_gitlab_webhook_for_resource,
deregister_webhook_for_path,
ensure_webhook_registered,
has_live_resource_for_path,
register_gitlab_webhook_for_resource,
)

__all__ = [
"apply_initial_tag",
"apply_tag_for_event",
"deregister_gitlab_webhook_for_resource",
"deregister_webhook_for_path",
"ensure_webhook_registered",
"has_live_resource_for_path",
"parse_project_path",
"parse_resource_iid",
"post_feature_deleted_comment",
"post_gitlab_state_change_comment_for_feature_state",
"post_linked_comment",
"post_state_change_comment",
"post_unlinked_comment",
"register_gitlab_webhook_for_resource",
"set_gitlab_tag",
]
Loading
Loading