diff --git a/src/firefighter/incidents/models/group.py b/src/firefighter/incidents/models/group.py index 6c1da4c..6c3b3e3 100644 --- a/src/firefighter/incidents/models/group.py +++ b/src/firefighter/incidents/models/group.py @@ -7,6 +7,7 @@ class Group(models.Model): + """Group of [Components][firefighter.incidents.models.component.Component]. Not a group of users.""" id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) name = models.CharField(max_length=128, unique=True) description = models.TextField(blank=True) diff --git a/src/firefighter/incidents/models/incident.py b/src/firefighter/incidents/models/incident.py index 373e0f6..5c65c03 100644 --- a/src/firefighter/incidents/models/incident.py +++ b/src/firefighter/incidents/models/incident.py @@ -53,7 +53,7 @@ logger = logging.getLogger(__name__) if TYPE_CHECKING: - from collections.abc import Sequence # noqa: F401 + from collections.abc import Iterable, Sequence # noqa: F401 from decimal import Decimal from uuid import UUID @@ -460,7 +460,7 @@ def build_invite_list(self) -> list[User]: users_list: list[User] = [] # Send signal to modules (Confluence, PagerDuty...) - result_users: list[tuple[Any, Exception | list[User]]] = ( + result_users: list[tuple[Any, Exception | Iterable[User]]] = ( signals.get_invites.send_robust(sender=None, incident=self) ) diff --git a/src/firefighter/slack/admin.py b/src/firefighter/slack/admin.py index 0b81af1..aa7b7fa 100644 --- a/src/firefighter/slack/admin.py +++ b/src/firefighter/slack/admin.py @@ -189,8 +189,45 @@ class MessageAdmin(admin.ModelAdmin[Message]): class UserGroupAdmin(admin.ModelAdmin[UserGroup]): model = UserGroup + list_display = [ + "name", + "handle", + "usergroup_id", + "is_external", + "tag", + ] + + list_display_links = [ + "name", + "handle", + "usergroup_id", + ] + + readonly_fields = ( + "created_at", + "updated_at", + ) + autocomplete_fields = ["components", "members"] - search_fields = ["name", "handle", "usergroup_id"] + search_fields = ["name", "handle", "description", "usergroup_id", "tag"] + + fieldsets = ( + ( + ("Slack attributes"), + { + "description" : ("These fields are synchronized automatically with Slack API"), + "fields": ( + "name", + "handle", + "usergroup_id", + "description", + "is_external", + "members", + ) + }, + ), + (_("Firefighter attributes"), {"fields": ("tag", "components", "created_at", "updated_at")}), + ) def save_model( self, diff --git a/src/firefighter/slack/migrations/0002_usergroup_tag.py b/src/firefighter/slack/migrations/0002_usergroup_tag.py new file mode 100644 index 0000000..f95b587 --- /dev/null +++ b/src/firefighter/slack/migrations/0002_usergroup_tag.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.16 on 2024-10-14 10:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("slack", "0001_initial_oss"), + ] + + operations = [ + migrations.AddField( + model_name="usergroup", + name="tag", + field=models.CharField( + blank=True, + help_text="Used by FireFighter internally to mark special users group (@team-secu, @team-incidents ...). Must be empty or unique.", + max_length=80, + ), + ), + ] diff --git a/src/firefighter/slack/models/user_group.py b/src/firefighter/slack/models/user_group.py index 6f8eae6..f3b7f66 100644 --- a/src/firefighter/slack/models/user_group.py +++ b/src/firefighter/slack/models/user_group.py @@ -174,6 +174,12 @@ class UserGroup(models.Model): help_text="Incident created with this usergroup automatically add the group members to these components.", ) + tag = models.CharField( + max_length=80, + blank=True, + help_text="Used by FireFighter internally to mark special users group (@team-secu, @team-incidents ...). Must be empty or unique.", + ) + created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) diff --git a/src/firefighter/slack/signals/get_users.py b/src/firefighter/slack/signals/get_users.py index 3f7fdfa..05efb59 100644 --- a/src/firefighter/slack/signals/get_users.py +++ b/src/firefighter/slack/signals/get_users.py @@ -8,20 +8,22 @@ from firefighter.incidents import signals from firefighter.incidents.models.user import User +from firefighter.slack.models.user_group import UserGroup from firefighter.slack.slack_app import SlackApp if TYPE_CHECKING: + from collections.abc import Iterable + from django.db.models.query import QuerySet from firefighter.incidents.models.incident import Incident from firefighter.slack.models.conversation import Conversation - from firefighter.slack.models.user_group import UserGroup logger = logging.getLogger(__name__) @receiver(signal=signals.get_invites) -def get_invites_from_slack(incident: Incident, **_kwargs: Any) -> list[User]: +def get_invites_from_slack(incident: Incident, **_kwargs: Any) -> Iterable[User]: """New version using cached users instead of querying Slack API.""" # Prepare sub-queries slack_usergroups: QuerySet[UserGroup] = incident.component.usergroups.all() @@ -39,4 +41,28 @@ def get_invites_from_slack(incident: Incident, **_kwargs: Any) -> list[User]: ) .distinct() ) - return list(queryset) + return set(queryset) + + +@receiver(signal=signals.get_invites) +def get_invites_from_slack_for_p1(incident: Incident, **kwargs: Any) -> Iterable[User]: + + if incident.priority.value > 1: + return [] + + if incident.private: + return [] + + slack_usergroups: QuerySet[UserGroup] = UserGroup.objects.filter( + tag="invited_for_all_public_p1" + ) + + queryset = ( + User.objects.filter(slack_user__isnull=False) + .exclude(slack_user__slack_id=SlackApp().details["user_id"]) + .exclude(slack_user__slack_id="") + .exclude(slack_user__slack_id__isnull=True) + .filter(usergroup__in=slack_usergroups) + .distinct() + ) + return set(queryset)