Skip to content
Open
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
12 changes: 10 additions & 2 deletions aw_query/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,8 +280,16 @@ def q2_union_no_overlap(events1: list, events2: list) -> List[Event]:

@q2_function(flood)
@q2_typecheck
def q2_flood(events: list) -> List[Event]:
return flood(events)
def q2_flood(events: list, pulsetime: float = 5.0) -> List[Event]:
"""Fill gaps between events up to pulsetime seconds.

The default pulsetime of 5s works well for the default 1s poll interval.
If you have set a higher polling interval (e.g. 30s), set pulsetime to
at least poll_time + 1 to avoid gaps in the activity timeline.

See ActivityWatch/activitywatch#1177.
"""
return flood(events, pulsetime)
Comment on lines +283 to +292

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 pulsetime type is not validated by q2_typecheck

The q2_typecheck decorator only enforces type annotations on required parameters (param.default == param.empty). Because pulsetime has a default value of 5.0, it is silently skipped. If a user calls flood(events, "30") in a query, Python will pass a string into aw_transform.flood, which then calls timedelta(seconds="30"), producing a cryptic TypeError instead of a QueryFunctionException. A manual guard (if not isinstance(pulsetime, (int, float)): raise QueryFunctionException(...)) before the return would give users a clear error message.



"""
Expand Down
30 changes: 30 additions & 0 deletions tests/test_flood.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,33 @@ def test_flood_negative_small_gap_differing_data():
flooded = flood(events)
duration = sum((e.duration for e in flooded), timedelta(0))
assert duration == timedelta(seconds=100 + 99.99)


def test_flood_large_gap_not_filled_with_default_pulsetime():
"""A gap larger than the default pulsetime (5s) should not be filled."""
events = [
Event(timestamp=now, duration=10, data={"a": 0}),
Event(timestamp=now + 25 * td1s, duration=10, data={"b": 1}),
]
flooded = flood(events)
# Gap is 25s - 10s = 15s, larger than default pulsetime=5; stays as a gap
assert len(flooded) == 2
gap = flooded[1].timestamp - (flooded[0].timestamp + flooded[0].duration)
assert gap > timedelta(0)


def test_flood_large_gap_filled_with_custom_pulsetime():
"""A gap larger than default pulsetime should be filled when pulsetime is increased.

This is the fix for ActivityWatch/activitywatch#1177: users with a high
poll interval (e.g. 30s) need to call flood with pulsetime=poll_time+1.
"""
events = [
Event(timestamp=now, duration=10, data={"a": 0}),
Event(timestamp=now + 25 * td1s, duration=10, data={"b": 1}),
]
flooded = flood(events, pulsetime=20)
# Gap is 15s, within pulsetime=20; should be filled
assert len(flooded) == 2
gap = flooded[1].timestamp - (flooded[0].timestamp + flooded[0].duration)
assert gap == timedelta(0)
Comment on lines +83 to +110

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 New tests exercise aw_transform.flood directly, not q2_flood

Both new tests import and call flood from aw_transform, bypassing the q2_flood wrapper in aw_query/functions.py that was actually changed. A bug in how pulsetime is threaded through q2_flood (e.g. a copy-paste that keeps the old return flood(events)) would leave these tests green while the query API remains broken. Adding a test (or even a smoke-test) that calls q2_flood or exercises it through the query engine would close this gap.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!