ICTU/quality-time

View on GitHub
components/shared_code/src/shared/model/report.py

Summary

Maintainability
A
25 mins
Test Coverage
"""A class that represents a report."""

from typing import cast

from shared.model.source import Source
from shared.utils.type import Color, MetricId, ReportId, SourceId, Status, SubjectId

from .measurement import Measurement
from .metric import Metric
from .subject import Subject

STATUS_COLOR_MAPPING = cast(
    dict[Status, Color],
    {
        "target_met": "green",
        "debt_target_met": "grey",
        "near_target_met": "yellow",
        "target_not_met": "red",
        "informative": "blue",
    },
)


class Report(dict):
    """Class representing a report."""

    def __init__(self, data_model: dict, report_data: dict) -> None:
        """Instantiate a report."""
        self.__data_model = data_model

        subject_data = report_data.get("subjects", {})
        report_data["subjects"] = self._subjects(subject_data)
        super().__init__(report_data)

        self.subjects = list(self.subjects_dict.values())
        self.subject_uuids = set(self.subjects_dict.keys())

        self.metrics_dict = self._metrics()
        self.metrics = list(self.metrics_dict.values())

        self.sources_dict = self._sources()
        self.sources = list(self.sources_dict.values())

        if "_id" in self:  # pragma: no feature-test-cover
            self["_id"] = str(self["_id"])

    @property
    def metric_uuids(self) -> set[MetricId]:
        """Return only the metric ids."""
        return set(self.metrics_dict.keys())

    @property
    def source_uuids(self) -> set[SourceId]:
        """Return only the source ids."""
        return set(self.sources_dict.keys())

    @property
    def uuid(self) -> ReportId:
        """Return the uuid of this report."""
        return cast(ReportId, self["report_uuid"])  # pragma: no feature-test-cover

    @property
    def subjects_dict(self) -> dict[SubjectId, Subject]:
        """Return the dict with subject uuids as keys and subject instances as values."""
        return cast(dict[SubjectId, Subject], self.get("subjects", {}))

    @property
    def name(self) -> str:
        """A different access to title."""
        return cast(str, self.get("title", ""))

    def __eq__(self, other: object) -> bool:
        """Return whether the reports are equal."""
        return self.uuid == other.uuid if isinstance(other, self.__class__) else False  # pragma: no feature-test-cover

    def _subjects(self, subject_data: dict) -> dict[str, Subject]:
        """Instantiate subjects of this report."""
        return {
            subject_uuid: Subject(self.__data_model, subject, subject_uuid, self)
            for subject_uuid, subject in subject_data.items()
        }

    def _metrics(self) -> dict[MetricId, Metric]:
        """All metrics of all subjects of this report."""
        metrics = {}
        for subject in self.subjects:
            metrics.update(subject.metrics_dict)
        return metrics

    def _sources(self) -> dict[SourceId, Source]:
        """All sources of this report."""
        sources = {}
        for metric in self.metrics:
            sources.update(metric.sources_dict)
        return sources

    def summarize(self, measurements: dict[MetricId, list[Measurement]]) -> dict:
        """Create a summary dict of this report."""
        summary = dict(self)
        summary["summary"] = {"red": 0, "green": 0, "yellow": 0, "grey": 0, "blue": 0, "white": 0}
        summary["subjects"] = {subject.uuid: subject.summarize(measurements) for subject in self.subjects}

        for metric in self.metrics:
            latest_measurement = measurements[metric.uuid][-1] if measurements and metric.uuid in measurements else None
            metric_status = metric.status(latest_measurement)
            color = STATUS_COLOR_MAPPING[metric_status] if metric_status is not None else "white"
            summary["summary"][color] += 1
        return summary

    def instance_and_parents_for_uuid(
        self,
        metric_uuid: MetricId | None = None,
        source_uuid: SourceId | None = None,
    ) -> tuple:
        """Find an instance and its parents.

        For example, if a metric_uuid is provided, this function will return the metric, its subject and its report in
        that order: (Metric, Subject)

        Only one of the uuid arguments should be filled. If more are filled, all but the first one will be ignored.
        """
        if metric_uuid:
            metric = self.metrics_dict[metric_uuid]
            subject = self.subjects_dict[metric.subject_uuid]
            return metric, subject
        if source_uuid:  # pragma: no feature-test-cover
            source = self.sources_dict[source_uuid]
            metric = source.metric
            subject = self.subjects_dict[metric.subject_uuid]
            return source, metric, subject
        msg = "metric_uuid and source_uuid cannot both be None"  # pragma: no feature-test-cover
        raise RuntimeError(msg)  # pragma: no feature-test-cover


def get_metrics_from_reports(reports: list[Report]) -> dict[MetricId, Metric]:  # pragma: no feature-test-cover
    """Return the metrics from the reports."""
    metrics: dict[MetricId, Metric] = {}

    for report in reports:
        metrics_dict: dict[MetricId, Metric] = report.metrics_dict.copy()
        for metric in metrics_dict.values():
            metric["report_uuid"] = report["report_uuid"]
            metric["issue_tracker"] = report.get("issue_tracker", {})
        metrics.update(metrics_dict)
    return metrics