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

Show spinner on first edit of source parameters. #8747

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
95 changes: 59 additions & 36 deletions components/api_server/src/initialization/migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,39 @@

import logging

import pymongo
from pymongo.collection import Collection
from pymongo.database import Database

from shared.model.metric import Metric


def perform_migrations(database: Database) -> None: # pragma: no feature-test-cover
"""Perform database migrations."""
change_accessibility_violation_metrics_to_violations(database)
fix_branch_parameters_without_value(database)
for report in database.reports.find(filter={"last": True, "deleted": {"$exists": False}}):
change_accessibility_violation_metrics_to_violations(database, report)
fix_branch_parameters_without_value(database, report)
add_source_parameter_hash(database, report)


def change_accessibility_violation_metrics_to_violations(database: Database) -> None: # pragma: no feature-test-cover
def change_accessibility_violation_metrics_to_violations( # pragma: no feature-test-cover
database: Database, report
) -> None:
"""Replace accessibility metrics with the violations metric."""
# Added after Quality-time v5.5.0, see https://github.com/ICTU/quality-time/issues/562
for report in database.reports.find(filter={"last": True, "deleted": {"$exists": False}}):
report_uuid = report["report_uuid"]
logging.info("Checking report for accessibility metrics: %s", report_uuid)
changed = False
for subject in report["subjects"].values():
for metric in subject["metrics"].values():
if metric["type"] == "accessibility":
change_accessibility_violations_metric_to_violations(metric)
changed = True
if changed:
logging.info("Updating report to change its accessibility metrics to violations metrics: %s", report_uuid)
replace_report(database, report)
else:
logging.info("No accessibility metrics found in report: %s", report_uuid)
report_uuid = report["report_uuid"]
logging.info("Checking report for accessibility metrics: %s", report_uuid)
changed = False
for subject in report["subjects"].values():
for metric in subject["metrics"].values():
if metric["type"] == "accessibility":
change_accessibility_violations_metric_to_violations(metric)
changed = True
if changed:
logging.info("Updating report to change its accessibility metrics to violations metrics: %s", report_uuid)
replace_document(database.reports, report)
else:
logging.info("No accessibility metrics found in report: %s", report_uuid)


def change_accessibility_violations_metric_to_violations(metric: dict) -> None: # pragma: no feature-test-cover
Expand All @@ -39,22 +46,21 @@ def change_accessibility_violations_metric_to_violations(metric: dict) -> None:
metric["unit"] = "accessibility violations"


def fix_branch_parameters_without_value(database: Database) -> None: # pragma: no feature-test-cover
def fix_branch_parameters_without_value(database: Database, report) -> None: # pragma: no feature-test-cover
"""Set the branch parameter of sources to 'master' (the previous default) if they have no value."""
# Added after Quality-time v5.11.0, see https://github.com/ICTU/quality-time/issues/8045
for report in database.reports.find(filter={"last": True, "deleted": {"$exists": False}}):
report_uuid = report["report_uuid"]
logging.info("Checking report for sources with empty branch parameters: %s", report_uuid)
changed = False
for source in sources_with_branch_parameter(report):
if not source["parameters"].get("branch"):
source["parameters"]["branch"] = "master"
changed = True
if changed:
logging.info("Updating report to change sources with empty branch parameter: %s", report_uuid)
replace_report(database, report)
else:
logging.info("No sources with empty branch parameters found in report: %s", report_uuid)
report_uuid = report["report_uuid"]
logging.info("Checking report for sources with empty branch parameters: %s", report_uuid)
changed = False
for source in sources_with_branch_parameter(report):
if not source["parameters"].get("branch"):
source["parameters"]["branch"] = "master"
changed = True
if changed:
logging.info("Updating report to change sources with empty branch parameter: %s", report_uuid)
replace_document(database.reports, report)
else:
logging.info("No sources with empty branch parameters found in report: %s", report_uuid)


METRICS_WITH_SOURCES_WITH_BRANCH_PARAMETER = {
Expand Down Expand Up @@ -87,8 +93,25 @@ def sources_with_branch_parameter(report: dict): # pragma: no feature-test-cove
yield source


def replace_report(database: Database, report) -> None: # pragma: no feature-test-cover
"""Replace the report in the database."""
report_id = report["_id"]
del report["_id"]
database.reports.replace_one({"_id": report_id}, report)
def add_source_parameter_hash(database: Database, report) -> None: # pragma: no feature-test-cover
"""Add source parameter hashes to the latest measurements."""
# Added after Quality-time v5.12.0, see https://github.com/ICTU/quality-time/issues/8736
for subject in report["subjects"].values():
for metric_uuid, metric in subject["metrics"].items():
latest_measurement = database.measurements.find_one(
filter={"metric_uuid": metric_uuid},
sort=[("start", pymongo.DESCENDING)],
)
if not latest_measurement:
continue
if latest_measurement.get("source_parameter_hash"):
continue
latest_measurement["source_parameter_hash"] = Metric({}, metric, metric_uuid).source_parameter_hash()
replace_document(database.measurements, latest_measurement)


def replace_document(collection: Collection, document) -> None: # pragma: no feature-test-cover
"""Replace the document in the collection."""
document_id = document["_id"]
del document["_id"]
collection.replace_one({"_id": document_id}, document)
76 changes: 50 additions & 26 deletions components/api_server/tests/initialization/test_migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,22 @@ def inserted_report(self, **kwargs):
return report


class NoOpMigrationTest(MigrationTestCase):
"""Unit tests for empty database and empty reports."""

def test_no_reports(self):
"""Test that the migration succeeds without reports."""
self.database.reports.find.return_value = []
perform_migrations(self.database)
self.database.reports.replace_one.assert_not_called()

def test_empty_reports(self):
"""Test that the migration succeeds when the report does not have anything to migrate."""
self.database.reports.find.return_value = [self.existing_report("issues")]
perform_migrations(self.database)
self.database.reports.replace_one.assert_not_called()


class ChangeAccessibilityViolationsTest(MigrationTestCase):
"""Unit tests for the accessibility violations database migration."""

Expand Down Expand Up @@ -55,18 +71,6 @@ def inserted_report(
metric_type="violations", metric_name=metric_name, metric_unit=metric_unit, **kwargs
)

def test_no_reports(self):
"""Test that the migration succeeds without reports."""
self.database.reports.find.return_value = []
perform_migrations(self.database)
self.database.reports.replace_one.assert_not_called()

def test_report_without_accessibility_metrics(self):
"""Test that the migration succeeds with reports, but without accessibility metrics."""
self.database.reports.find.return_value = [self.existing_report(metric_type="loc")]
perform_migrations(self.database)
self.database.reports.replace_one.assert_not_called()

def test_report_with_accessibility_metric(self):
"""Test that the migration succeeds with an accessibility metric."""
self.database.reports.find.return_value = [self.existing_report()]
Expand Down Expand Up @@ -101,22 +105,9 @@ def existing_report(
"""Extend to add sources and an extra metric without sources."""
report = super().existing_report(metric_type=metric_type)
report["subjects"][SUBJECT_ID]["metrics"][METRIC_ID2] = {"type": "issues"}
if sources:
report["subjects"][SUBJECT_ID]["metrics"][METRIC_ID]["sources"] = sources
report["subjects"][SUBJECT_ID]["metrics"][METRIC_ID]["sources"] = sources
return report

def test_no_reports(self):
"""Test that the migration succeeds without reports."""
self.database.reports.find.return_value = []
perform_migrations(self.database)
self.database.reports.replace_one.assert_not_called()

def test_report_without_branch_parameter(self):
"""Test that the migration succeeds with reports, but without metrics with a branch parameter."""
self.database.reports.find.return_value = [self.existing_report()]
perform_migrations(self.database)
self.database.reports.replace_one.assert_not_called()

def test_report_with_non_empty_branch_parameter(self):
"""Test that the migration succeeds when the branch parameter is not empty."""
self.database.reports.find.return_value = [
Expand All @@ -140,3 +131,36 @@ def test_report_with_branch_parameter_without_value(self):
}
inserted_report = self.inserted_report(metric_type="source_up_to_dateness", sources=inserted_sources)
self.database.reports.replace_one.assert_called_once_with({"_id": "id"}, inserted_report)


class SourceParameterHashMigrationTest(MigrationTestCase):
"""Unit tests for the source parameter hash database migration."""

def existing_report(self, sources: dict[SourceId, dict[str, str | dict[str, str]]] | None = None):
"""Extend to add sources and an extra metric without sources."""
report = super().existing_report(metric_type="loc")
report["subjects"][SUBJECT_ID]["metrics"][METRIC_ID2] = {"type": "issues"}
if sources:
report["subjects"][SUBJECT_ID]["metrics"][METRIC_ID]["sources"] = sources
return report

def test_report_with_sources_without_source_parameter_hash(self):
"""Test a report with sources and measurements."""
self.database.measurements.find_one.return_value = {"_id": "id", "metric_uuid": METRIC_ID}
self.database.reports.find.return_value = [self.existing_report(sources={SOURCE_ID: {"type": "cloc"}})]
perform_migrations(self.database)
inserted_measurement = {"metric_uuid": METRIC_ID, "source_parameter_hash": "8c3b464958e9ad0f20fb2e3b74c80519"}
self.database.measurements.replace_one.assert_called_once_with({"_id": "id"}, inserted_measurement)

def test_report_without_sources(self):
"""Test a report without sources."""
self.database.reports.find.return_value = [self.existing_report()]
perform_migrations(self.database)
self.database.measurements.replace_one.assert_not_called()

def test_metric_without_measurement(self):
"""Test a metric without measurements."""
self.database.measurements.find_one.return_value = None
self.database.reports.find.return_value = [self.existing_report(sources={SOURCE_ID: {"type": "cloc"}})]
perform_migrations(self.database)
self.database.measurements.replace_one.assert_not_called()
1 change: 1 addition & 0 deletions docs/src/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ If your currently installed *Quality-time* version is v4.10.0 or older, please r
- When creating an issue fails, show the reason in the toaster message instead of "undefined". Fixes [#8567](https://github.com/ICTU/quality-time/issues/8567).
- Hiding metrics without issues would not hide metrics with deleted issues. Fixes [#8699](https://github.com/ICTU/quality-time/issues/8699).
- The spinner indicating that the latest measurement of a metric is not up-to-date with the latest source configuration would not disappear if the measurement value made with the latest source configuration was equal to the measurement value made with the previous source configuration. Fixes [#8702](https://github.com/ICTU/quality-time/issues/8702).
- The spinner indicating that the latest measurement of a metric is not up-to-date with the latest source configuration would not appear on the first edit of a metric after upgrading to v5.12.0. Fixes [#8736](https://github.com/ICTU/quality-time/issues/8736).

### Added

Expand Down