From 4f358db5a2d78cf6b314be23104a4c5a26d4d0ff Mon Sep 17 00:00:00 2001 From: juanjogarciatorres43 <168570831+juanjogarciatorres43@users.noreply.github.com> Date: Wed, 8 May 2024 15:39:06 +0200 Subject: [PATCH] Fir 307 (#52) * Add new FeatureTeam model FIR-307 * set Admin register for FeatureTeam FIR-307 * remove qualifier code * Feature Team as mandatory pick at front end FIR-307 * add migrations to raid app FIR-307 --------- Co-authored-by: Roc Itxart --- .env.example | 2 - .../0003_alter_apitokenproxy_options.py | 30 ++++++ .../firefighter/settings/components/raid.py | 3 - src/firefighter/incidents/admin.py | 10 ++ ...ty_name_alter_user_password_featureteam.py | 35 +++++++ .../migrations/0003_delete_featureteam.py | 16 +++ src/firefighter/incidents/signals.py | 5 - src/firefighter/raid/admin.py | 87 ----------------- src/firefighter/raid/apps.py | 1 - src/firefighter/raid/client.py | 3 - src/firefighter/raid/forms.py | 22 +---- src/firefighter/raid/messages.py | 89 +---------------- ...ve_qualifierrotation_jira_user_and_more.py | 36 +++++++ src/firefighter/raid/models.py | 70 +++++-------- src/firefighter/raid/service.py | 33 +------ .../raid/signals/incident_created.py | 15 +-- .../signals/update_qualifiers_rotation.py | 97 ------------------- src/firefighter/raid/tasks/__init__.py | 3 - src/firefighter/raid/tasks/daily_qualifier.py | 67 ------------- .../raid/tasks/weekly_qualifier.py | 63 ------------ src/firefighter/raid/views/open_normal.py | 2 +- 21 files changed, 169 insertions(+), 520 deletions(-) create mode 100644 src/firefighter/api/migrations/0003_alter_apitokenproxy_options.py create mode 100644 src/firefighter/incidents/migrations/0002_alter_severity_name_alter_user_password_featureteam.py create mode 100644 src/firefighter/incidents/migrations/0003_delete_featureteam.py create mode 100644 src/firefighter/raid/migrations/0002_featureteam_remove_qualifierrotation_jira_user_and_more.py delete mode 100644 src/firefighter/raid/signals/update_qualifiers_rotation.py delete mode 100644 src/firefighter/raid/tasks/daily_qualifier.py delete mode 100644 src/firefighter/raid/tasks/weekly_qualifier.py diff --git a/.env.example b/.env.example index 9c4e6c39..5f04b9ff 100644 --- a/.env.example +++ b/.env.example @@ -95,8 +95,6 @@ ENABLE_RAID=False RAID_DEFAULT_JIRA_QRAFT_USER_ID="XXXXXXXX" #gitleaks:allow RAID_JIRA_PROJECT_KEY="T2" -RAID_QUALIFIER_URL="https://mycompany.atlassian.local/secure/RapidBoard.jspa?rapidView=12345" - FF_SLACK_SKIP_CHECKS=true # Disable SSO redirect for local dev by setting to true. When SSO is disabled, go to /admin/ to login diff --git a/src/firefighter/api/migrations/0003_alter_apitokenproxy_options.py b/src/firefighter/api/migrations/0003_alter_apitokenproxy_options.py new file mode 100644 index 00000000..e2bd2c1c --- /dev/null +++ b/src/firefighter/api/migrations/0003_alter_apitokenproxy_options.py @@ -0,0 +1,30 @@ +# Generated by Django 4.2.11 on 2024-04-30 15:39 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("api", "0002_alter_apitokenproxy_options"), + ] + + operations = [ + migrations.AlterModelOptions( + name="apitokenproxy", + options={ + "default_permissions": [], + "permissions": [ + ("can_edit_any", "Can reassign token to any user"), + ("can_add_any", "Can add token to any user"), + ("can_view_any", "Can view token of all users"), + ("can_delete_any", "Can delete token of any user"), + ("can_add_own", "Can add own tokens"), + ("can_view_own", "Can view own tokens"), + ("can_delete_own", "Can delete own tokens"), + ], + "verbose_name": "API Token", + "verbose_name_plural": "API Tokens", + }, + ), + ] diff --git a/src/firefighter/firefighter/settings/components/raid.py b/src/firefighter/firefighter/settings/components/raid.py index 04d9faa6..5cf2c66d 100644 --- a/src/firefighter/firefighter/settings/components/raid.py +++ b/src/firefighter/firefighter/settings/components/raid.py @@ -15,8 +15,5 @@ RAID_JIRA_PROJECT_KEY: str = config("RAID_JIRA_PROJECT_KEY") "The Jira project key to use for creating issues, e.g. 'INC'" - RAID_QUALIFIER_URL: str = config("RAID_QUALIFIER_URL") - "Link to the board with issues to qualify" - RAID_JIRA_USER_IDS: dict[str, str] = {} "Mapping of domain to default Jira user ID" diff --git a/src/firefighter/incidents/admin.py b/src/firefighter/incidents/admin.py index 7ead2471..a77be5f5 100644 --- a/src/firefighter/incidents/admin.py +++ b/src/firefighter/incidents/admin.py @@ -43,6 +43,7 @@ from firefighter.incidents.models.metric_type import IncidentMetric, MetricType from firefighter.incidents.models.milestone_type import MilestoneType from firefighter.incidents.models.priority import Priority +from firefighter.raid.models import FeatureTeam if TYPE_CHECKING: from collections.abc import MutableSequence @@ -174,6 +175,15 @@ def get_readonly_fields( return self.readonly_fields +@admin.register(FeatureTeam) +class FeatureTeamAdmin(admin.ModelAdmin[FeatureTeam]): + model = FeatureTeam + list_display = [ + "name", + "jira_project_key", + ] + + @admin.register(Severity) class SeverityAdmin(admin.ModelAdmin[Severity]): model = Severity diff --git a/src/firefighter/incidents/migrations/0002_alter_severity_name_alter_user_password_featureteam.py b/src/firefighter/incidents/migrations/0002_alter_severity_name_alter_user_password_featureteam.py new file mode 100644 index 00000000..2931e20e --- /dev/null +++ b/src/firefighter/incidents/migrations/0002_alter_severity_name_alter_user_password_featureteam.py @@ -0,0 +1,35 @@ +# Generated by Django 4.2.11 on 2024-04-30 15:39 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("incidents", "0001_initial_oss"), + ] + + operations = [ + migrations.AlterField( + model_name="severity", + name="name", + field=models.CharField(max_length=128, unique=True), + ), + migrations.AlterField( + model_name="user", + name="password", + field=models.CharField(max_length=128, verbose_name="password"), + ), + migrations.CreateModel( + name="FeatureTeam", + fields=[ + ("id", models.AutoField(primary_key=True, serialize=False)), + ("name", models.CharField(max_length=80)), + ("jira_project_key", models.CharField(max_length=10, unique=True)), + ], + options={ + "unique_together": {("name", "jira_project_key")}, + "verbose_name": "Feature Team", + "verbose_name_plural": "Feature Teams", + }, + ), + ] diff --git a/src/firefighter/incidents/migrations/0003_delete_featureteam.py b/src/firefighter/incidents/migrations/0003_delete_featureteam.py new file mode 100644 index 00000000..248b99df --- /dev/null +++ b/src/firefighter/incidents/migrations/0003_delete_featureteam.py @@ -0,0 +1,16 @@ +# Generated by Django 4.2.11 on 2024-05-03 09:13 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("incidents", "0002_alter_severity_name_alter_user_password_featureteam"), + ] + + operations = [ + migrations.DeleteModel( + name="FeatureTeam", + ), + ] diff --git a/src/firefighter/incidents/signals.py b/src/firefighter/incidents/signals.py index 6f61cc66..4226823a 100644 --- a/src/firefighter/incidents/signals.py +++ b/src/firefighter/incidents/signals.py @@ -71,8 +71,3 @@ Args: incident (Incident): The incident for which to create a conversation. """ - - -new_qualifier = django.dispatch.Signal() -"""Signal sent when a new qualifier. -""" diff --git a/src/firefighter/raid/admin.py b/src/firefighter/raid/admin.py index 00f294be..ee65da4f 100644 --- a/src/firefighter/raid/admin.py +++ b/src/firefighter/raid/admin.py @@ -1,24 +1,14 @@ from __future__ import annotations -from typing import TYPE_CHECKING - from django.contrib import admin -from django.utils.translation import gettext_lazy as _ from firefighter.jira_app.admin import JiraIssueAdmin from firefighter.raid.models import ( JiraTicket, JiraTicketImpact, - Qualifier, - QualifierRotation, RaidArea, ) -if TYPE_CHECKING: - from django.db.models.query import QuerySet - from django.forms.models import ModelForm - from django.http import HttpRequest - class JiraTicketImpactInline(admin.TabularInline[JiraTicketImpact, JiraTicket]): model = JiraTicketImpact @@ -38,83 +28,6 @@ class JiraTicketAdmin(JiraIssueAdmin): inlines = [JiraTicketImpactInline] -@admin.register(QualifierRotation) -class QualifierRotationAdmin(admin.ModelAdmin[QualifierRotation]): - model = QualifierRotation - list_display = ["day", "jira_user", "protected"] - list_select_related = ["jira_user", "jira_user__user"] - list_display_links = ["day"] - - ordering = ["-day"] - search_fields = ["day", "jira_user__user__username"] - date_hierarchy = "day" - - autocomplete_fields = ["jira_user"] - - def has_add_permission( - self, _request: HttpRequest, _obj: QualifierRotation | None = None - ) -> bool: - return False - - def has_delete_permission( - self, _request: HttpRequest, _obj: QualifierRotation | None = None - ) -> bool: - return False - - def save_model( - self, - request: HttpRequest, - obj: QualifierRotation, - form: ModelForm[QualifierRotation], - change: bool, # noqa: FBT001 - ) -> None: - obj.protected = True - super().save_model(request, obj, form, change) - - @admin.action(description="Unprotect rotation date", permissions=["change"]) - def make_unprotected( - self, _request: HttpRequest, queryset: QuerySet[QualifierRotation] - ) -> None: - queryset.update(protected=False) - - actions = [make_unprotected] - - fieldsets = ( - ( - _("Rotation"), - { - "fields": ( - "day", - "jira_user", - ) - }, - ), - ( - _("Protected"), - { - "fields": ("protected",), - }, - ), - ) - - -@admin.register(Qualifier) -class QualifiersListAdmin(admin.ModelAdmin[Qualifier]): - model = Qualifier - list_display = ["jira_user"] - list_select_related = ["jira_user", "jira_user__user"] - - ordering = ["id"] - search_fields = ["jira_user__user__username"] - - autocomplete_fields = ["jira_user"] - - def has_change_permission( - self, _request: HttpRequest, _obj: Qualifier | None = None - ) -> bool: - return False - - @admin.register(RaidArea) class RaidAreaAdmin(admin.ModelAdmin[RaidArea]): model = RaidArea diff --git a/src/firefighter/raid/apps.py b/src/firefighter/raid/apps.py index acc9c45b..fa51c0bb 100644 --- a/src/firefighter/raid/apps.py +++ b/src/firefighter/raid/apps.py @@ -19,7 +19,6 @@ def ready(self) -> None: from firefighter.raid.signals import ( incident_created, incident_updated, - update_qualifiers_rotation, ) from firefighter.raid.views.open_normal import ( OpeningRaidCustomerModal, diff --git a/src/firefighter/raid/client.py b/src/firefighter/raid/client.py index 85ecf4bf..87f89107 100644 --- a/src/firefighter/raid/client.py +++ b/src/firefighter/raid/client.py @@ -53,14 +53,11 @@ def create_issue( # noqa: PLR0912, PLR0913, C901, PLR0917 area: str | None = None, environments: list[str] | None = None, project: str | None = None, - qualifier: str | None = None, ) -> JiraObject: description_addendum: list[str] = [] extra_args: dict[str, Any] = {} if assignee: extra_args["assignee"] = {"id": assignee} - if qualifier: - extra_args["customfield_10892"] = {"id": qualifier} if labels is None: labels = [""] if priority is None: diff --git a/src/firefighter/raid/forms.py b/src/firefighter/raid/forms.py index 6c5cd9e0..3495ebaa 100644 --- a/src/firefighter/raid/forms.py +++ b/src/firefighter/raid/forms.py @@ -21,14 +21,13 @@ SlackMessageRaidCreatedIssue, SlackMessageRaidModifiedIssue, ) -from firefighter.raid.models import JiraTicket, JiraUser, RaidArea +from firefighter.raid.models import FeatureTeam, JiraTicket, RaidArea from firefighter.raid.service import ( create_issue_customer, create_issue_documentation_request, create_issue_feature_request, create_issue_internal, create_issue_seller, - get_current_qualifier, get_jira_user_from_user, ) from firefighter.raid.utils import get_domain_from_email @@ -39,6 +38,7 @@ from firefighter.incidents.models.impact import ImpactLevel from firefighter.incidents.models.user import User + from firefighter.jira_app.models import JiraUser from firefighter.raid.types import JiraObject from firefighter.slack.messages.base import SlackMessageSurface @@ -76,11 +76,9 @@ class CreateNormalIncidentFormBase(CreateIncidentFormBase): min_length=10, max_length=1200, ) - - suggested_team_routing = forms.CharField( - label="Feature Team or Train to be routed", - max_length=128, - min_length=2, + suggested_team_routing = forms.ModelChoiceField( + queryset=FeatureTeam.objects.only("name"), + label="Feature Team or Train", required=True, ) priority = forms.ModelChoiceField( @@ -283,7 +281,6 @@ def process_jira_issue( def set_jira_ticket_watchers_raid(jira_ticket: JiraTicket) -> None: issue_id = jira_ticket.id reporter = jira_ticket.reporter.id - qualifier = get_current_qualifier().id try: default_jira_user = jira_client.get_jira_user_from_jira_id( @@ -299,15 +296,6 @@ def set_jira_ticket_watchers_raid(jira_ticket: JiraTicket) -> None: logger.exception( f"Could not add the watcher {jira_ticket.reporter.id} to the ticket {issue_id}" ) - - if qualifier != RAID_DEFAULT_JIRA_QRAFT_USER_ID: - try: - jira_client.jira.add_watcher(issue=issue_id, watcher=qualifier) - - except JiraAPIError: - logger.exception( - f"Could not add the watcher {jira_ticket.reporter.id} to the ticket {issue_id}" - ) try: jira_client.jira.remove_watcher(issue=issue_id, watcher=default_jira_user) diff --git a/src/firefighter/raid/messages.py b/src/firefighter/raid/messages.py index 71acf248..b943adef 100644 --- a/src/firefighter/raid/messages.py +++ b/src/firefighter/raid/messages.py @@ -3,7 +3,6 @@ import textwrap from typing import TYPE_CHECKING -from django.conf import settings from slack_sdk.models.blocks.basic_components import MarkdownTextObject from slack_sdk.models.blocks.blocks import ( Block, @@ -15,13 +14,12 @@ from firefighter.slack.slack_templating import user_slack_handle_or_name if TYPE_CHECKING: - from collections.abc import Iterable + from django.conf import settings from firefighter.incidents.models.user import User - from firefighter.raid.models import JiraTicket, JiraUser, QualifierRotation -RAID_QUALIFIER_URL: str = settings.RAID_QUALIFIER_URL -RAID_JIRA_API_URL: str = settings.RAID_JIRA_API_URL + RAID_JIRA_API_URL: str = settings.RAID_JIRA_API_URL + from firefighter.raid.models import JiraTicket class SlackMessageRaidCreatedIssue(SlackMessageSurface): @@ -156,84 +154,3 @@ def get_blocks(self) -> list[Block]: ] ), ] - - -class SlackMessageRaidDailyQualifierPublic(SlackMessageSurface): - id = "raid_daily_qualifier_public" - - def __init__(self, qualifier: JiraUser) -> None: - self.qualifier = qualifier - super().__init__() - - def get_text(self) -> str: - return f"Daily qualifier {user_slack_handle_or_name(self.qualifier.user)}" - - def get_blocks(self) -> list[Block]: - return [ - SectionBlock(text="*Daily qualifier*"), - DividerBlock(), - SectionBlock( - fields=[ - MarkdownTextObject( - text=f"Hello! :wave: \nToday the Qualifier is {user_slack_handle_or_name(self.qualifier.user)}" - ), - ] - ), - ] - - -class SlackMessageRaidDailyQualifierPrivate(SlackMessageSurface): - id = "raid_daily_qualifier_private" - - def get_text(self) -> str: - return "Daily qualifier" - - def get_blocks(self) -> list[Block]: - return [ - SectionBlock(text="*Daily qualifier*"), - DividerBlock(), - SectionBlock( - fields=[ - MarkdownTextObject( - text="Don't forget, you're the Qualifier today! :muscle:\n" - ), - MarkdownTextObject( - text=f"\nIssues to qualify can be tracked here: {RAID_QUALIFIER_URL}\n" - ), - MarkdownTextObject(text="Have a good day! :lovecommunity:"), - ] - ), - ] - - -class SlackMessageRaidWeeklyQualifiers(SlackMessageSurface): - id = "raid_weekly_qualifiers_public" - - def __init__(self, qualifiers_rotation: Iterable[QualifierRotation]) -> None: - # Obtain weekly qualifier rotation in a readable format - self.rotation_text = "" - for rotation in qualifiers_rotation: - self.rotation_text = ( - self.rotation_text - + f"{(rotation.day.strftime('%a %d/%m/%Y'))} --> {user_slack_handle_or_name(rotation.jira_user.user)}\n" - ) - super().__init__() - - def get_text(self) -> str: - return "Weekly qualifier rotation" - - def get_blocks(self) -> list[Block]: - return [ - SectionBlock( - text="Hello! :wave: \nHere you can find *next week qualifier rotation*:" - ), - DividerBlock(), - SectionBlock(fields=[MarkdownTextObject(text=f"{self.rotation_text}")]), - SectionBlock( - fields=[ - MarkdownTextObject( - text="\n:pray: Please find a *backup* in case you won't be available." - ), - ] - ), - ] diff --git a/src/firefighter/raid/migrations/0002_featureteam_remove_qualifierrotation_jira_user_and_more.py b/src/firefighter/raid/migrations/0002_featureteam_remove_qualifierrotation_jira_user_and_more.py new file mode 100644 index 00000000..9e58e2ac --- /dev/null +++ b/src/firefighter/raid/migrations/0002_featureteam_remove_qualifierrotation_jira_user_and_more.py @@ -0,0 +1,36 @@ +# Generated by Django 4.2.11 on 2024-05-03 09:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("raid", "0001_initial_oss"), + ] + + operations = [ + migrations.CreateModel( + name="FeatureTeam", + fields=[ + ("id", models.AutoField(primary_key=True, serialize=False)), + ("name", models.CharField(max_length=80)), + ("jira_project_key", models.CharField(max_length=10, unique=True)), + ], + options={ + "verbose_name": "Feature Team", + "verbose_name_plural": "Feature Teams", + "unique_together": {("name", "jira_project_key")}, + }, + ), + migrations.RemoveField( + model_name="qualifierrotation", + name="jira_user", + ), + migrations.DeleteModel( + name="Qualifier", + ), + migrations.DeleteModel( + name="QualifierRotation", + ), + ] diff --git a/src/firefighter/raid/models.py b/src/firefighter/raid/models.py index 7a7f8a03..7fb6d1b3 100644 --- a/src/firefighter/raid/models.py +++ b/src/firefighter/raid/models.py @@ -7,7 +7,7 @@ from django_stubs_ext.db.models import TypedModelMeta from firefighter.incidents.models.incident import Incident -from firefighter.jira_app.models import JiraIssue, JiraUser +from firefighter.jira_app.models import JiraIssue if TYPE_CHECKING: from firefighter.incidents.models.impact import Impact # noqa: F401 @@ -58,49 +58,6 @@ def __str__(self) -> str: return f"{self.jira_ticket.key}: {self.impact}" -class QualifierRotation(models.Model): - """Model to store the rotation of the incident qualifiers.""" - - id = models.AutoField(primary_key=True) - jira_user = models.ForeignKey[JiraUser, JiraUser]( - JiraUser, on_delete=models.CASCADE - ) - day = models.DateField(unique=True) - protected = models.BooleanField( - default=False, - help_text="If checked, it is because this rotation was previously modified so this date won't be deleted and it can not be unprotected again from here.", - ) - - if TYPE_CHECKING: - jira_user_id: str - - class Meta: - verbose_name = "Qualifiers rotation" - verbose_name_plural = "Qualifiers rotation" - - def __str__(self) -> str: - return f"{self.day}: {self.jira_user}" - - -class Qualifier(models.Model): - """Model to store users that can be incidents qualifiers.""" - - id = models.AutoField(primary_key=True) - jira_user = models.OneToOneField[JiraUser, JiraUser]( - JiraUser, on_delete=models.CASCADE - ) - - if TYPE_CHECKING: - jira_user_id: str - - class Meta: - verbose_name = "Qualifier" - verbose_name_plural = "Qualifiers" - - def __str__(self) -> str: - return f"{self.jira_user}" - - class RaidArea(models.Model): id = models.AutoField(primary_key=True) name = models.CharField(max_length=128) @@ -118,3 +75,28 @@ class Meta(TypedModelMeta): def __str__(self) -> str: return self.name + + +class FeatureTeam(models.Model): + id = models.AutoField(primary_key=True) + name = models.CharField(max_length=80) + jira_project_key = models.CharField( + max_length=10, + unique=True, + ) + + class Meta(TypedModelMeta): + unique_together = ("name", "jira_project_key") + verbose_name = "Feature Team" + verbose_name_plural = "Feature Teams" + + def __str__(self) -> str: + return self.name + + @property + def get_team(self) -> str: + return "{self.name} {self.jira_project_key}" + + @property + def get_key(self) -> str: + return "{self.jira_project_key}" diff --git a/src/firefighter/raid/service.py b/src/firefighter/raid/service.py index 6b560f48..3a41b9e4 100644 --- a/src/firefighter/raid/service.py +++ b/src/firefighter/raid/service.py @@ -4,7 +4,6 @@ from typing import TYPE_CHECKING from django.conf import settings -from django.utils import timezone from firefighter.jira_app.client import ( JiraAPIError, @@ -12,7 +11,6 @@ ) from firefighter.jira_app.models import JiraUser from firefighter.raid.client import client as jira_client -from firefighter.raid.models import QualifierRotation if TYPE_CHECKING: from firefighter.incidents.models.user import User @@ -34,22 +32,6 @@ def check_issue_id(issue: JiraObject, title: str, reporter: str) -> int | str: return issue_id -def get_current_qualifier() -> JiraUser: - """Returns the qualifier Jira account id for today. On weekends use Qraft generic account. - - Returns: - JiraUser: JiraUser object - """ - if timezone.now().date().weekday() in {5, 6}: - return jira_client.get_jira_user_from_jira_id(RAID_DEFAULT_JIRA_QRAFT_USER_ID) - try: - qualifier_rotation = QualifierRotation.objects.get(day=timezone.now().date()) - except QualifierRotation.DoesNotExist: - logger.warning("Qualifier rotation not found for today.") - return jira_client.get_jira_user_from_jira_id(RAID_DEFAULT_JIRA_QRAFT_USER_ID) - return qualifier_rotation.jira_user - - def get_jira_user_from_user(user: User) -> JiraUser: """Returns the JiraUser object for a given user, if it exists in DB or can be fetched on Jira API. Returns the default JiraUser if not found.""" try: @@ -96,9 +78,8 @@ def create_issue_feature_request( issuetype="Feature Request", summary=title, description=description, - assignee=get_current_qualifier().id, + assignee=None, reporter=reporter, - qualifier=get_current_qualifier().id, priority=priority, labels=labels, platform=platform, @@ -133,9 +114,8 @@ def create_issue_documentation_request( issuetype="Documentation/Process Request", summary=title, description=description, - assignee=get_current_qualifier().id, + assignee=None, reporter=reporter, - qualifier=get_current_qualifier().id, priority=priority, labels=labels, platform=platform, @@ -172,9 +152,8 @@ def create_issue_internal( issuetype="Incident", summary=title, description=description, - assignee=get_current_qualifier().id, + assignee=None, reporter=reporter, - qualifier=get_current_qualifier().id, priority=priority, labels=labels, platform=platform, @@ -216,9 +195,8 @@ def create_issue_customer( issuetype="Incident", summary=title, description=description, - assignee=get_current_qualifier().id, + assignee=None, reporter=reporter, - qualifier=get_current_qualifier().id, priority=priority, labels=labels, platform=platform, @@ -267,9 +245,8 @@ def create_issue_seller( # noqa: PLR0913, PLR0917 issuetype="Incident", summary=title, description=description, - assignee=get_current_qualifier().id, + assignee=None, reporter=reporter, - qualifier=get_current_qualifier().id, priority=priority, labels=labels, platform=platform, diff --git a/src/firefighter/raid/signals/incident_created.py b/src/firefighter/raid/signals/incident_created.py index 446a5807..217e944b 100644 --- a/src/firefighter/raid/signals/incident_created.py +++ b/src/firefighter/raid/signals/incident_created.py @@ -9,7 +9,7 @@ from firefighter.jira_app.client import JiraAPIError, JiraUserNotFoundError from firefighter.raid.client import client from firefighter.raid.models import JiraTicket -from firefighter.raid.service import get_current_qualifier, get_jira_user_from_user +from firefighter.raid.service import get_jira_user_from_user from firefighter.slack.messages.slack_messages import ( SlackMessageIncidentDeclaredAnnouncement, ) @@ -17,7 +17,6 @@ if TYPE_CHECKING: from firefighter.incidents.models.incident import Incident - from firefighter.jira_app.models import JiraUser from firefighter.slack.models.incident_channel import IncidentChannel logger = logging.getLogger(__name__) @@ -37,7 +36,6 @@ def create_ticket( # XXX Custom field with FireFighter ID/link? # XXX Set affected environment custom field # XXX Set custom field impacted area to group/domain? - qualifier: JiraUser = get_current_qualifier() priority: int = incident.priority.value if 1 <= incident.priority.value <= 4 else 1 issue = client.create_issue( issuetype="Incident", @@ -47,9 +45,8 @@ def create_ticket( 🧯 This incident has been created for a critical incident. Links below to Slack and {APP_DISPLAY_NAME}.\n 📦 Component: {incident.component.name} ({incident.component.group.name})\n {incident.priority.emoji} Priority: {incident.priority.name}\n""", - assignee=qualifier.id, + assignee=None, reporter=account_id, - qualifier=qualifier.id, priority=priority, ) issue_id = issue.get("id") @@ -71,14 +68,6 @@ def create_ticket( logger.exception( f"Could not add the watcher with account id {account_id} to the ticket {issue_id}" ) - if qualifier.id != RAID_DEFAULT_JIRA_QRAFT_USER_ID: - # Add watcher qualifier - try: - client.jira.add_watcher(issue=issue_id, watcher=qualifier.id) - except JiraAPIError: - logger.exception( - f"Could not add the watcher {qualifier.id} to the ticket {issue_id}" - ) # Removing default watcher TeamQraft try: client.jira.remove_watcher(issue=issue_id, watcher=default_jira_user) diff --git a/src/firefighter/raid/signals/update_qualifiers_rotation.py b/src/firefighter/raid/signals/update_qualifiers_rotation.py deleted file mode 100644 index f616d04a..00000000 --- a/src/firefighter/raid/signals/update_qualifiers_rotation.py +++ /dev/null @@ -1,97 +0,0 @@ -from __future__ import annotations - -import datetime -from typing import Any - -from django.conf import settings -from django.db.models import Max -from django.db.models.signals import post_delete, post_save -from django.dispatch.dispatcher import receiver - -from firefighter.jira_app.models import JiraUser -from firefighter.raid.models import Qualifier, QualifierRotation - -RAID_DEFAULT_JIRA_QRAFT_USER_ID: str = settings.RAID_DEFAULT_JIRA_QRAFT_USER_ID - - -def complete_qualifiers_rotation() -> None: - qualifiers_rotation_last_date = last_date_to_maintain(only_protected=False) - adding_twice_rotation(qualifiers_rotation_last_date) - - -def adding_twice_rotation(next_day_to_add: datetime.date) -> None: - qualifiers_queryset = ( - Qualifier.objects.select_related("jira_user") - .filter(jira_user__user__is_active=True) - .order_by("id") - ) - # Adding twice the rotation - for jira_user in list(qualifiers_queryset) * 2: - next_day_to_add = next_day_to_add + datetime.timedelta(days=1) - # We avoid weekends - if next_day_to_add.weekday() == 5: - next_day_to_add = next_day_to_add + datetime.timedelta(days=2) - # Updating the rotation - QualifierRotation.objects.update_or_create( - day=next_day_to_add, - defaults={ - "jira_user": jira_user.jira_user, - "protected": False, - }, - ) - - -def last_date_to_maintain(*, only_protected: bool) -> datetime.date: - base_queryset = ( - QualifierRotation.objects.filter( - day__gte=datetime.datetime.now(tz=datetime.UTC).date(), - ) - .order_by("day") - .select_related("day") - ) - if only_protected: - base_queryset = base_queryset.filter(protected=True) - qualifiers_rotation_last_date = base_queryset.aggregate(Max("day")).get("day__max") - if qualifiers_rotation_last_date is None: - qualifiers_rotation_last_date = datetime.datetime.now(tz=datetime.UTC).date() - return qualifiers_rotation_last_date - - -@receiver(signal=post_save, sender=Qualifier) -def new_qualifier(sender: Any, instance: Any, created: int, **kwargs: Any) -> None: - if created == 1: - qualifiers_rotation_last_date_protected = last_date_to_maintain( - only_protected=True - ) - QualifierRotation.objects.filter( - day__gte=qualifiers_rotation_last_date_protected - + datetime.timedelta(days=1), - ).delete() - adding_twice_rotation(qualifiers_rotation_last_date_protected) - - -@receiver(signal=post_delete, sender=Qualifier) -def deleted_qualifier(sender: Any, instance: Qualifier, **kwargs: Any) -> None: - qualifiers_rotation_last_date_protected = last_date_to_maintain(only_protected=True) - qualifier_rotation_dates_to_use_default_value = ( - QualifierRotation.objects.filter( - day__gte=datetime.datetime.now(tz=datetime.UTC).date(), - day__lte=qualifiers_rotation_last_date_protected, - ) - .order_by("day") - .filter(jira_user=instance.jira_user) - ) - # Replacing with default qualifier for dates assigned to the deleted qualifier before last protected date - for day in qualifier_rotation_dates_to_use_default_value: - QualifierRotation.objects.update_or_create( - day=day.day, - defaults={ - "jira_user": JiraUser.objects.get(id=RAID_DEFAULT_JIRA_QRAFT_USER_ID), - }, - ) - # Deleting rotation after last protected date - QualifierRotation.objects.filter( - day__gte=qualifiers_rotation_last_date_protected + datetime.timedelta(days=1), - ).delete() - # Adding new rotation after last protected date - adding_twice_rotation(qualifiers_rotation_last_date_protected) diff --git a/src/firefighter/raid/tasks/__init__.py b/src/firefighter/raid/tasks/__init__.py index 271f79b8..9d48db4f 100644 --- a/src/firefighter/raid/tasks/__init__.py +++ b/src/firefighter/raid/tasks/__init__.py @@ -1,4 +1 @@ from __future__ import annotations - -import firefighter.raid.tasks.daily_qualifier -import firefighter.raid.tasks.weekly_qualifier diff --git a/src/firefighter/raid/tasks/daily_qualifier.py b/src/firefighter/raid/tasks/daily_qualifier.py deleted file mode 100644 index ee797fe9..00000000 --- a/src/firefighter/raid/tasks/daily_qualifier.py +++ /dev/null @@ -1,67 +0,0 @@ -from __future__ import annotations - -import logging -from typing import TYPE_CHECKING - -from celery import shared_task -from slack_sdk.errors import SlackApiError - -from firefighter.raid.client import client as jira_client -from firefighter.raid.messages import ( - SlackMessageRaidDailyQualifierPrivate, - SlackMessageRaidDailyQualifierPublic, -) -from firefighter.raid.service import get_current_qualifier -from firefighter.slack.models.conversation import Conversation - -if TYPE_CHECKING: - from firefighter.raid.models import JiraUser -logger = logging.getLogger(__name__) - - -def _daily_qualifier_message() -> None: - """Sends a message in Slack with the daily qualifier both in public and private.""" - # Get daily qualifier - qualifier: JiraUser = get_current_qualifier() - if not qualifier: - logger.warning("Qualifier not found for today.") - return - - # Send message to public channel - message_public = SlackMessageRaidDailyQualifierPublic(qualifier) - qualifier_conversation = Conversation.objects.get_or_none(tag="qualification") - if qualifier_conversation: - try: - qualifier_conversation.send_message_and_save(message_public) - except SlackApiError: - logger.exception( - f"Couldn't send message to the channel {qualifier_conversation} for the user {qualifier.user}" - ) - - # Send private message to the qualifier - message_private = SlackMessageRaidDailyQualifierPrivate() - try: - if qualifier.id: - qualifier_jira_user = jira_client.get_jira_user_from_jira_id(qualifier.id) - if qualifier_jira_user.user.slack_user is None: - logger.warning( - f"Couldn't find Slack account ID for the qualifier {qualifier}" - ) - return - qualifier_jira_user.user.slack_user.send_private_message( - message_private, - unfurl_links=False, - ) - except SlackApiError: - logger.exception( - f"Couldn't send private message to the qualifier {qualifier.user.slack_user}" - ) - - -@shared_task(name="raid._daily_qualifier_message") -def send_daily_qualifier_message() -> None: - """Send a message with the daily qualifier. - - Ignore qualifiers that are not in the JiraUser table. - """ - _daily_qualifier_message() diff --git a/src/firefighter/raid/tasks/weekly_qualifier.py b/src/firefighter/raid/tasks/weekly_qualifier.py deleted file mode 100644 index fd8363dd..00000000 --- a/src/firefighter/raid/tasks/weekly_qualifier.py +++ /dev/null @@ -1,63 +0,0 @@ -from __future__ import annotations - -import datetime -import logging - -from celery import shared_task -from slack_sdk.errors import SlackApiError - -from firefighter.raid.messages import SlackMessageRaidWeeklyQualifiers -from firefighter.raid.models import QualifierRotation -from firefighter.raid.signals.update_qualifiers_rotation import ( - complete_qualifiers_rotation, -) -from firefighter.slack.models.conversation import Conversation - -logger = logging.getLogger(__name__) - - -@shared_task(name="raid._weekly_qualifiers_message") -def send_weekly_qualifiers_message() -> None: - """Send a message with the weekly qualifiers. - - Ignore qualifiers that are not in the JiraUser table. - """ - _weekly_qualifiers_message() - - -def _weekly_qualifiers_message() -> None: - """Sends a message in Slack with the weekly qualifiers rotation.""" - # Get weekly qualifiers in desired format - today = datetime.datetime.now(tz=datetime.UTC).date() - next_monday = today + datetime.timedelta(days=7 - today.weekday()) - qualifiers_weekly_rotation_queryset = ( - QualifierRotation.objects.filter( - day__gte=next_monday, - day__lte=next_monday + datetime.timedelta(days=4), - ) - .order_by("day") - .select_related("jira_user__user") - ) - if ( - not qualifiers_weekly_rotation_queryset - or qualifiers_weekly_rotation_queryset.count() < 5 - ): - logger.warning("Next week qualifiers rotation is incomplete. Completing it...") - try: - complete_qualifiers_rotation() - except QualifierRotation.DoesNotExist: - logger.exception("Couldn't complete qualifier rotation.") - return - - # Send message to public channel - message_public = SlackMessageRaidWeeklyQualifiers( - qualifiers_weekly_rotation_queryset - ) - qualifier_conversation = Conversation.objects.get_or_none(tag="qualification") - if qualifier_conversation: - try: - qualifier_conversation.send_message_and_save(message_public) - except SlackApiError: - logger.exception( - f"Couldn't send message to channel {qualifier_conversation} with weekly qualifiers rotation." - ) diff --git a/src/firefighter/raid/views/open_normal.py b/src/firefighter/raid/views/open_normal.py index abd3af60..eb4b58aa 100644 --- a/src/firefighter/raid/views/open_normal.py +++ b/src/firefighter/raid/views/open_normal.py @@ -42,7 +42,7 @@ "post_block": ContextBlock( elements=[ MarkdownTextObject( - text="Feature Team or Train that should own the issue. Type \"I don't know\" if you don't have the information." + text="Feature Team or Train that should own the issue. If you don't know access for guidance." ), ] )