diff --git a/component-config.yaml.sample b/component-config.yaml.sample index 7620541..72d8770 100644 --- a/component-config.yaml.sample +++ b/component-config.yaml.sample @@ -8,11 +8,16 @@ transitions: dropped: - Dropped +defaults: + assignee: '{{ ERRATUM.people_assigned_to }}' +# fields: +# "Pool Team": "my_great_team" +# "Story Points": 0 + issues: - summary: "Errata Workflow Checklist {% if ERRATUM.respin_count > 0 %}(respin {{ ERRATUM.respin_count }}){% endif %}" description: "Task tracking particular respin of errata." - assignee: '{{ ERRATUM.people_assigned_to }}' type: task id: errata_task parent_id: errata_epic @@ -20,14 +25,12 @@ issues: - summary: "Testing ER#{{ ERRATUM.id }} {{ ERRATUM.summary }} ({{ERRATUM.release}})" description: "Epic tracking all work on errata related to a specific release." - assignee: '{{ ERRATUM.people_assigned_to }}' type: epic id: errata_epic on_respin: keep - summary: "Errata filelist check" description: "Compare errata filelist with a previously released advisory" - assignee: '{{ ERRATUM.people_assigned_to }}' type: subtask id: subtask_filelist parent_id: errata_task @@ -35,7 +38,6 @@ issues: - summary: "SPEC file review" description: "Review changes made in the SPEC file" - assignee: '{{ ERRATUM.people_assigned_to }}' type: subtask id: subtask_spec parent_id: errata_task @@ -43,7 +45,6 @@ issues: - summary: "rpminspect review" description: "Review rpminspect results in the CI Dashboard for all builds" - assignee: '{{ ERRATUM.people_assigned_to }}' type: subtask id: subtask_rpminspect parent_id: errata_task @@ -51,7 +52,6 @@ issues: - summary: "regression testing" description: "Run automated tests" - assignee: '{{ ERRATUM.people_assigned_to }}' type: subtask id: subtask_regression parent_id: errata_task diff --git a/newa/__init__.py b/newa/__init__.py index 96834db..471382d 100644 --- a/newa/__init__.py +++ b/newa/__init__.py @@ -1101,13 +1101,13 @@ class OnRespinAction(Enum): @define -class IssueAction: # type: ignore[no-untyped-def] - summary: str - description: str - id: str - type: IssueType = field(converter=IssueType) +class IssueAction(Serializable): # type: ignore[no-untyped-def] + type: IssueType = field(converter=IssueType, default=IssueType.TASK) on_respin: OnRespinAction = field( # type: ignore[var-annotated] converter=lambda value: OnRespinAction(value), default=OnRespinAction.CLOSE) + summary: Optional[str] = None + description: Optional[str] = None + id: Optional[str] = None assignee: Optional[str] = None parent_id: Optional[str] = None job_recipe: Optional[str] = None @@ -1118,9 +1118,10 @@ class IssueAction: # type: ignore[no-untyped-def] @define class IssueConfig(Serializable): # type: ignore[no-untyped-def] - project: str = field() transitions: dict[str, list[str]] = field() + defaults: Optional[IssueAction] = field( # type: ignore[var-annotated] + converter=lambda action: IssueAction(**action) if action else None, default=None) issues: list[IssueAction] = field( # type: ignore[var-annotated] factory=list, converter=lambda issues: [ IssueAction(**issue) for issue in issues]) diff --git a/newa/cli.py b/newa/cli.py index 6d45b46..590be9b 100644 --- a/newa/cli.py +++ b/newa/cli.py @@ -1,3 +1,4 @@ +import copy import datetime import logging import multiprocessing @@ -26,6 +27,7 @@ ExecuteJob, Execution, Issue, + IssueAction, IssueConfig, IssueHandler, JiraJob, @@ -456,8 +458,16 @@ def _jira_fake_id_generator() -> Generator[str, int, None]: jira_none_id = _jira_fake_id_generator() + def _default_action_id_generator() -> Generator[str, int, None]: + n = 1 + while True: + yield f'DEFAULT_ACTION_ID_{n}' + n += 1 + + default_action_id = _default_action_id_generator() + # load issue mapping specified on a command line - issue_mapping = {} + issue_mapping: dict[str, str] = {} artifact_jobs = ctx.load_artifact_jobs('event-') # issue mapping is relevant only when using issue-config file @@ -511,6 +521,25 @@ def _jira_fake_id_generator() -> Generator[str, int, None]: # Use to prevent endless loop over the issue actions. endless_loop_check: dict[str, int] = {} + # updates + def _update_action_with_defaults( + action: IssueAction, + defaults: Optional[IssueAction] = None) -> IssueAction: + new_action = copy.deepcopy(action) + if not isinstance(defaults, IssueAction): + return new_action + for attr_name in dir(defaults): + attr = getattr(defaults, attr_name) + if attr and (not attr_name.startswith('_') or callable(attr)): + if attr_name == 'fields' and defaults.fields: + if new_action.fields: + new_action.fields.update(copy.deepcopy(defaults.fields)) + else: + setattr(new_action, attr_name, copy.deepcopy(defaults.fields)) + elif not getattr(new_action, attr_name, None): + setattr(new_action, attr_name, copy.deepcopy(attr)) + return new_action + # Iterate over issue actions. Take one, if it's not possible to finish it, # put it back at the end of the queue. while issue_actions: @@ -518,6 +547,9 @@ def _jira_fake_id_generator() -> Generator[str, int, None]: ctx.logger.info(f"Processing {action.id}") + # update action object with default attributes when not present + action = _update_action_with_defaults(action, config.defaults) + if action.when and not eval_test(action.when, JOB=artifact_job, EVENT=artifact_job.event, @@ -527,6 +559,13 @@ def _jira_fake_id_generator() -> Generator[str, int, None]: ctx.logger.info(f"Skipped, issue action is irrelevant ({action.when})") continue + if not action.id: + action.id = next(default_action_id) + if not action.summary: + raise Exception(f"Action {action} does not have a 'summary' defined.") + if not action.description: + raise Exception(f"Action {action} does not have a 'description' defined.") + rendered_summary = render_template( action.summary, ERRATUM=artifact_job.erratum,