Skip to content
Closed
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
28 changes: 27 additions & 1 deletion test/integration/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ all: \
report-timezone-daylight-change \
report-hello-only-today \
report-project \
report-project-summary \
report-project-summary-perc \
report-per-day \
report-project-per-day \
report-project-per-day-csv \
Expand Down Expand Up @@ -56,7 +58,7 @@ completion: $(UTT)
@echo ">> COMPLETION"

register-python-argcomplete utt >> ~/.bashrc
bash -i -c 'diff <(COMP_LINE="utt" COMP_POINT=4 _python_argcomplete utt && echo $${COMPREPLY[@]} | tr " " "\n" | sort) <(echo -h --help --data --now --timezone --version add config edit hello report stretch | tr " " "\n" | sort)'
bash -i -c 'diff <(COMP_LINE="utt" COMP_POINT=4 _python_argcomplete utt && echo $${COMPREPLY[@]} | tr " " "\n" | sort) <(echo -h --help --data --now --timezone --version add config edit hello project-summary report stretch | tr " " "\n" | sort)'

@echo "<< COMPLETION"

Expand Down Expand Up @@ -255,6 +257,30 @@ report-project: $(UTT)
@echo "<< REPORT-PROJECT"


.PHONY: report-project-summary
report-project-summary: $(UTT)
@echo
@echo ">> REPORT-PROJECT-SUMMARY"

mkdir -p `dirname $(UTT_DATA_FILENAME)`
cp data/utt-1.log $(UTT_DATA_FILENAME)
bash -c 'diff -u <(utt --now "2014-3-19 18:30" project-summary 2014-3-19 --no-current-activity) data/utt-project-summary.stdout'

@echo "<< REPORT-PROJECT-SUMMARY"


.PHONY: report-project-summary-perc
report-project-summary-perc: $(UTT)
@echo
@echo ">> REPORT-PROJECT-SUMMARY-PERC"

mkdir -p `dirname $(UTT_DATA_FILENAME)`
cp data/utt-1.log $(UTT_DATA_FILENAME)
bash -c 'diff -u <(utt --now "2014-3-19 18:30" project-summary 2014-3-19 --no-current-activity --show-perc) data/utt-project-summary-perc.stdout'

@echo "<< REPORT-PROJECT-SUMMARY-PERC"


.PHONY: report-per-day
report-per-day: $(UTT)
# NOTE: this is not an intended use of the `--per-day` switch.
Expand Down
10 changes: 10 additions & 0 deletions test/integration/data/utt-project-summary-perc.stdout
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

------------------------------- Project Summary --------------------------------

asd : 3h15 ( 59.1%)
: 1h00 ( 18.2%)
qwer: 0h45 ( 13.6%)
A : 0h30 ( 9.1%)

Total: 5h30 (100.0%)

10 changes: 10 additions & 0 deletions test/integration/data/utt-project-summary.stdout
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

------------------------------- Project Summary --------------------------------

asd : 3h15
: 1h00
qwer: 0h45
A : 0h30

Total: 5h30

248 changes: 248 additions & 0 deletions test/unit/report/test_project_summary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
from datetime import datetime, timedelta
from io import StringIO

import pytz

from utt.data_structures.activity import Activity
from utt.report.project_summary.model import ProjectSummaryModel
from utt.report.project_summary.view import ProjectSummaryView


def create_activity(name, start_time, duration_minutes, is_current=False):
start = pytz.UTC.localize(start_time)
end = start + timedelta(minutes=duration_minutes)
return Activity(name, start, end, is_current)


def test_view_output_with_aligned_colons():
activities = [
create_activity("project1: task1", datetime(2024, 1, 1, 9, 0), 240),
create_activity("project2: task1", datetime(2024, 1, 1, 13, 0), 165),
create_activity("project3: task1", datetime(2024, 1, 1, 16, 0), 30),
create_activity("project4: task1", datetime(2024, 1, 1, 17, 0), 30),
create_activity("project5: task1", datetime(2024, 1, 1, 18, 0), 15),
]
model = ProjectSummaryModel(activities)
view = ProjectSummaryView(model)
output = StringIO()

view.render(output)
result = output.getvalue()

