"""Constants for Jira offboarding ticket queries and regex patterns.""" import re from dataclasses import dataclass @dataclass(frozen=True) class RegexPatterns: """Compiled regex patterns for text searching.""" EMAIL_RE = re.compile(r'[\w.+%-]+@[\w][\w.-]*\.[A-Za-z]{2,}', re.UNICODE) # Shared fragments composed into the public patterns below. The spine # "request (has been|was) submitted" is common to both ticket types' # sentinels and to the full-name prefix. _SUBMITTED = r'request\s+(?:has been|was)\s+submitted' # Name prefix + capture, shared by both *_FULL_NAME_RE patterns. The # capture is non-greedy up to the first period and requires the name to end # in two letters, so a trailing middle initial ("John A. Doe.") isn't # truncated. _NAME_PREFIX = _SUBMITTED + r' for\s+' _NAME_CAPTURE = r'(.+?[^\W\d_]{2})\.' OFFBOARDING_FULL_NAME_RE = re.compile( _NAME_PREFIX + _NAME_CAPTURE, re.UNICODE, ) OFFBOARDING_SENTINEL_RE = re.compile( r'offboarding ' + _SUBMITTED, re.IGNORECASE, ) # Suspend tickets use the same "submitted for ." phrasing as # offboarding tickets (e.g. "...request has been submitted for John Doe. # Please ensure..."). Kept as a separate constant from # OFFBOARDING_FULL_NAME_RE to preserve the per-type registry symmetry and # leave room for future divergence. SUSPEND_FULL_NAME_RE = re.compile( _NAME_PREFIX + _NAME_CAPTURE, re.UNICODE, ) # Suspend sentinel matches phrasings like "suspend network access request # has been submitted" without overlapping the offboarding sentinel. SUSPEND_SENTINEL_RE = re.compile( r'suspend\s+\w+\s+access\s+' + _SUBMITTED, re.IGNORECASE, ) @dataclass(frozen=True) class JiraLabels: """Label strings applied to Jira offboarding tickets by the automation. Phase 1 labels (exclude from Phase 1 re-processing — JQL filters ``AND labels = empty``): - ``AUTOMATION_COMPLETE`` — all offboarding steps ran successfully. - ``NO_ACTIONS_REQUIRED`` — user not found in any system. - ``PENDING_APPROVAL`` — findings posted; awaiting human approval. - ``VALIDATION_FAILED`` — ticket source could not be verified. Phase 2 label (used to *include* tickets in Phase 2 — JQL filters ``AND labels = "approved-for-offboarding"``): - ``APPROVED_FOR_OFFBOARDING`` — a human has reviewed the findings and approved destructive actions. """ AUTOMATION_COMPLETE = 'automation-complete' NO_ACTIONS_REQUIRED = 'no-actions-required' PENDING_APPROVAL = 'pending-approval' VALIDATION_FAILED = 'validation-failed' APPROVED_FOR_OFFBOARDING = 'approved-for-offboarding' # Suspension-flow equivalents. Kept separate from the offboarding labels so # the two flows never cross-trigger each other's Phase 2 JQL. SUSPENSION_COMPLETE = 'suspension-complete' SUSPEND_NO_ACTIONS_REQUIRED = 'suspend-no-actions-required' SUSPEND_PENDING_APPROVAL = 'suspend-pending-approval' SUSPEND_VALIDATION_FAILED = 'suspend-validation-failed' APPROVED_FOR_SUSPENSION = 'approved-for-suspension' @dataclass(frozen=True) class TicketValidation: """Constants for validating that offboarding tickets originate from the expected source.""" REPORTER_ALLOWLIST = frozenset({'jira-email-reporter@sonymusic-pde.com'}) FOOTER_SENTINEL = 'Created via e-mail received from' FOOTER_EMAIL = 'helpdesk.request@sonymusic.com' @dataclass(frozen=True) class JQLQueries: """JQL query strings for Jira offboarding tickets.""" ALL_OFFBOARDING_TICKETS = """ project = SYS AND textfields ~ "Global Technology Offboarding" AND labels = empty ORDER BY created DESC """ MY_OFFBOARDING_TICKETS = """ project = SYS AND status = "In Development" AND textfields ~ "Global Technology Offboarding" AND assignee = currentUser() AND labels = empty ORDER BY created DESC """ NEW_OPEN_OFFBOARDING_TICKETS = """ created >= -90d AND project = SYS AND status = Backlog AND textfields ~ "Global Technology Offboarding" AND labels = empty AND assignee IS EMPTY AND duedate IS EMPTY ORDER BY created DESC """ APPROVED_OFFBOARDING_TICKETS = f""" project = SYS AND status = Backlog AND textfields ~ "Global Technology Offboarding" AND labels = "{JiraLabels.APPROVED_FOR_OFFBOARDING}" AND labels != "{JiraLabels.AUTOMATION_COMPLETE}" AND duedate < now() ORDER BY created DESC """ NEW_OPEN_SUSPEND_TICKETS = """ created >= -90d AND project = SYS AND status = Backlog AND textfields ~ "Global Technology Suspend Request" AND labels = empty AND assignee IS EMPTY AND duedate IS EMPTY ORDER BY created DESC """ APPROVED_SUSPEND_TICKETS = f""" project = SYS AND status = Backlog AND textfields ~ "Global Technology Suspend Request" AND labels = "{JiraLabels.APPROVED_FOR_SUSPENSION}" AND labels != "{JiraLabels.SUSPENSION_COMPLETE}" AND duedate < now() ORDER BY created DESC """ COMPLETED_TICKETS = f""" project = SYS AND ( labels = "{JiraLabels.AUTOMATION_COMPLETE}" OR labels = "{JiraLabels.SUSPENSION_COMPLETE}" OR labels = "{JiraLabels.NO_ACTIONS_REQUIRED}" OR labels = "{JiraLabels.SUSPEND_NO_ACTIONS_REQUIRED}" ) AND status != "Closed" ORDER BY created DESC """