Skip to content

feat(gui): expose CLI recommendation filters in per-service Settings - #1094

Merged
cristim merged 3 commits into
mainfrom
feat/gui-cli-filters
Jun 9, 2026
Merged

cristim merged 3 commits into
mainfrom
feat/gui-cli-filters

Conversation

@cristim

@cristim cristim commented Jun 7, 2026 •

Copy link
Copy Markdown
Member

What

Exposes the CLI recommendation-filter knobs in the per-service Settings cards,
end to end. Closes #1093.

A. Already backend-wired -> GUI exposed

Include/exclude engines, instance types, and regions already
persist on config.ServiceConfig and are applied server-side in
scheduler.filterRecsByResolvedConfigs. This adds the GUI controls and makes
the save path persist them.

B. Newly backend-wired -> GUI exposed

Per-service min-count (the CLI --min-count flag):

  • ServiceConfig.MinCount (migration 000068, postgres Get/Save/List,
    resolver).
  • Read-time filter in scheduler.filterRecsByResolvedConfigs (drops recs whose
    net Count is below the floor; 0 disables, matching the CLI 0-no-floor
    semantics).
  • Validated (>= 0, <= MaxServiceMinCount = 10000) so a typo can't silently
    suppress every rec.
  • openapi.yaml updated.

Merge path hardening

updateServiceConfig / mergeServiceConfig are now presence-aware: the
filter fields are overlaid from the request only when the JSON body carries the
key, so a partial PUT from a non-UI client never zeroes a filter set
out-of-band. The four scalar fields (enabled/term/payment/coverage) are still
overlaid unconditionally.

Frontend

New serviceFilters.ts renders a "Recommendation filters" panel into each
Service Defaults card (one code path driven by SERVICE_FIELDS, not ~20 HTML
blocks). DOM is built with createElement + .value only (no innerHTML), so
user values cannot inject markup. settings.ts injects + populates on load and
reads + validates on save (a bad min-count aborts the whole save with a
targeted toast).

End-to-end path verified

GUI form -> updateServiceConfig -> PUT /config/service/{p}/{s} -> persisted
ServiceConfig -> scheduler.filterRecsByResolvedConfigs on the next
recommendations fetch. Covered for both an engine/type/region filter and the
min-count filter.

Deferred (with rationale)

