-
Notifications
You must be signed in to change notification settings - Fork 3.7k
improvement(billing): ux around on demand toggling and one-off credits #5307
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+315
−42
Merged
Changes from all commits
Commits
Show all changes
108 commits
Select commit
Hold shift + click to select a range
0b9019d
v0.6.23: MCP fixes, remove local state in favor of server state, moth…
waleedlatif1 a54dcbe
v0.6.24: copilot feedback wiring, captcha fixes
waleedlatif1 28af223
v0.6.25: cloudwatch, cloudformation, live kb sync, linear fixes, post…
waleedlatif1 d889f32
v0.6.26: ui improvements, multiple response blocks, docx previews, ol…
waleedlatif1 316bc8c
v0.6.27: new triggers, mothership improvements, files archive, queuei…
waleedlatif1 3f508e4
v0.6.28: new docs, delete confirmation standardization, dagster integ…
waleedlatif1 d6ec115
v0.6.29: login improvements, posthog telemetry (#4026)
TheodoreSpeaks d7da35b
v0.6.30: slack trigger enhancements, connectors performance improveme…
waleedlatif1 cf233bb
v0.6.31: elevenlabs voice, trigger.dev fixes, cloud whitelabeling for…
waleedlatif1 f8f3758
v0.6.32: BYOK fixes, ui improvements, cloudwatch tools, jsm tools ext…
waleedlatif1 3c8bb40
v0.6.33: polling improvements, jsm forms tools, credentials reactquer…
waleedlatif1 d33acf4
v0.6.34: trigger.dev fixes, CI speedup, atlassian error extractor
waleedlatif1 4f40c4c
v0.6.35: additional jira fields, HITL docs, logs cleanup efficiency
waleedlatif1 cbfab1c
v0.6.36: new chunkers, sockets state machine, google sheets/drive/cal…
waleedlatif1 4309d06
v0.6.37: audit logs page, isolated-vm worker rotation, permission gro…
waleedlatif1 8b57476
v0.6.38: models page
waleedlatif1 e3d0e74
v0.6.39: billing fixes, tools audit, landing fix
waleedlatif1 0ac0539
v0.6.40: mothership tool loop, new skills, agiloft, STS, IAM integrat…
waleedlatif1 3838b6e
v0.6.41: webhooks fix, workers removal
waleedlatif1 fc07922
v0.6.42: mothership nested file reads, search modal improvements
waleedlatif1 3a1b1a8
v0.6.43: mothership billing idempotency, env var resolution fixes
waleedlatif1 46ffc49
v0.6.44: streamdown, mothership intelligence, excel extension
waleedlatif1 010435c
v0.6.45: superagent, csp, brightdata integration, gemini response for…
Sg312 c0bc62c
Merge pull request #4190 from simstudioai/staging
icecrasher321 387cc97
v0.6.46: mothership queueing, web vitals
waleedlatif1 2dbc7fd
v0.6.47: files focusing, documentation, opus 4.7
waleedlatif1 8a50f18
v0.6.48: import csv into tables, subflow fixes, CSP updates
waleedlatif1 dcf3302
v0.6.49: deploy sockets event, resolver, logs improvements, monday.co…
waleedlatif1 bc09865
v0.6.50: ppt/doc/pdf worker isolation, docs, chat, sidebar improvements
icecrasher321 5f56e46
v0.6.51: tables improvements, billing fixes, 404 pages, code hygiene
waleedlatif1 ca3bbf1
v0.6.52: data retention, docs updates, slack manifest generator, secu…
waleedlatif1 bbf400f
v0.6.53: permissions groups migration, docs updates
waleedlatif1 7c619e7
Merge pull request #4261 from simstudioai/staging
icecrasher321 64cfda5
v0.6.54: mothership tracing, db pool size increase
icecrasher321 7ca736a
v0.6.55: standardize monorepo conventions, api key hash, thinking tex…
waleedlatif1 6066fc1
v0.6.56: data retention improvements, tables column double click resi…
waleedlatif1 3422f64
Merge pull request #4285 from simstudioai/staging
waleedlatif1 595c4c3
Merge pull request #4293 from simstudioai/staging
TheodoreSpeaks d6c1bc2
v0.6.58: queue abort state machine improvement, contributing guide
icecrasher321 58a3ae2
v0.6.59: gpt 5.5, security hardening, parallel subagents rendering
icecrasher321 489f2d3
v0.6.60: copilot security improvements, slack canvas ops, retention j…
icecrasher321 6aa3fe3
v0.6.61: SAP integration, live URLs for browser use, 5xx error catego…
icecrasher321 ecbf5e5
Merge pull request #4342 from simstudioai/staging
TheodoreSpeaks 2aaf2b7
v0.6.62: firecrawl parse, new gmail tools, trace improvements, tool f…
waleedlatif1 d445b9c
v0.6.63: knowledgebase UI, folder search in mothership
waleedlatif1 4bc6a17
v0.6.64: table limits env vars, workspace files improvements, integra…
waleedlatif1 5be12f8
v0.6.65: memory fix, image uploads in files
waleedlatif1 4253e57
v0.6.66: child trace spans, reranker controls, attachment previews, l…
waleedlatif1 8d6b615
v0.6.67: VFS upload fix, posthog/copilot correlation, exa date filter…
TheodoreSpeaks efcd51a
v0.6.68: atlassian service accounts, 30 day wait block, markdown rend…
waleedlatif1 8d934f3
v0.6.69: security hardening, nextjs upgrade, SAP Concur, Emailbison i…
waleedlatif1 5ea80a8
v0.6.70: legacy workflow sanitization
icecrasher321 3cc581e
v0.6.71: build error fix
icecrasher321 273e608
Merge pull request #4496 from simstudioai/staging
TheodoreSpeaks 07b8f1b
v0.6.72: tables improvements, search and replace, logs with files, im…
waleedlatif1 dcaf3e9
v0.6.73: zustand v5 migration fix
icecrasher321 6aeb981
v0.6.74: security hardening, workers recycling, next-mdx-remote and o…
waleedlatif1 3e9849b
v0.6.75: scheduler claim-budget drain, helm chart hardening, mothersh…
TheodoreSpeaks 64d855a
v0.6.76: helm updates, media centering, lazy loading, security hardening
waleedlatif1 ab156b5
v0.6.77: mothership improvements, trigger.dev telemetry
icecrasher321 c09a2c9
v0.6.78: file block get
Sg312 6a5eebc
v0.6.79: rate limits, tables checkboxes, drizzle config changes, bill…
waleedlatif1 4efe999
v0.6.80: security hardening, nextjs minor version bump, cloudwatch to…
waleedlatif1 f69a9a0
v0.6.81: files in agent block, file block update, mermaid version upd…
waleedlatif1 db7f1c1
v0.6.82: fix duplicate migration
Sg312 dbe8e51
v0.6.83: redis TLS SNI override for IP-based REDIS_URL, zod schema fixes
TheodoreSpeaks 11bcb8f
v0.6.84: redis pub/sub SNI override, security hardening, copilot read…
TheodoreSpeaks d14af04
v0.6.85: mothership stream, resource column spacing, prospeo, findyma…
waleedlatif1 e6b3cce
v0.6.86: gemini 3.5 flash, wiza integration, CORS cleanup, railway an…
waleedlatif1 97a609a
v0.6.86: CORS updates, OAuth MCP, navigation pinning dynamic pages, g…
waleedlatif1 fde70e2
v0.6.87: performance improvements
icecrasher321 e9ee351
v0.6.88: mutex lock on oauth refresh, files export fix, hubspot trigg…
waleedlatif1 b5b2d83
v0.6.89: connectors ui, perf improvements, mcp hardening, og image
waleedlatif1 f6c9998
v0.6.90: resource breadcrumb flash fix, dedupe external URL fetches, …
icecrasher321 e532e0a
v0.6.91: file zoom, Zoom KB connector, error classifications, LiteLLM…
waleedlatif1 fd19470
v0.6.92: enrichment table column type, table run fixes, scheduled jit…
TheodoreSpeaks 856182b
v0.6.93: schedules/mcp performance improvements, integration bugfixes
icecrasher321 6bf9e96
v0.6.94: 4.8 opus, better auth upgrade, zoominfo integration, copilot…
waleedlatif1 503432c
v0.6.95: data enrichment block, nullable workflow description fix
TheodoreSpeaks a8dcdd5
v0.6.96: pinned table columns, sequence number in copilot messages, t…
waleedlatif1 2f1f633
v0.6.97: migration fix for copilot_messages
icecrasher321 e32699d
v0.6.98: redundant index, security hardening, new copilot messages ta…
waleedlatif1 12ada0c
v0.6.99: tables filter operators, copilot chat persistence consolidat…
waleedlatif1 e8f09ae
v0.6.100: auth, mothership, scopes improvements, new apify tools
icecrasher321 3ba8668
v0.6.101: 11 new knowledgebase connectors, slack scopes update, login…
waleedlatif1 1192e20
v0.6.102: support S3-compatible in object storage, GitLab code knowle…
waleedlatif1 1ce8e92
v0.6.103: readme updates, tables lifecycle improvements, new connecto…
waleedlatif1 0c2df1e
v0.7.0: vibes improvement, new UI, new tools, chat-first, mothership …
waleedlatif1 7ffc495
v0.7.1: chat voice mode model update, sim trigger, codepipeline integ…
waleedlatif1 d4722f9
v0.7.2: logs export security, code hygiene, mship cost attribution
icecrasher321 f4d22ff
v0.7.3: jira oauth scope fix, read-replica client, table wire data fi…
TheodoreSpeaks a48b4a1
v0.7.4: round-robin byok support, table block fix, db read replica ro…
waleedlatif1 79d98b3
v0.7.5: deployments API and block, vanta integration, performance imp…
waleedlatif1 e6587ca
v0.7.6: calendar scheduled tasks, new hubspot tools, virtualized chat…
waleedlatif1 8c3706e
v0.7.7: square, context.dev integrations, scheduled tasks styling cha…
waleedlatif1 59d9496
v0.7.8: security hardening, code hygiene, MSFT oauth provider, new at…
waleedlatif1 56a88a2
v0.7.9: agent file attachments, chat autoscroll, knowledge base uploa…
icecrasher321 db47da5
v0.7.10: models sorting, compress/decompress file block tools, new en…
icecrasher321 8df34a3
v0.7.11: parallel subagents, new tools, rich markdown editor, governa…
waleedlatif1 aaca750
v0.7.12: mcp servers ui/ux fixes, nuqs for query param management
icecrasher321 ad0b867
v0.7.13: pii redaction, react query frontend refactor, pi coding agen…
TheodoreSpeaks 11168f9
v0.7.14: perf improvements, code hygiene, GitLab private host support
waleedlatif1 613e8ea
v0.7.15: academy, perf improvements, db attribution, kb tags filter f…
waleedlatif1 38c088a
v0.7.16: security hardening, db o11y and profiling, settings UI
waleedlatif1 0371856
v0.7.17: emcn and workflow renderer isolated package, popular blocks …
waleedlatif1 c360daa
improvement(billing): ux around on demand toggling and one-off credits
icecrasher321 9355fa5
minor ux improvement
icecrasher321 13b87fe
fix lint
icecrasher321 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,157 @@ | ||
| /** | ||
| * @vitest-environment node | ||
| */ | ||
| import { describe, expect, it } from 'vitest' | ||
| import { ON_DEMAND_UNLIMITED } from '@/lib/billing/constants' | ||
| import { dollarsToCredits } from '@/lib/billing/credits/conversion' | ||
| import { | ||
| getCoveredUsage, | ||
| getIsOnDemandActive, | ||
| getOnDemandOffLimit, | ||
| getPooledCreditsRemaining, | ||
| isOnDemandOffDisabled, | ||
| } from '@/lib/billing/on-demand' | ||
|
|
||
| describe('getPooledCreditsRemaining', () => { | ||
| it('returns limit minus usage, matching enforcement (usage >= limit blocks)', () => { | ||
| expect(getPooledCreditsRemaining(120, 62)).toBe(58) | ||
| expect(getPooledCreditsRemaining(30, 10)).toBe(20) | ||
| }) | ||
|
|
||
| it('does not add the credit balance back (the double-count regression)', () => { | ||
| // team_6000, 2 seats: planBase $60 + credits $60 → limit $120, usage ~$62. | ||
| // Remaining is $58 ≈ 11,600 credits — NOT $118 ≈ 23,600 (limit + credits - usage). | ||
| const remaining = getPooledCreditsRemaining(120, 62) | ||
| expect(remaining).toBe(58) | ||
| expect(dollarsToCredits(remaining)).toBe(11_600) | ||
| expect(dollarsToCredits(remaining)).not.toBe(23_600) | ||
| }) | ||
|
|
||
| it('clamps at zero when usage meets or exceeds the limit', () => { | ||
| expect(getPooledCreditsRemaining(100, 100)).toBe(0) | ||
| expect(getPooledCreditsRemaining(60, 100)).toBe(0) | ||
| }) | ||
|
|
||
| it('short-circuits the unlimited sentinel to ∞ instead of subtracting usage', () => { | ||
| expect(getPooledCreditsRemaining(ON_DEMAND_UNLIMITED, 500)).toBe(ON_DEMAND_UNLIMITED) | ||
| expect(getPooledCreditsRemaining(ON_DEMAND_UNLIMITED + 1, 0)).toBe(ON_DEMAND_UNLIMITED) | ||
| }) | ||
| }) | ||
|
|
||
| describe('getCoveredUsage', () => { | ||
| it('sums the plan included amount and the goodwill credit balance', () => { | ||
| expect(getCoveredUsage(60, 60)).toBe(120) | ||
| expect(getCoveredUsage(30, 0)).toBe(30) | ||
| }) | ||
| }) | ||
|
|
||
| describe('getIsOnDemandActive', () => { | ||
| it('reads OFF when the limit only covers planBase + credits (credit grant is not on-demand)', () => { | ||
| // The concrete regression case: limit == covered, so the toggle reads OFF. | ||
| expect( | ||
| getIsOnDemandActive({ | ||
| isPaid: true, | ||
| planIncludedAmount: 60, | ||
| effectiveUsageLimit: 120, | ||
| covered: getCoveredUsage(60, 60), | ||
| }) | ||
| ).toBe(false) | ||
| }) | ||
|
|
||
| it('reads ON when the limit is raised above the covered ceiling', () => { | ||
| expect( | ||
| getIsOnDemandActive({ | ||
| isPaid: true, | ||
| planIncludedAmount: 60, | ||
| effectiveUsageLimit: ON_DEMAND_UNLIMITED, | ||
| covered: getCoveredUsage(60, 60), | ||
| }) | ||
| ).toBe(true) | ||
| expect( | ||
| getIsOnDemandActive({ | ||
| isPaid: true, | ||
| planIncludedAmount: 60, | ||
| effectiveUsageLimit: 121, | ||
| covered: getCoveredUsage(60, 60), | ||
| }) | ||
| ).toBe(true) | ||
| }) | ||
|
|
||
| it('is never active for non-paid plans or a zero included allowance', () => { | ||
| expect( | ||
| getIsOnDemandActive({ | ||
| isPaid: false, | ||
| planIncludedAmount: 60, | ||
| effectiveUsageLimit: ON_DEMAND_UNLIMITED, | ||
| covered: 120, | ||
| }) | ||
| ).toBe(false) | ||
| expect( | ||
| getIsOnDemandActive({ | ||
| isPaid: true, | ||
| planIncludedAmount: 0, | ||
| effectiveUsageLimit: ON_DEMAND_UNLIMITED, | ||
| covered: 0, | ||
| }) | ||
| ).toBe(false) | ||
| }) | ||
|
|
||
| it('behaves equivalently on the personal Pro path (no credits)', () => { | ||
| const covered = getCoveredUsage(30, 0) | ||
| expect( | ||
| getIsOnDemandActive({ | ||
| isPaid: true, | ||
| planIncludedAmount: 30, | ||
| effectiveUsageLimit: 30, | ||
| covered, | ||
| }) | ||
| ).toBe(false) | ||
| expect( | ||
| getIsOnDemandActive({ | ||
| isPaid: true, | ||
| planIncludedAmount: 30, | ||
| effectiveUsageLimit: ON_DEMAND_UNLIMITED, | ||
| covered, | ||
| }) | ||
| ).toBe(true) | ||
| }) | ||
| }) | ||
|
|
||
| describe('getOnDemandOffLimit', () => { | ||
| it('drops the limit to the covered ceiling when usage is below it', () => { | ||
| expect(getOnDemandOffLimit(62, 120)).toBe(120) | ||
| expect(getOnDemandOffLimit(10, 30)).toBe(30) | ||
| }) | ||
|
|
||
| it('never lowers the limit below current usage', () => { | ||
| expect(getOnDemandOffLimit(150, 120)).toBe(150) | ||
| }) | ||
|
|
||
| it('lands on covered when usage equals it', () => { | ||
| expect(getOnDemandOffLimit(120, 120)).toBe(120) | ||
| }) | ||
| }) | ||
|
|
||
| describe('isOnDemandOffDisabled', () => { | ||
| it('disables the toggle when on-demand is on and usage is above covered', () => { | ||
| // Turning off here would re-cap at usage (150) and bounce back on, so lock it. | ||
| expect( | ||
| isOnDemandOffDisabled({ isOnDemandActive: true, effectiveCurrentUsage: 150, covered: 120 }) | ||
| ).toBe(true) | ||
| }) | ||
|
|
||
| it('allows turning off when usage is at or below covered', () => { | ||
| expect( | ||
| isOnDemandOffDisabled({ isOnDemandActive: true, effectiveCurrentUsage: 120, covered: 120 }) | ||
| ).toBe(false) | ||
| expect( | ||
| isOnDemandOffDisabled({ isOnDemandActive: true, effectiveCurrentUsage: 62, covered: 120 }) | ||
| ).toBe(false) | ||
| }) | ||
|
|
||
| it('never disables when on-demand is already off (turning on stays allowed)', () => { | ||
| expect( | ||
| isOnDemandOffDisabled({ isOnDemandActive: false, effectiveCurrentUsage: 150, covered: 120 }) | ||
| ).toBe(false) | ||
| }) | ||
| }) |
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.