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

Add support for returning frames partially within span in filter and… #277

Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Stylize prompt to create new project or tag (#310).
- Aggregate calculates wrong time if used with `--current` (#293)
- The `start` command now correctly checks if project is empty (#322)
- Aggregate ignores frames that crosses aggreagate boundary (#248)
- The `report` and `aggregate` commands with `--json` option now correctly
encode Arrow objects (#329)

Expand Down
34 changes: 34 additions & 0 deletions tests/test_watson.py
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,40 @@ def test_report_current(mocker, config_dir):
assert len(report['projects']) == 0


@pytest.mark.parametrize(
"date_as_unixtime,include_partial,sum_", (
(3600 * 24, False, 0.0),
(3600 * 48, False, 0.0),
(3600 * 24, True, 7200.0),
(3600 * 48, True, 3600.0),
)
)
def test_report_include_partial_frames(mocker, watson, date_as_unixtime,
include_partial, sum_):
"""Test report building with frames that cross report boundaries

1 event is added that has 2 hours in one day and 1 in the next. The
parametrization checks that the report for both days is empty with
`include_partial=False` and report the correct amount of hours with
`include_partial=False`

"""
content = json.dumps([[
3600 * 46,
3600 * 49,
"programming",
"3e76c820909840f89cabaf106ab7d12a",
["cli"],
1548797432
]])
mocker.patch('%s.open' % builtins, mocker.mock_open(read_data=content))
date = arrow.get(date_as_unixtime)
report = watson.report(
from_=date, to=date, include_partial_frames=include_partial,
)
assert report["time"] == pytest.approx(sum_, abs=1e-3)


# renaming project updates frame last_updated time
def test_rename_project_with_time(watson):
"""
Expand Down
8 changes: 5 additions & 3 deletions watson/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,7 @@ def status(watson, project, tags, elapsed):
@catch_watson_error
def report(watson, current, from_, to, projects, tags, ignore_projects,
ignore_tags, year, month, week, day, luna, all, output_format,
pager, aggregated=False):
pager, aggregated=False, include_partial_frames=True):
"""
Display a report of the time spent on each project.

Expand Down Expand Up @@ -606,7 +606,8 @@ def report(watson, current, from_, to, projects, tags, ignore_projects,
report = watson.report(from_, to, current, projects, tags,
ignore_projects, ignore_tags,
year=year, month=month, week=week, day=day,
luna=luna, all=all)
luna=luna, all=all,
include_partial_frames=include_partial_frames)

if 'json' in output_format and not aggregated:
click.echo(json.dumps(report, indent=4, sort_keys=True,
Expand Down Expand Up @@ -825,7 +826,8 @@ def aggregate(ctx, watson, current, from_, to, projects, tags, output_format,
output = ctx.invoke(report, current=current, from_=from_offset,
to=from_offset, projects=projects, tags=tags,
output_format=output_format,
pager=pager, aggregated=True)
pager=pager, aggregated=True,
include_partial_frames=True)

if 'json' in output_format:
lines.append(output)
Expand Down
39 changes: 29 additions & 10 deletions watson/frames.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ def __init__(self, start, stop, timeframe='day'):
self.start = start.floor(self.timeframe)
self.stop = stop.ceil(self.timeframe)

def overlaps(self, frame):
return frame.start <= self.stop and frame.stop >= self.start

def __contains__(self, frame):
return frame.start >= self.start and frame.stop <= self.stop

Expand Down Expand Up @@ -150,17 +153,33 @@ def filter(
ignore_projects=None,
ignore_tags=None,
span=None,
include_partial_frames=False,
):
return (
frame for frame in self._rows
if (projects is None or frame.project in projects) and
(ignore_projects is None
or frame.project not in ignore_projects) and
(tags is None or any(tag in frame.tags for tag in tags)) and
(ignore_tags is None
or all(tag not in frame.tags for tag in ignore_tags)) and
(span is None or frame in span)
)

for frame in self._rows:
if projects is not None and frame.project not in projects:
continue
if ignore_projects is not None and\
frame.project in ignore_projects:
continue

if tags is not None and not any(tag in frame.tags for tag in tags):
continue
if ignore_tags is not None and\
any(tag in frame.tags for tag in ignore_tags):
continue

if span is None:
yield frame
elif frame in span:
yield frame
elif include_partial_frames and span.overlaps(frame):
# If requested, return the part of the frame that is within the
# span, for frames that are *partially* within span or reaching
# over span
start = span.start if frame.start < span.start else frame.start
stop = span.stop if frame.stop > span.stop else frame.stop
yield frame._replace(start=start, stop=stop)

def span(self, start, stop):
return Span(start, stop)
5 changes: 3 additions & 2 deletions watson/watson.py
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,8 @@ def _validate_report_options(self, filtrate, ignored):

def report(self, from_, to, current=None, projects=None, tags=None,
ignore_projects=None, ignore_tags=None, year=None,
month=None, week=None, day=None, luna=None, all=None):
month=None, week=None, day=None, luna=None, all=None,
include_partial_frames=False):
for start_time in (_ for _ in [day, week, month, year, luna, all]
if _ is not None):
from_ = start_time
Expand Down Expand Up @@ -481,7 +482,7 @@ def report(self, from_, to, current=None, projects=None, tags=None,
projects=projects or None, tags=tags or None,
ignore_projects=ignore_projects or None,
ignore_tags=ignore_tags or None,
span=span
span=span, include_partial_frames=include_partial_frames,
),
operator.attrgetter('project')
)
Expand Down