Skip to content

fix(planned-purchases): Pause is visible + reversible (badge, toast, Resume) - #904

Merged
cristim merged 3 commits into
feat/multicloud-web-frontendfrom
fix/planned-pause-visible-reversible
Jun 1, 2026
Merged

cristim merged 3 commits into
feat/multicloud-web-frontendfrom
fix/planned-pause-visible-reversible

Conversation

@cristim

@cristim cristim commented Jun 1, 2026 •

Copy link
Copy Markdown
Member

Closes #903. Resolves QA Planned 6.1.

Problem

After #779/#772 added the paused status to the purchase_executions valid_status CHECK constraint, clicking Pause on a scheduled purchase made the row silently disappear from the Scheduled (Planned) Purchases list with no feedback. A Pause that looks like a delete.

Root cause: getPlannedPurchases read GetPendingExecutions, whose status set (pending, notified) the scheduler relies on to decide what to fire and which excludes paused. So paused rows dropped out of the list.

Fix

  • Backend: list handler now calls GetExecutionsByStatuses(ctx, [pending, notified, paused], MaxListLimit) so paused executions stay listed. The scheduler keeps using the narrower GetPendingExecutions and never fires paused rows. Ordering restored to soonest-first (the store returns DESC).
  • Frontend: success toasts on Pause ("Purchase paused") and Resume ("Purchase resumed"). The Paused badge + Resume button were already present; the row now stays visible.
  • Distinct lifecycle actions preserved: Pause (one execution, reversible, plan stays enabled) vs Disable plan (Disable plan: scheduled purchase removed but plan toggle stays ON (Planned 6.4) #774, whole plan) vs Cancel (terminal).

The Resume endpoint (paused -> pending, 409 on ineligible transitions) already existed from #772 -- no new endpoint needed.

Tests

  • Backend: new regression test asserts the [pending, notified, paused] status set is requested, the paused row stays returned, and rows are ordered ascending. Existing list/error tests updated.
  • Frontend: paused row renders visibly with a status-paused badge (not the empty state); Pause and Resume each fire a success toast.

go test ./internal/api/... and ./internal/config/... pass; npx jest --testPathPattern='plan|purchase|history' passes; tsc --noEmit clean.

Summary by CodeRabbit

  • New Features
    • Paused purchases now remain visible in your planned purchases list, allowing you to easily track their status alongside upcoming purchases
    • Success notifications appear when you pause or resume a planned purchase, providing immediate confirmation of your action

@cristim cristim added triaged Item has been triaged priority/p2 Backlog-worthy severity/medium Moderate harm urgency/this-sprint Within the current sprint impact/many Affects most users effort/m Days type/bug Defect labels Jun 1, 2026
@cristim

cristim commented Jun 1, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 1, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@cristim, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 45 minutes and 51 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 49511073-0cd5-4f8a-ae0d-4d622b74a197

📥 Commits

Reviewing files that changed from the base of the PR and between ef451cf and d5aef84.

📒 Files selected for processing (13)
  • frontend/src/__tests__/plans.test.ts
  • frontend/src/plans.ts
  • internal/analytics/collector_test.go
  • internal/api/handler_purchases.go
  • internal/api/handler_purchases_test.go
  • internal/api/handler_test.go
  • internal/api/mocks_test.go
  • internal/config/interfaces.go
  • internal/config/store_postgres.go
  • internal/config/store_postgres_pgxmock_test.go
  • internal/purchase/mocks_test.go
  • internal/scheduler/scheduler_test.go
  • internal/server/test_helpers_test.go
📝 Walkthrough

Walkthrough

This PR implements pause/resume visibility and reversibility for planned purchases. The backend now includes paused executions in the planned-purchases API list and re-sorts results to maintain soonest-first order. The frontend action handler displays success toasts on pause/resume, and tests verify the paused row stays visible with user feedback.

Changes

Planned purchase pause visibility and reversibility

Layer / File(s) Summary
Backend execution list contract and planned-purchase data fetch
internal/api/handler_purchases.go
Introduce plannedListStatuses constant to document which execution statuses (pending, notified, paused) appear in the planned list. Update getPlannedPurchases to fetch via GetExecutionsByStatuses instead of GetPendingExecutions, and add stable in-memory sort to restore ascending (soonest-first) ScheduledDate order since the store returns descending.
Backend test suite for planned-purchases fetch
internal/api/handler_purchases_test.go, internal/api/handler_test.go
Update success-path, error-path, and integration tests to expect GetExecutionsByStatuses with paused status included. Add new regression test TestHandler_getPlannedPurchases_PausedStaysVisible to ensure paused executions remain in the list and are correctly sorted. Adjust error messages and mock setup for the new data-access contract.
Frontend action handler pause/resume toast feedback
frontend/src/plans.ts
Add showToast calls in handlePlannedPurchaseAction for pause and resume cases to display success messages after the API call.
Frontend UI test assertions for paused visibility and feedback
frontend/src/__tests__/plans.test.ts
Assert paused rows remain visible with status-paused class, empty-state message is not shown, and pause/resume actions trigger success toasts with expected messages (Purchase paused, Purchase resumed) and kind: 'success'.

Possibly related PRs

  • LeanerCloud/CUDly#780: Overlaps at frontend/src/plans.ts handlePlannedPurchaseAction to add planId parameter for the edit action alongside the main PR's pause/resume toast additions.
  • LeanerCloud/CUDly#779: Adds paused status to the database valid_status constraint so pause operations succeed; this PR then surfaces paused executions in the planned-purchases list and UI.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A pause that once would vanish,
Now lingers with a badge of grace,
A toast celebrates the banish—
A reversible, visible pause.
No silent disappearing acts,
Just feedback, kindness, facts!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: making paused purchases visible in the list with a badge, adding user feedback via toast, and enabling resumption.
Linked Issues check ✅ Passed All five objectives from #903 are met: paused rows remain visible with badge, success toasts are shown, Resume action is implemented, paused executions excluded from scheduler, and lifecycle semantics are preserved.
Out of Scope Changes check ✅ Passed All code changes directly address the linked issue requirements; no unrelated modifications to other features or refactoring are present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/planned-pause-visible-reversible

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cristim

cristim commented Jun 1, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

…Resume)

After #779/#772 added the paused DB status, clicking Pause made the scheduled
purchase silently disappear from the list with no feedback. The
planned-purchases list read GetPendingExecutions, whose status set
(pending, notified) the scheduler relies on to decide what to FIRE and which
excludes paused, so paused rows dropped out.

Switch the list handler to GetExecutionsByStatuses with
[pending, notified, paused] so paused executions stay listed (with the
existing Paused badge and Resume button) while the scheduler keeps using the
narrower GetPendingExecutions and never fires paused rows. Restore the
soonest-first ordering. Add success toasts on Pause and Resume.

Pause stays distinct from Disable plan (#774, whole plan) and Cancel
(terminal): it is reversible and scoped to a single execution; the plan stays
enabled. Resume (paused -> pending) and the 409 on ineligible transitions
already existed from #772.

Tests: backend asserts the paused status set is requested, paused rows stay
returned, and ordering is ascending; frontend asserts the paused row renders
visibly with a badge and that Pause/Resume fire success toasts.
@cristim
cristim force-pushed the fix/planned-pause-visible-reversible branch from ef451cf to fcb2fab Compare June 1, 2026 15:00

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
frontend/src/__tests__/plans.test.ts (1)

645-647: ⚡ Quick win

Make paused-label assertion less brittle

The innerHTML substring check ('>paused<') is tightly coupled to exact markup/casing and can fail on harmless rendering changes. Prefer DOM/text-based assertions.

Suggested test hardening
-      expect(container?.innerHTML).toContain('status-paused');
-      expect(container?.innerHTML).toContain('>paused<');
+      expect(container?.querySelector('.status-paused')).toBeTruthy();
+      expect(container?.textContent?.toLowerCase()).toContain('paused');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/__tests__/plans.test.ts` around lines 645 - 647, Replace the
brittle innerHTML substring check
expect(container?.innerHTML).toContain('>paused<') with a DOM/text-based
assertion: locate the paused label via a semantic query or selector (e.g. use
screen.getByText(/paused/i) or within(container).getByText(/paused/i), or check
container.querySelector('.status-paused')?.textContent.trim().toLowerCase() ===
'paused') and assert it is present; keep the existing status-paused class
assertion but remove the raw innerHTML match to avoid markup/casing brittleness.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/api/handler_purchases.go`:
- Around line 101-110: Handler.getPlannedPurchases is fetching executions with
GetExecutionsByStatuses(plannedListStatuses, config.MaxListLimit) and then
re-sorting in-memory, but PostgresStore.GetExecutionsByStatuses currently uses
"ORDER BY scheduled_date DESC LIMIT" causing the DB to truncate the newest rows
before you re-sort; change the store/query to accept an order direction (or add
a dedicated method) so that when called from getPlannedPurchases it queries with
"ORDER BY scheduled_date ASC LIMIT $2" (or a parameterized ASC order) so the DB
returns the soonest rows before LIMIT, then remove the in-memory corrective sort
in Handler.getPlannedPurchases.

---

Nitpick comments:
In `@frontend/src/__tests__/plans.test.ts`:
- Around line 645-647: Replace the brittle innerHTML substring check
expect(container?.innerHTML).toContain('>paused<') with a DOM/text-based
assertion: locate the paused label via a semantic query or selector (e.g. use
screen.getByText(/paused/i) or within(container).getByText(/paused/i), or check
container.querySelector('.status-paused')?.textContent.trim().toLowerCase() ===
'paused') and assert it is present; keep the existing status-paused class
assertion but remove the raw innerHTML match to avoid markup/casing brittleness.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 13db4f64-15f8-4b78-b2d9-29709055a242

📥 Commits

Reviewing files that changed from the base of the PR and between 1b99460 and ef451cf.

📒 Files selected for processing (5)
  • frontend/src/__tests__/plans.test.ts
  • frontend/src/plans.ts
  • internal/api/handler_purchases.go
  • internal/api/handler_purchases_test.go
  • internal/api/handler_test.go

Comment thread internal/api/handler_purchases.go Outdated
cristim added 2 commits June 1, 2026 18:05
GetExecutionsByStatuses uses ORDER BY scheduled_date DESC + LIMIT, which is
correct for History (newest-first) but truncates the soonest rows when used
for the Planned Purchases list and total pending/notified/paused rows exceed
MaxListLimit. An in-memory ASC re-sort of the already-truncated subset cannot
recover what LIMIT dropped at the DB.

Add a dedicated GetPlannedExecutions method that mirrors the
GetExecutionsByStatuses shape (same status filter, same limit clamping,
same scan logic via queryExecutions) but flips the ORDER BY to
scheduled_date ASC NULLS LAST, id ASC. The secondary id ASC sort keeps the
ordering stable when multiple rows share a scheduled_date; NULLS LAST is
defensive against a future schema relaxation (today scheduled_date is
NOT NULL).

GetExecutionsByStatuses is left untouched so its other callers (History
queries that want newest-first) keep their semantics.

pgxmock regression coverage anchors the SQL contract:
- TestPGXMock_GetPlannedExecutions_UsesASCOrdering asserts the query has
  ASC + NULLS LAST + id ASC + LIMIT $2 via a strict regex matcher, so a
  regression to DESC or a dropped secondary sort fails the test.
- TestPGXMock_GetPlannedExecutions_EmptyStatuses guards the short-circuit.
- TestPGXMock_GetPlannedExecutions_LimitClamping covers the negative ->
  DefaultListLimit and over-MaxListLimit -> MaxListLimit clamps.

All store mocks (api, purchase, scheduler, analytics, server health) gain
a GetPlannedExecutions method so the StoreInterface contract is satisfied
across the codebase.

Refs CR on #904.
…est rows

Handler.getPlannedPurchases was calling GetExecutionsByStatuses (ORDER BY
scheduled_date DESC + LIMIT $2) and then re-sorting ASC in-memory. When the
planned set (pending + notified + paused) exceeds MaxListLimit, the DB
returns only the LATEST rows; the in-memory sort just re-orders that
already-truncated subset. The soonest rows, exactly the ones the user has
to act on, can be omitted from the response entirely.

Switch the handler to the new GetPlannedExecutions store method (ASC at the
SQL level so LIMIT keeps the soonest rows), and drop the post-fetch
sort.SliceStable since the DB now returns rows in the correct order. The
"sort" import stays (still used for tuple normalisation elsewhere in the
file).

End-to-end regression coverage:
- TestHandler_getPlannedPurchases_SoonestRowsNotTruncated seeds 5 rows in
  ASC order, asserts all 5 reach the response in ASC order, and uses
  AssertNotCalled to guard against any future refactor re-introducing a
  parallel GetExecutionsByStatuses call on this code path.
- TestHandler_getPlannedPurchases_PausedStaysVisible updated to reflect
  the new ASC-from-store contract (was returning DESC then re-sorting).

Closes CR finding on #904.
@cristim

cristim commented Jun 1, 2026

Copy link
Copy Markdown
Member Author

Added GetPlannedExecutions store method with ASC ordering (vs the existing DESC GetExecutionsByStatuses). Handler.getPlannedPurchases now uses it; in-memory re-sort dropped. Regression test asserts the SQL uses ASC ordering AND a new handler test asserts the soonest rows are returned end-to-end. @coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

@cristim That's a clean improvement — pushing the ASC ordering down to the DB layer and eliminating the in-memory re-sort is the right call. I'll kick off a fresh review of the updated commits now.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cristim
cristim merged commit cec0918 into feat/multicloud-web-frontend Jun 1, 2026
5 checks passed
@cristim
cristim deleted the fix/planned-pause-visible-reversible branch June 3, 2026 21:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/m Days impact/many Affects most users priority/p2 Backlog-worthy severity/medium Moderate harm triaged Item has been triaged type/bug Defect urgency/this-sprint Within the current sprint

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant