Skip to content

feat: [MP-3098] exclude admin-listed loan ids from the ads feed - #7155

Merged
roger-in-kiva merged 1 commit into
mainfrom
MP-3098_live_loans_ads_loans_exclusion
Aug 12, 2026
Merged

roger-in-kiva merged 1 commit into
mainfrom
MP-3098_live_loans_ads_loans_exclusion

Conversation

@roger-in-kiva

@roger-in-kiva roger-in-kiva commented Aug 12, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Adds an admin-editable denylist that keeps specific loans out of the live-loan Google Ads feed. The excluded ids are read from a uiConfigSetting (live_loan_ads_excluded_loan_ids, edited in KivaAdmin → Settings Manager) each time the feed regenerates, and pushed into the FLSS fundraisingLoans query as loanIds: { none: [...] } — so excluded loans never enter the candidate set (no post-fetch filtering, no wasted slots against the 100-candidate cap).

Approach / decisions to flag

  • In-query exclusion, not a post-fetch gate — an excluded loan never returns from FLSS, so it's dropped even if it passes every other eligibility criterion.
  • Fail-open: an empty, unset, or malformed setting → no exclusions; a failed read returns [] + a warn so the feed never errors or empties.
  • Parsing: comma-delimited integers, whitespace-tolerant, non-numeric ignored, de-duplicated; strict /^\d+$/ so Number()-coercible junk (0x1A, 1e3, -7, 5.5) can't slip a bad id into the filter.
  • No separate setting cache — the feed's existing ~5-min fresh cache already bounds reads, and keeps "flush the one feed key = immediate" true.
  • Reusable seam for MP-3099: fetchExcludedIds(key) + excludeIds(field, ids) + the options-object signature make partner/sector exclusion purely additive.
  • Setting key string is bare live_loan_ads_excluded_loan_ids (the ui. prefix is the Settings Manager storage namespace), matching the excluded_partners/excluded_sectors precedent — ⚠️ pending an ops confirmation before launch.
  • Urgent "pull now" is an ops-level flush of memjs key google-ads-feed (runbook item); no flush endpoint in this PR.

Related

Related tests (54 pass)

  • test/unit/specs/server/util/live-loan/ads/excluded-ids.spec.js — parseIdList edge cases; fetchExcludedIds key wiring, absent setting, read failure
  • .../ads/ads-eligibility.spec.js — buildAdFeedFilters adds/omits loanIds:{none}; fetchAdEligibleLoans threads exclusions into the query
  • .../ads/google-display/google-feed.spec.js — generateGoogleFeed reads the setting, logs the applied count, passes ids through

Dev verification

  1. npx vitest run test/unit/specs/server/util/live-loan/ads/ → 54 pass.
  2. Set ui.live_loan_ads_excluded_loan_ids to a loan id currently in the feed; regenerate /live-loan/ads/google-feed.tsv (wait ≤5 min or flush google-ads-feed); confirm that id is gone from the TSV.
  3. Blank the setting; confirm the feed returns to full (no loanIds filter sent).

Read a uiConfigSetting denylist (live_loan_ads_excluded_loan_ids) each generation and push it into the FLSS fundraisingLoans query as loanIds: { none: [...] }, so excluded loans never enter the candidate set. Empty, unset, or malformed settings mean no exclusions; a failed read fails open with a warn. Adds excluded-ids.js (generic key-based reader + strict id-list parser) that partner/sector exclusion can reuse, and logs the applied count on each generation.
@roger-in-kiva roger-in-kiva added the b2c Sends B2C team a message in Slack on PR creation label Aug 12, 2026
@roger-in-kiva
roger-in-kiva requested a review from a team August 12, 2026 00:42

@mcstover mcstover left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Seems fine but I wonder how large this list might grow to be...

@roger-in-kiva

Copy link
Copy Markdown
Collaborator Author

Seems fine but I wonder how large this list might grow to be...

I think this depends about how the list is managed, if an already funded loan stills there should be removed as won't be fetched again... Based on Nearly a third of loans get funded within a day of activation this option probably won't be used, but worth adding for any edge case as required from legal.

@roger-in-kiva
roger-in-kiva merged commit 11e8ad5 into main Aug 12, 2026
8 checks passed
@roger-in-kiva
roger-in-kiva deleted the MP-3098_live_loans_ads_loans_exclusion branch August 12, 2026 16:44
kiva-robot added a commit that referenced this pull request Aug 14, 2026
## [3.84.0-rc.1](v3.83.0...v3.84.0-rc.1) (2026-08-14)

### 🎉 New Features

