Skip to content

Commit

Permalink
Add support for returning frames partially within span in filter and …
Browse files Browse the repository at this point in the history
…wire it up for aggregate
  • Loading branch information
Kenneth Nielsen authored and jmaupetit committed Mar 20, 2020
1 parent 9b3b40d commit 6d00707
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 15 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Improve Arrow 0.15.0 support after changes in `arrow.get()` behavior (#296)
- Watson now suggests correct command if users make small typo (#318)


### Fixed

- 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(mock, 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
]])
mock.patch('%s.open' % builtins, mock.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=False):
"""
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

0 comments on commit 6d00707

Please sign in to comment.