Repository navigation
feat: [MP-3098] exclude admin-listed loan ids from the ads feed - #7155
Merged
Merged
Conversation
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.
mcstover
approved these changes
Aug 12, 2026
mcstover
left a comment
Collaborator
There was a problem hiding this comment.
Seems fine but I wonder how large this list might grow to be...
dyersituations
approved these changes
Aug 12, 2026
Collaborator
Author
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. |
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))
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)
Collaborator
|
🎉 This PR is included in version 3.84.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 FLSSfundraisingLoansquery asloanIds: { 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
[]+ awarnso the feed never errors or empties./^\d+$/soNumber()-coercible junk (0x1A,1e3,-7,5.5) can't slip a bad id into the filter.fetchExcludedIds(key)+excludeIds(field, ids)+ the options-object signature make partner/sector exclusion purely additive.live_loan_ads_excluded_loan_ids(theui.prefix is the Settings Manager storage namespace), matching theexcluded_partners/excluded_sectorsprecedent —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—parseIdListedge cases;fetchExcludedIdskey wiring, absent setting, read failure.../ads/ads-eligibility.spec.js—buildAdFeedFiltersadds/omitsloanIds:{none};fetchAdEligibleLoansthreads exclusions into the query.../ads/google-display/google-feed.spec.js—generateGoogleFeedreads the setting, logs the applied count, passes ids throughDev verification
npx vitest run test/unit/specs/server/util/live-loan/ads/→ 54 pass.ui.live_loan_ads_excluded_loan_idsto a loan id currently in the feed; regenerate/live-loan/ads/google-feed.tsv(wait ≤5 min or flushgoogle-ads-feed); confirm that id is gone from the TSV.loanIdsfilter sent).