lines = result.split("\n")
assert "Project Summary" in lines[1]
assert "project1: 4h00" in lines[3]
assert "project2: 2h45" in lines[4]
assert "project3: 0h30" in lines[5]
assert "project4: 0h30" in lines[6]
assert "project5: 0h15" in lines[7]
assert "Total : 8h00" in lines[9]


def test_view_output_with_current_activity():
activities = [
create_activity("project1: task1", datetime(2024, 1, 1, 9, 0), 240),
create_activity("project2: task1", datetime(2024, 1, 1, 13, 0), 165),
create_activity("project3: task1", datetime(2024, 1, 1, 16, 0), 30),
create_activity("project4: task1", datetime(2024, 1, 1, 17, 0), 30),
create_activity("project5: task1", datetime(2024, 1, 1, 18, 0), 15),
create_activity("-- Current Activity --", datetime(2024, 1, 1, 19, 0), 220, is_current=True),
]
model = ProjectSummaryModel(activities)
view = ProjectSummaryView(model)
output = StringIO()

view.render(output)
result = output.getvalue()

lines = result.split("\n")
assert "project1: 4h00" in lines[3]
assert "project2: 2h45" in lines[4]
assert "project3: 0h30" in lines[5]
assert "project4: 0h30" in lines[6]
assert "project5: 0h15" in lines[7]
assert "-- Current Activity --: 3h40" in lines[8]
assert "Total : 11h40" in lines[10]


def test_view_colons_aligned_with_varying_project_lengths():
activities = [
create_activity("a: task1", datetime(2024, 1, 1, 9, 0), 60),
create_activity("medium-name: task1", datetime(2024, 1, 1, 10, 0), 120),
create_activity("very-long-project-name: task1", datetime(2024, 1, 1, 12, 0), 30),
]
model = ProjectSummaryModel(activities)
view = ProjectSummaryView(model)
output = StringIO()

view.render(output)
result = output.getvalue()

lines = result.split("\n")
colon_positions = []
for line in lines[3:6]:
if ":" in line and "---" not in line:
colon_positions.append(line.index(":"))

assert len(set(colon_positions)) == 1, "All colons should be at the same position"


def test_view_empty_activities():
model = ProjectSummaryModel([])
view = ProjectSummaryView(model)
output = StringIO()

view.render(output)
result = output.getvalue()

assert "Project Summary" in result
assert "Total: 0h00" in result


def test_view_single_project():
activities = [
create_activity("backend: api work", datetime(2024, 1, 1, 9, 0), 180),
]
model = ProjectSummaryModel(activities)
view = ProjectSummaryView(model)
output = StringIO()

view.render(output)
result = output.getvalue()

assert "backend: 3h00" in result
assert "Total : 3h00" in result


def test_view_projects_without_names():
activities = [
create_activity("standalone task", datetime(2024, 1, 1, 9, 0), 60),
create_activity("another task", datetime(2024, 1, 1, 10, 0), 30),
]
model = ProjectSummaryModel(activities)
view = ProjectSummaryView(model)
output = StringIO()

view.render(output)
result = output.getvalue()

lines = result.split("\n")
assert ": 1h30" in lines[3]
assert "Total: 1h30" in lines[5]


def test_view_sorting_by_duration():
activities = [
create_activity("alpha: task1", datetime(2024, 1, 1, 9, 0), 30),
create_activity("beta: task1", datetime(2024, 1, 1, 10, 0), 90),
create_activity("gamma: task1", datetime(2024, 1, 1, 12, 0), 60),
]
model = ProjectSummaryModel(activities)
view = ProjectSummaryView(model)
output = StringIO()

view.render(output)
result = output.getvalue()

lines = [line for line in result.split("\n") if ":" in line and "Project Summary" not in line and "---" not in line]
project_lines = [line for line in lines if "Total" not in line]

assert "beta" in project_lines[0]
assert "gamma" in project_lines[1]
assert "alpha" in project_lines[2]


def test_view_large_durations():
activities = [
create_activity("marathon: task1", datetime(2024, 1, 1, 9, 0), 1500),
create_activity("sprint: task1", datetime(2024, 1, 2, 10, 0), 600),
]
model = ProjectSummaryModel(activities)
view = ProjectSummaryView(model)
output = StringIO()

view.render(output)
result = output.getvalue()