* [MP-3082] - goal in review content animations ([#7152](#7152)) ([e9cb684](e9cb684))
* [MP-3101] - goal in review slide1 new changes ([#7156](#7156)) ([a00cc04](a00cc04))
* add button to impact progress row to see goal recap ([59ff20f](59ff20f))
* add start date to trigger goal in review for in-progress goals ([b3cccbc](b3cccbc))
* deploy seen-once logic for goal in review modal in my kiva and portfolio pages ([62ded92](62ded92))
* **live-loan-ads:** exclude admin-listed loan ids from the ads feed ([#7155](#7155)) ([11e8ad5](11e8ad5))
* **live-loan-ads:** exclude admin-listed partner and sector ids from the ads feed ([#7159](#7159)) ([86c8c2f](86c8c2f))
* **live-loan-ads:** funded-state landing event and ad fallback headline ([#7162](#7162)) ([7ea0b6e](7ea0b6e))
* **live-loan-ads:** serve the live-loan google ads feed from the kiva-ui server ([#7150](#7150)) ([3a1269b](3a1269b))
* make the goal sign up hiding respect the recapDate param override ([2b93f55](2b93f55))
* mp-3058-meta-new-events ([70c0f3f](70c0f3f))
* mp-3058-meta-new-events ([caf938e](caf938e))
* mp-3058-meta-new-events ([a3ac5fa](a3ac5fa))
* mp-3058-meta-new-events ([66b156f](66b156f))
* mp-3058-meta-new-events ([49579fb](49579fb))
* mp-3058-meta-new-events ([d31c43a](d31c43a))
* mp-3058-meta-tracking-new-events-pt-2 ([0d4ae4f](0d4ae4f))
* **prefetch-discovery:** discover the children of <script setup> components ([9cbc46b](9cbc46b))
* stop asking for goal sign ups previous to goal recap start date ([e66208c](e66208c))

### 🐛 Bugfixes

* **apollo:** guard against caching an empty GraphQL possibleTypes payload ([099e3f2](099e3f2))
* **apollo:** log non-GraphQL error bodies from schema introspection ([e998cc0](e998cc0))
* avoid multiple instances of useGoalData ([0597e0b](0597e0b))
* change logic to show goal in review after next session or login ([d398342](d398342))
* form padding bottom ([e590f24](e590f24))
* goal in review session cookie name ([76020e1](76020e1))
* **HeroBackground:** skip the prefetch on routes without a loan id ([b62137e](b62137e))
* **LendCta:** singularize lender count ([8d08dcd](8d08dcd))
* **live-loan-ads:** use "Support" title copy and show sector in Item description ([#7166](#7166)) ([148df9c](148df9c))
* **LoanProgress:** distinguish ended loans that carry currency loss ([fe958a1](fe958a1))
* make comments related to hide goal sign up clear ([29e3a1e](29e3a1e))
* refactor useGoalData new methods to avoid duplication or similar logic ([de3747d](de3747d))
* rename goal in review slides ([6a5865a](6a5865a))
* resolve rename missing for storybook from recent goal in review refactor ([#7161](#7161)) ([1a67c8a](1a67c8a))
* revert repeated logic and moving tracking to the parent ([7950188](7950188))
* solve pr comments about refactoring ([ae9d040](ae9d040))
* temp view changes for disaster relief giving fund on mykiva and portfolio ([#7168](#7168)) ([14c5419](14c5419))
* trim some unnecessary comments ([725a5e7](725a5e7))
* **TrusteeDetails:** render "Not enough data" for missing trustee stats ([d01f71b](d01f71b))
* **useMyKivaHome:** resolve the my kiva paths before first render ([8b5e798](8b5e798))
* user has goal validation was thought to check null value instead of empty object ([8a71743](8a71743))
* wrong comment mentioning cdn ([3034a1b](3034a1b))

### 🪚 Refactors

* add a small method to confirm user has no goal so logic is centralized ([bd6876a](bd6876a))
* expose hasGoal through the composable instead of a module export ([9eedc94](9eedc94))
* **LendCta:** remove dead lendAgainButton reference ([9360c76](9360c76))
* **loanDetailsRailPreference:** drop redundant SSR window guard ([13ef021](13ef021))
* move all goal in review files to one util, instead of multiples per component ([1cb434b](1cb434b))
* **prefetch-discovery:** call the attached children loaders ([0c187b8](0c187b8))
* **SideSheetCountry:** remove unused component ([3a6d197](3a6d197))

### 📚 Documentation Changes

* **prefetch:** describe how prefetch discovery reaches components ([cb19470](cb19470))

### 🔍 Tests

* **composables:** mount composable specs through the real prefetch transform ([bbab7d6](bbab7d6))
* **prefetch-discovery:** declare module resolutions per case ([fbf2084](fbf2084))

### 🧹 Chores

* automatically install node modules in new worktrees AD-397 ([74ab000](74ab000))
@kiva-robot

Copy link
Copy Markdown
Collaborator

🎉 This PR is included in version 3.84.0-rc.1 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

kiva-robot added a commit that referenced this pull request Aug 17, 2026
## [3.84.0](v3.83.0...v3.84.0) (2026-08-17)

### 🎉 New Features

* [MP-3082] - goal in review content animations ([#7152](#7152)) ([e9cb684](e9cb684))
* [MP-3101] - goal in review slide1 new changes ([#7156](#7156)) ([a00cc04](a00cc04))
* add button to impact progress row to see goal recap ([59ff20f](59ff20f))
* add start date to trigger goal in review for in-progress goals ([b3cccbc](b3cccbc))
* deploy seen-once logic for goal in review modal in my kiva and portfolio pages ([62ded92](62ded92))
* **live-loan-ads:** exclude admin-listed loan ids from the ads feed ([#7155](#7155)) ([11e8ad5](11e8ad5))
* **live-loan-ads:** exclude admin-listed partner and sector ids from the ads feed ([#7159](#7159)) ([86c8c2f](86c8c2f))
* **live-loan-ads:** funded-state landing event and ad fallback headline ([#7162](#7162)) ([7ea0b6e](7ea0b6e))
* **live-loan-ads:** serve the live-loan google ads feed from the kiva-ui server ([#7150](#7150)) ([3a1269b](3a1269b))
* make the goal sign up hiding respect the recapDate param override ([2b93f55](2b93f55))
* mp-3058-meta-new-events ([70c0f3f](70c0f3f))
* mp-3058-meta-new-events ([caf938e](caf938e))
* mp-3058-meta-new-events ([a3ac5fa](a3ac5fa))
* mp-3058-meta-new-events ([66b156f](66b156f))
* mp-3058-meta-new-events ([49579fb](49579fb))
* mp-3058-meta-new-events ([d31c43a](d31c43a))
* mp-3058-meta-tracking-new-events-pt-2 ([0d4ae4f](0d4ae4f))
* **prefetch-discovery:** discover the children of <script setup> components ([9cbc46b](9cbc46b))
* stop asking for goal sign ups previous to goal recap start date ([e66208c](e66208c))

### 🐛 Bugfixes

* **apollo:** guard against caching an empty GraphQL possibleTypes payload ([099e3f2](099e3f2))
* **apollo:** log non-GraphQL error bodies from schema introspection ([e998cc0](e998cc0))
* avoid multiple instances of useGoalData ([0597e0b](0597e0b))
* change logic to show goal in review after next session or login ([d398342](d398342))
* form padding bottom ([e590f24](e590f24))
* goal in review session cookie name ([76020e1](76020e1))
* **HeroBackground:** skip the prefetch on routes without a loan id ([b62137e](b62137e))
* **LendCta:** singularize lender count ([8d08dcd](8d08dcd))
* **live-loan-ads:** use "Support" title copy and show sector in Item description ([#7166](#7166)) ([148df9c](148df9c))
* **LoanProgress:** distinguish ended loans that carry currency loss ([fe958a1](fe958a1))
* make comments related to hide goal sign up clear ([29e3a1e](29e3a1e))
* refactor useGoalData new methods to avoid duplication or similar logic ([de3747d](de3747d))
* rename goal in review slides ([6a5865a](6a5865a))
* resolve rename missing for storybook from recent goal in review refactor ([#7161](#7161)) ([1a67c8a](1a67c8a))
* revert repeated logic and moving tracking to the parent ([7950188](7950188))
* solve pr comments about refactoring ([ae9d040](ae9d040))
* temp view changes for disaster relief giving fund on mykiva and portfolio ([#7168](#7168)) ([14c5419](14c5419))
* trim some unnecessary comments ([725a5e7](725a5e7))
* **TrusteeDetails:** render "Not enough data" for missing trustee stats ([d01f71b](d01f71b))
* **useMyKivaHome:** resolve the my kiva paths before first render ([8b5e798](8b5e798))
* user has goal validation was thought to check null value instead of empty object ([8a71743](8a71743))
* wrong comment mentioning cdn ([3034a1b](3034a1b))

### 🪚 Refactors

* add a small method to confirm user has no goal so logic is centralized ([bd6876a](bd6876a))
* expose hasGoal through the composable instead of a module export ([9eedc94](9eedc94))
* **LendCta:** remove dead lendAgainButton reference ([9360c76](9360c76))
* **loanDetailsRailPreference:** drop redundant SSR window guard ([13ef021](13ef021))
* move all goal in review files to one util, instead of multiples per component ([1cb434b](1cb434b))
* **prefetch-discovery:** call the attached children loaders ([0c187b8](0c187b8))
* **SideSheetCountry:** remove unused component ([3a6d197](3a6d197))

### 📚 Documentation Changes

* **prefetch:** describe how prefetch discovery reaches components ([cb19470](cb19470))

### 🔍 Tests

* **composables:** mount composable specs through the real prefetch transform ([bbab7d6](bbab7d6))
* **prefetch-discovery:** declare module resolutions per case ([fbf2084](fbf2084))

### 🧹 Chores

* automatically install node modules in new worktrees AD-397 ([74ab000](74ab000))
* **release:** 3.84.0-rc.1 [skip ci] ([248ea61](248ea61)), closes [#7152](#7152) [#7156](#7156) [#7155](#7155) [#7159](#7159) [#7162](#7162) [#7150](#7150) [#7166](#7166) [#7161](#7161) [#7168](#7168)
@kiva-robot

Copy link
Copy Markdown
Collaborator

🎉 This PR is included in version 3.84.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

b2c Sends B2C team a message in Slack on PR creation released on @staging released

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants