diff --git a/components/frontend/src/api/report.js b/components/frontend/src/api/report.js index 21cdd421ba..b52db57898 100644 --- a/components/frontend/src/api/report.js +++ b/components/frontend/src/api/report.js @@ -34,7 +34,10 @@ function get_changelog(nr_changes, uuids) { if (Object.keys(uuids).includes("subject_uuid")) { return fetch_server_api('get', `changelog/report/${uuids.report_uuid}/subject/${uuids.subject_uuid}/${nr_changes}`) } - return fetch_server_api('get', `changelog/report/${uuids.report_uuid}/${nr_changes}`) + if (Object.keys(uuids).includes("report_uuid")) { + return fetch_server_api('get', `changelog/report/${uuids.report_uuid}/${nr_changes}`) + } + return fetch_server_api('get', `changelog/${nr_changes}`) } export { diff --git a/components/frontend/src/changelog/ChangeLog.js b/components/frontend/src/changelog/ChangeLog.js index 7b62318f13..9610c4f9ac 100644 --- a/components/frontend/src/changelog/ChangeLog.js +++ b/components/frontend/src/changelog/ChangeLog.js @@ -12,7 +12,10 @@ function fetch_changelog(uuids, nrChanges, setChanges) { } export function ChangeLog(props) { - let scope = "Changes in this report"; + let scope = "Changes in this instance of Quality-time"; + if (props.report_uuid) { + scope = "Changes in this report" + } if (props.subject_uuid) { scope = "Changes to this subject"; } @@ -25,7 +28,10 @@ export function ChangeLog(props) { const [nrChanges, setNrChanges] = useState(10); const [changes, setChanges] = useState([]); useEffect(() => { - let uuids = { report_uuid: props.report.report_uuid }; + let uuids = {}; + if (props.report_uuid) { + uuids.report_uuid = props.report_uuid; + } if (props.subject_uuid) { uuids.subject_uuid = props.subject_uuid; } @@ -36,7 +42,7 @@ export function ChangeLog(props) { uuids.source_uuid = props.source_uuid; } fetch_changelog(uuids, nrChanges, setChanges) - }, [props.report.report_uuid, props.subject_uuid, props.metric_uuid, props.source_uuid, props.report.timestamp, nrChanges]); + }, [props.report_uuid, props.subject_uuid, props.metric_uuid, props.source_uuid, props.timestamp, nrChanges]); let rows = []; changes.forEach((change) => rows.push( diff --git a/components/frontend/src/metric/MeasurementDetails.js b/components/frontend/src/metric/MeasurementDetails.js index 37ea883ad7..c87f454bc1 100644 --- a/components/frontend/src/metric/MeasurementDetails.js +++ b/components/frontend/src/metric/MeasurementDetails.js @@ -61,7 +61,7 @@ export function MeasurementDetails(props) { reload={props.reload} report_uuid={report_uuid} /> - + } ); diff --git a/components/frontend/src/report/ReportTitle.js b/components/frontend/src/report/ReportTitle.js index 34f45f6ca0..dab32a0ecf 100644 --- a/components/frontend/src/report/ReportTitle.js +++ b/components/frontend/src/report/ReportTitle.js @@ -30,7 +30,10 @@ export function ReportTitle(props) { - + {!props.readOnly && diff --git a/components/frontend/src/report/ReportsTitle.js b/components/frontend/src/report/ReportsTitle.js index d73bcc7bda..e482d87741 100644 --- a/components/frontend/src/report/ReportsTitle.js +++ b/components/frontend/src/report/ReportsTitle.js @@ -1,6 +1,7 @@ import React from 'react'; import { HeaderWithDetails } from '../widgets/HeaderWithDetails'; import { Grid, Segment } from 'semantic-ui-react'; +import { ChangeLog } from '../changelog/ChangeLog'; import { StringInput } from '../fields/StringInput'; import { set_reports_attribute } from '../api/report'; @@ -27,6 +28,11 @@ export function ReportsTitle(props) { /> + + + + + diff --git a/components/frontend/src/source/Source.js b/components/frontend/src/source/Source.js index 5b85c74888..653ab584a7 100644 --- a/components/frontend/src/source/Source.js +++ b/components/frontend/src/source/Source.js @@ -74,7 +74,11 @@ export function Source(props) { } - + {!props.readOnly && diff --git a/components/frontend/src/subject/SubjectTitle.js b/components/frontend/src/subject/SubjectTitle.js index c8aa29a1c9..2ff9ade844 100644 --- a/components/frontend/src/subject/SubjectTitle.js +++ b/components/frontend/src/subject/SubjectTitle.js @@ -9,7 +9,9 @@ import { delete_subject, set_subject_attribute } from '../api/subject'; export function SubjectTitle(props) { const current_subject_type = props.datamodel.subjects[props.subject.type] || { name: "Unknown subject type", description: "No description" }; + const report_uuid = props.report.report_uuid; const subject_name = props.subject.name || current_subject_type.name; + const subject_uuid = props.subject_uuid; return ( @@ -27,7 +29,7 @@ export function SubjectTitle(props) { set_subject_attribute(props.report.report_uuid, props.subject_uuid, "type", value, props.reload)} + set_value={(value) => set_subject_attribute(report_uuid, subject_uuid, "type", value, props.reload)} subject_type={props.subject.type} /> @@ -36,14 +38,18 @@ export function SubjectTitle(props) { label="Subject name" placeholder={current_subject_type.name} readOnly={props.readOnly} - set_value={(value) => set_subject_attribute(props.report.report_uuid, props.subject_uuid, "name", value, props.reload)} + set_value={(value) => set_subject_attribute(report_uuid, subject_uuid, "name", value, props.reload)} value={props.subject.name} /> - + {!props.readOnly && @@ -54,7 +60,7 @@ export function SubjectTitle(props) { last={props.last_subject} moveable="subject" onClick={(direction) => { - set_subject_attribute(props.report.report_uuid, props.subject_uuid, "position", direction, props.reload) + set_subject_attribute(report_uuid, subject_uuid, "position", direction, props.reload) }} slot="position" /> @@ -63,7 +69,7 @@ export function SubjectTitle(props) { floated='right' negative icon - onClick={() => delete_subject(props.report.report_uuid, props.subject_uuid, props.reload)} + onClick={() => delete_subject(report_uuid, subject_uuid, props.reload)} primary > Delete subject diff --git a/components/server/src/database/reports.py b/components/server/src/database/reports.py index a5e7bba28d..ed701e4ef4 100644 --- a/components/server/src/database/reports.py +++ b/components/server/src/database/reports.py @@ -90,15 +90,15 @@ def insert_new_reports_overview(database: Database, reports_overview): return dict(ok=True) -def changelog(database: Database, nr_changes: int, report_uuid: ReportId, **uuids): - """Return the changelog for the report, narrowed to a single subject, metric, or source if so required. - The uuids keyword arguments should contain report_uuid="report_uuid" and optionally subject_uuid="subject_uuid", +def changelog(database: Database, nr_changes: int, **uuids): + """Return the changelog, narrowed to a single report, subject, metric, or source if so required. + The uuids keyword arguments may contain report_uuid="report_uuid", and one of subject_uuid="subject_uuid", metric_uuid="metric_uuid", and source_uuid="source_uuid".""" - # Build a filter for finding the right "delta" subdocuments using the passed uuid's + sort_order = [("timestamp", pymongo.DESCENDING)] + projection = {"delta.description": True, "timestamp": True} + changes = [] + if not uuids: + changes.extend(database.reports_overviews.find(sort=sort_order, limit=nr_changes, projection=projection)) delta_filter = {f"delta.{key}": value for key, value in uuids.items() if value} - delta_filter["delta.report_uuid"] = report_uuid - # Find the "nr_changes" most recent "delta" subdocuments with the required uuid's and return (using the projection) - # the description and the timestamp of the report: - return database.reports.find( - filter=delta_filter, sort=[("timestamp", pymongo.DESCENDING)], limit=nr_changes, - projection={"delta.description": True, "timestamp": True}) + changes.extend(database.reports.find(filter=delta_filter, sort=sort_order, limit=nr_changes, projection=projection)) + return sorted(changes, reverse=True, key=lambda change: change["timestamp"])[:nr_changes] diff --git a/components/server/src/routes/changelog.py b/components/server/src/routes/changelog.py index aba7d9eadc..461465a1a0 100644 --- a/components/server/src/routes/changelog.py +++ b/components/server/src/routes/changelog.py @@ -7,36 +7,45 @@ from server_utilities.type import MetricId, ReportId, SourceId, SubjectId -def get_changelog(database: Database, nr_changes: str, report_uuid: ReportId, **uuids: str): +def _get_changelog(database: Database, nr_changes: str, **uuids: str): """Return the recent most nr_changes changes from the changelog.""" limit = int(nr_changes) - changes = [dict(delta=change["delta"], timestamp=change["start"]) - for change in measurements.changelog(database, report_uuid, limit)] + changes = [] + if "report_uuid" in uuids: + changes.extend( + [dict(delta=change["delta"], timestamp=change["start"]) + for change in measurements.changelog(database, uuids["report_uuid"], limit)]) changes.extend( [dict(delta=change["delta"]["description"], timestamp=change["timestamp"]) for change in - reports.changelog(database, limit, report_uuid, **uuids) if "delta" in change]) + reports.changelog(database, limit, **uuids) if "delta" in change]) return dict(changelog=sorted(changes, reverse=True, key=lambda change: change["timestamp"])[:limit]) @bottle.get("/api/v1/changelog/report//source//") def get_source_changelog(report_uuid: ReportId, source_uuid: SourceId, nr_changes: str, database: Database): """Return the recent most nr_changes changes from the source changelog.""" - return get_changelog(database, nr_changes, report_uuid, source_uuid=source_uuid) + return _get_changelog(database, nr_changes, report_uuid=report_uuid, source_uuid=source_uuid) @bottle.get("/api/v1/changelog/report//metric//") def get_metric_changelog(report_uuid: ReportId, metric_uuid: MetricId, nr_changes: str, database: Database): """Return the recent most nr_changes changes from the metric changelog.""" - return get_changelog(database, nr_changes, report_uuid, metric_uuid=metric_uuid) + return _get_changelog(database, nr_changes, report_uuid=report_uuid, metric_uuid=metric_uuid) @bottle.get("/api/v1/changelog/report//subject//") def get_subject_changelog(report_uuid: ReportId, subject_uuid: SubjectId, nr_changes: str, database: Database): """Return the recent most nr_changes changes from the subject changelog.""" - return get_changelog(database, nr_changes, report_uuid, subject_uuid=subject_uuid) + return _get_changelog(database, nr_changes, report_uuid=report_uuid, subject_uuid=subject_uuid) @bottle.get("/api/v1/changelog/report//") def get_report_changelog(report_uuid: ReportId, nr_changes: str, database: Database): """Return the recent most nr_changes changes from the report changelog.""" - return get_changelog(database, nr_changes, report_uuid) + return _get_changelog(database, nr_changes, report_uuid=report_uuid) + + +@bottle.get("/api/v1/changelog/") +def get_changelog(nr_changes: str, database: Database): + """Return the recent most nr_changes changes from the changelog.""" + return _get_changelog(database, nr_changes) diff --git a/components/server/src/routes/report.py b/components/server/src/routes/report.py index b354e8aeae..bf114d5456 100644 --- a/components/server/src/routes/report.py +++ b/components/server/src/routes/report.py @@ -322,7 +322,11 @@ def post_reports_attribute(reports_attribute: str, database: Database): """Set a reports overview attribute.""" value = dict(bottle.request.json)[reports_attribute] overview = latest_reports_overview(database) + old_value = overview[reports_attribute] overview[reports_attribute] = value + overview["delta"] = dict( + description=f"{sessions.user(database)} changed the {reports_attribute} of the reports overview " + f"from '{old_value}' to '{value}'.") return insert_new_reports_overview(database, overview) @@ -332,16 +336,18 @@ def post_report_new(database: Database): report_uuid = uuid() report = dict( report_uuid=report_uuid, title="New report", subjects={}, - delta=dict(report_uuid=report_uuid, description=f"{sessions.user(database)} created this report.")) + delta=dict(report_uuid=report_uuid, description=f"{sessions.user(database)} created a new report.")) return insert_new_report(database, report) @bottle.delete("/api/v1/report/") def delete_report(report_uuid: ReportId, database: Database): """Delete a report.""" - report = latest_report(database, report_uuid) - report["deleted"] = "true" - return insert_new_report(database, report) + data = get_data(database, report_uuid) + data.report["deleted"] = "true" + data.report["delta"] = dict( + report_uuid=report_uuid, description=f"{sessions.user(database)} deleted the report '{data.report_name}'.") + return insert_new_report(database, data.report) @bottle.get("/api/v1/tagreport/") diff --git a/components/server/tests/unittests/routes/test_changelog.py b/components/server/tests/unittests/routes/test_changelog.py index 139009254e..44e944bf32 100644 --- a/components/server/tests/unittests/routes/test_changelog.py +++ b/components/server/tests/unittests/routes/test_changelog.py @@ -3,7 +3,8 @@ import unittest from unittest.mock import Mock -from routes.changelog import get_report_changelog, get_subject_changelog, get_metric_changelog, get_source_changelog +from routes.changelog import get_changelog, get_report_changelog, get_subject_changelog, get_metric_changelog, \ + get_source_changelog class ChangeLogTest(unittest.TestCase): @@ -18,6 +19,17 @@ def test_get_changelog(self): report1 = dict(timestamp="1", delta=dict(description="delta1")) report2 = dict(timestamp="2", delta=dict(description="delta2")) self.database.reports.find.return_value = [report2, report1] + self.database.reports_overviews.find.return_value = [] + self.database.measurements.find.return_value = [] + self.assertEqual( + dict(changelog=[dict(delta="delta2", timestamp="2"), dict(delta="delta1", timestamp="1")]), + get_changelog("10", self.database)) + + def test_get_report_changelog(self): + """Test that the report changelog is returned.""" + report1 = dict(timestamp="1", delta=dict(description="delta1")) + report2 = dict(timestamp="2", delta=dict(description="delta2")) + self.database.reports.find.return_value = [report2, report1] self.database.measurements.find.return_value = [] self.assertEqual( dict(changelog=[dict(delta="delta2", timestamp="2"), dict(delta="delta1", timestamp="1")]), diff --git a/components/server/tests/unittests/routes/test_report.py b/components/server/tests/unittests/routes/test_report.py index 3dbfc1672b..f5bdf7d314 100644 --- a/components/server/tests/unittests/routes/test_report.py +++ b/components/server/tests/unittests/routes/test_report.py @@ -2,6 +2,7 @@ import unittest from unittest.mock import Mock, patch, MagicMock +from typing import cast import requests @@ -11,6 +12,15 @@ post_reports_attribute, post_source_attribute, post_source_new, post_source_parameter, post_subject_attribute ) from server_utilities.functions import iso_timestamp +from server_utilities.type import MetricId, ReportId, SourceId, SubjectId + + +METRIC_ID = cast(MetricId, "metric_uuid") +METRIC_ID2 = cast(MetricId, "metric_uuid2") +REPORT_ID = cast(ReportId, "report_uuid") +SOURCE_ID = cast(SourceId, "source_uuid") +SUBJECT_ID = cast(SubjectId, "subject_uuid") +SUBJECT_ID2 = cast(SubjectId, "subject_uuid2") @patch("bottle.request") @@ -18,16 +28,16 @@ class PostReportAttributeTest(unittest.TestCase): """Unit tests for the post report attribute route.""" def test_post_report_name(self, request): """Test that the report name can be changed.""" - report = dict(_id="id", report_uuid="report_uuid") + report = dict(_id="id", report_uuid=REPORT_ID) request.json = dict(name="name") database = Mock() database.reports.find_one.return_value = report database.sessions.find_one.return_value = dict(user="John") database.datamodels.find_one.return_value = dict() - self.assertEqual(dict(ok=True), post_report_attribute("report_uuid", "name", database)) + self.assertEqual(dict(ok=True), post_report_attribute(REPORT_ID, "name", database)) database.reports.insert.assert_called_once_with(report) self.assertEqual( - dict(description="John changed the name of report '' from '' to 'name'.", report_uuid="report_uuid"), + dict(description="John changed the name of report '' from '' to 'name'.", report_uuid=REPORT_ID), report["delta"]) @@ -38,8 +48,8 @@ class PostSubjectAttributeTest(unittest.TestCase): def setUp(self): self.database = Mock() self.report = dict( - _id="id", report_uuid="report_uuid", title="Report", - subjects=dict(subject_uuid=dict(name="subject1"), subject_uuid2=dict(name="subject2"))) + _id="id", report_uuid=REPORT_ID, title="Report", + subjects={SUBJECT_ID: dict(name="subject1"), SUBJECT_ID2: dict(name="subject2")}) self.database.reports.find_one.return_value = self.report self.database.datamodels.find_one.return_value = dict() self.database.sessions.find_one.return_value = dict(user="John") @@ -47,78 +57,72 @@ def setUp(self): def test_post_subject_name(self, request): """Test that the subject name can be changed.""" request.json = dict(name="new name") - self.assertEqual(dict(ok=True), post_subject_attribute("report_uuid", "subject_uuid", "name", self.database)) + self.assertEqual(dict(ok=True), post_subject_attribute(REPORT_ID, SUBJECT_ID, "name", self.database)) self.database.reports.insert.assert_called_once_with(self.report) self.assertEqual( dict( description="John changed the name of subject 'subject1' in report 'Report' from 'subject1' to " "'new name'.", - report_uuid="report_uuid", subject_uuid="subject_uuid"), + report_uuid=REPORT_ID, subject_uuid=SUBJECT_ID), self.report["delta"]) def test_post_position_first(self, request): """Test that a subject can be moved to the top.""" request.json = dict(position="first") - self.assertEqual( - dict(ok=True), post_subject_attribute("report_uuid", "subject_uuid2", "position", self.database)) + self.assertEqual(dict(ok=True), post_subject_attribute(REPORT_ID, SUBJECT_ID2, "position", self.database)) self.database.reports.insert.assert_called_once_with(self.report) - self.assertEqual(["subject_uuid2", "subject_uuid"], list(self.report["subjects"].keys())) + self.assertEqual([SUBJECT_ID2, SUBJECT_ID], list(self.report["subjects"].keys())) self.assertEqual( - dict(report_uuid="report_uuid", subject_uuid="subject_uuid2", + dict(report_uuid=REPORT_ID, subject_uuid=SUBJECT_ID2, description="John changed the position of subject 'subject2' in report 'Report' from '1' to '0'."), self.report["delta"]) def test_post_position_last(self, request): """Test that a subject can be moved to the bottom.""" request.json = dict(position="last") - self.assertEqual( - dict(ok=True), post_subject_attribute("report_uuid", "subject_uuid", "position", self.database)) + self.assertEqual(dict(ok=True), post_subject_attribute(REPORT_ID, SUBJECT_ID, "position", self.database)) self.database.reports.insert.assert_called_once_with(self.report) - self.assertEqual(["subject_uuid2", "subject_uuid"], list(self.report["subjects"].keys())) + self.assertEqual([SUBJECT_ID2, SUBJECT_ID], list(self.report["subjects"].keys())) self.assertEqual( - dict(report_uuid="report_uuid", subject_uuid="subject_uuid", + dict(report_uuid=REPORT_ID, subject_uuid=SUBJECT_ID, description="John changed the position of subject 'subject1' in report 'Report' from '0' to '1'."), self.report["delta"]) def test_post_position_previous(self, request): """Test that a subject can be moved up.""" request.json = dict(position="previous") - self.assertEqual( - dict(ok=True), post_subject_attribute("report_uuid", "subject_uuid2", "position", self.database)) + self.assertEqual(dict(ok=True), post_subject_attribute(REPORT_ID, SUBJECT_ID2, "position", self.database)) self.database.reports.insert.assert_called_once_with(self.report) - self.assertEqual(["subject_uuid2", "subject_uuid"], list(self.report["subjects"].keys())) + self.assertEqual([SUBJECT_ID2, SUBJECT_ID], list(self.report["subjects"].keys())) self.assertEqual( - dict(report_uuid="report_uuid", subject_uuid="subject_uuid2", + dict(report_uuid=REPORT_ID, subject_uuid=SUBJECT_ID2, description="John changed the position of subject 'subject2' in report 'Report' from '1' to '0'."), self.report["delta"]) def test_post_position_next(self, request): """Test that a subject can be moved down.""" request.json = dict(position="next") - self.assertEqual( - dict(ok=True), post_subject_attribute("report_uuid", "subject_uuid", "position", self.database)) + self.assertEqual(dict(ok=True), post_subject_attribute(REPORT_ID, SUBJECT_ID, "position", self.database)) self.database.reports.insert.assert_called_once_with(self.report) - self.assertEqual(["subject_uuid2", "subject_uuid"], list(self.report["subjects"].keys())) + self.assertEqual([SUBJECT_ID2, SUBJECT_ID], list(self.report["subjects"].keys())) self.assertEqual( - dict(report_uuid="report_uuid", subject_uuid="subject_uuid", + dict(report_uuid=REPORT_ID, subject_uuid=SUBJECT_ID, description="John changed the position of subject 'subject1' in report 'Report' from '0' to '1'."), self.report["delta"]) def test_post_position_first_previous(self, request): """Test that moving the first subject up does nothing.""" request.json = dict(position="previous") - self.assertEqual( - dict(ok=True), post_subject_attribute("report_uuid", "subject_uuid", "position", self.database)) + self.assertEqual(dict(ok=True), post_subject_attribute(REPORT_ID, SUBJECT_ID, "position", self.database)) self.database.reports.insert.assert_not_called() - self.assertEqual(["subject_uuid", "subject_uuid2"], list(self.report["subjects"].keys())) + self.assertEqual([SUBJECT_ID, SUBJECT_ID2], list(self.report["subjects"].keys())) def test_post_position_last_next(self, request): """Test that moving the last subject down does nothing.""" request.json = dict(position="next") - self.assertEqual( - dict(ok=True), post_subject_attribute("report_uuid", "subject_uuid2", "position", self.database)) + self.assertEqual(dict(ok=True), post_subject_attribute(REPORT_ID, SUBJECT_ID2, "position", self.database)) self.database.reports.insert.assert_not_called() - self.assertEqual(["subject_uuid", "subject_uuid2"], list(self.report["subjects"].keys())) + self.assertEqual([SUBJECT_ID, SUBJECT_ID2], list(self.report["subjects"].keys())) @patch("database.reports.iso_timestamp", new=Mock(return_value="2019-01-01")) @@ -128,17 +132,17 @@ class PostMetricAttributeTest(unittest.TestCase): def setUp(self): self.report = dict( - _id="id", report_uuid="report_uuid", title="Report", - subjects=dict( - other_subject=dict(metrics=dict()), - subject_uuid=dict( + _id="id", report_uuid=REPORT_ID, title="Report", + subjects={ + "other_subject": dict(metrics=dict()), + SUBJECT_ID: dict( name='Subject', - metrics=dict( - metric_uuid=dict( + metrics={ + METRIC_ID: dict( name="name", type="old_type", scale="count", addition="sum", direction="<", target="0", near_target="10", debt_target=None, accept_debt=False, tags=[], - sources=dict(source_uuid=dict())), - metric_uuid2=dict(name="name2"))))) + sources={SOURCE_ID: dict()}), + METRIC_ID2: dict(name="name2")})}) self.database = Mock() self.database.reports.find_one.return_value = self.report self.database.sessions.find_one.return_value = dict(user="John") @@ -153,10 +157,10 @@ def setUp(self): def test_post_metric_name(self, request): """Test that the metric name can be changed.""" request.json = dict(name="ABC") - self.assertEqual(dict(ok=True), post_metric_attribute("report_uuid", "metric_uuid", "name", self.database)) + self.assertEqual(dict(ok=True), post_metric_attribute(REPORT_ID, METRIC_ID, "name", self.database)) self.database.reports.insert.assert_called_once_with(self.report) self.assertEqual( - dict(report_uuid="report_uuid", subject_uuid="subject_uuid", metric_uuid="metric_uuid", + dict(report_uuid=REPORT_ID, subject_uuid=SUBJECT_ID, metric_uuid=METRIC_ID, description="John changed the name of metric 'name' of subject 'Subject' in report 'Report' " "from 'name' to 'ABC'."), self.report["delta"]) @@ -164,10 +168,10 @@ def test_post_metric_name(self, request): def test_post_metric_type(self, request): """Test that the metric type can be changed.""" request.json = dict(type="new_type") - self.assertEqual(dict(ok=True), post_metric_attribute("report_uuid", "metric_uuid", "type", self.database)) + self.assertEqual(dict(ok=True), post_metric_attribute(REPORT_ID, METRIC_ID, "type", self.database)) self.database.reports.insert.assert_called_once_with(self.report) self.assertEqual( - dict(report_uuid="report_uuid", subject_uuid="subject_uuid", metric_uuid="metric_uuid", + dict(report_uuid=REPORT_ID, subject_uuid=SUBJECT_ID, metric_uuid=METRIC_ID, description="John changed the type of metric 'name' of subject 'Subject' in report 'Report' " "from 'old_type' to 'new_type'."), self.report["delta"]) @@ -176,9 +180,9 @@ def test_post_metric_target_without_measurements(self, request): """Test that changing the metric target doesnt't add a new measurement if none exist.""" self.database.measurements.find_one.return_value = None request.json = dict(target="10") - self.assertEqual(dict(ok=True), post_metric_attribute("report_uuid", "metric_uuid", "target", self.database)) + self.assertEqual(dict(ok=True), post_metric_attribute(REPORT_ID, METRIC_ID, "target", self.database)) self.assertEqual( - dict(report_uuid="report_uuid", subject_uuid="subject_uuid", metric_uuid="metric_uuid", + dict(report_uuid=REPORT_ID, subject_uuid=SUBJECT_ID, metric_uuid=METRIC_ID, description="John changed the target of metric 'name' of subject 'Subject' in report 'Report' " "from '0' to '10'."), self.report["delta"]) @@ -186,7 +190,7 @@ def test_post_metric_target_without_measurements(self, request): @patch("database.measurements.iso_timestamp", new=Mock(return_value="2019-01-01")) def test_post_metric_target_with_measurements(self, request): """Test that changing the metric target adds a new measurement if one or more exist.""" - self.database.measurements.find_one.return_value = dict(_id="id", metric_uuid="metric_uuid", sources=[]) + self.database.measurements.find_one.return_value = dict(_id="id", metric_uuid=METRIC_ID, sources=[]) def set_measurement_id(measurement): measurement["_id"] = "measurement_id" @@ -196,10 +200,10 @@ def set_measurement_id(measurement): self.assertEqual( dict( _id="measurement_id", end="2019-01-01", sources=[], start="2019-01-01", - count=dict(status=None, value=None), metric_uuid="metric_uuid", last=True), - post_metric_attribute("report_uuid", "metric_uuid", "target", self.database)) + count=dict(status=None, value=None), metric_uuid=METRIC_ID, last=True), + post_metric_attribute(REPORT_ID, METRIC_ID, "target", self.database)) self.assertEqual( - dict(report_uuid="report_uuid", subject_uuid="subject_uuid", metric_uuid="metric_uuid", + dict(report_uuid=REPORT_ID, subject_uuid=SUBJECT_ID, metric_uuid=METRIC_ID, description="John changed the target of metric 'name' of subject 'Subject' in report 'Report' " "from '0' to '10'."), self.report["delta"]) @@ -207,7 +211,7 @@ def set_measurement_id(measurement): @patch("database.measurements.iso_timestamp", new=Mock(return_value="2019-01-01")) def test_post_metric_debt_end_date_with_measurements(self, request): """Test that changing the metric debt end date adds a new measurement if one or more exist.""" - self.database.measurements.find_one.return_value = dict(_id="id", metric_uuid="metric_uuid", sources=[]) + self.database.measurements.find_one.return_value = dict(_id="id", metric_uuid=METRIC_ID, sources=[]) def set_measurement_id(measurement): measurement["_id"] = "measurement_id" @@ -217,10 +221,10 @@ def set_measurement_id(measurement): self.assertEqual( dict( _id="measurement_id", end="2019-01-01", sources=[], start="2019-01-01", last=True, - metric_uuid="metric_uuid", count=dict(value=None, status=None)), - post_metric_attribute("report_uuid", "metric_uuid", "debt_end_date", self.database)) + metric_uuid=METRIC_ID, count=dict(value=None, status=None)), + post_metric_attribute(REPORT_ID, METRIC_ID, "debt_end_date", self.database)) self.assertEqual( - dict(report_uuid="report_uuid", subject_uuid="subject_uuid", metric_uuid="metric_uuid", + dict(report_uuid=REPORT_ID, subject_uuid=SUBJECT_ID, metric_uuid=METRIC_ID, description="John changed the debt_end_date of metric 'name' of subject 'Subject' in report " "'Report' from '' to '2019-06-07'."), self.report["delta"]) @@ -228,10 +232,10 @@ def set_measurement_id(measurement): def test_post_unsafe_comment(self, request): """Test that comments are sanitized, since they are displayed as inner HTML in the frontend.""" request.json = dict(comment='Comment with script') - self.assertEqual(dict(ok=True), post_metric_attribute("report_uuid", "metric_uuid", "comment", self.database)) + self.assertEqual(dict(ok=True), post_metric_attribute(REPORT_ID, METRIC_ID, "comment", self.database)) self.database.reports.insert.assert_called_once_with(self.report) self.assertEqual( - dict(report_uuid="report_uuid", subject_uuid="subject_uuid", metric_uuid="metric_uuid", + dict(report_uuid=REPORT_ID, subject_uuid=SUBJECT_ID, metric_uuid=METRIC_ID, description="John changed the comment of metric 'name' of subject 'Subject' in report 'Report' " "from '' to 'Comment with script'."), self.report["delta"]) @@ -239,10 +243,10 @@ def test_post_unsafe_comment(self, request): def test_post_comment_with_link(self, request): """Test that urls in comments are transformed into anchors.""" request.json = dict(comment='Comment with url https://google.com') - self.assertEqual(dict(ok=True), post_metric_attribute("report_uuid", "metric_uuid", "comment", self.database)) + self.assertEqual(dict(ok=True), post_metric_attribute(REPORT_ID, METRIC_ID, "comment", self.database)) self.database.reports.insert.assert_called_once_with(self.report) self.assertEqual( - dict(report_uuid="report_uuid", subject_uuid="subject_uuid", metric_uuid="metric_uuid", + dict(report_uuid=REPORT_ID, subject_uuid=SUBJECT_ID, metric_uuid=METRIC_ID, description="""John changed the comment of metric 'name' of subject 'Subject' in report 'Report' \ from '' to '

Comment with url https://google.com

'."""), self.report["delta"]) @@ -250,12 +254,11 @@ def test_post_comment_with_link(self, request): def test_post_position_first(self, request): """Test that a metric can be moved to the top of the list.""" request.json = dict(position="first") - self.assertEqual(dict(ok=True), post_metric_attribute("report_uuid", "metric_uuid2", "position", self.database)) + self.assertEqual(dict(ok=True), post_metric_attribute(REPORT_ID, METRIC_ID2, "position", self.database)) self.database.reports.insert.assert_called_once_with(self.report) + self.assertEqual([METRIC_ID2, METRIC_ID], list(self.report["subjects"][SUBJECT_ID]["metrics"].keys())) self.assertEqual( - ["metric_uuid2", "metric_uuid"], list(self.report["subjects"]["subject_uuid"]["metrics"].keys())) - self.assertEqual( - dict(report_uuid="report_uuid", subject_uuid="subject_uuid", metric_uuid="metric_uuid2", + dict(report_uuid=REPORT_ID, subject_uuid=SUBJECT_ID, metric_uuid=METRIC_ID2, description="John changed the position of metric 'name2' of subject 'Subject' in report " "'Report' from '1' to '0'."), self.report["delta"]) @@ -263,12 +266,11 @@ def test_post_position_first(self, request): def test_post_position_last(self, request): """Test that a metric can be moved to the bottom of the list.""" request.json = dict(position="last") - self.assertEqual(dict(ok=True), post_metric_attribute("report_uuid", "metric_uuid", "position", self.database)) + self.assertEqual(dict(ok=True), post_metric_attribute(REPORT_ID, METRIC_ID, "position", self.database)) self.database.reports.insert.assert_called_once_with(self.report) + self.assertEqual([METRIC_ID2, METRIC_ID], list(self.report["subjects"][SUBJECT_ID]["metrics"].keys())) self.assertEqual( - ["metric_uuid2", "metric_uuid"], list(self.report["subjects"]["subject_uuid"]["metrics"].keys())) - self.assertEqual( - dict(report_uuid="report_uuid", subject_uuid="subject_uuid", metric_uuid="metric_uuid", + dict(report_uuid=REPORT_ID, subject_uuid=SUBJECT_ID, metric_uuid=METRIC_ID, description="John changed the position of metric 'name' of subject 'Subject' in report " "'Report' from '0' to '1'."), self.report["delta"]) @@ -276,12 +278,11 @@ def test_post_position_last(self, request): def test_post_position_previous(self, request): """Test that a metric can be moved up.""" request.json = dict(position="previous") - self.assertEqual(dict(ok=True), post_metric_attribute("report_uuid", "metric_uuid2", "position", self.database)) + self.assertEqual(dict(ok=True), post_metric_attribute(REPORT_ID, METRIC_ID2, "position", self.database)) self.database.reports.insert.assert_called_once_with(self.report) + self.assertEqual([METRIC_ID2, METRIC_ID], list(self.report["subjects"][SUBJECT_ID]["metrics"].keys())) self.assertEqual( - ["metric_uuid2", "metric_uuid"], list(self.report["subjects"]["subject_uuid"]["metrics"].keys())) - self.assertEqual( - dict(report_uuid="report_uuid", subject_uuid="subject_uuid", metric_uuid="metric_uuid2", + dict(report_uuid=REPORT_ID, subject_uuid=SUBJECT_ID, metric_uuid=METRIC_ID2, description="John changed the position of metric 'name2' of subject 'Subject' in report " "'Report' from '1' to '0'."), self.report["delta"]) @@ -289,12 +290,11 @@ def test_post_position_previous(self, request): def test_post_position_next(self, request): """Test that a metric can be moved down.""" request.json = dict(position="next") - self.assertEqual(dict(ok=True), post_metric_attribute("report_uuid", "metric_uuid", "position", self.database)) + self.assertEqual(dict(ok=True), post_metric_attribute(REPORT_ID, METRIC_ID, "position", self.database)) self.database.reports.insert.assert_called_once_with(self.report) + self.assertEqual([METRIC_ID2, METRIC_ID], list(self.report["subjects"][SUBJECT_ID]["metrics"].keys())) self.assertEqual( - ["metric_uuid2", "metric_uuid"], list(self.report["subjects"]["subject_uuid"]["metrics"].keys())) - self.assertEqual( - dict(report_uuid="report_uuid", subject_uuid="subject_uuid", metric_uuid="metric_uuid", + dict(report_uuid=REPORT_ID, subject_uuid=SUBJECT_ID, metric_uuid=METRIC_ID, description="John changed the position of metric 'name' of subject 'Subject' in report " "'Report' from '0' to '1'."), self.report["delta"]) @@ -302,18 +302,16 @@ def test_post_position_next(self, request): def test_post_position_first_previous(self, request): """Test that moving the first metric up does nothing.""" request.json = dict(position="previous") - self.assertEqual(dict(ok=True), post_metric_attribute("report_uuid", "metric_uuid", "position", self.database)) + self.assertEqual(dict(ok=True), post_metric_attribute(REPORT_ID, METRIC_ID, "position", self.database)) self.database.reports.insert.assert_not_called() - self.assertEqual( - ["metric_uuid", "metric_uuid2"], list(self.report["subjects"]["subject_uuid"]["metrics"].keys())) + self.assertEqual([METRIC_ID, METRIC_ID2], list(self.report["subjects"][SUBJECT_ID]["metrics"].keys())) def test_post_position_last_next(self, request): """Test that moving the last metric down does nothing.""" request.json = dict(position="next") - self.assertEqual(dict(ok=True), post_metric_attribute("report_uuid", "metric_uuid2", "position", self.database)) + self.assertEqual(dict(ok=True), post_metric_attribute(REPORT_ID, METRIC_ID2, "position", self.database)) self.database.reports.insert.assert_not_called() - self.assertEqual( - ["metric_uuid", "metric_uuid2"], list(self.report["subjects"]["subject_uuid"]["metrics"].keys())) + self.assertEqual([METRIC_ID, METRIC_ID2], list(self.report["subjects"][SUBJECT_ID]["metrics"].keys())) @patch("bottle.request") @@ -322,13 +320,13 @@ class PostSourceAttributeTest(unittest.TestCase): def setUp(self): self.report = dict( - _id="report_uuid", title="Report", - subjects=dict( - subject_uuid=dict( + _id=REPORT_ID, title="Report", + subjects={ + SUBJECT_ID: dict( name="Subject", - metrics=dict( - metric_uuid=dict( - name="Metric", type="type", sources=dict(source_uuid=dict(name="Source", type="type"))))))) + metrics={ + METRIC_ID: dict( + name="Metric", type="type", sources={SOURCE_ID: dict(name="Source", type="type")})})}) self.database = Mock() self.database.reports.find_one.return_value = self.report self.database.sessions.find_one.return_value = dict(user="Jenny") @@ -338,11 +336,10 @@ def setUp(self): def test_name(self, request): """Test that the source name can be changed.""" request.json = dict(name="New source name") - self.assertEqual(dict(ok=True), post_source_attribute("report_uuid", "source_uuid", "name", self.database)) + self.assertEqual(dict(ok=True), post_source_attribute(REPORT_ID, SOURCE_ID, "name", self.database)) self.database.reports.insert.assert_called_once_with(self.report) self.assertEqual( - dict(report_uuid="report_uuid", subject_uuid="subject_uuid", metric_uuid="metric_uuid", - source_uuid="source_uuid", + dict(report_uuid=REPORT_ID, subject_uuid=SUBJECT_ID, metric_uuid=METRIC_ID, source_uuid=SOURCE_ID, description="Jenny changed the name of source 'Source' of metric 'Metric' of subject 'Subject' in " "report 'Report' from 'Source' to 'New source name'."), self.report["delta"]) @@ -350,11 +347,10 @@ def test_name(self, request): def test_post_source_type(self, request): """Test that the source type can be changed.""" request.json = dict(type="new_type") - self.assertEqual(dict(ok=True), post_source_attribute("report_uuid", "source_uuid", "type", self.database)) + self.assertEqual(dict(ok=True), post_source_attribute(REPORT_ID, SOURCE_ID, "type", self.database)) self.database.reports.insert.assert_called_once_with(self.report) self.assertEqual( - dict(report_uuid="report_uuid", subject_uuid="subject_uuid", metric_uuid="metric_uuid", - source_uuid="source_uuid", + dict(report_uuid=REPORT_ID, subject_uuid=SUBJECT_ID, metric_uuid=METRIC_ID, source_uuid=SOURCE_ID, description="Jenny changed the type of source 'Source' of metric 'Metric' of subject 'Subject' in " "report 'Report' from 'type' to 'new_type'."), self.report["delta"]) @@ -366,14 +362,14 @@ class PostSourceParameterTest(unittest.TestCase): def setUp(self): self.report = dict( - _id="report_uuid", title="Report", - subjects=dict( - subject_uuid=dict( + _id=REPORT_ID, title="Report", + subjects={ + SUBJECT_ID: dict( name="Subject", - metrics=dict( - metric_uuid=dict( + metrics={ + METRIC_ID: dict( name="Metric", type="type", - sources=dict(source_uuid=dict(name="Source", type="type", parameters=dict()))))))) + sources={SOURCE_ID: dict(name="Source", type="type", parameters=dict())})})}) self.database = Mock() self.database.sessions.find_one.return_value = dict(user="Jenny") self.database.reports.find_one.return_value = self.report @@ -385,15 +381,14 @@ def test_url(self, mock_get, request): """Test that the source url can be changed and that the availability is checked.""" mock_get.return_value = MagicMock(status_code=123, reason='A good reason') request.json = dict(url="https://url") - response = post_source_parameter("report_uuid", "source_uuid", "url", self.database) + response = post_source_parameter(REPORT_ID, SOURCE_ID, "url", self.database) self.assertTrue(response['ok']) self.assertEqual(response['availability'], [{"status_code": 123, "reason": 'A good reason', - 'source_uuid': 'source_uuid', "parameter_key": 'url'}]) + 'source_uuid': SOURCE_ID, "parameter_key": 'url'}]) self.database.reports.insert.assert_called_once_with(self.report) mock_get.assert_called_once_with('https://url', auth=None) self.assertEqual( - dict(report_uuid="report_uuid", subject_uuid="subject_uuid", metric_uuid="metric_uuid", - source_uuid="source_uuid", + dict(report_uuid=REPORT_ID, subject_uuid=SUBJECT_ID, metric_uuid=METRIC_ID, source_uuid=SOURCE_ID, description="Jenny changed the url of source 'Source' of metric 'Metric' of subject 'Subject' in " "report 'Report' from '' to 'https://url'."), self.report["delta"]) @@ -403,14 +398,13 @@ def test_url_http_error(self, mock_get, request): """Test that the error is reported if a request exception occurs, while checking connection of a url.""" mock_get.side_effect = requests.exceptions.RequestException request.json = dict(url="https://url") - response = post_source_parameter("report_uuid", "source_uuid", "url", self.database) + response = post_source_parameter(REPORT_ID, SOURCE_ID, "url", self.database) self.assertTrue(response['ok']) self.assertEqual(response['availability'], [{"status_code": -1, "reason": 'Unknown error', - 'source_uuid': 'source_uuid', "parameter_key": 'url'}]) + 'source_uuid': SOURCE_ID, "parameter_key": 'url'}]) self.database.reports.insert.assert_called_once_with(self.report) self.assertEqual( - dict(report_uuid="report_uuid", subject_uuid="subject_uuid", metric_uuid="metric_uuid", - source_uuid="source_uuid", + dict(report_uuid=REPORT_ID, subject_uuid=SUBJECT_ID, metric_uuid=METRIC_ID, source_uuid=SOURCE_ID, description="Jenny changed the url of source 'Source' of metric 'Metric' of subject 'Subject' in " "report 'Report' from '' to 'https://url'."), self.report["delta"]) @@ -421,15 +415,16 @@ def test_url_with_user(self, mock_get, request): database = self.database database.datamodels.find_one.return_value = dict(_id="id", sources=dict(type=dict(parameters=dict(url=dict( type="url"), username=dict(type="string"), password=dict(type="pwd"))))) - srcs = database.reports.find_one.return_value['subjects']['subject_uuid']['metrics']['metric_uuid']['sources'] - srcs['source_uuid']['parameters']['username'] = 'un' - srcs['source_uuid']['parameters']['password'] = 'pwd' + srcs = database.reports.find_one.return_value['subjects'][SUBJECT_ID]['metrics'][METRIC_ID]['sources'] + srcs[SOURCE_ID]['parameters']['username'] = 'un' + srcs[SOURCE_ID]['parameters']['password'] = 'pwd' mock_get.return_value = MagicMock(status_code=123, reason='A good reason') request.json = dict(url="https://url") - response = post_source_parameter("report_uuid", "source_uuid", "url", database) + response = post_source_parameter(REPORT_ID, SOURCE_ID, "url", database) self.assertTrue(response['ok']) - self.assertEqual(response['availability'], [{"status_code": 123, "reason": 'A good reason', - 'source_uuid': 'source_uuid', "parameter_key": 'url'}]) + self.assertEqual( + response['availability'], + [{"status_code": 123, "reason": 'A good reason', 'source_uuid': SOURCE_ID, "parameter_key": 'url'}]) self.database.reports.insert.assert_called_once_with(self.report) mock_get.assert_called_once_with('https://url', auth=('un', 'pwd')) @@ -441,7 +436,7 @@ def test_url_no_url_type(self, mock_get, request): type="string"), username=dict(type="string"), password=dict(type="pwd"))))) mock_get.return_value = MagicMock(status_code=123, reason='A good reason') request.json = dict(url="unimportant") - response = post_source_parameter("report_uuid", "source_uuid", "url", database) + response = post_source_parameter(REPORT_ID, SOURCE_ID, "url", database) self.assertEqual(response, dict(ok=True)) self.database.reports.insert.assert_called_once_with(self.report) mock_get.assert_not_called() @@ -452,7 +447,7 @@ def test_empty_url(self, request): database.datamodels.find_one.return_value = dict(_id="id", sources=dict(type=dict(parameters=dict(url=dict( type="url"), username=dict(type="string"), password=dict(type="pwd"))))) request.json = dict(url="") - response = post_source_parameter("report_uuid", "source_uuid", "url", database) + response = post_source_parameter(REPORT_ID, SOURCE_ID, "url", database) self.assertEqual(response, dict(ok=True)) self.database.reports.insert.assert_called_once_with(self.report) @@ -464,12 +459,13 @@ def test_url_with_token(self, mock_get, request): type="url"), username=dict(type="string"), private_token=dict(type="pwd"))))) mock_get.return_value = MagicMock(status_code=123, reason='A good reason') request.json = dict(url="https://url") - srcs = database.reports.find_one.return_value['subjects']['subject_uuid']['metrics']['metric_uuid']['sources'] - srcs['source_uuid']['parameters']['private_token'] = 'xxx' - response = post_source_parameter("report_uuid", "source_uuid", "url", database) + srcs = database.reports.find_one.return_value['subjects'][SUBJECT_ID]['metrics'][METRIC_ID]['sources'] + srcs[SOURCE_ID]['parameters']['private_token'] = 'xxx' + response = post_source_parameter(REPORT_ID, SOURCE_ID, "url", database) self.assertTrue(response['ok']) - self.assertEqual(response['availability'], [{"status_code": 123, "reason": 'A good reason', - 'source_uuid': 'source_uuid', "parameter_key": 'url'}]) + self.assertEqual( + response['availability'], + [{"status_code": 123, "reason": 'A good reason', 'source_uuid': SOURCE_ID, "parameter_key": 'url'}]) self.database.reports.insert.assert_called_once_with(self.report) mock_get.assert_called_once_with('https://url', auth=('xxx', '')) @@ -483,23 +479,22 @@ def test_urls_connection_on_update_other_field(self, mock_get, request): password=dict(type="password"))))) mock_get.side_effect = [MagicMock(status_code=123, reason='A good reason')] request.json = dict(password="changed") - srcs = database.reports.find_one.return_value['subjects']['subject_uuid']['metrics']['metric_uuid']['sources'] - srcs['source_uuid']['parameters']['url'] = "https://url" - response = post_source_parameter("report_uuid", "source_uuid", "password", database) + sources = database.reports.find_one.return_value['subjects'][SUBJECT_ID]['metrics'][METRIC_ID]['sources'] + sources[SOURCE_ID]['parameters']['url'] = "https://url" + response = post_source_parameter(REPORT_ID, SOURCE_ID, "password", database) self.assertTrue(response['ok']) self.assertEqual(response['availability'], [{"status_code": 123, "reason": 'A good reason', - 'source_uuid': 'source_uuid', "parameter_key": 'url'}]) + 'source_uuid': SOURCE_ID, "parameter_key": 'url'}]) self.database.reports.insert.assert_called_once_with(self.report) def test_password(self, request): """Test that the password can be changed and is not logged.""" request.json = dict(url="unimportant", password="secret") - response = post_source_parameter("report_uuid", "source_uuid", "password", self.database) + response = post_source_parameter(REPORT_ID, SOURCE_ID, "password", self.database) self.assertTrue(response['ok']) self.database.reports.insert.assert_called_once_with(self.report) self.assertEqual( - dict(report_uuid="report_uuid", subject_uuid="subject_uuid", metric_uuid="metric_uuid", - source_uuid="source_uuid", + dict(report_uuid=REPORT_ID, subject_uuid=SUBJECT_ID, metric_uuid=METRIC_ID, source_uuid=SOURCE_ID, description="Jenny changed the password of source 'Source' of metric 'Metric' of subject 'Subject' in " "report 'Report' from '' to '******'."), self.report["delta"]) @@ -519,19 +514,19 @@ def setUp(self): def test_add_source(self): """Test that a new source is added.""" report = dict( - _id="report_uuid", title="Report", - subjects=dict( - subject_uuid=dict( + _id=REPORT_ID, title="Report", + subjects={ + SUBJECT_ID: dict( name="Subject", - metrics=dict( - metric_uuid=dict( + metrics={ + METRIC_ID: dict( name=None, type="metric_type", addition="sum", target="0", near_target="10", - debt_target=None, accept_debt=False, tags=[], sources=dict()))))) + debt_target=None, accept_debt=False, tags=[], sources=dict())})}) self.database.reports.find_one.return_value = report - self.assertEqual(dict(ok=True), post_source_new("report_uuid", "metric_uuid", self.database)) + self.assertEqual(dict(ok=True), post_source_new(REPORT_ID, METRIC_ID, self.database)) self.database.reports.insert.assert_called_once_with(report) self.assertEqual( - dict(report_uuid="report_uuid", subject_uuid="subject_uuid", metric_uuid="metric_uuid", + dict(report_uuid=REPORT_ID, subject_uuid=SUBJECT_ID, metric_uuid=METRIC_ID, description="Jenny added a new source to metric 'Metric Type' of subject 'Subject' in report " "'Report'."), report["delta"]) @@ -539,18 +534,18 @@ def test_add_source(self): def test_delete_source(self): """Test that the source can be deleted.""" report = dict( - _id="report_uuid", title="Report", - subjects=dict( - subject_uuid=dict( + _id=REPORT_ID, title="Report", + subjects={ + SUBJECT_ID: dict( name="Subject", - metrics=dict( - metric_uuid=dict( - type="type", name="Metric", sources=dict(source_uuid=dict(name="Source"))))))) + metrics={ + METRIC_ID: dict( + type="type", name="Metric", sources={SOURCE_ID: dict(name="Source")})})}) self.database.reports.find_one.return_value = report - self.assertEqual(dict(ok=True), delete_source("report_uuid", "source_uuid", self.database)) + self.assertEqual(dict(ok=True), delete_source(REPORT_ID, SOURCE_ID, self.database)) self.database.reports.insert.assert_called_once_with(report) self.assertEqual( - dict(report_uuid="report_uuid", subject_uuid="subject_uuid", metric_uuid="metric_uuid", + dict(report_uuid=REPORT_ID, subject_uuid=SUBJECT_ID, metric_uuid=METRIC_ID, description="Jenny deleted the source 'Source' from metric 'Metric' of subject 'Subject' in report " "'Report'."), report["delta"]) @@ -570,38 +565,37 @@ def setUp(self): def test_add_metric(self): """Test that a metric can be added.""" - report = dict( - _id="report_uuid", title="Report", subjects=dict(subject_uuid=dict(name="Subject", metrics=dict()))) + report = dict(_id=REPORT_ID, title="Report", subjects={SUBJECT_ID: dict(name="Subject", metrics=dict())}) self.database.reports.find_one.return_value = report - self.assertEqual(dict(ok=True), post_metric_new("report_uuid", "subject_uuid", self.database)) + self.assertEqual(dict(ok=True), post_metric_new(REPORT_ID, SUBJECT_ID, self.database)) self.assertEqual( - dict(report_uuid="report_uuid", subject_uuid="subject_uuid", + dict(report_uuid=REPORT_ID, subject_uuid=SUBJECT_ID, description="Jenny added a new metric to subject 'Subject' in report 'Report'."), report["delta"]) def test_get_metrics(self): """Test that the metrics can be retrieved and deleted reports are skipped.""" report = dict( - _id="id", report_uuid="report_uuid", - subjects=dict(subject_uuid=dict(metrics=dict(metric_uuid=dict(type="metric_type", tags=[]))))) + _id="id", report_uuid=REPORT_ID, + subjects={SUBJECT_ID: dict(metrics={METRIC_ID: dict(type="metric_type", tags=[])})}) self.database.reports_overviews.find_one.return_value = dict(_id="id", title="Reports", subtitle="") - self.database.reports.distinct.return_value = ["report_uuid", "deleted_report"] + self.database.reports.distinct.return_value = [REPORT_ID, "deleted_report"] self.database.reports.find_one.side_effect = [report, dict(deleted=True)] self.database.measurements.find.return_value = [dict( - _id="id", metric_uuid="metric_uuid", status="red", - sources=[dict(source_uuid="source_uuid", parse_error=None, connection_error=None, value="42")])] - self.assertEqual(dict(metric_uuid=dict(type="metric_type", tags=[])), get_metrics(self.database)) + _id="id", metric_uuid=METRIC_ID, status="red", + sources=[dict(source_uuid=SOURCE_ID, parse_error=None, connection_error=None, value="42")])] + self.assertEqual({METRIC_ID: dict(type="metric_type", tags=[])}, get_metrics(self.database)) def test_delete_metric(self): """Test that the metric can be deleted.""" report = dict( - _id="report_uuid", title="Report", - subjects=dict(subject_uuid=dict(name="Subject", metrics=dict(metric_uuid=dict(name="Metric"))))) + _id=REPORT_ID, title="Report", + subjects={SUBJECT_ID: dict(name="Subject", metrics={METRIC_ID: dict(name="Metric")})}) self.database.reports.find_one.return_value = report - self.assertEqual(dict(ok=True), delete_metric("report_uuid", "metric_uuid", self.database)) + self.assertEqual(dict(ok=True), delete_metric(REPORT_ID, METRIC_ID, self.database)) self.database.reports.insert.assert_called_once_with(report) self.assertEqual( - dict(report_uuid="report_uuid", subject_uuid="subject_uuid", + dict(report_uuid=REPORT_ID, subject_uuid=SUBJECT_ID, description=f"Jenny deleted metric 'Metric' from subject 'Subject' in report 'Report'."), report["delta"]) @@ -612,7 +606,7 @@ class SubjectTest(unittest.TestCase): def setUp(self): self.database = Mock() self.database.sessions.find_one.return_value = dict(user="Jenny") - self.report = dict(title="Report", subjects=dict(subject_uuid=dict(name="ABC"))) + self.report = dict(title="Report", subjects={SUBJECT_ID: dict(name="ABC")}) self.database.reports.find_one.return_value = self.report self.database.datamodels.find_one.return_value = dict() @@ -620,16 +614,16 @@ def test_add_subject(self): """Test that a subject can be added.""" self.database.datamodels.find_one.return_value = dict( _id="", subjects=dict(subject_type=dict(name="Subject", description=""))) - self.assertEqual(dict(ok=True), post_new_subject("report_uuid", self.database)) + self.assertEqual(dict(ok=True), post_new_subject(REPORT_ID, self.database)) self.assertEqual( - dict(report_uuid="report_uuid", description="Jenny created a new subject in report 'Report'."), + dict(report_uuid=REPORT_ID, description="Jenny created a new subject in report 'Report'."), self.report["delta"]) def test_delete_subject(self): """Test that a subject can be deleted.""" - self.assertEqual(dict(ok=True), delete_subject("report_uuid", "subject_uuid", self.database)) + self.assertEqual(dict(ok=True), delete_subject(REPORT_ID, SUBJECT_ID, self.database)) self.assertEqual( - dict(report_uuid="report_uuid", description="Jenny deleted the subject 'ABC' from report 'Report'."), + dict(report_uuid=REPORT_ID, description="Jenny deleted the subject 'ABC' from report 'Report'."), self.report["delta"]) @@ -646,7 +640,7 @@ def test_add_report(self): self.database.reports.insert.assert_called_once() inserted = self.database.reports.insert.call_args_list[0][0][0] self.assertEqual("New report", inserted["title"]) - self.assertEqual("Jenny created this report.", inserted["delta"]["description"]) + self.assertEqual("Jenny created a new report.", inserted["delta"]["description"]) def test_get_report(self): """Test that a report can be retrieved.""" @@ -655,28 +649,31 @@ def test_get_report(self): self.database.reports_overviews.find_one.return_value = dict(_id="id", title="Reports", subtitle="") self.database.measurements.find.return_value = [ dict( - _id="id", metric_uuid="metric_uuid", status="red", - sources=[dict(source_uuid="source_uuid", parse_error=None, connection_error=None, value="42")])] - self.database.reports.distinct.return_value = ["report_uuid"] + _id="id", metric_uuid=METRIC_ID, status="red", + sources=[dict(source_uuid=SOURCE_ID, parse_error=None, connection_error=None, value="42")])] + self.database.reports.distinct.return_value = [REPORT_ID] report = dict( - _id="id", report_uuid="report_uuid", - subjects=dict( - subject_uuid=dict( - metrics=dict( - metric_uuid=dict( + _id="id", report_uuid=REPORT_ID, + subjects={ + SUBJECT_ID: dict( + metrics={ + METRIC_ID: dict( type="metric_type", addition="sum", target="0", near_target="10", debt_target="0", - accept_debt=False, tags=["a"]))))) + accept_debt=False, tags=["a"])})}) self.database.reports.find_one.return_value = report report["summary"] = dict(red=0, green=0, yellow=0, grey=0, white=1) - report["summary_by_subject"] = dict(subject_uuid=dict(red=0, green=0, yellow=0, grey=0, white=1)) + report["summary_by_subject"] = {SUBJECT_ID: dict(red=0, green=0, yellow=0, grey=0, white=1)} report["summary_by_tag"] = {} self.assertEqual(dict(_id="id", title="Reports", subtitle="", reports=[report]), get_reports(self.database)) def test_delete_report(self): """Test that the report can be deleted.""" - report = dict(_id="1", report_uuid="report_uuid", title="Report") + self.database.datamodels.find_one.return_value = dict(_id="id") + report = dict(_id="1", report_uuid=REPORT_ID, title="Report") self.database.reports.find_one.return_value = report - self.assertEqual(dict(ok=True), delete_report("report_uuid", self.database)) + self.assertEqual(dict(ok=True), delete_report(REPORT_ID, self.database)) + inserted = self.database.reports.insert.call_args_list[0][0][0] + self.assertEqual("Jenny deleted the report 'Report'.", inserted["delta"]["description"]) @patch("bottle.request") def test_post_reports_attribute(self, request): @@ -693,21 +690,21 @@ def test_get_tag_report(self, request): _id="id", metrics=dict(metric_type=dict(default_scale="count"))) self.database.reports.find_one.return_value = None self.database.measurements.find.return_value = [] - self.database.reports.distinct.return_value = ["report_uuid"] + self.database.reports.distinct.return_value = [REPORT_ID] self.database.reports.find_one.return_value = dict( - _id="id", report_uuid="report_uuid", - subjects=dict( - subject_without_metrics=dict(metrics=dict()), - subject_uuid=dict( + _id="id", report_uuid=REPORT_ID, + subjects={ + "subject_without_metrics": dict(metrics=dict()), + SUBJECT_ID: dict( metrics=dict( metric_with_tag=dict(type="metric_type", tags=["tag"]), - metric_without_tag=dict(type="metric_type", tags=["other tag"]))))) + metric_without_tag=dict(type="metric_type", tags=["other tag"])))}) self.assertEqual( dict( summary=dict(red=0, green=0, yellow=0, grey=0, white=1), summary_by_tag=dict(tag=dict(red=0, green=0, yellow=0, grey=0, white=1)), - summary_by_subject=dict(subject_uuid=dict(red=0, green=0, yellow=0, grey=0, white=1)), + summary_by_subject={SUBJECT_ID: dict(red=0, green=0, yellow=0, grey=0, white=1)}, title='Report for tag "tag"', subtitle="Note: tag reports are read-only", report_uuid="tag-tag", - timestamp=date_time, subjects=dict( - subject_uuid=dict(metrics=dict(metric_with_tag=dict(type="metric_type", tags=["tag"]))))), + timestamp=date_time, subjects={ + SUBJECT_ID: dict(metrics=dict(metric_with_tag=dict(type="metric_type", tags=["tag"])))}), get_tag_report("tag", self.database)) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index ecaade7058..36b5f68e48 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -15,6 +15,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Add keep-alive messages to the server-sent events stream so it does not time out when there are no new measurements for a while. Fixes [#787](https://github.com/ICTU/quality-time/issues/787). +### Added + +- In addition to a changelog per report, also keep a changelog for the reports overview. Closes [#746](https://github.com/ICTU/quality-time/issues/746). + ## [0.17.0] - [2019-11-10] ### Fixed