Skip to content

Commit 80f41f9

Browse files
committed
fix: stop the usage popovers reporting limits that did not apply
Two places told an organization something untrue about its plan. A word plan neither charges nor enforces per seat — getSeatsLimit returns unlimited for any metric that does not use seats — yet the plan still carries an includedSeats allowance for its free tier. The dashboard rendered a bar from that allowance, so an organization with eight members sat permanently at "8 of 3" in red with the top-bar critical warning stuck on, against a limit nothing would ever enforce. The bar now follows the enforced limit rather than the allowance. The plan-limit popover offered auto-upgrade whenever the plan was word based, not when the word limit was what failed. A batch job running out of MT credits raises the same counter, so those users were told their word limit was exhausted and shown a one-click billing change for an unrelated problem. It now requires the words to actually be exhausted, which the popover can already see in the usage data it renders. The seat rule is covered by tests, confirmed to fail without it, and one case keeps the bar for a plan that does enforce seats so the assertions discriminate.
1 parent 9042b32 commit 80f41f9

3 files changed

Lines changed: 66 additions & 4 deletions

File tree

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { getProgressData } from './getProgressData';
3+
4+
/**
5+
* A word plan carries an includedSeats allowance for its free tier, but nothing enforces it —
6+
* the server reports seatsLimit as unlimited. Rendering a bar for it puts an organization
7+
* permanently over a limit that does not exist.
8+
*/
9+
describe('usage progress', () => {
10+
const usage = (overrides = {}) =>
11+
({
12+
includedSeats: 3,
13+
currentSeats: 8,
14+
seatsLimit: -1,
15+
includedTranslations: 0,
16+
currentTranslations: 0,
17+
includedKeys: 0,
18+
currentKeys: 0,
19+
includedMtCredits: 0,
20+
usedMtCredits: 0,
21+
includedWords: 50_000,
22+
currentWords: 100,
23+
isPayAsYouGo: false,
24+
...overrides,
25+
}) as any;
26+
27+
it('hides the seat bar when seats are not enforced', () => {
28+
const { seatsProgress } = getProgressData({ usage: usage() });
29+
30+
expect(seatsProgress.isInUse).toBe(false);
31+
});
32+
33+
it('does not let an unenforced seat count raise the critical warning', () => {
34+
// 8 of 3 would otherwise be 266% and pin the top-bar warning on permanently.
35+
const { isCritical } = getProgressData({ usage: usage() });
36+
37+
expect(isCritical).toBe(false);
38+
});
39+
40+
it('still shows the seat bar when seats are enforced', () => {
41+
const { seatsProgress } = getProgressData({
42+
usage: usage({ seatsLimit: 3 }),
43+
});
44+
45+
expect(seatsProgress.isInUse).toBe(true);
46+
expect(seatsProgress.progress).toBeGreaterThan(1);
47+
});
48+
});

webapp/src/ee/billing/component/getProgressData.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,13 @@ export const getProgressData = ({ usage }: { usage: UsageModel }) => {
1111

1212
const keysProgress = new ProgressItem(usage.includedKeys, usage.currentKeys);
1313

14+
// A word plan neither charges nor enforces per seat — the server reports its seat limit as
15+
// unlimited — yet the plan still carries an includedSeats allowance for the free tier. Showing
16+
// a bar for it puts an organization permanently over a limit that does not exist, in red, with
17+
// the top-bar critical warning stuck on.
18+
const seatsEnforced = usage.seatsLimit !== -1;
1419
const seatsProgress = new ProgressItem(
15-
usage.includedSeats,
20+
seatsEnforced ? usage.includedSeats : 0,
1621
usage.currentSeats
1722
);
1823

webapp/src/ee/billing/limitPopover/PlanLimitPopoverCloud.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,22 @@ export const PlanLimitPopoverCloud: React.FC<
4141
});
4242

4343
const subscription = subscriptionLoadable.data;
44+
const progressData = usage && getProgressData({ usage });
45+
46+
// The popover opens for any plan-limit error, including a batch job running out of MT credits,
47+
// which shares the same counter. Offering auto-upgrade off the plan's metric alone told those
48+
// users their word limit was exhausted and put a billing setting one click away for a problem
49+
// that had nothing to do with words — so the words have to actually be exhausted.
50+
const wordsExhausted = Boolean(
51+
progressData?.wordsProgress.isInUse &&
52+
progressData.wordsProgress.progress >= 1
53+
);
4454
const wordsAutoUpgradeAvailable = Boolean(
4555
subscription &&
4656
subscription.plan.metricType === 'HOSTED_WORDS' &&
4757
!subscription.plan.free &&
48-
!subscription.autoUpgradeEnabled
58+
!subscription.autoUpgradeEnabled &&
59+
wordsExhausted
4960
);
5061

5162
const autoUpgradeMutation = useBillingApiMutation({
@@ -77,8 +88,6 @@ export const PlanLimitPopoverCloud: React.FC<
7788
);
7889
};
7990

80-
const progressData = usage && getProgressData({ usage });
81-
8291
return progressData ? (
8392
<GenericPlanLimitPopover
8493
onClose={onClose}

0 commit comments

Comments
 (0)