assert "marathon: 25h00" in result
assert "sprint : 10h00" in result
assert "Total : 35h00" in result


def test_view_mixed_named_and_unnamed_projects():
activities = [
create_activity("asd: A-526", datetime(2024, 1, 1, 9, 0), 195),
create_activity("qwer: b-73", datetime(2024, 1, 1, 12, 15), 45),
create_activity("hard work", datetime(2024, 1, 1, 13, 0), 60),
create_activity("A: z-8", datetime(2024, 1, 1, 14, 0), 30),
]
model = ProjectSummaryModel(activities)
view = ProjectSummaryView(model)
output = StringIO()

view.render(output)
result = output.getvalue()

lines = result.split("\n")
assert "asd : 3h15" in lines[3]
assert " : 1h00" in lines[4]
assert "qwer: 0h45" in lines[5]
assert "A : 0h30" in lines[6]
assert "Total: 5h30" in lines[8]


def test_view_with_percentages():
activities = [
create_activity("project1: task1", datetime(2024, 1, 1, 9, 0), 240),
create_activity("project2: task1", datetime(2024, 1, 1, 13, 0), 120),
create_activity("project3: task1", datetime(2024, 1, 1, 15, 0), 60),
]
model = ProjectSummaryModel(activities)
view = ProjectSummaryView(model, show_perc=True)
output = StringIO()

view.render(output)
result = output.getvalue()

lines = result.split("\n")
assert "project1: 4h00 ( 57.1%)" in lines[3]
assert "project2: 2h00 ( 28.6%)" in lines[4]
assert "project3: 1h00 ( 14.3%)" in lines[5]
assert "Total : 7h00 (100.0%)" in lines[7]


def test_view_with_percentages_and_current_activity():
activities = [
create_activity("project1: task1", datetime(2024, 1, 1, 9, 0), 240),
create_activity("project2: task1", datetime(2024, 1, 1, 13, 0), 120),
create_activity("-- Current Activity --", datetime(2024, 1, 1, 15, 0), 60, is_current=True),
]
model = ProjectSummaryModel(activities)
view = ProjectSummaryView(model, show_perc=True)
output = StringIO()

view.render(output)
result = output.getvalue()

lines = result.split("\n")
assert "project1: 4h00 ( 57.1%)" in lines[3]
assert "project2: 2h00 ( 28.6%)" in lines[4]
assert "-- Current Activity --: 1h00 ( 14.3%)" in lines[5]
assert "Total : 7h00 (100.0%)" in lines[7]


def test_view_percentages_without_flag():
activities = [
create_activity("project1: task1", datetime(2024, 1, 1, 9, 0), 240),
create_activity("project2: task1", datetime(2024, 1, 1, 13, 0), 120),
]
model = ProjectSummaryModel(activities)
view = ProjectSummaryView(model, show_perc=False)
output = StringIO()

view.render(output)
result = output.getvalue()

assert "%" not in result
assert "project1: 4h00" in result
assert "project2: 2h00" in result
2 changes: 2 additions & 0 deletions utt/api/_v1/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
from ...report.activities.view import ActivitiesView
from ...report.details.view import DetailsView
from ...report.per_day.view import PerDayView
from ...report.project_summary.model import ProjectSummaryModel
from ...report.project_summary.view import ProjectSummaryView
from ...report.projects.view import ProjectsView
from ...report.summary.view import SummaryView
from ._private import register_command, register_component
2 changes: 2 additions & 0 deletions utt/components/report_model/model.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from ...report.activities.model import ActivitiesModel
from ...report.details.model import DetailsModel
from ...report.per_day.model import PerDayModel
from ...report.project_summary.model import ProjectSummaryModel
from ...report.projects.model import ProjectsModel
from ...report.summary.model import SummaryModel
from ..activities import Activities
Expand All @@ -17,6 +18,7 @@ def __init__(self, activities: Activities, args: ReportArgs, local_timezone: Loc
self.args = args
self.summary_model = SummaryModel(activities, args.range)
self.projects_model = ProjectsModel(activities)
self.project_summary_model = ProjectSummaryModel(activities)
self.per_day_model = PerDayModel(activities)
self.activities_model = ActivitiesModel(activities)
self.details_model = DetailsModel(activities, local_timezone)
Loading
Loading