diff --git a/cli/fields.py b/cli/fields.py index 1e2fd0b..3815024 100644 --- a/cli/fields.py +++ b/cli/fields.py @@ -40,6 +40,30 @@ from dataclasses import dataclass from enum import Enum from typing import Any, Callable +from jira import Issue +from jira.resources import CustomFieldOption, User + + +class DeploymentRequirements(Enum): + """Checklist of deployment requirements for the Jira 'Deployment Requirements' multi-select field.""" + CODE_REVIEW_COMPLETED = "Code Review Completed" + UNIT_TESTS_PASSED = "Unit Tests Passed" + QA_SIGN_OFF = "QA Sign-off" + DOCUMENTATION_UPDATED = "Documentation Updated" + SECURITY_SCAN_APPROVED = "Security Scan Approved" + CHANGE_MANAGEMENT_TICKET_LINKED = "Change Management Ticket Linked" + + +class ReleaseTrain(Enum): + """Valid release train options for the Jira 'Release Train' single-select field.""" + ALPHA_TRAIN = "Alpha Train" + BETA_TRAIN = "Beta Train" + GAMMA_TRAIN = "Gamma Train" + STABLE_TRAIN = "Stable Train" + + def __str__(self): + return self.value + class FieldType(Enum): """ @@ -68,9 +92,13 @@ class FieldType(Enum): LABELS = "labels" def single_select_formatter(value: Any): + if isinstance(value, Enum): + return {"value": value.value} return {"value": value} def multi_select_formatter(values: Any): + if all(isinstance(v, Enum) for v in values): + return [{"value": v.value} for v in values] return [{"value": value} for value in (values if isinstance(values, list) else [values])] def user_formatter(value: Any): @@ -91,6 +119,35 @@ FIELD_FORMATTERS = { } +def get_single_select_formatter(value: CustomFieldOption) -> str: + if not isinstance(value, CustomFieldOption): + raise ValueError(f"Expected CustomFieldOption, got {type(value)}") + return value.value + + +def get_multi_select_formatter(values: list[CustomFieldOption]) -> list[str]: + if not isinstance(values, list): + raise ValueError(f"Expected list of CustomFieldOption, got {type(values)}") + if not all(isinstance(v, CustomFieldOption) for v in values): + raise ValueError("All items in the list must be CustomFieldOption instances") + return [get_single_select_formatter(v) for v in values] + + +def get_user_formatter(user: User) -> dict: + if not isinstance(user, User): + raise ValueError(f"Expected User, got {type(user)}") + return user.name + + +GET_FIELD_FORMATTERS = { + FieldType.TEXT: lambda v: v, + FieldType.SINGLE_SELECT: get_single_select_formatter, + FieldType.MULTI_SELECT: get_multi_select_formatter, + FieldType.USER: get_user_formatter, + FieldType.LABELS: lambda v: v, +} + + class JiraFields(Enum): """ Enumeration of high-level Jira field names used in the CLI. @@ -154,6 +211,10 @@ class JiraFieldInfo: def formatter(self) -> Callable[[Any], Any]: return FIELD_FORMATTERS[self.field_type] + @property + def get_field_formatter(self) -> Callable[[Any], Any]: + return GET_FIELD_FORMATTERS[self.field_type] + FIELD_REGISTRY = { JiraFields.RELEASE_TRAIN: JiraFieldInfo( @@ -171,6 +232,34 @@ FIELD_REGISTRY = { } +def get_field(issue: Issue, name: JiraFields) -> JiraFieldInfo: + """ + Retrieve a field from a Jira issue and format it according to FieldType. + + Args: + issue (Issue): Jira issue instance (from jira-python). + name (JiraFields): Logical Jira field enum member. + + Returns: + Any: The formatted field data (e.g. str, list, dict) appropriate to the type. + + Raises: + ValueError: If the field name is not a valid JiraFields enum member + AttributeError: If the issue does not have the field + """ + if not isinstance(name, JiraFields): + raise ValueError(f"Expected JiraFields enum, got {type(name)}") + try: + info = FIELD_REGISTRY[name] + except KeyError: + raise ValueError(f"Field {name} is not registered in FIELD_REGISTRY") + try: + data = getattr(issue.fields, info.field_id) + except AttributeError: + raise ValueError(f"Issue does not have field {name} ({FIELD_REGISTRY[name].field_id})") + return info.get_field_formatter(data) + + class UpdateFields: """ Builder class for constructing Jira issue field update payloads. diff --git a/cli/fields_test.py b/cli/fields_test.py new file mode 100644 index 0000000..fceeaec --- /dev/null +++ b/cli/fields_test.py @@ -0,0 +1,30 @@ +from jira import JIRA +from rich.console import Console + +from fields import JiraFields as jf +from fields import DeploymentRequirements as dr +from fields import ReleaseTrain as rt +from fields import UpdateFields, get_field +from jql_utils import load_config + + +config = load_config() +console = Console(color_system="truecolor") +jira = JIRA(server=config["server"], basic_auth=(config["username"], config["token"])) + + +tick = jira.issue("AETHER-1") + +deployment_requirements = get_field(tick, jf.DEPLOYMENT_REQUIREMENTS) +reporter = get_field(tick, jf.REPORTER) + +update_fields = UpdateFields() +update_fields.add_field(jf.RELEASE_TRAIN, rt.GAMMA_TRAIN) +update_fields.add_field( + jf.DEPLOYMENT_REQUIREMENTS, [dr.CODE_REVIEW_COMPLETED, dr.QA_SIGN_OFF] +) + +tick.update(fields=update_fields.as_dict()) + +for name, value in tick.raw["fields"].items(): + console.print(f"[bold green]{name}[/bold green]: {value}")