Add a Read-Only Redmine Tickets View to the Tickets Page - #536
SarkarShubhdeep wants to merge 4 commits into
Conversation
Add a second view to the tickets page, switched via a heading dropdown, that
lists the connected user's Redmine issues read-only.
- redmine-client.listIssues() -> GET /issues.json (assigned_to_id=me | all)
- redmine-issues.js shapes a minimal, client-safe DTO (id, subject, project,
status, assignedTo); everything else Redmine returns is dropped
- Meteor method redmine.issues.list({ scope }) + Wormhole.expose for REST
- redmineApi.issues.list() + RedmineIssue types on the frontend
- RedmineTicketsView: scope toggle, client-side search, assignee filter derived
from fetched issues, per-scope session cache, refresh, external row links,
loading / empty / not-connected states
- New Ticket is disabled with a Read only tooltip in the Redmine view
- Tickets page owns its heading, so AppLayout withholds the registry title
🚀 Preview Deployment Ready
Preview auto-deletes when this PR is closed. |
There was a problem hiding this comment.
Pull request overview
Adds a read-only Redmine ticket view backed by each user’s linked Redmine account.
Changes:
- Adds Redmine issue fetching, DTO shaping, and REST exposure.
- Adds filtering, caching, refresh, and external issue links.
- Adds a tickets view switcher and supporting tests/documentation.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
src/ui/AppLayout.tsx |
Suppresses the static tickets title. |
src/lib/api.ts |
Adds Redmine issue types and API client. |
src/features/tickets/TicketsPage.tsx |
Adds the tickets view switcher. |
src/features/tickets/RedmineTicketsView.tsx |
Implements the Redmine issue interface. |
meteor-backend/tests/redmine.test.ts |
Tests method authentication and validation. |
meteor-backend/tests/redmine-issues.test.ts |
Tests issue DTO shaping. |
meteor-backend/server/redmine.js |
Adds the issue-list Meteor method. |
meteor-backend/server/redmine-issues.js |
Shapes Redmine responses. |
meteor-backend/server/redmine-client.js |
Adds Redmine issue retrieval. |
meteor-backend/server/main.js |
Exposes the method through Wormhole. |
huddle_redmine_clock.md |
Documents the integration milestones. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
- Reset the assignee filter on scope change; a name present in "All" can be absent from "Assigned to me", which left the controlled select on a value with no matching option and every issue filtered out. - Key the issue cache by user id as well as scope. Sign-out does not reload the page, so a second user signing in could be served the first user's issues. - Only cache connected responses, so linking an account in Settings and coming back refetches instead of replaying a stale "not connected" state. - Bump the request sequence before the cache check so a cache hit also supersedes an in-flight request for a previously selected scope. - Gate the pull-to-refresh handler on the tickets route, matching the v1 view; TicketsPage stays mounted behind other routes. - Render the view switcher inside an h1 using the @mieweb/ui Button primitive, restoring the page heading semantics AppLayout no longer supplies.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.
Suppressed comments (2)
Previously missed (2) — in code that hasn't changed since the last review.
meteor-backend/server/redmine-client.js:70
- Redmine defaults
GET /issues.jsonto open issues. Since this request omitsstatus_id=*, both scopes exclude closed issues, so the documented “All = everything the key can see” contract is not met. Addstatus_id=*, or explicitly redefine the API and UI as open issues only.
src/features/tickets/RedmineTicketsView.tsx:248 - The issue count changes as search, scope, and assignee filters change, but it is not exposed as a live status. Screen-reader users receive no result-count feedback after filtering; mark this text as a polite status region.
- Refetch the Redmine view when it becomes visible again. TicketsPage stays mounted behind other routes, so linking or unlinking an account in Settings previously left the stale connection state on screen after navigating back. - Gate the v1 pull-to-refresh handler on the active view as well as the route; RefreshContext runs every registered handler, so the hidden v1 list was refetching alongside the visible Redmine view. - Include the active view in the switcher's accessible name so the h1 announces "Tickets v1" / "Redmine Tickets" instead of only the control's purpose.
| const listCache = new Map<string, RedmineIssueList>(); | ||
|
|
||
| const cacheKey = (userId: string, scope: RedmineScope) => `${userId}:${scope}`; |
| } catch (err: unknown) { | ||
| if (seq !== requestSeq.current) return; | ||
| setError(err instanceof Error ? err.message : 'Failed to load Redmine issues.'); |
Co-authored-by: SarkarShubhdeep <30292255+SarkarShubhdeep@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.
Suppressed comments (2)
Previously missed (2) — in code that hasn't changed since the last review.
meteor-backend/server/redmine-client.js:70
- The
allbranch does not actually request all visible issues: Redmine defaults an omittedstatus_idfilter to open issues, so closed issues are silently excluded even though this scope is documented as “everything the key can see.” Addstatus_id=*for theallscope (and keep the assignee filter only formine).
src/features/tickets/RedmineTicketsView.tsx:135 - This return-from-Settings refresh only works while
RedmineTicketsViewis mounted.TicketsPageunmounts it whenever the v1 view is selected, so after issues have been cached a user can switch to v1, connect/disconnect in Settings, return, and reopen Redmine; the initializer replays the stale connected cache andwasVisiblestarts astrue, so no forced fetch occurs. Invalidate this user's cache on link mutations, or keep the view mounted and gate it by an active flag.
TL;DR
Adds a "Redmine Tickets" view to the tickets page — a read-only list of the signed-in user's Redmine issues, switched from the existing "Tickets v1" list via a heading dropdown. Issues flow Redmine → TimeHuddle only; nothing is written to Redmine. This is Milestone 2 of
huddle_redmine_clock.md."Redmine Tickets" view on Tickets page
CleanShot.2026-09-10.at.15.58.47.mp4
Syncing a New Assigned ticket on Huddle
CleanShot.2026-09-10.at.16.04.24.mp4
Overview
A user should not have to open Redmine to see what is assigned to them. Milestone 1 let a user link their personal Redmine API key in Settings. Milestone 2 uses that key to fetch and render their issues inside TimeHuddle, read-only, with a scope toggle, search, and click-through links back to Redmine.
Current State
redmine_links.redmine-client.jsonly implementsgetCurrentUser— there is no way to read issues.h1supplied by the route registry.Proposed Changes
1. Backend — read Redmine issues
redmine-client.listIssues(apiKey, { scope, limit, offset })—GET /issues.json, addingassigned_to_id=meforscope: 'mine'. Pagination bounded bylimit/offset(Redmine capslimitat 100). Returns the rawissuesarray.redmine-issues.js— pure, Meteor-free shaping into a minimal client-safe DTO:id,subject,project,status,assignedTo. Tracker, priority, due date, custom fields, etc. are intentionally dropped.redmine.issues.list({ scope })— validates scope, decrypts the caller's own key, calls Redmine, returns{ connected, baseUrl, issues }.{ connected: false, issues: [] }so the view renders its not-connected state without a second round-trip.401/403→invalid-key; anything else →unreachable. The API key is never returned to the client.Wormhole.expose('redmine.issues.list', …)inmain.jsfor REST access.2. Frontend — data layer
redmineApi.issues.list(scope)plusRedmineIssue,RedmineNamed,RedmineIssueList, andRedmineScopetypes insrc/lib/api.ts.Ticketshape, and no generic "ticket source" abstraction is introduced (YAGNI/KISS).3. Frontend —
RedmineTicketsView@mieweb/uiprimitives (Card,Table,Badge,Button,Input,Select,Spinner,Text,Tooltip).{baseUrl}/issues/{id}in a new tab.4. Tickets page view switcher
h1is replaced by aDropdowntoggling Tickets v1 / Redmine Tickets.TicketsPagestays always-mounted (its state is preserved); the v1 subtree is hidden rather than unmounted when the Redmine view is active.AppLayoutnow always withholds the registryPageTitleContextvalue from the tickets instance, since the feature owns its own heading — this avoids a duplicateh1.Acceptance Criteria
npm run typecheckpasses;redmine-issuesunit tests pass.Out of Scope (for Now)
WorkItem.limit=100) is fetched.TicketPicker.Testing
npm run typecheck— clean.meteor-backend/tests/redmine-issues.test.ts— unit tests for DTO shaping (pure, no server needed).meteor-backend/tests/redmine.test.ts— method-level tests; these require a running Meteor test server on:3101and skip without it.Notes for Reviewers
docker-compose.override.ymlis intentionally not committed — it is a local-only dev override.huddle_redmine_clock.mdis included so the milestone plan and the decisions made along the way (separate data layer, cache strategy, scope-toggle semantics) are reviewable alongside the code.