max-break-even-months, min-pool-size (filter on values not on the persisted
RecommendationRecord; config can't import pkg/common), max-instances
(global selection cap, not per-service), SP-types (already distinct services
with per-card enable toggles; CLI field unused), include-extended-support
(CLI-only engine-version lookups absent from the server pipeline). See
docs/code-review/open-questions/gui-filters.md. Each warrants its own
follow-up.

Tests

  • Go: scheduler min-count drop/keep; config rejects out-of-range MinCount;
    presence-aware merge (overlay present / preserve omitted / clear explicit
    empty). go build ./..., go vet, and
    go test ./internal/config/... ./internal/api/... ./internal/scheduler/...
    all green (2168 pass).
  • Frontend: serviceFilters.test.ts (15 cases). 197 settings+filter tests
    pass; npx tsc --noEmit clean; production webpack build succeeds. The only
    failing suite (utils.test.ts, a timezone-dependent getDateParts test) is
    pre-existing on the base branch and unrelated to this change.

Summary by CodeRabbit

  • New Features

    • Added per-service recommendation filters for minimum instance count, allowing users to include/exclude specific engines, types, and regions in settings.
  • Tests

    • Added comprehensive test coverage for filter functionality and validation.
  • Chores

    • Added database schema migration to persist filter configurations.

cristim added 2 commits June 7, 2026 13:33
…lter

Wire the CLI --min-count filter into the server pipeline so it can be set
per service and persisted, ahead of GUI exposure.

- Add ServiceConfig.MinCount (migration 000068, postgres Get/Save/List, resolver).
- Apply it at read time in scheduler.filterRecsByResolvedConfigs: drop recs
  whose net Count is below the floor; 0 disables (matches CLI 0-no-floor).
- Validate MinCount (>= 0, <= MaxServiceMinCount=10000) to stop a typo
  silently suppressing every recommendation.
- Make updateServiceConfig presence-aware: overlay the include/exclude
  engine/region/type lists and min_count from the request only when the body
  carries the key, so a partial PUT never zeroes a filter set out-of-band.
- openapi: document min_count on ServiceConfig.

Regression tests: scheduler drops/keeps recs by MinCount; config rejects
out-of-range MinCount; merge overlays present filters, preserves omitted ones,
and clears an explicitly-empty list.
Add the missing GUI surface for the CLI recommendation-filter knobs that the
backend already persists and applies: include/exclude engines, instance types,
and regions (config.ServiceConfig, applied server-side in
scheduler.filterRecsByResolvedConfigs) plus the new per-service min-count.

- New serviceFilters.ts: pure, testable inject / populate / read helpers that
  render a "Recommendation filters" panel into each Service Defaults card
  (keyed off SERVICE_FIELDS, one code path for all ~20 cards). DOM is built via
  createElement + .value only (no innerHTML), so user values cannot inject
  markup.
- settings.ts load: inject (idempotent) + populate the controls from the
  service config. save: read + validate them up front (a bad min-count aborts
  the whole save with a targeted toast) and send them in the ServiceConfig
  payload so they round-trip through PUT /config/service/{p}/{s}.
- api/types.ts: add ServiceConfig.min_count.

The values flow end-to-end: GUI form -> updateServiceConfig -> persisted
ServiceConfig -> scheduler read-time filter on the next recommendations fetch.

Tests: serviceFilters.test.ts (15 cases: csv parse/join, idempotent inject,
populate/read round-trip, min-count validation, no-panel fallback). Existing
182 settings tests still pass.
@cristim cristim added triaged Item has been triaged priority/p2 Backlog-worthy severity/medium Moderate harm urgency/this-quarter Within the quarter impact/many Affects most users effort/l Weeks type/feat New capability labels Jun 7, 2026
@coderabbitai

coderabbitai Bot commented Jun 7, 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 18 minutes and 42 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: 4f318651-186a-4508-b174-7da54ce2dec0

📥 Commits

Reviewing files that changed from the base of the PR and between fc5d5ec and 1ab5cfe.

📒 Files selected for processing (2)
  • internal/database/postgres/migrations/000075_service_configs_min_count.down.sql
  • internal/database/postgres/migrations/000075_service_configs_min_count.up.sql
📝 Walkthrough

Walkthrough

This PR adds a complete minimum-instance-count recommendation filter from backend schema through frontend Settings UI. It introduces ServiceConfig.MinCount with validation, database persistence, presence-aware API merge semantics to avoid unwanted overwrites, scheduler-side filtering at read-time, and user-facing controls for per-service configuration with form validation and persistence.

Changes

Per-Service Minimum-Count Recommendation Filter

Layer / File(s) Summary
Backend schema, validation rules, and constants
internal/config/types.go, internal/config/constants.go, internal/config/validation.go, internal/config/validation_test.go
ServiceConfig gains a MinCount field; a MaxServiceMinCount constant caps it at 10000; new validateMinCount() enforces non-negative and below-limit constraints with targeted error messages; test cases verify acceptance and rejection boundaries.
Database migrations and store persistence
internal/database/postgres/migrations/000068_service_configs_min_count.*, internal/config/store_postgres.go, internal/config/store_postgres_pgxmock_test.go
Database migration adds min_count column with NOT NULL DEFAULT 0; PostgresStore wires the column into SELECT/INSERT/UPDATE statements for all three config operations; mock tests verify column presence and value binding.
Config resolution and presence-aware API merge
internal/config/resolver.go, internal/api/handler_config.go, internal/api/handler_config_test.go, internal/api/coverage_gaps_test.go
ResolveServiceConfig carries baseline.MinCount forward through override resolution; mergeServiceConfig gains presence-detection logic to overlay scalar UI fields unconditionally but filter fields (include/exclude engines/types/regions, min-count) only when present in the request body, preventing accidental overwrites; new helpers presentKeys and overlayPresentFilterFields handle selective field copying; comprehensive tests verify all merge scenarios.
Scheduler runtime filtering by minimum count
internal/scheduler/scheduler_overrides.go, internal/scheduler/scheduler_overrides_test.go
filterRecsByResolvedConfigs adds a meetsMinCount gate after engine/region/type checks, dropping recommendations when instance count is below ServiceConfig.MinCount (unless MinCount <= 0); tests verify filtering and disable-by-zero behavior.
Frontend service-filter controls module
frontend/src/serviceFilters.ts, frontend/src/__tests__/serviceFilters.test.ts
New module exports deterministic input-id helpers, CSV parsing/normalization (trim, lowercase, deduplicate), idempotent DOM injection of a details panel with include/exclude fields and numeric min-count input, population from ServiceConfig, and reading/validation with error-state distinction; tests cover normalization round-trips, id generation, DOM idempotency, config population, input reading, and validation error cases.
Frontend API types and Settings integration
frontend/src/api/types.ts, frontend/src/settings.ts
ServiceConfig type gains optional min_count?: number field; Settings load injects and populates filter controls for each service; Settings save reads and validates all service filters up-front (aborting on error) and includes validated filter fields in per-service ServiceConfig updates to the API.
API specification documentation
internal/api/openapi.yaml
ServiceConfig schema adds min_count as an integer property with minimum: 0 constraint and description of filtering semantics.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

This PR combines multiple layers (schema, persistence, API semantics, scheduler logic, and frontend UI) with presence-aware merge logic that requires careful validation and cross-layer testing. The logic density is moderate but spread across files; the primary complexity lies in the presence-aware overlay semantics in the API handler and the interaction between validation, persistence, and read-time filtering.

Possibly related PRs

  • LeanerCloud/CUDly#241: Both PRs modify ResolveServiceConfig override resolution in internal/config/resolver.go—this PR copies baseline.MinCount into the resolved config, while the retrieved PR safeguards resolution when global is nil—so they directly intersect in how per-account overrides are merged.
  • LeanerCloud/CUDly#200: Both PRs extend filterRecsByResolvedConfigs in internal/scheduler/scheduler_overrides.go; the retrieved PR introduced the function for engine/region/type filtering, while this PR adds the MinCount gate to the same pipeline.

Suggested labels

urgency/this-sprint, effort/m

Poem

🐰 A filter is born in the config's heart,
Min-count whispers which recs to chart,
From database rows through scheduler's gate,
To Settings controls that validate,
Now users choose: "show only the great!"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.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: exposing CLI recommendation filters in the per-service Settings GUI, which is the primary objective of the PR.
Linked Issues check ✅ Passed The PR fully addresses the linked issue #1093 requirements: implements GUI wiring for existing filters (engines/types/regions), adds backend wiring for min_count (field, migration, validation, persistence, scheduler filter), includes comprehensive tests, and documents deferred items.
Out of Scope Changes check ✅ Passed All changes are within scope of the linked issue #1093: GUI filters, min_count backend wiring, database migration, validation, and tests. No unrelated changes 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 feat/gui-cli-filters

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

@cristim

cristim commented Jun 7, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 7, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

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 commented Jun 8, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review


Generated by Claude Code

@coderabbitai

coderabbitai Bot commented Jun 8, 2026 •

Copy link
Copy Markdown
Contributor

@cristim: I'll kick off a review of the PR now.


🧠 Learnings used
Learnt from: cristim
Repo: LeanerCloud/CUDly PR: 0
File: :0-0
Timestamp: 2026-05-05T15:44:54.463Z
Learning: In the LeanerCloud/CUDly repository, when a CodeRabbit review flags something as "outside-diff" that was already implemented in an earlier commit of the same PR, it is likely a stale-snapshot artifact where the review diff was generated against a pre-fix view. The inline comment convention `(CR pass on PR #<number> Finding N)` is used by the maintainer (cristim) to mark lines that were changed in direct response to a prior CodeRabbit review pass.

Learnt from: cristim
Repo: LeanerCloud/CUDly PR: 0
File: :0-0
Timestamp: 2026-06-07T18:28:28.139Z
Learning: In LeanerCloud/CUDly GCP service clients (computeengine, cloudsql, memorystore, cloudstorage), `GetRecommendations` filters out non-ACTIVE recommendations using the typed `recommenderpb.RecommendationStateInfo_ACTIVE` enum (CLAIMED/SUCCEEDED/FAILED/DISMISSED are skipped). State is accessed nil-safely via the proto getter. Fixed in PR `#1047` commit 95a390f (H-1 finding). Regression tests: `TestGetRecommendations_FiltersNonActiveStates` (5-rec mock, only ACTIVE survives) and `TestGetRecommendations_ActiveRecIncluded`. Existing mock tests in all four services had StateInfo added to satisfy the new filter.

Learnt from: cristim
Repo: LeanerCloud/CUDly PR: 0
File: :0-0
Timestamp: 2026-06-07T18:28:28.139Z
Learning: In LeanerCloud/CUDly `providers/gcp/recommendations.go`, `collectRegion` fans out to all four GCP service clients (computeengine, cloudsql, memorystore, cloudstorage) concurrently (fixed in PR `#1047` commit 95a390f, H-2 finding). `regionResult` gains `cache` and `storage` slices; merge is updated accordingly. The decision was to wire (not trim) memorystore and cloudstorage because both have complete `GetRecommendations` implementations and their no-op `PurchaseCommitment` paths are orthogonal to surfacing advisory recommendations. Regression tests: `TestRegionResult_HasCacheAndStorageFields` and `TestShouldIncludeService_Cache_Storage`.
✅ Action performed

Review finished.

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 commented Jun 8, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai full review


Generated by Claude Code

@coderabbitai

coderabbitai Bot commented Jun 8, 2026 •

Copy link
Copy Markdown
Contributor

@cristim: I'll perform a full review of PR #1094 now, covering all changes end to end.


🧠 Learnings used
Learnt from: cristim
Repo: LeanerCloud/CUDly PR: 0
File: :0-0
Timestamp: 2026-05-05T15:44:54.463Z
Learning: In the LeanerCloud/CUDly repository, when a CodeRabbit review flags something as "outside-diff" that was already implemented in an earlier commit of the same PR, it is likely a stale-snapshot artifact where the review diff was generated against a pre-fix view. The inline comment convention `(CR pass on PR #<number> Finding N)` is used by the maintainer (cristim) to mark lines that were changed in direct response to a prior CodeRabbit review pass.

Learnt from: cristim
Repo: LeanerCloud/CUDly PR: 0
File: :0-0
Timestamp: 2026-06-07T18:28:28.139Z
Learning: In LeanerCloud/CUDly `providers/gcp/recommendations.go`, `collectRegion` fans out to all four GCP service clients (computeengine, cloudsql, memorystore, cloudstorage) concurrently (fixed in PR `#1047` commit 95a390f, H-2 finding). `regionResult` gains `cache` and `storage` slices; merge is updated accordingly. The decision was to wire (not trim) memorystore and cloudstorage because both have complete `GetRecommendations` implementations and their no-op `PurchaseCommitment` paths are orthogonal to surfacing advisory recommendations. Regression tests: `TestRegionResult_HasCacheAndStorageFields` and `TestShouldIncludeService_Cache_Storage`.

Learnt from: cristim
Repo: LeanerCloud/CUDly PR: 0
File: :0-0
Timestamp: 2026-06-07T18:28:28.139Z
Learning: In LeanerCloud/CUDly GCP service clients (computeengine, cloudsql, memorystore, cloudstorage), `GetRecommendations` filters out non-ACTIVE recommendations using the typed `recommenderpb.RecommendationStateInfo_ACTIVE` enum (CLAIMED/SUCCEEDED/FAILED/DISMISSED are skipped). State is accessed nil-safely via the proto getter. Fixed in PR `#1047` commit 95a390f (H-1 finding). Regression tests: `TestGetRecommendations_FiltersNonActiveStates` (5-rec mock, only ACTIVE survives) and `TestGetRecommendations_ActiveRecIncluded`. Existing mock tests in all four services had StateInfo added to satisfy the new filter.

Learnt from: cristim
Repo: LeanerCloud/CUDly PR: 0
File: :0-0
Timestamp: 2026-06-07T17:38:19.433Z
Learning: In LeanerCloud/CUDly `providers/gcp/services/computeengine/client.go`, `convertGCPRecommendation` must force `paymentOption = "monthly"` unconditionally (logging a WARN when a non-monthly value such as "upfront", "all-upfront", or "partial-upfront" is supplied) because GCP CUDs are monthly-only and any non-monthly value passed through would be a silent misconfiguration. Fixed in PR `#1047` commit c6280c390 (F1). Regression test: `TestConvertGCPRecommendation_NonMonthlyPaymentOptionForcedToMonthly`.

Learnt from: cristim
Repo: LeanerCloud/CUDly PR: 0
File: :0-0
Timestamp: 2026-06-08T04:21:35.802Z
Learning: In LeanerCloud/CUDly `frontend/src/recommendations.ts` (PR `#838`, commit d7c5764), `perRecPayments` in `FanOutBucket` must be seeded **sparsely** (override-only: only recs whose resolved payment differs from the bucket-level default). The execute path in `frontend/src/app.ts` resolves each rec's payment as `perRecPayments.get(rec.id) ?? b.payment`; if the map is fully populated the fallback is dead and changing the bucket-level dropdown has no effect on unedited rows. The per-rec change handler deletes the map entry when the user reverts to the bucket default (resuming tracking via the fallback); the bucket-level change handler re-syncs visible per-rec selects that have no override, skipping rows whose service does not support the new payment (mixed-SP buckets). Tests (i) and (j) cover the override-only contract and bucket-level propagation respectively; 322 tests pass, `tsc --noEmit` clean.

Learnt from: cristim
Repo: LeanerCloud/CUDly PR: 0
File: :0-0
Timestamp: 2026-06-07T17:38:19.433Z
Learning: In LeanerCloud/CUDly GCP service converters (`convertGCPRecommendation` in memorystore/client.go and cloudstorage/client.go), `rec.Term` must be derived from `params.Term` with a `"1yr"` default — not hardcoded to `"1yr"`. Without this, 3-year callers always emit 1-year commitments even though the downstream `termYears` derivation from `rec.Term` is correct. Fixed in PR `#1047` commit c6280c390 (F2 for memorystore, F4 for cloudstorage). Regression tests: `TestConvertGCPRecommendation_PropagatesTermFromParams` and `TestCloudStorageConvertGCPRecommendation_PropagatesTermFromParams`.

Learnt from: cristim
Repo: LeanerCloud/CUDly PR: 0
File: :0-0
Timestamp: 2026-05-03T16:07:51.732Z
Learning: In the LeanerCloud/CUDly repository, approximately 50% docstring coverage is a pre-existing project-wide baseline. It should not be flagged as an actionable issue in individual PR reviews, as addressing it is out of scope for focused/surgical fixes.

Learnt from: cristim
Repo: LeanerCloud/CUDly PR: 0
File: :0-0
Timestamp: 2026-06-07T17:45:38.069Z
Learning: In LeanerCloud/CUDly `providers/gcp/services/cloudsql/client.go`, `getSQLPricing` had the same per-hour/term-total unit mismatch as memorystore and cloudstorage: `commitmentPrice` (per-hour SKU rate) was passed directly to savings math that expected a term total, producing ~99.99% savings, and `HourlyRate` was set to `per-hour / hoursInTerm` (near-zero). Fixed in PR `#1047` commit 20590c6b4 (issue `#1078` folded in): `commitmentPriceTerm := commitmentPrice * hoursInTerm`; `HourlyRate = commitmentPrice` (raw per-hour). `convertGCPRecommendation` also hardcoded `rec.Term = "1yr"`, ignoring `params.Term`; fixed with same defaulting pattern as memorystore/cloudstorage. Regression tests: `TestGetSQLPricing_CommitmentPriceIsTermTotal` and `TestCloudSQLConvertGCPRecommendation_PropagatesTermFromParams`. PaymentOption was already correct (`if paymentOption == "" { paymentOption = "monthly" }`).

Learnt from: cristim
Repo: LeanerCloud/CUDly PR: 0
File: :0-0
Timestamp: 2026-06-07T18:13:37.445Z
Learning: In LeanerCloud/CUDly `providers/gcp/services/computeengine/client.go`, `convertGCPRecommendation` no longer hardcodes `Term = "1yr"` (fixed in PR `#1047` commit 8f9a787, H-3 audit finding). It propagates `params.Term` with a `"1yr"` default, validates via `termPlan`, and returns nil on unknown term. This is stricter than memorystore/cloudstorage/cloudsql (which default and continue): computeengine returns nil on unknown term because it is on the purchase path. Regression tests: `TestConvertGCPRecommendation_PropagatesParamsTerm`, `TestConvertGCPRecommendation_RejectsUnknownTerm`.

Learnt from: cristim
Repo: LeanerCloud/CUDly PR: 0
File: :0-0
Timestamp: 2026-06-07T18:13:37.445Z
Learning: In LeanerCloud/CUDly `providers/gcp/services/computeengine/client.go`, the `memMBPerVCPU = 4096` const was removed in PR `#1047` commit 8f9a787. MEMORY MB is now extracted directly from the Recommender payload via `memoryMBFromOperationGroups` (sibling of `vcpuCountFromOperationGroups`). `buildInsertRequest` and `GroupCommitments` call `memoryMBFromDetails(rec)` which returns an error (no silent fallback) when `Details.MemoryGB` is absent. This ensures high-memory families (e.g. N2-highmem) get the correct MB from the payload rather than the GENERAL_PURPOSE approximation. Regression test: `TestBuildInsertRequest_RefusesMissingMemory`.
✅ Action performed

Full review finished.

@cristim

cristim commented Jun 8, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jun 8, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@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.

🧹 Nitpick comments (1)
frontend/src/settings.ts (1)

136-151: ⚡ Quick win

Consider adding filter input IDs to dirty tracking.

The filter input IDs returned by serviceFilterInputIds(field) are not included in TRACKED_FIELDS, so changes to recommendation filters won't trigger the "Unsaved changes" indicator. Users could edit filters, navigate away, and lose their changes without a prompt.

💡 Suggested improvement
 const TRACKED_FIELDS = [
   'provider-aws', 'provider-azure', 'provider-gcp',
   'setting-notification-email', 'setting-auto-collect',
   'setting-collection-schedule', 'setting-notification-days',
   'setting-default-term', 'setting-default-payment', 'setting-default-coverage',
   // Per-provider grace-period inputs
   'setting-grace-aws', 'setting-grace-azure', 'setting-grace-gcp',
   // Recommendations cycle params
   'setting-recs-stale-hours', 'setting-recs-lookback-days',
   // Per-service fields
   ...SERVICE_FIELDS.map(f => f.termId),
   ...SERVICE_FIELDS.filter(f => f.paymentId !== null).map(f => f.paymentId as string),
   // Per-product SP fields (issue `#136`)
   ...SERVICE_FIELDS.filter(f => 'coverageId' in f).map(f => (f as { coverageId: string }).coverageId),
   ...SERVICE_FIELDS.filter(f => 'enabledId' in f).map(f => (f as { enabledId: string }).enabledId),
+  // Per-service filter inputs (PR `#1094`)
+  ...SERVICE_FIELDS.flatMap(f => serviceFilterInputIds(f)),
 ];
🤖 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/settings.ts` around lines 136 - 151, TRACKED_FIELDS is missing
the input IDs returned by serviceFilterInputIds(field), so edits to
recommendation filters aren't marked dirty; update the TRACKED_FIELDS
construction to include all filter input IDs for each entry in SERVICE_FIELDS
(e.g., append the results of mapping SERVICE_FIELDS through
serviceFilterInputIds) so those input IDs are tracked for unsaved-change
detection; ensure you reference TRACKED_FIELDS, SERVICE_FIELDS and
serviceFilterInputIds when adding the mapped/flattened filter IDs.
🤖 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.

Nitpick comments:
In `@frontend/src/settings.ts`:
- Around line 136-151: TRACKED_FIELDS is missing the input IDs returned by
serviceFilterInputIds(field), so edits to recommendation filters aren't marked
dirty; update the TRACKED_FIELDS construction to include all filter input IDs
for each entry in SERVICE_FIELDS (e.g., append the results of mapping
SERVICE_FIELDS through serviceFilterInputIds) so those input IDs are tracked for
unsaved-change detection; ensure you reference TRACKED_FIELDS, SERVICE_FIELDS
and serviceFilterInputIds when adding the mapped/flattened filter IDs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4f5c8857-ee24-4929-8541-3a473c8069cd

📥 Commits

Reviewing files that changed from the base of the PR and between ee34869 and fc5d5ec.

📒 Files selected for processing (19)
  • frontend/src/__tests__/serviceFilters.test.ts
  • frontend/src/api/types.ts
  • frontend/src/serviceFilters.ts
  • frontend/src/settings.ts
  • internal/api/coverage_gaps_test.go
  • internal/api/handler_config.go
  • internal/api/handler_config_test.go
  • internal/api/openapi.yaml
  • internal/config/constants.go
  • internal/config/resolver.go
  • internal/config/store_postgres.go
  • internal/config/store_postgres_pgxmock_test.go
  • internal/config/types.go
  • internal/config/validation.go
  • internal/config/validation_test.go
  • internal/database/postgres/migrations/000068_service_configs_min_count.down.sql
  • internal/database/postgres/migrations/000068_service_configs_min_count.up.sql
  • internal/scheduler/scheduler_overrides.go
  • internal/scheduler/scheduler_overrides_test.go

Migration 000068_service_configs_min_count collided with PRs #808 and
#1011. Renumber to 000075 (next free slot above the current base tip of
000073 and the 000074 in-flight slot). Updated the comment in the down
migration to reference the new number.
@cristim

cristim commented Jun 9, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 9, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

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 added a commit that referenced this pull request Jun 9, 2026
Rebasing #297 onto feat/multicloud-web-frontend surfaced a migration-number
collision: the base sequence already extends past 000061 (tops at 000073),
and 000074/000075 are taken by in-flight PRs #1011 and #1094. Move the
per-recipient mute migration to the next free slot, 000076, for both the
.up.sql and .down.sql, so the merge-ref check-migration-conflicts hook stays
green. Update the matching "migration 000061" reference in the
config.StoreInterface doc comment.
@cristim
cristim changed the base branch from feat/multicloud-web-frontend to main June 9, 2026 15:39
@cristim
cristim merged commit 5382322 into main Jun 9, 2026
6 checks passed
@cristim
cristim deleted the feat/gui-cli-filters branch July 27, 2026 11:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/l Weeks impact/many Affects most users priority/p2 Backlog-worthy severity/medium Moderate harm triaged Item has been triaged type/feat New capability urgency/this-quarter Within the quarter

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(gui): expose CLI recommendation filters (engines/types/regions/SP-types/extended-support/thresholds) in Settings

1 participant