Skip to content

Commit

Permalink
envoy.ci.report: Add package (#2292)
Browse files Browse the repository at this point in the history
Signed-off-by: Ryan Northey <[email protected]>
  • Loading branch information
phlax authored Oct 10, 2024
1 parent a65415f commit 768d065
Show file tree
Hide file tree
Showing 27 changed files with 2,604 additions and 0 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,22 @@ pypi: https://pypi.org/project/envoy.base.utils
---


#### [envoy.ci.report](envoy.ci.report)

version: 0.0.1.dev0

pypi: https://pypi.org/project/envoy.ci.report

##### requirements:

- [abstracts](https://pypi.org/project/abstracts) >=0.0.12
- [aio.api.github](https://pypi.org/project/aio.api.github) >=0.2.7
- [aio.core](https://pypi.org/project/aio.core) >=0.10.3
- [aio.run.runner](https://pypi.org/project/aio.run.runner) >=0.3.3

---


#### [envoy.code.check](envoy.code.check)

version: 0.5.14.dev0
Expand Down
2 changes: 2 additions & 0 deletions envoy.ci.report/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

toolshed_package("envoy.ci.report")
5 changes: 5 additions & 0 deletions envoy.ci.report/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

envoy.ci.report
==========================

Release publishing tool used in Envoy proxy's CI
1 change: 1 addition & 0 deletions envoy.ci.report/VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.0.1
24 changes: 24 additions & 0 deletions envoy.ci.report/envoy/ci/report/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@

toolshed_library(
"envoy.ci.report",
dependencies=[
"//deps:reqs#abstracts",
"//deps:reqs#aio.api.github",
"//deps:reqs#aio.core",
"//deps:reqs#aio.run.runner",
],
sources=[
"__init__.py",
"ci.py",
"cmd.py",
"exceptions.py",
"interface.py",
"runner.py",
"abstract/__init__.py",
"abstract/filters.py",
"abstract/format.py",
"abstract/runner.py",
"abstract/runs.py",
],
tags=["abstract-types"],
)
21 changes: 21 additions & 0 deletions envoy.ci.report/envoy/ci/report/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

from . import abstract, exceptions, interface
from .ci import CIRuns
from .runner import (
CreationTimeFilter, JSONFormat, MarkdownFormat,
ReportRunner, StatusFilter)
from .cmd import cmd, main


__all__ = (
"abstract",
"CIRuns",
"cmd",
"CreationTimeFilter",
"exceptions",
"interface",
"JSONFormat",
"main",
"MarkdownFormat",
"ReportRunner",
"StatusFilter")
17 changes: 17 additions & 0 deletions envoy.ci.report/envoy/ci/report/abstract/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#
from .filters import ACreationTimeFilter, AStatusFilter, AWorkflowFilter
from .format import AFormat, AJSONFormat, AMarkdownFormat
from .runner import AReportRunner
from .runs import ACIRuns


__all__ = [
"ACIRuns",
"ACreationTimeFilter",
"AFormat",
"AJSONFormat",
"AMarkdownFormat",
"AReportRunner",
"AStatusFilter",
"AWorkflowFilter",
]
119 changes: 119 additions & 0 deletions envoy.ci.report/envoy/ci/report/abstract/filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@

import argparse
from datetime import datetime, timedelta
from functools import cached_property

import abstracts

from envoy.ci.report import interface


class AWorkflowFilter(metaclass=abstracts.Abstraction):

def __init__(self, args: argparse.Namespace) -> None:
self.args = args

def __str__(self) -> str:
return self.filter_string

@property
@abstracts.interfacemethod
def filter_string(self) -> str:
raise NotImplementedError


@abstracts.implementer(interface.IWorkflowFilter)
class AStatusFilter(AWorkflowFilter):

@property
def filter_string(self) -> str:
if self.args.status == "all":
return ""
return self.args.status


@abstracts.implementer(interface.IWorkflowFilter)
class ACreationTimeFilter(AWorkflowFilter):

@property
def filter_string(self) -> str:
if self.time_start_string and self.time_end_string:
return f"{self.time_start_string}..{self.time_end_string}"
elif self.time_start_string:
return f">{self.time_start_string}"
return ""

@cached_property
def now(self) -> datetime:
return datetime.utcnow()

@cached_property
def start_day(self) -> datetime:
return self.now.replace(
hour=0,
minute=0,
second=0,
microsecond=0)

@cached_property
def start_hour(self) -> datetime:
return self.now.replace(
minute=0,
second=0,
microsecond=0)

@cached_property
def start_week(self) -> datetime:
return (
self.now
- timedelta(days=self.now.weekday())).replace(
hour=0,
minute=0,
second=0,
microsecond=0)

@cached_property
def time_end(self) -> datetime | None:
match self.args.previous:
case "day":
return self.start_day
case "week":
return self.start_week
case "hour":
return self.start_hour
case _:
return None

@property
def time_end_string(self) -> str:
return (
self.time_end.strftime("%Y-%m-%dT%H:%M:%SZ")
if self.time_end
else "")

@cached_property
def time_start(self) -> datetime:
match (self.args.current, self.args.previous):
case ("day", _):
return self.start_day
case ("week", _):
return self.start_week
case ("hour", _):
return self.start_hour
case (_, "day"):
return self.start_day - timedelta(days=1)
case (_, "week"):
return self.start_week - timedelta(weeks=1)
case (_, "hour"):
return self.start_hour - timedelta(hours=1)
case _:
# default max is 1 week
# TODO: allow start/end times to be set directly
return self.now - timedelta(hours=(24 * 7))

@property
def time_start_string(self) -> str:
return (
self.time_start.strftime("%Y-%m-%dT%H:%M:%SZ")
if self.time_start
else "")
63 changes: 63 additions & 0 deletions envoy.ci.report/envoy/ci/report/abstract/format.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@

import json
from datetime import datetime

import abstracts

from envoy.ci.report import interface


class AFormat(metaclass=abstracts.Abstraction):

def __call__(self, data: dict) -> None:
self.out(data)

@abstracts.interfacemethod
def out(self, data: dict) -> None:
raise NotImplementedError


@abstracts.implementer(interface.IFormat)
class AJSONFormat(AFormat):

def out(self, data: dict) -> None:
print(json.dumps(data))


@abstracts.implementer(interface.IFormat)
class AMarkdownFormat(AFormat):

def out(self, data: dict) -> None:
for commit, events in data.items():
self._handle_commit(commit, events)

def _handle_commit(self, commit: str, events: list[dict]) -> None:
outcome = (
"failed"
if any(event["workflow"]["conclusion"] == "failure"
for event
in events)
else "succeeded")
target_branch = events[0]["request"]["target-branch"]
commit_url = f"https://github.com/envoyproxy/envoy/commit/{commit}"
print(f"[{target_branch}@{commit[:7]}]({commit_url}): {outcome}")
for event in events:
self._handle_event(event)

def _handle_event(self, event: dict) -> None:
event_type = event["event"]
request_started = datetime.utcfromtimestamp(
int(event["request"]["started"])).isoformat()
workflow_name = event["workflow"]["name"]
conclusion = event["workflow"]["conclusion"]
workflow_id = event["workflow_id"]
request_id = event["request_id"]
workflow_url = (
"https://github.com/envoyproxy/envoy/"
f"actions/runs/{workflow_id}")
request_url = (
"https://github.com/envoyproxy/envoy/"
f"actions/runs/{request_id}")
print(
f" -> [[{event_type}@{request_started}]({request_url})]: "
f"[{workflow_name} ({conclusion})]({workflow_url})")
Loading

0 comments on commit 768d065

Please sign in to comment.