Skip to content

Commit

Permalink
In addition to a changelog per report, also keep a changelog for the …
Browse files Browse the repository at this point in the history
…reports overview. Closes #746.
  • Loading branch information
fniessink committed Nov 12, 2019
1 parent 287c653 commit 114a17c
Show file tree
Hide file tree
Showing 13 changed files with 271 additions and 215 deletions.
5 changes: 4 additions & 1 deletion components/frontend/src/api/report.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
12 changes: 9 additions & 3 deletions components/frontend/src/changelog/ChangeLog.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
Expand All @@ -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;
}
Expand All @@ -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(<Table.Row key={change.timestamp + change.delta}>
<Table.Cell>
Expand Down
2 changes: 1 addition & 1 deletion components/frontend/src/metric/MeasurementDetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export function MeasurementDetails(props) {
reload={props.reload}
report_uuid={report_uuid}
/>
<ChangeLog report={props.report} metric_uuid={props.metric_uuid} />
<ChangeLog report_uuid={report_uuid} timestamp={props.report.timestamp} metric_uuid={props.metric_uuid} />
</Tab.Pane>
}
);
Expand Down
5 changes: 4 additions & 1 deletion components/frontend/src/report/ReportTitle.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ export function ReportTitle(props) {
</Grid.Row>
<Grid.Row>
<Grid.Column>
<ChangeLog report={props.report} />
<ChangeLog
report_uuid={props.report.report_uuid}
timestamp={props.report.timestamp}
/>
</Grid.Column>
</Grid.Row>
{!props.readOnly &&
Expand Down
6 changes: 6 additions & 0 deletions components/frontend/src/report/ReportsTitle.js
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -27,6 +28,11 @@ export function ReportsTitle(props) {
/>
</Grid.Column>
</Grid.Row>
<Grid.Row>
<Grid.Column>
<ChangeLog />
</Grid.Column>
</Grid.Row>
</Grid>
</Segment>
</HeaderWithDetails>
Expand Down
6 changes: 5 additions & 1 deletion components/frontend/src/source/Source.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,11 @@ export function Source(props) {
</Grid.Row>}
<Grid.Row>
<Grid.Column>
<ChangeLog report={props.report} source_uuid={props.source_uuid} />
<ChangeLog
report_uuid={props.report.report_uuid}
source_uuid={props.source_uuid}
timestamp={props.report.timestamp}
/>
</Grid.Column>
</Grid.Row>
{!props.readOnly &&
Expand Down
16 changes: 11 additions & 5 deletions components/frontend/src/subject/SubjectTitle.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<HeaderWithDetails level="h2" header={subject_name} style={{ marginTop: 50 }}>
<Segment>
Expand All @@ -27,7 +29,7 @@ export function SubjectTitle(props) {
<SubjectType
datamodel={props.datamodel}
readOnly={props.readOnly}
set_value={(value) => 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}
/>
</Grid.Column>
Expand All @@ -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}
/>
</Grid.Column>
</Grid.Row>
<Grid.Row>
<Grid.Column>
<ChangeLog report={props.report} subject_uuid={props.subject_uuid} />
<ChangeLog
report_uuid={report_uuid}
subject_uuid={subject_uuid}
timestamp={props.report.timestamp}
/>
</Grid.Column>
</Grid.Row>
{!props.readOnly &&
Expand All @@ -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"
/>
Expand All @@ -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
>
<Icon name='trash' /> Delete subject
Expand Down
20 changes: 10 additions & 10 deletions components/server/src/database/reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
25 changes: 17 additions & 8 deletions components/server/src/routes/changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -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/<report_uuid>/source/<source_uuid>/<nr_changes>")
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/<report_uuid>/metric/<metric_uuid>/<nr_changes>")
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/<report_uuid>/subject/<subject_uuid>/<nr_changes>")
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/<report_uuid>/<nr_changes>")
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/<nr_changes>")
def get_changelog(nr_changes: str, database: Database):
"""Return the recent most nr_changes changes from the changelog."""
return _get_changelog(database, nr_changes)
14 changes: 10 additions & 4 deletions components/server/src/routes/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)


Expand All @@ -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/<report_uuid>")
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/<tag>")
Expand Down
14 changes: 13 additions & 1 deletion components/server/tests/unittests/routes/test_changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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")]),
Expand Down
Loading

0 comments on commit 114a17c

Please sign in to comment.