Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add skeleton of issue actions #7

Merged
merged 3 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ repos:
- "click>=8.0.3,!=8.1.4"
- "attrs>=20.3.0"
- "ruamel.yaml>=0.16.6"
- "jinja2>=2.11.3"
pass_filenames: false
args: [--config-file=pyproject.toml]

Expand Down
39 changes: 39 additions & 0 deletions component-config.yaml.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
issues:
- summary: "Errata respin {{ ERRATUM.respin_count }}"
description: "{{ ERRATUM.srpms }}"
assignee: '{{ ERRATUM.people_assigned_to }}'
type: task
id: errata_task
parent: errata_epic
on_respin: close

- summary: "Testing ER#{{ ERRATUM.event.id }} {{ ERRATUM.summary }}"
description: "{{ ERRATUM.url }}"
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: errata_task
on_respin: close

- summary: "SPEC file review"
description: "Review changes made in the SPEC file"
assignee: '{{ ERRATUM.people_assigned_to }}'
type: subtask
id: subtask_spec
parent: errata_task
on_respin: close

- 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: errata_task
on_respin: close
78 changes: 77 additions & 1 deletion newa/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import io
from enum import Enum
from pathlib import Path
from typing import TYPE_CHECKING, TypeVar
from typing import TYPE_CHECKING, Any, Optional, TypeVar

import attrs
import jinja2
import ruamel.yaml
import ruamel.yaml.nodes
import ruamel.yaml.representer
Expand Down Expand Up @@ -40,6 +41,47 @@ def _represent_enum(
return yaml


def default_template_environment() -> jinja2.Environment:
"""
Create a Jinja2 environment with default settings.

Adds common filters, and enables block trimming and left strip.
"""

environment = jinja2.Environment()

environment.trim_blocks = True
environment.lstrip_blocks = True

return environment


def render_template(
template: str,
environment: Optional[jinja2.Environment] = None,
**variables: Any,
) -> str:
"""
Render a template.

:param template: template to render.
:param environment: Jinja2 environment to use.
:param variables: variables to pass to the template.
"""

environment = environment or default_template_environment()

try:
return environment.from_string(template).render(**variables).strip()

except jinja2.exceptions.TemplateSyntaxError as exc:
raise Exception(
f"Could not parse template at line {exc.lineno}.") from exc

except jinja2.exceptions.TemplateError as exc:
raise Exception("Could not render template.") from exc


class EventType(Enum):
""" Event types """

Expand Down Expand Up @@ -141,3 +183,37 @@ class ErratumJob(Job):
@property
def id(self) -> str:
return f'{self.event.id} @ {self.erratum.release}'


#
# Component configuration
#
class IssueType(Enum):
EPIC = 'epic'
TASK = 'task'
SUBTASK = 'subtask'


class OnRespinAction(Enum):
# TODO: what's the default? It would simplify the class a bit.
KEEP = 'keep'
CLOSE = 'close'


@define
class IssueAction: # type: ignore[no-untyped-def]
summary: str
description: str
assignee: str
id: str
on_respin: Optional[OnRespinAction] = field( # type: ignore[var-annotated]
converter=lambda value: OnRespinAction(value) if value else None)
type: IssueType = field(converter=IssueType)
parent: Optional[str] = None


@define
class ErratumConfig(Serializable):
issues: list[IssueAction] = field( # type: ignore[var-annotated]
factory=list, converter=lambda issues: [
IssueAction(**issue) for issue in issues])
47 changes: 38 additions & 9 deletions newa/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
import os.path
from collections.abc import Iterable, Iterator
from pathlib import Path
from typing import Any

import click
from attrs import define

from . import Erratum, ErratumJob, Event, EventType, InitialErratum
from . import Erratum, ErratumConfig, ErratumJob, Event, EventType, InitialErratum, render_template

logging.basicConfig(
format='%(asctime)s %(message)s',
Expand Down Expand Up @@ -125,14 +126,42 @@ def cmd_jira(ctx: CLIContext) -> None:

for erratum_job in ctx.load_erratum_jobs('event-'):
# read Jira issue configuration
# get list of matching actions

# for action in actions:
# create epic
# or create task
# or create sutask
# if subtask assoc. with recipes
# clone object with yaml
config = ErratumConfig.from_yaml_file(Path('component-config.yaml.sample'))

# TODO: record created/existing issues. Instead of `Any`, maybe something
# from the Jira library would be stored. Or just a Jira ticket ID.
known_issues: dict[str, Any] = {}

# Iterate over issue actions. Take one, if it's not possible to finish it,
# put it back at the end of the queue.
issue_actions = config.issues[:]

while issue_actions:
action = issue_actions.pop(0)

print(f'* Would create a {action.type.name} issue:')
print(f' summary: {action.summary}')
print(f' summary: {action.description}')

if action.id in known_issues:
raise Exception(f'Issue "{action.id}" is already created!')
thrix marked this conversation as resolved.
Show resolved Hide resolved

if action.parent and action.parent not in known_issues:
print(f' !! Parent issue, "{action.parent}", is unknown, will try later')
print()

issue_actions.append(action)
continue

print()
print(f' Issue would be assigned to {action.assignee}.')
print(f' rendered: >>{render_template(action.assignee, ERRATUM=erratum_job)}<<')
print(f' Will remember the issue as `{action.id}`.')
if action.parent:
print(f' Issue would have issue `{action.parent}` as its parent.')
print()

known_issues[action.id] = True

# erratum_job.issue = ...
# what's recipe? doesn't it belong to "schedule"?
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ keywords = [
dependencies = [
"click>=8.0.3,!=8.1.4",
"attrs>=20.3.0",
"ruamel.yaml>=0.16.6"
"ruamel.yaml>=0.16.6",
"jinja2>=2.11.3"
]

[project.optional-dependencies]
Expand Down
Loading