Skip to content
Open
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
26 changes: 26 additions & 0 deletions api/integrations/gitlab/mappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,29 @@ def map_gitlab_webhook_payload_to_tag_label(
is_draft=bool(attrs.get("draft") or attrs.get("work_in_progress")),
)
return None


def map_gitlab_webhook_payload_to_resource_metadata(
payload: GitLabWebhookPayload,
) -> GitLabResourceMetadata:
attrs = payload.get("object_attributes") or {}
object_kind = payload.get("object_kind")
state = attrs.get("state")
title = attrs.get("title")
metadata: GitLabResourceMetadata = {}
if object_kind == "issue" and state:
metadata["state"] = state
elif object_kind == "merge_request":
if attrs.get("action") == "merge":
metadata["state"] = "merged"
elif state:
metadata["state"] = state
if "draft" in attrs or "work_in_progress" in attrs:
metadata["draft"] = bool(
attrs.get("draft") or attrs.get("work_in_progress")
)
else:
return metadata
if title:
metadata["title"] = title
return metadata
2 changes: 2 additions & 0 deletions api/integrations/gitlab/services/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
GITLAB_RESOURCE_KIND_BY_TYPE,
apply_flagsmith_label_to_resource,
)
from integrations.gitlab.services.metadata import update_resource_metadata
from integrations.gitlab.services.tagging import (
apply_initial_tag,
apply_tag_for_event,
Expand Down Expand Up @@ -46,4 +47,5 @@
"post_unlinked_comment",
"register_gitlab_webhook_for_resource",
"set_gitlab_tag",
"update_resource_metadata",
]
45 changes: 45 additions & 0 deletions api/integrations/gitlab/services/metadata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import json

from features.feature_external_resources.models import (
FeatureExternalResource,
ResourceType,
)
from integrations.gitlab.mappers import (
map_gitlab_webhook_payload_to_resource_metadata,
map_resource_url_to_filter_value,
)
from integrations.gitlab.models import GitLabWebhook
from integrations.gitlab.types import GitLabResourceMetadata, GitLabWebhookPayload

_RESOURCE_TYPE_BY_OBJECT_KIND: dict[str, str] = {
"issue": ResourceType.GITLAB_ISSUE.value,
"merge_request": ResourceType.GITLAB_MR.value,
}


def update_resource_metadata(
webhook: GitLabWebhook,
payload: GitLabWebhookPayload,
) -> None:
new_fields = map_gitlab_webhook_payload_to_resource_metadata(payload)
resource_type = _RESOURCE_TYPE_BY_OBJECT_KIND.get(payload.get("object_kind") or "")
resource_url = (payload.get("object_attributes") or {}).get("url")
if not (new_fields and resource_type and resource_url):
return

resources = FeatureExternalResource.objects.filter(
feature__project=webhook.gitlab_configuration.project,
type=resource_type,
url__in=map_resource_url_to_filter_value(resource_url),
)

for resource in resources:
current = json.loads(resource.metadata) if resource.metadata else {}
merged: GitLabResourceMetadata = {**current, **new_fields}
changed = sorted(
name for name, value in new_fields.items() if current.get(name) != value
)
if not changed:
continue
resource.metadata = json.dumps(merged)
resource.save(update_fields=["metadata"])
19 changes: 1 addition & 18 deletions api/integrations/gitlab/services/tagging.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import structlog

from features.feature_external_resources.models import (
GITLAB_RESOURCE_TYPES,
FeatureExternalResource,
Expand All @@ -21,8 +19,6 @@
from integrations.gitlab.types import GitLabWebhookPayload
from projects.tags.models import Tag, TagType

logger = structlog.get_logger("gitlab")


def set_gitlab_tag(feature: Feature, new_label: GitLabTagLabel) -> None:
"""Apply a GitLab system tag to ``feature``, replacing any existing GitLab
Expand Down Expand Up @@ -67,9 +63,7 @@ def apply_tag_for_event(
if not (label := map_gitlab_webhook_payload_to_tag_label(payload)):
return

if not (
resource_url := (attrs := payload.get("object_attributes") or {}).get("url")
):
if not (resource_url := (payload.get("object_attributes") or {}).get("url")):
return

if not (
Expand All @@ -82,17 +76,6 @@ def apply_tag_for_event(
return

set_gitlab_tag(feature, label)
log = logger.bind(
organisation__id=webhook.gitlab_configuration.project.organisation_id,
project__id=webhook.gitlab_configuration.project_id,
)
log.info(
"feature.tagged",
feature__id=feature.id,
tag__label=label.value,
object_kind=payload.get("object_kind"),
action=attrs.get("action"),
)


def clear_tag_for_resource(resource: FeatureExternalResource) -> None:
Expand Down
1 change: 1 addition & 0 deletions api/integrations/gitlab/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class GitLabWebhookObjectAttributes(TypedDict, total=False):
action: str
draft: bool
work_in_progress: bool
title: str


class GitLabWebhookPayload(TypedDict, total=False):
Expand Down
14 changes: 10 additions & 4 deletions api/integrations/gitlab/views/webhook.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from rest_framework.response import Response

from integrations.gitlab.models import GitLabWebhook
from integrations.gitlab.services import apply_tag_for_event
from integrations.gitlab.services import apply_tag_for_event, update_resource_metadata
from integrations.gitlab.types import GitLabWebhookPayload

logger = structlog.get_logger("gitlab")
Expand All @@ -29,8 +29,14 @@ def gitlab_webhook(request: Request, webhook_uuid: str) -> Response:
if not hmac.compare_digest(token, webhook.secret):
return Response(status=status.HTTP_401_UNAUTHORIZED)

apply_tag_for_event(
webhook=webhook,
payload=cast(GitLabWebhookPayload, request.data),
payload = cast(GitLabWebhookPayload, request.data)
apply_tag_for_event(webhook=webhook, payload=payload)
update_resource_metadata(webhook=webhook, payload=payload)
logger.info(
"webhook.processed",
organisation__id=webhook.gitlab_configuration.project.organisation_id,
project__id=webhook.gitlab_configuration.project_id,
object_kind=payload.get("object_kind"),
action=(payload.get("object_attributes") or {}).get("action"),
)
return Response(status=status.HTTP_200_OK)
15 changes: 14 additions & 1 deletion api/tests/unit/integrations/gitlab/conftest.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from __future__ import annotations

import uuid
from typing import TYPE_CHECKING

import pytest

from integrations.gitlab.models import GitLabConfiguration
from integrations.gitlab.models import GitLabConfiguration, GitLabWebhook

if TYPE_CHECKING:
from projects.models import Project
Expand All @@ -17,3 +18,15 @@ def gitlab_config(project: Project) -> GitLabConfiguration:
gitlab_instance_url="https://gitlab.example.com",
access_token="glpat-test-token",
)


@pytest.fixture()
def gitlab_webhook(gitlab_config: GitLabConfiguration) -> GitLabWebhook:
return GitLabWebhook.objects.create( # type: ignore[no-any-return]
uuid=uuid.uuid4(),
gitlab_configuration=gitlab_config,
gitlab_project_id=42,
gitlab_path_with_namespace="testorg/testrepo",
gitlab_hook_id=1,
secret="topsecret",
)
Loading
Loading