Skip to content

feat: workspace user listing and per-user report filtering - #46

Closed
vincentardern wants to merge 2 commits into
verygoodplugins:mainfrom
vincentardern:feat/workspace-user-reporting
Closed

feat: workspace user listing and per-user report filtering#46
vincentardern wants to merge 2 commits into
verygoodplugins:mainfrom
vincentardern:feat/workspace-user-reporting

Conversation

@vincentardern

@vincentardern vincentardern commented May 11, 2026

Copy link
Copy Markdown

Summary

  • Adds toggl_list_workspace_users — lists all members of a workspace with their uid values
  • Extends all four report tools (toggl_daily_report, toggl_weekly_report, toggl_project_summary, toggl_workspace_summary) with optional uid + workspace_id parameters that route through the Reports API v3 to fetch a specific team member's entries instead of the authenticated user's
  • Refactors the duplicated retry/rate-limit loop into a shared fetchWithRetry() private method used by both request() and the new reportsRequest() helper, and removes the unused getDetailedReport() stub
  • Extracts resolveDateRange() to eliminate repeated date-range logic across the four report handlers

How it works

When uid is omitted the existing code path is unchanged — no extra API calls, no performance impact. When uid is provided, the Reports API v3 (/search/time_entries) is used with user_ids filtering and automatic cursor-based pagination via the X-Next-Row-Number response header.

The Reports API returns grouped rows with nested time_entries arrays; these are flattened into the same TimeEntry shape the rest of the codebase uses, so hydration and report generation work without modification. The Reports API uses inclusive end dates — conversion from the codebase's exclusive-end convention happens inside getTimeEntriesForUserAndDateRange().

Test plan

  • npm test — all 37 tests pass
  • toggl_list_workspace_users returns workspace members with uid values
  • toggl_daily_report / toggl_weekly_report with uid returns correct entries for that member
  • All report tools without uid behave identically to before

🤖 Generated with Claude Code

Adds a new toggl_list_workspace_users tool and extends all four
reporting tools (toggl_daily_report, toggl_weekly_report,
toggl_project_summary, toggl_workspace_summary) with optional
uid + workspace_id parameters that route through the Reports API v3
to fetch another workspace member's time entries.

Also refactors the duplicated retry/rate-limit loop into a shared
fetchWithRetry() private method used by both request() and the new
reportsRequest() helper, removes the unused getDetailedReport() stub,
and extracts resolveDateRange() to eliminate repeated date-range
resolution logic in the report handlers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vincentardern
vincentardern force-pushed the feat/workspace-user-reporting branch from 8b11a2c to 7ef6ca3 Compare May 11, 2026 00:42
@vincentardern

Copy link
Copy Markdown
Author

Hi – just a gentle ping on this one. The CI workflows are still awaiting maintainer approval to run. Would you be able to approve them so the test suite can execute? Happy to address any feedback once the checks are through. Thanks!

@jack-arturo

Copy link
Copy Markdown
Member

Thanks for the ping @vincentardern , and glad to see CI is passing.

I’m open to the capability here. I’m generally cautious about expanding the MCP tool surface, but toggl_list_workspace_users seems justified because uid is otherwise not discoverable, and per-user reporting needs a clear lookup path.

Before merging, could you address two things?

  1. Please verify the workspace users endpoint. I think Toggl’s current API uses /workspaces/{workspace_id}/users rather than /workspace_users.
  2. Please make the Reports API response shape explicit. The code assumes grouped rows with nested time_entries, so we should either send grouped: true or parse the default ungrouped response shape.

One smaller privacy nit: I’d prefer the new user-listing tool not return emails/admin fields by default unless they’re needed. uid, name, and active status should be enough for report filtering.

Once those are cleaned up, I think this is mergeable 🙏

- Use documented v9 GET /workspaces/{id}/users endpoint (was the
  undocumented /workspace_users); map id->uid, fullname->name,
  is_active->active.
- Drop misleading grouped:true from the reports query and make
  time-entry flattening defensive: use nested time_entries when
  present, else treat the row as a single entry (v3 search response
  body schema is undocumented).
- Extract TogglAPI.toWorkspaceMemberSummary returning only
  {uid,name,active}; regression test asserts email/admin/role/rate
  never leak.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vincentardern

Copy link
Copy Markdown
Author

Thanks for the review — all three addressed in c2f411c:

  1. Endpoint — switched to the documented v9 GET /workspaces/{id}/users. It returns the user id as id (no uid), so the mapper does id→uid, fullname→name, is_active→active. The old /workspace_users call (without an org id) was undocumented.
  2. Reports shape — dropped grouped:true (the docs say it defaults false and only affects description-filtered queries, so it had no effect here). The v3 search/time_entries response body schema isn't documented, so I made the flattening defensive: use the nested time_entries array when present, otherwise treat the row as a single entry. Verified live against my workspace.
  3. Privacy — extracted TogglAPI.toWorkspaceMemberSummary, which returns only {uid, name, active}. Added a regression test asserting email/admin/role/rate never leak. Worth noting: that's the only endpoint that returns member PII at all — the per-user report tools (daily/weekly/project/workspace summaries with a uid) go through a separate path that maps the Reports API rows field-by-field into time-entry data only (id, project, task, billable, description, tags, start/stop, duration), so they carry no email/rate/admin by construction.

Build clean, 40/40 tests, lint 0 errors.

@jack-arturo

Copy link
Copy Markdown
Member

I opened a maintainer refresh branch as #57: #57

This keeps the original contribution moving without force-pushing to the fork, and stacks the work on the refreshed self-host HTTP foundation from #36. The refresh preserves the workspace-user/report-filtering intent from this PR, adds MCP handler coverage, uses the documented /workspaces/{id}/users response shape, and keeps the member listing privacy-safe as uid/name/active only. I credited the original work in the commit and PR body.

Local verification on #57:

  • npm run build
  • npm test
  • npm run test:coverage
  • npm run lint
  • npm audit --audit-level=high

@vincentardern

Copy link
Copy Markdown
Author

Closing in favour of #57 – thanks for the attribution. (Copilot flagged a few things on #57 if you haven't seen them yet.)

jack-arturo added a commit that referenced this pull request Jun 8, 2026
* feat(toggl): add workspace user reporting

Refreshes PR #46 on top of feat/self-host-streamable-http.

Adds privacy-safe workspace user lookup and optional uid report filtering through Toggl Reports API v3.

Co-authored-by: Vincent Ardern <vincent@charityaccounts.co.nz>

* fix: address review feedback for workspace users and header casing

---------

Co-authored-by: Vincent Ardern <vincent@charityaccounts.co.nz>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants