gsBanner.spec.tsx 65 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134
  1. import moment from 'moment-timezone';
  2. import {OrganizationFixture} from 'sentry-fixture/organization';
  3. import {MetricHistoryFixture} from 'getsentry-test/fixtures/metricHistory';
  4. import {
  5. InvoicedSubscriptionFixture,
  6. SubscriptionFixture,
  7. } from 'getsentry-test/fixtures/subscription';
  8. import {
  9. act,
  10. render,
  11. renderGlobalModal,
  12. screen,
  13. userEvent,
  14. waitFor,
  15. } from 'sentry-test/reactTestingLibrary';
  16. import {textWithMarkupMatcher} from 'sentry-test/utils';
  17. import ConfigStore from 'sentry/stores/configStore';
  18. import ModalStore from 'sentry/stores/modalStore';
  19. import {DataCategory} from 'sentry/types/core';
  20. import {browserHistory} from 'sentry/utils/browserHistory';
  21. import {PendingChangesFixture} from 'getsentry/__fixtures__/pendingChanges';
  22. import {PlanFixture} from 'getsentry/__fixtures__/plan';
  23. import {
  24. openForcedTrialModal,
  25. openPartnerPlanEndingModal,
  26. openTrialEndingModal,
  27. } from 'getsentry/actionCreators/modal';
  28. import GSBanner from 'getsentry/components/gsBanner';
  29. import SubscriptionStore from 'getsentry/stores/subscriptionStore';
  30. jest.mock('getsentry/actionCreators/modal');
  31. const guideMock = jest.requireMock('sentry/stores/guideStore');
  32. jest.mock('sentry/stores/guideStore', () => ({
  33. state: {},
  34. }));
  35. describe('GSBanner', function () {
  36. beforeEach(() => {
  37. ModalStore.reset();
  38. jest.clearAllMocks();
  39. delete window.pendo;
  40. MockApiClient.clearMockResponses();
  41. MockApiClient.addMockResponse({
  42. method: 'POST',
  43. url: '/_experiment/log_exposure/',
  44. body: {},
  45. });
  46. MockApiClient.addMockResponse({
  47. url: `/organizations/org-slug/projects/`,
  48. body: [],
  49. });
  50. MockApiClient.addMockResponse({
  51. url: `/organizations/org-slug/teams/`,
  52. body: [],
  53. });
  54. MockApiClient.addMockResponse({
  55. url: `/subscriptions/org-slug/`,
  56. body: {},
  57. });
  58. MockApiClient.addMockResponse({
  59. url: `/organizations/org-slug/`,
  60. body: {},
  61. });
  62. [
  63. 'another-slug-1',
  64. 'another-slug-2',
  65. 'another-slug-3',
  66. 'another-slug-4',
  67. 'org-slug',
  68. 'promotion-platform',
  69. 'forced-trial',
  70. 'soft-cap',
  71. 'grace-period',
  72. 'trial-ending',
  73. 'partner-plan-ending',
  74. 'suspended',
  75. 'exceeded',
  76. 'past-due',
  77. 'past-due-2',
  78. 'past-due-3',
  79. 'past-due-4',
  80. ].forEach(slug => {
  81. SubscriptionStore.set(slug, {});
  82. MockApiClient.addMockResponse({
  83. url: `/organizations/${slug}/promotions/trigger-check/`,
  84. method: 'POST',
  85. });
  86. MockApiClient.addMockResponse({
  87. method: 'GET',
  88. url: `/organizations/${slug}/prompts-activity/`,
  89. body: {},
  90. });
  91. });
  92. });
  93. it('renders empty', async function () {
  94. const organization = OrganizationFixture();
  95. SubscriptionStore.set(organization.slug, {});
  96. const {container} = render(<GSBanner organization={organization} />, {organization});
  97. // wait for requests to finish
  98. await act(tick);
  99. expect(container).toBeEmptyDOMElement();
  100. });
  101. it('shows overage notification banner and request more events btn for members', async function () {
  102. const organization = OrganizationFixture();
  103. const subscription = SubscriptionFixture({
  104. organization,
  105. plan: 'am2_team',
  106. categories: {
  107. errors: MetricHistoryFixture({usageExceeded: false}),
  108. transactions: MetricHistoryFixture({usageExceeded: true}),
  109. replays: MetricHistoryFixture({usageExceeded: false}),
  110. attachments: MetricHistoryFixture({usageExceeded: true}),
  111. monitorSeats: MetricHistoryFixture({usageExceeded: false}),
  112. },
  113. canSelfServe: true,
  114. });
  115. SubscriptionStore.set(organization.slug, subscription);
  116. render(<GSBanner organization={organization} />, {organization});
  117. expect(
  118. await screen.findByTestId('overage-banner-transaction-attachment')
  119. ).toBeInTheDocument();
  120. expect(screen.getByText('performance unit')).toBeInTheDocument();
  121. expect(screen.queryByText('transaction')).not.toBeInTheDocument();
  122. expect(screen.getByTestId('btn-request_add_events')).toBeInTheDocument();
  123. });
  124. it('shows overage notification banner for multiple categories', async function () {
  125. const organization = OrganizationFixture();
  126. const subscription = SubscriptionFixture({
  127. organization,
  128. plan: 'am1_t',
  129. categories: {
  130. errors: MetricHistoryFixture({usageExceeded: true}),
  131. transactions: MetricHistoryFixture({usageExceeded: true}),
  132. replays: MetricHistoryFixture({usageExceeded: false}),
  133. attachments: MetricHistoryFixture({usageExceeded: true}),
  134. monitorSeats: MetricHistoryFixture({usageExceeded: true}),
  135. },
  136. canSelfServe: true,
  137. });
  138. SubscriptionStore.set(organization.slug, subscription);
  139. render(<GSBanner organization={organization} />, {organization});
  140. expect(
  141. await screen.findByTestId('overage-banner-error-transaction-attachment-monitorSeat')
  142. ).toBeInTheDocument();
  143. expect(screen.getByText('transaction')).toBeInTheDocument();
  144. expect(screen.getByText('cron monitor')).toBeInTheDocument();
  145. expect(screen.queryByText('performance unit')).not.toBeInTheDocument();
  146. });
  147. it('does not show overage notification banner if is not self served', async function () {
  148. const organization = OrganizationFixture();
  149. const subscription = SubscriptionFixture({
  150. organization,
  151. plan: 'am1_t',
  152. categories: {attachments: MetricHistoryFixture({usageExceeded: true})},
  153. canSelfServe: false,
  154. });
  155. SubscriptionStore.set(organization.slug, subscription);
  156. const {container} = render(<GSBanner organization={organization} />, {organization});
  157. await act(tick);
  158. expect(container).toBeEmptyDOMElement();
  159. });
  160. it('does not show overage notification banner if active product trial', async function () {
  161. const organization = OrganizationFixture();
  162. const subscription = SubscriptionFixture({
  163. organization,
  164. plan: 'am2_team',
  165. categories: {replays: MetricHistoryFixture({usageExceeded: true})},
  166. canSelfServe: true,
  167. productTrials: [
  168. {
  169. category: DataCategory.REPLAYS,
  170. isStarted: true,
  171. reasonCode: 1001,
  172. startDate: moment().utc().subtract(10, 'days').format(),
  173. endDate: moment().utc().add(20, 'days').format(),
  174. },
  175. ],
  176. });
  177. SubscriptionStore.set(organization.slug, subscription);
  178. const {container} = render(<GSBanner organization={organization} />, {organization});
  179. await act(tick);
  180. expect(container).toBeEmptyDOMElement();
  181. });
  182. it('shows overage notification banner even if other category is on active trial', async function () {
  183. const organization = OrganizationFixture();
  184. const subscription = SubscriptionFixture({
  185. organization,
  186. plan: 'am2_team',
  187. categories: {
  188. attachments: MetricHistoryFixture({usageExceeded: true}),
  189. replays: MetricHistoryFixture({usageExceeded: true}),
  190. },
  191. canSelfServe: true,
  192. productTrials: [
  193. {
  194. category: DataCategory.REPLAYS,
  195. isStarted: true,
  196. reasonCode: 1001,
  197. startDate: moment().utc().subtract(10, 'days').format(),
  198. endDate: moment().utc().add(20, 'days').format(),
  199. },
  200. ],
  201. });
  202. SubscriptionStore.set(organization.slug, subscription);
  203. render(<GSBanner organization={organization} />, {organization});
  204. expect(await screen.findByTestId('overage-banner-attachment')).toBeInTheDocument();
  205. expect(screen.queryByTestId('overage-banner-replay')).not.toBeInTheDocument();
  206. });
  207. it('shows start trial btn for billing admins if can trial', async function () {
  208. const organization = OrganizationFixture({
  209. access: ['org:billing'],
  210. });
  211. const subscription = SubscriptionFixture({
  212. organization,
  213. plan: 'am1_f',
  214. categories: {
  215. errors: MetricHistoryFixture({usageExceeded: false}),
  216. transactions: MetricHistoryFixture({usageExceeded: true}),
  217. attachments: MetricHistoryFixture({usageExceeded: true}),
  218. },
  219. canSelfServe: true,
  220. canTrial: true,
  221. });
  222. SubscriptionStore.set(organization.slug, subscription);
  223. render(<GSBanner organization={organization} />, {organization});
  224. expect(
  225. await screen.findByTestId('overage-banner-transaction-attachment')
  226. ).toBeInTheDocument();
  227. expect(screen.getByTestId('btn-start_trial')).toBeInTheDocument();
  228. });
  229. it('shows upgrade plan btn for free plans for admins', async function () {
  230. const organization = OrganizationFixture({
  231. access: ['org:billing'],
  232. });
  233. const subscription = SubscriptionFixture({
  234. organization,
  235. plan: 'am1_f',
  236. categories: {
  237. errors: MetricHistoryFixture({usageExceeded: true}),
  238. transactions: MetricHistoryFixture({usageExceeded: false}),
  239. attachments: MetricHistoryFixture({usageExceeded: true}),
  240. },
  241. canSelfServe: true,
  242. canTrial: false,
  243. });
  244. SubscriptionStore.set(organization.slug, subscription);
  245. render(<GSBanner organization={organization} />, {organization});
  246. expect(
  247. await screen.findByTestId('overage-banner-error-attachment')
  248. ).toBeInTheDocument();
  249. expect(screen.getByRole('button', {name: /upgrade plan/i})).toBeInTheDocument();
  250. });
  251. it('shows add quota btn for paid plans for admins', async function () {
  252. const organization = OrganizationFixture({
  253. access: ['org:billing'],
  254. });
  255. const subscription = SubscriptionFixture({
  256. organization,
  257. plan: 'am1_team',
  258. categories: {
  259. errors: MetricHistoryFixture({usageExceeded: true}),
  260. transactions: MetricHistoryFixture({usageExceeded: true}),
  261. replays: MetricHistoryFixture({usageExceeded: true}),
  262. attachments: MetricHistoryFixture({usageExceeded: true}),
  263. monitorSeats: MetricHistoryFixture({usageExceeded: true}),
  264. },
  265. canSelfServe: true,
  266. canTrial: false,
  267. });
  268. SubscriptionStore.set(organization.slug, subscription);
  269. render(<GSBanner organization={organization} />, {organization});
  270. expect(
  271. await screen.findByTestId(
  272. 'overage-banner-error-transaction-replay-attachment-monitorSeat'
  273. )
  274. ).toBeInTheDocument();
  275. expect(
  276. screen.getByRole('button', {name: /increase reserved limits/i})
  277. ).toBeInTheDocument();
  278. });
  279. it('shows add quota button for paid plans without active product trial', async function () {
  280. const organization = OrganizationFixture({
  281. access: ['org:billing'],
  282. });
  283. const subscription = SubscriptionFixture({
  284. organization,
  285. plan: 'am3_team',
  286. categories: {
  287. errors: MetricHistoryFixture({usageExceeded: false}),
  288. spans: MetricHistoryFixture({usageExceeded: true}),
  289. replays: MetricHistoryFixture({usageExceeded: false}),
  290. attachments: MetricHistoryFixture({usageExceeded: false}),
  291. monitorSeats: MetricHistoryFixture({usageExceeded: false}),
  292. profileDuration: MetricHistoryFixture({usageExceeded: false}),
  293. },
  294. canSelfServe: true,
  295. productTrials: [
  296. {
  297. category: 'spans',
  298. reasonCode: 1001,
  299. isStarted: false,
  300. lengthDays: undefined,
  301. startDate: undefined,
  302. endDate: undefined,
  303. },
  304. ],
  305. });
  306. SubscriptionStore.set(organization.slug, subscription);
  307. render(<GSBanner organization={organization} />, {organization});
  308. expect(
  309. await screen.findByRole('button', {name: /increase reserved limits/i})
  310. ).toBeInTheDocument();
  311. });
  312. it('does not show add quota button for paid plans with active product trial', async function () {
  313. const organization = OrganizationFixture({
  314. access: ['org:billing'],
  315. });
  316. const subscription = SubscriptionFixture({
  317. organization,
  318. plan: 'am3_team',
  319. categories: {
  320. errors: MetricHistoryFixture({usageExceeded: false}),
  321. spans: MetricHistoryFixture({usageExceeded: true}),
  322. replays: MetricHistoryFixture({usageExceeded: false}),
  323. attachments: MetricHistoryFixture({usageExceeded: false}),
  324. monitorSeats: MetricHistoryFixture({usageExceeded: false}),
  325. profileDuration: MetricHistoryFixture({usageExceeded: false}),
  326. },
  327. canSelfServe: true,
  328. productTrials: [
  329. {
  330. category: 'spans',
  331. reasonCode: 1001,
  332. isStarted: true,
  333. lengthDays: 20,
  334. startDate: moment().utc().subtract(10, 'days').format(),
  335. endDate: moment().utc().add(10, 'days').format(),
  336. },
  337. ],
  338. });
  339. SubscriptionStore.set(organization.slug, subscription);
  340. render(<GSBanner organization={organization} />, {organization});
  341. await act(tick);
  342. expect(
  343. screen.queryByRole('button', {name: /increase reserved limits/i})
  344. ).not.toBeInTheDocument();
  345. });
  346. it('user can dismiss notification', async function () {
  347. const organization = OrganizationFixture();
  348. const subscription = SubscriptionFixture({
  349. organization,
  350. plan: 'am1_t',
  351. categories: {
  352. errors: MetricHistoryFixture({usageExceeded: true}),
  353. transactions: MetricHistoryFixture({usageExceeded: true}),
  354. replays: MetricHistoryFixture({usageExceeded: true}),
  355. attachments: MetricHistoryFixture({usageExceeded: true}),
  356. monitorSeats: MetricHistoryFixture({usageExceeded: true}),
  357. spans: MetricHistoryFixture({usageExceeded: true}),
  358. profileDuration: MetricHistoryFixture({usageExceeded: true}),
  359. },
  360. canSelfServe: true,
  361. });
  362. SubscriptionStore.set(organization.slug, subscription);
  363. const snoozeEndpoint = MockApiClient.addMockResponse({
  364. method: 'PUT',
  365. url: `/organizations/${organization.slug}/prompts-activity/`,
  366. body: {},
  367. });
  368. render(<GSBanner organization={organization} />, {organization});
  369. expect(
  370. await screen.findByTestId(
  371. 'overage-banner-error-transaction-replay-attachment-monitorSeat-span-profileDuration'
  372. )
  373. ).toBeInTheDocument();
  374. expect(screen.getByTestId('btn-overage-notification-snooze')).toBeInTheDocument();
  375. await userEvent.click(screen.getByTestId('btn-overage-notification-snooze'));
  376. for (const feature of [
  377. 'errors_overage_alert',
  378. 'transactions_overage_alert',
  379. 'replays_overage_alert',
  380. 'attachments_overage_alert',
  381. 'spans_overage_alert',
  382. 'profile_duration_overage_alert',
  383. ]) {
  384. expect(snoozeEndpoint).toHaveBeenCalledWith(
  385. `/organizations/${organization.slug}/prompts-activity/`,
  386. expect.objectContaining({
  387. data: {
  388. feature,
  389. organization_id: organization.id,
  390. status: 'snoozed',
  391. },
  392. })
  393. );
  394. }
  395. expect(screen.queryByTestId(/overage-banner/)).not.toBeInTheDocument();
  396. });
  397. it('do not show banner when dismissed', async function () {
  398. const organization = OrganizationFixture();
  399. const snoozeTime = new Date('2020-02-02');
  400. snoozeTime.setHours(23, 59, 59);
  401. MockApiClient.addMockResponse({
  402. method: 'GET',
  403. url: `/organizations/${organization.slug}/prompts-activity/`,
  404. body: {
  405. features: {
  406. transactions_overage_alert: {
  407. snoozed_ts: snoozeTime.getTime() / 1000,
  408. },
  409. replays_overage_alert: {
  410. snoozed_ts: snoozeTime.getTime() / 1000,
  411. },
  412. },
  413. },
  414. });
  415. const subscription = SubscriptionFixture({
  416. organization,
  417. plan: 'am1_t',
  418. categories: {
  419. errors: MetricHistoryFixture({usageExceeded: false}),
  420. transactions: MetricHistoryFixture({usageExceeded: true}),
  421. replays: MetricHistoryFixture({usageExceeded: true}),
  422. attachments: MetricHistoryFixture({usageExceeded: false}),
  423. monitorSeats: MetricHistoryFixture({usageExceeded: false}),
  424. },
  425. canSelfServe: true,
  426. onDemandPeriodEnd: '2020-02-02',
  427. });
  428. SubscriptionStore.set(organization.slug, subscription);
  429. const {container} = render(<GSBanner organization={organization} />, {organization});
  430. await act(tick);
  431. expect(container).toBeEmptyDOMElement();
  432. });
  433. it('shows add quota btn for paid plans for admins for warnings', async function () {
  434. const organization = OrganizationFixture({
  435. access: ['org:billing'],
  436. slug: 'another-slug-1',
  437. });
  438. const subscription = SubscriptionFixture({
  439. organization,
  440. plan: 'am1_team',
  441. categories: {
  442. errors: MetricHistoryFixture({sentUsageWarning: true}),
  443. transactions: MetricHistoryFixture({sentUsageWarning: false}),
  444. replays: MetricHistoryFixture({usageExceeded: false}),
  445. attachments: MetricHistoryFixture({sentUsageWarning: false}),
  446. monitorSeats: MetricHistoryFixture({sentUsageWarning: false}),
  447. },
  448. canSelfServe: true,
  449. });
  450. SubscriptionStore.set(organization.slug, subscription);
  451. render(<GSBanner organization={organization} />, {organization});
  452. expect(
  453. await screen.findByRole('button', {name: /increase reserved limits/i})
  454. ).toBeInTheDocument();
  455. });
  456. it('shows add quota button for paid plans warnings with no active product trial', async function () {
  457. const organization = OrganizationFixture({
  458. access: ['org:billing'],
  459. slug: 'another-slug-1',
  460. });
  461. const subscription = SubscriptionFixture({
  462. organization,
  463. plan: 'am3_team',
  464. categories: {
  465. errors: MetricHistoryFixture({sentUsageWarning: false}),
  466. spans: MetricHistoryFixture({sentUsageWarning: false}),
  467. replays: MetricHistoryFixture({usageExceeded: true}),
  468. attachments: MetricHistoryFixture({sentUsageWarning: false}),
  469. monitorSeats: MetricHistoryFixture({sentUsageWarning: false}),
  470. profileDuration: MetricHistoryFixture({sentUsageWarning: false}),
  471. },
  472. canSelfServe: true,
  473. productTrials: [
  474. {
  475. category: 'replays',
  476. reasonCode: 1001,
  477. isStarted: false,
  478. lengthDays: undefined,
  479. startDate: undefined,
  480. endDate: undefined,
  481. },
  482. ],
  483. });
  484. SubscriptionStore.set(organization.slug, subscription);
  485. render(<GSBanner organization={organization} />, {organization});
  486. expect(
  487. await screen.findByRole('button', {name: /increase reserved limits/i})
  488. ).toBeInTheDocument();
  489. });
  490. it('does not show add quota btn for paid plans warnings with active product trial', async function () {
  491. const organization = OrganizationFixture({
  492. access: ['org:billing'],
  493. slug: 'another-slug-1',
  494. });
  495. const subscription = SubscriptionFixture({
  496. organization,
  497. plan: 'am3_team',
  498. categories: {
  499. errors: MetricHistoryFixture({sentUsageWarning: false}),
  500. spans: MetricHistoryFixture({sentUsageWarning: false}),
  501. replays: MetricHistoryFixture({usageExceeded: true}),
  502. attachments: MetricHistoryFixture({sentUsageWarning: false}),
  503. monitorSeats: MetricHistoryFixture({sentUsageWarning: false}),
  504. },
  505. canSelfServe: true,
  506. productTrials: [
  507. {
  508. category: 'replays',
  509. reasonCode: 1001,
  510. isStarted: true,
  511. lengthDays: 20,
  512. startDate: moment().utc().subtract(10, 'days').format(),
  513. endDate: moment().utc().add(10, 'days').format(),
  514. },
  515. ],
  516. });
  517. SubscriptionStore.set(organization.slug, subscription);
  518. render(<GSBanner organization={organization} />, {organization});
  519. await act(tick);
  520. expect(
  521. screen.queryByRole('button', {name: /increase reserved limits/i})
  522. ).not.toBeInTheDocument();
  523. });
  524. it('does not show warning if on-demand is set', async function () {
  525. const organization = OrganizationFixture({
  526. access: ['org:billing'],
  527. slug: 'another-slug-1',
  528. });
  529. const subscription = SubscriptionFixture({
  530. organization,
  531. plan: 'am1_team',
  532. categories: {errors: MetricHistoryFixture({sentUsageWarning: true})},
  533. canSelfServe: true,
  534. onDemandMaxSpend: 10,
  535. });
  536. SubscriptionStore.set(organization.slug, subscription);
  537. render(<GSBanner organization={organization} />, {organization});
  538. await act(tick);
  539. expect(
  540. screen.queryByRole('button', {name: /increase reserved limits/i})
  541. ).not.toBeInTheDocument();
  542. });
  543. it('does not show warning if active product trial', async function () {
  544. const organization = OrganizationFixture({
  545. access: ['org:billing'],
  546. slug: 'another-slug-1',
  547. });
  548. const subscription = SubscriptionFixture({
  549. organization,
  550. plan: 'am1_team',
  551. categories: {monitorSeats: MetricHistoryFixture({sentUsageWarning: true})},
  552. canSelfServe: true,
  553. productTrials: [
  554. {
  555. category: DataCategory.MONITOR_SEATS,
  556. isStarted: true,
  557. reasonCode: 1001,
  558. startDate: moment().utc().subtract(10, 'days').format(),
  559. endDate: moment().utc().add(20, 'days').format(),
  560. },
  561. ],
  562. });
  563. SubscriptionStore.set(organization.slug, subscription);
  564. const {container} = render(<GSBanner organization={organization} />, {organization});
  565. await act(tick);
  566. expect(container).toBeEmptyDOMElement();
  567. });
  568. it('shows warning even if other category is on active trial', async function () {
  569. const organization = OrganizationFixture({
  570. access: ['org:billing'],
  571. slug: 'another-slug-1',
  572. });
  573. const subscription = SubscriptionFixture({
  574. organization,
  575. plan: 'am1_team',
  576. categories: {
  577. errors: MetricHistoryFixture({sentUsageWarning: true}),
  578. monitorSeats: MetricHistoryFixture({sentUsageWarning: true}),
  579. },
  580. canSelfServe: true,
  581. productTrials: [
  582. {
  583. category: DataCategory.MONITOR_SEATS,
  584. isStarted: true,
  585. reasonCode: 1001,
  586. startDate: moment().utc().subtract(10, 'days').format(),
  587. endDate: moment().utc().add(20, 'days').format(),
  588. },
  589. ],
  590. });
  591. SubscriptionStore.set(organization.slug, subscription);
  592. render(<GSBanner organization={organization} />, {organization});
  593. expect(await screen.findByTestId('overage-banner-error')).toBeInTheDocument();
  594. expect(screen.queryByTestId('overage-banner-monitorSeat')).not.toBeInTheDocument();
  595. });
  596. it('does not show alert if hasOverageNotificationsDisabled is set', async function () {
  597. const organization = OrganizationFixture({
  598. access: ['org:billing'],
  599. slug: 'another-slug-2',
  600. });
  601. const subscription = SubscriptionFixture({
  602. organization,
  603. plan: 'am1_team',
  604. categories: {
  605. errors: MetricHistoryFixture({usageExceeded: true}),
  606. transactions: MetricHistoryFixture({usageExceeded: true}),
  607. replays: MetricHistoryFixture({usageExceeded: true}),
  608. attachments: MetricHistoryFixture({usageExceeded: true}),
  609. monitorSeats: MetricHistoryFixture({usageExceeded: true}),
  610. },
  611. canSelfServe: true,
  612. hasOverageNotificationsDisabled: true,
  613. });
  614. SubscriptionStore.set(organization.slug, subscription);
  615. render(<GSBanner organization={organization} />, {organization});
  616. await act(tick);
  617. expect(
  618. screen.queryByRole('button', {name: /increase reserved limits/i})
  619. ).not.toBeInTheDocument();
  620. });
  621. it('does not show notification if hasOverageNotificationsDisabled is set', async function () {
  622. const organization = OrganizationFixture({
  623. access: ['org:billing'],
  624. slug: 'another-slug-3',
  625. });
  626. const subscription = SubscriptionFixture({
  627. organization,
  628. plan: 'am1_team',
  629. categories: {
  630. errors: MetricHistoryFixture({usageExceeded: true}),
  631. transactions: MetricHistoryFixture({usageExceeded: true}),
  632. replays: MetricHistoryFixture({usageExceeded: true}),
  633. attachments: MetricHistoryFixture({usageExceeded: true}),
  634. monitorSeats: MetricHistoryFixture({usageExceeded: true}),
  635. },
  636. canSelfServe: true,
  637. hasOverageNotificationsDisabled: true,
  638. });
  639. SubscriptionStore.set(organization.slug, subscription);
  640. render(<GSBanner organization={organization} />, {organization});
  641. await act(tick);
  642. expect(
  643. screen.queryByRole('button', {name: /increase reserved limits/i})
  644. ).not.toBeInTheDocument();
  645. });
  646. it('does not show warning if hasOverageNotificationsDisabled is set', async function () {
  647. const organization = OrganizationFixture({
  648. access: ['org:billing'],
  649. slug: 'another-slug-4',
  650. });
  651. const subscription = SubscriptionFixture({
  652. organization,
  653. plan: 'am1_team',
  654. categories: {
  655. errors: MetricHistoryFixture({
  656. category: DataCategory.ERRORS,
  657. sentUsageWarning: true,
  658. }),
  659. transactions: MetricHistoryFixture({
  660. category: DataCategory.TRANSACTIONS,
  661. sentUsageWarning: true,
  662. }),
  663. replays: MetricHistoryFixture({
  664. category: DataCategory.REPLAYS,
  665. sentUsageWarning: true,
  666. }),
  667. attachments: MetricHistoryFixture({
  668. category: DataCategory.ATTACHMENTS,
  669. sentUsageWarning: true,
  670. }),
  671. monitorSeats: MetricHistoryFixture({
  672. category: DataCategory.MONITOR_SEATS,
  673. sentUsageWarning: true,
  674. }),
  675. },
  676. canSelfServe: true,
  677. hasOverageNotificationsDisabled: true,
  678. });
  679. SubscriptionStore.set(organization.slug, subscription);
  680. render(<GSBanner organization={organization} />, {
  681. organization,
  682. });
  683. await act(tick);
  684. expect(screen.queryByTestId('overage-banner-error')).not.toBeInTheDocument();
  685. expect(screen.queryByTestId('overage-banner-transaction')).not.toBeInTheDocument();
  686. expect(screen.queryByTestId('overage-banner-replay')).not.toBeInTheDocument();
  687. expect(screen.queryByTestId('overage-banner-attachment')).not.toBeInTheDocument();
  688. expect(screen.queryByTestId('overage-banner-monitorSeat')).not.toBeInTheDocument();
  689. });
  690. it('renders suspension modal', async function () {
  691. const organization = OrganizationFixture({slug: 'suspended'});
  692. SubscriptionStore.set(
  693. organization.slug,
  694. SubscriptionFixture({organization, isSuspended: true})
  695. );
  696. render(<GSBanner organization={organization} />, {organization});
  697. renderGlobalModal();
  698. expect(await screen.findByRole('dialog')).toBeInTheDocument();
  699. });
  700. it('renders usage exceeded modal', async function () {
  701. const organization = OrganizationFixture({slug: 'exceeded'});
  702. SubscriptionStore.set(
  703. organization.slug,
  704. SubscriptionFixture({organization, usageExceeded: true})
  705. );
  706. render(<GSBanner organization={organization} />, {organization});
  707. renderGlobalModal();
  708. expect(await screen.findByTestId('modal-usage-exceeded')).toBeInTheDocument();
  709. });
  710. it('renders grace period modal with billing access', async function () {
  711. const organization = OrganizationFixture({
  712. slug: 'grace-period',
  713. access: ['org:billing'],
  714. });
  715. SubscriptionStore.set(
  716. organization.slug,
  717. SubscriptionFixture({organization, isGracePeriod: true})
  718. );
  719. render(<GSBanner organization={organization} />, {organization});
  720. renderGlobalModal();
  721. expect(await screen.findByTestId('modal-grace-period')).toBeInTheDocument();
  722. });
  723. it('does not render overage banner without grace period and usage exceeded', async function () {
  724. const organization = OrganizationFixture({
  725. slug: 'soft-cap',
  726. access: ['org:billing'],
  727. });
  728. SubscriptionStore.set(
  729. organization.slug,
  730. SubscriptionFixture({
  731. organization,
  732. isGracePeriod: false,
  733. usageExceeded: false,
  734. })
  735. );
  736. render(<GSBanner organization={organization} />, {organization});
  737. await act(tick);
  738. expect(screen.queryByTestId('grace-period-banner')).not.toBeInTheDocument();
  739. expect(screen.queryByTestId('usage-exceeded-banner')).not.toBeInTheDocument();
  740. expect(screen.queryByTestId('modal-usage-exceeded')).not.toBeInTheDocument();
  741. expect(screen.queryByTestId('modal-grace-period')).not.toBeInTheDocument();
  742. });
  743. it('opens the trialEndingModal within 3 days of ending', async function () {
  744. const now = moment();
  745. const organization = OrganizationFixture({
  746. slug: 'trial-ending',
  747. });
  748. SubscriptionStore.set(
  749. organization.slug,
  750. SubscriptionFixture({
  751. organization,
  752. isTrial: true,
  753. hasDismissedTrialEndingNotice: false,
  754. plan: 'am1_t',
  755. trialEnd: now.add(2, 'day').toString(),
  756. })
  757. );
  758. render(<GSBanner organization={organization} />, {organization});
  759. await waitFor(() => expect(openTrialEndingModal).toHaveBeenCalled());
  760. });
  761. it('does not display trial ending modal more than 3 days', async function () {
  762. const now = moment();
  763. const organization = OrganizationFixture({});
  764. SubscriptionStore.set(
  765. organization.slug,
  766. SubscriptionFixture({
  767. organization,
  768. hasDismissedTrialEndingNotice: false,
  769. plan: 'am1_t',
  770. trialEnd: now.add(5, 'day').toString(),
  771. })
  772. );
  773. render(<GSBanner organization={organization} />, {organization});
  774. await act(tick);
  775. expect(openTrialEndingModal).not.toHaveBeenCalled();
  776. });
  777. it('does not display trial ending modal to free plan', async function () {
  778. const organization = OrganizationFixture({});
  779. SubscriptionStore.set(
  780. organization.slug,
  781. SubscriptionFixture({
  782. organization,
  783. hasDismissedTrialEndingNotice: false,
  784. plan: 'am1_f',
  785. })
  786. );
  787. render(<GSBanner organization={organization} />, {organization});
  788. await act(tick);
  789. expect(openTrialEndingModal).not.toHaveBeenCalled();
  790. });
  791. it('does not display trial ending modal to plan trial', async function () {
  792. const now = moment();
  793. const organization = OrganizationFixture({});
  794. SubscriptionStore.set(
  795. organization.slug,
  796. SubscriptionFixture({
  797. organization,
  798. hasDismissedTrialEndingNotice: false,
  799. plan: 'am1_team',
  800. trialEnd: now.add(2, 'day').toString(),
  801. })
  802. );
  803. render(<GSBanner organization={organization} />, {organization});
  804. await act(tick);
  805. expect(openTrialEndingModal).not.toHaveBeenCalled();
  806. });
  807. it('does not display trial ending modal to enterprise trial', async function () {
  808. const now = moment();
  809. const organization = OrganizationFixture({
  810. slug: 'trial-ending',
  811. });
  812. SubscriptionStore.set(
  813. organization.slug,
  814. SubscriptionFixture({
  815. organization,
  816. isTrial: true,
  817. hasDismissedTrialEndingNotice: false,
  818. plan: 'am1_business',
  819. trialEnd: now.add(2, 'day').toString(),
  820. isEnterpriseTrial: true,
  821. })
  822. );
  823. render(<GSBanner organization={organization} />, {organization});
  824. await act(tick);
  825. expect(openTrialEndingModal).not.toHaveBeenCalled();
  826. });
  827. it('opens the partnerPlanEndingModal within 30 days of ending', async function () {
  828. const now = moment();
  829. const organization = OrganizationFixture({
  830. slug: 'partner-plan-ending',
  831. features: ['partner-billing-migration'],
  832. });
  833. SubscriptionStore.set(
  834. organization.slug,
  835. SubscriptionFixture({
  836. organization,
  837. contractPeriodEnd: now.add(30, 'day').toString(),
  838. isTrial: true,
  839. plan: 'am2_sponsored_team_auf',
  840. partner: {
  841. externalId: '123',
  842. name: 'FOO',
  843. partnership: {
  844. id: 'FOO',
  845. displayName: 'FOO',
  846. supportNote: '',
  847. },
  848. isActive: true,
  849. },
  850. })
  851. );
  852. render(<GSBanner organization={organization} />, {organization});
  853. await waitFor(() => expect(openPartnerPlanEndingModal).toHaveBeenCalled());
  854. });
  855. it('opens the partnerPlanEndingModal within 7 days of ending', async function () {
  856. const now = moment();
  857. const organization = OrganizationFixture({
  858. slug: 'partner-plan-ending',
  859. features: ['partner-billing-migration'],
  860. });
  861. SubscriptionStore.set(
  862. organization.slug,
  863. SubscriptionFixture({
  864. organization,
  865. contractPeriodEnd: now.add(7, 'day').toString(),
  866. isTrial: true,
  867. plan: 'am2_sponsored_team_auf',
  868. partner: {
  869. externalId: '123',
  870. name: 'FOO',
  871. partnership: {
  872. id: 'FOO',
  873. displayName: 'FOO',
  874. supportNote: '',
  875. },
  876. isActive: true,
  877. },
  878. })
  879. );
  880. render(<GSBanner organization={organization} />, {organization});
  881. await waitFor(() => expect(openPartnerPlanEndingModal).toHaveBeenCalled());
  882. });
  883. it('opens the partnerPlanEndingModal with 2 days left', async function () {
  884. const now = moment();
  885. const organization = OrganizationFixture({
  886. slug: 'partner-plan-ending',
  887. features: ['partner-billing-migration'],
  888. });
  889. SubscriptionStore.set(
  890. organization.slug,
  891. SubscriptionFixture({
  892. organization,
  893. contractPeriodEnd: now.add(2, 'days').toString(),
  894. isTrial: true,
  895. plan: 'am2_sponsored_team_auf',
  896. partner: {
  897. externalId: '123',
  898. name: 'FOO',
  899. partnership: {
  900. id: 'FOO',
  901. displayName: 'FOO',
  902. supportNote: '',
  903. },
  904. isActive: true,
  905. },
  906. })
  907. );
  908. render(<GSBanner organization={organization} />, {organization});
  909. await waitFor(() => expect(openPartnerPlanEndingModal).toHaveBeenCalled());
  910. });
  911. it('opens the partnerPlanEndingModal on day that plan ends', async function () {
  912. const now = moment();
  913. const organization = OrganizationFixture({
  914. slug: 'partner-plan-ending',
  915. features: ['partner-billing-migration'],
  916. });
  917. SubscriptionStore.set(
  918. organization.slug,
  919. SubscriptionFixture({
  920. organization,
  921. contractPeriodEnd: now.toString(),
  922. isTrial: true,
  923. plan: 'am2_sponsored_team_auf',
  924. partner: {
  925. externalId: '123',
  926. name: 'FOO',
  927. partnership: {
  928. id: 'FOO',
  929. displayName: 'FOO',
  930. supportNote: '',
  931. },
  932. isActive: true,
  933. },
  934. })
  935. );
  936. render(<GSBanner organization={organization} />, {organization});
  937. await waitFor(() => expect(openPartnerPlanEndingModal).toHaveBeenCalled());
  938. });
  939. it('does not open the partnerPlanEndingModal without feature flag', async function () {
  940. const now = moment();
  941. const organization = OrganizationFixture({
  942. slug: 'partner-plan-ending',
  943. });
  944. SubscriptionStore.set(
  945. organization.slug,
  946. SubscriptionFixture({
  947. organization,
  948. contractPeriodEnd: now.add(7, 'day').toString(),
  949. isTrial: true,
  950. plan: 'am2_sponsored_team_auf',
  951. partner: {
  952. externalId: '123',
  953. name: 'FOO',
  954. partnership: {
  955. id: 'FOO',
  956. displayName: 'FOO',
  957. supportNote: '',
  958. },
  959. isActive: true,
  960. },
  961. })
  962. );
  963. render(<GSBanner organization={organization} />, {organization});
  964. await waitFor(() => expect(openPartnerPlanEndingModal).not.toHaveBeenCalled());
  965. });
  966. it('does not open the partnerPlanEndingModal with pending upgrade', async function () {
  967. const now = moment();
  968. const organization = OrganizationFixture({
  969. slug: 'partner-plan-ending',
  970. features: ['partner-billing-migration'],
  971. });
  972. SubscriptionStore.set(
  973. organization.slug,
  974. SubscriptionFixture({
  975. organization,
  976. contractPeriodEnd: now.add(7, 'day').toString(),
  977. isTrial: true,
  978. plan: 'am2_sponsored_team_auf',
  979. partner: {
  980. externalId: '123',
  981. name: 'FOO',
  982. partnership: {
  983. id: 'FOO',
  984. displayName: 'FOO',
  985. supportNote: '',
  986. },
  987. isActive: true,
  988. },
  989. pendingChanges: PendingChangesFixture({
  990. plan: 'am3_business',
  991. planDetails: PlanFixture({
  992. name: 'Business',
  993. price: 100,
  994. }),
  995. }),
  996. })
  997. );
  998. render(<GSBanner organization={organization} />, {organization});
  999. await waitFor(() => expect(openPartnerPlanEndingModal).not.toHaveBeenCalled());
  1000. });
  1001. it('opens the partnerPlanEndingModal with pending downgrade', async function () {
  1002. const now = moment();
  1003. const organization = OrganizationFixture({
  1004. slug: 'partner-plan-ending',
  1005. features: ['partner-billing-migration'],
  1006. });
  1007. SubscriptionStore.set(
  1008. organization.slug,
  1009. SubscriptionFixture({
  1010. organization,
  1011. contractPeriodEnd: now.add(7, 'day').toString(),
  1012. isTrial: true,
  1013. plan: 'am2_sponsored_team_auf',
  1014. partner: {
  1015. externalId: '123',
  1016. name: 'FOO',
  1017. partnership: {
  1018. id: 'FOO',
  1019. displayName: 'FOO',
  1020. supportNote: '',
  1021. },
  1022. isActive: true,
  1023. },
  1024. pendingChanges: PendingChangesFixture({
  1025. plan: 'am3_f',
  1026. planDetails: PlanFixture({
  1027. name: 'Developer',
  1028. price: 0,
  1029. }),
  1030. }),
  1031. })
  1032. );
  1033. render(<GSBanner organization={organization} />, {organization});
  1034. await waitFor(() => expect(openPartnerPlanEndingModal).toHaveBeenCalled());
  1035. });
  1036. it('does not open the partnerPlanEndingModal with more than 30 days before ending', async function () {
  1037. const now = moment();
  1038. const organization = OrganizationFixture({
  1039. slug: 'partner-plan-ending',
  1040. features: ['partner-billing-migration'],
  1041. });
  1042. SubscriptionStore.set(
  1043. organization.slug,
  1044. SubscriptionFixture({
  1045. organization,
  1046. contractPeriodEnd: now.add(31, 'day').toString(),
  1047. isTrial: true,
  1048. plan: 'am2_sponsored_team_auf',
  1049. partner: {
  1050. externalId: '123',
  1051. name: 'FOO',
  1052. partnership: {
  1053. id: 'FOO',
  1054. displayName: 'FOO',
  1055. supportNote: '',
  1056. },
  1057. isActive: true,
  1058. },
  1059. })
  1060. );
  1061. render(<GSBanner organization={organization} />, {organization});
  1062. await waitFor(() => expect(openPartnerPlanEndingModal).not.toHaveBeenCalled());
  1063. });
  1064. it('does not open the partnerPlanEndingModal if dismissed', async function () {
  1065. const now = moment();
  1066. const organization = OrganizationFixture({
  1067. slug: 'partner-plan-ending',
  1068. features: ['partner-billing-migration'],
  1069. });
  1070. MockApiClient.addMockResponse({
  1071. method: 'GET',
  1072. url: `/organizations/${organization.slug}/prompts-activity/`,
  1073. body: {
  1074. data: {
  1075. dismissed_ts: moment().subtract(2, 'days').unix(),
  1076. },
  1077. },
  1078. });
  1079. SubscriptionStore.set(
  1080. organization.slug,
  1081. SubscriptionFixture({
  1082. organization,
  1083. contractPeriodEnd: now.add(20, 'day').toString(),
  1084. isTrial: true,
  1085. plan: 'am2_sponsored_team_auf',
  1086. partner: {
  1087. externalId: '123',
  1088. name: 'FOO',
  1089. partnership: {
  1090. id: 'FOO',
  1091. displayName: 'FOO',
  1092. supportNote: '',
  1093. },
  1094. isActive: true,
  1095. },
  1096. })
  1097. );
  1098. render(<GSBanner organization={organization} />, {organization});
  1099. await waitFor(() => expect(openPartnerPlanEndingModal).not.toHaveBeenCalled());
  1100. });
  1101. it('opens partnerPlanEndingModal if dismissed in different time period', async function () {
  1102. const now = moment();
  1103. const organization = OrganizationFixture({
  1104. slug: 'partner-plan-ending',
  1105. features: ['partner-billing-migration'],
  1106. });
  1107. MockApiClient.addMockResponse({
  1108. method: 'GET',
  1109. url: `/organizations/${organization.slug}/prompts-activity/`,
  1110. body: {
  1111. data: {
  1112. dismissed_ts: moment().subtract(20, 'days').unix(),
  1113. },
  1114. },
  1115. });
  1116. SubscriptionStore.set(
  1117. organization.slug,
  1118. SubscriptionFixture({
  1119. organization,
  1120. contractPeriodEnd: now.add(1, 'day').toString(),
  1121. isTrial: true,
  1122. plan: 'am2_sponsored_team_auf',
  1123. partner: {
  1124. externalId: '123',
  1125. name: 'FOO',
  1126. partnership: {
  1127. id: 'FOO',
  1128. displayName: 'FOO',
  1129. supportNote: '',
  1130. },
  1131. isActive: true,
  1132. },
  1133. })
  1134. );
  1135. render(<GSBanner organization={organization} />, {organization});
  1136. await waitFor(() => expect(openPartnerPlanEndingModal).toHaveBeenCalled());
  1137. });
  1138. it('opens partnerPlanEndingModal if dismissed with 2 days left on day plan ends', async function () {
  1139. const now = moment();
  1140. const organization = OrganizationFixture({
  1141. slug: 'partner-plan-ending',
  1142. features: ['partner-billing-migration'],
  1143. });
  1144. MockApiClient.addMockResponse({
  1145. method: 'GET',
  1146. url: `/organizations/${organization.slug}/prompts-activity/`,
  1147. body: {
  1148. data: {
  1149. dismissed_ts: moment().subtract(2, 'days').unix(),
  1150. },
  1151. },
  1152. });
  1153. SubscriptionStore.set(
  1154. organization.slug,
  1155. SubscriptionFixture({
  1156. organization,
  1157. contractPeriodEnd: now.toString(),
  1158. isTrial: true,
  1159. plan: 'am2_sponsored_team_auf',
  1160. partner: {
  1161. externalId: '123',
  1162. name: 'FOO',
  1163. partnership: {
  1164. id: 'FOO',
  1165. displayName: 'FOO',
  1166. supportNote: '',
  1167. },
  1168. isActive: true,
  1169. },
  1170. })
  1171. );
  1172. render(<GSBanner organization={organization} />, {organization});
  1173. await waitFor(() => expect(openPartnerPlanEndingModal).toHaveBeenCalled());
  1174. });
  1175. it('does not open partnerPlanEndingModal if dismissed with two days left and logs in with one day left', async function () {
  1176. const now = moment();
  1177. const organization = OrganizationFixture({
  1178. slug: 'partner-plan-ending',
  1179. features: ['partner-billing-migration'],
  1180. });
  1181. MockApiClient.addMockResponse({
  1182. method: 'GET',
  1183. url: `/organizations/${organization.slug}/prompts-activity/`,
  1184. body: {
  1185. data: {
  1186. dismissed_ts: moment().subtract(1, 'days').unix(),
  1187. },
  1188. },
  1189. });
  1190. SubscriptionStore.set(
  1191. organization.slug,
  1192. SubscriptionFixture({
  1193. organization,
  1194. contractPeriodEnd: now.add(1, 'day').toString(),
  1195. isTrial: true,
  1196. plan: 'am2_sponsored_team_auf',
  1197. partner: {
  1198. externalId: '123',
  1199. name: 'FOO',
  1200. partnership: {
  1201. id: 'FOO',
  1202. displayName: 'FOO',
  1203. supportNote: '',
  1204. },
  1205. isActive: true,
  1206. },
  1207. })
  1208. );
  1209. render(<GSBanner organization={organization} />, {organization});
  1210. await waitFor(() => expect(openPartnerPlanEndingModal).not.toHaveBeenCalled());
  1211. });
  1212. it('show disabled member header', async function () {
  1213. const organization = OrganizationFixture({
  1214. access: ['org:billing'],
  1215. });
  1216. SubscriptionStore.set(
  1217. organization.slug,
  1218. SubscriptionFixture({
  1219. organization,
  1220. membersDeactivatedFromLimit: 2,
  1221. })
  1222. );
  1223. render(<GSBanner organization={organization} />, {organization});
  1224. expect(
  1225. await screen.findByText(textWithMarkupMatcher(/2 members have been deactivated/i))
  1226. ).toBeInTheDocument();
  1227. });
  1228. it('loads pendo', async function () {
  1229. guideMock.state.currentGuide = false;
  1230. const organization = OrganizationFixture({
  1231. slug: 'forced-trial',
  1232. orgRole: 'admin',
  1233. });
  1234. SubscriptionStore.set(
  1235. organization.slug,
  1236. SubscriptionFixture({
  1237. organization,
  1238. plan: 'am1_business',
  1239. onDemandMaxSpend: 1000,
  1240. totalMembers: 26,
  1241. reservedErrors: 5_000_000,
  1242. reservedTransactions: 10_000_001,
  1243. planDetails: PlanFixture({
  1244. totalPrice: 100_000 * 12,
  1245. billingInterval: 'annual',
  1246. }),
  1247. })
  1248. );
  1249. MockApiClient.addMockResponse({
  1250. method: 'POST',
  1251. url: `/organizations/${organization.slug}/promotions/lorem-ipsum/claim/`,
  1252. body: {},
  1253. });
  1254. const now = moment();
  1255. const promotionData = {
  1256. availablePromotions: [
  1257. {
  1258. endDate: null,
  1259. name: 'Lorem Ipsum',
  1260. slug: 'lorem-ipsum',
  1261. startDate: now.add(-14, 'day'),
  1262. timeLimit: 'null',
  1263. autoOptIn: true,
  1264. },
  1265. ],
  1266. };
  1267. MockApiClient.addMockResponse({
  1268. method: 'POST',
  1269. url: `/organizations/${organization.slug}/promotions/trigger-check/`,
  1270. body: promotionData,
  1271. });
  1272. window.pendo = {
  1273. initialize: jest.fn(),
  1274. };
  1275. MockApiClient.addMockResponse({
  1276. method: 'GET',
  1277. url: `/organizations/${organization.slug}/pendo-details/`,
  1278. body: {
  1279. userDetails: {
  1280. fieldA: 'valueA',
  1281. },
  1282. organizationDetails: {
  1283. fieldB: 'valueB',
  1284. },
  1285. },
  1286. });
  1287. const user = ConfigStore.get('user');
  1288. render(<GSBanner organization={organization} />, {
  1289. organization,
  1290. });
  1291. await waitFor(() => {
  1292. expect(window.pendo.initialize).toHaveBeenCalledWith({
  1293. visitor: {
  1294. id: `${organization.id}.${user.id}`,
  1295. userId: user.id,
  1296. role: 'admin',
  1297. isDarkMode: false,
  1298. fieldA: 'valueA',
  1299. },
  1300. account: expect.objectContaining({
  1301. id: organization.id,
  1302. hasOnDemandSpend: true,
  1303. reservedErrors: 'indigo',
  1304. reservedTransactions: 'violet',
  1305. totalMembers: 'blue',
  1306. arr: 'yellow',
  1307. fieldB: 'valueB',
  1308. plan: 'am1_business',
  1309. }),
  1310. guides: {
  1311. delay: false,
  1312. },
  1313. });
  1314. });
  1315. delete window.pendo;
  1316. });
  1317. it('delay pendo guides if other guides are active', async function () {
  1318. guideMock.state.currentGuide = true;
  1319. const organization = OrganizationFixture();
  1320. SubscriptionStore.set(
  1321. organization.slug,
  1322. SubscriptionFixture({
  1323. organization,
  1324. })
  1325. );
  1326. MockApiClient.addMockResponse({
  1327. method: 'POST',
  1328. url: `/organizations/${organization.slug}/promotions/lorem-ipsum/claim/`,
  1329. body: {},
  1330. });
  1331. window.pendo = {
  1332. initialize: jest.fn(),
  1333. };
  1334. const now = moment();
  1335. const promotionData = {
  1336. availablePromotions: [
  1337. {
  1338. endDate: null,
  1339. name: 'Lorem Ipsum',
  1340. slug: 'lorem-ipsum',
  1341. startDate: now.add(-14, 'day'),
  1342. timeLimit: 'null',
  1343. autoOptIn: true,
  1344. },
  1345. ],
  1346. };
  1347. MockApiClient.addMockResponse({
  1348. method: 'POST',
  1349. url: `/organizations/${organization.slug}/promotions/trigger-check/`,
  1350. body: promotionData,
  1351. });
  1352. MockApiClient.addMockResponse({
  1353. method: 'GET',
  1354. url: `/organizations/${organization.slug}/pendo-details/`,
  1355. body: {
  1356. userDetails: {
  1357. fieldA: 'valueA',
  1358. },
  1359. organizationDetails: {
  1360. fieldB: 'valueB',
  1361. },
  1362. },
  1363. });
  1364. render(<GSBanner organization={organization} />, {
  1365. organization,
  1366. });
  1367. await waitFor(() => {
  1368. expect(window.pendo.initialize).toHaveBeenCalledWith(
  1369. expect.objectContaining({
  1370. guides: {
  1371. delay: true,
  1372. },
  1373. })
  1374. );
  1375. });
  1376. });
  1377. it('forced trial automatically starts', async function () {
  1378. const organization = OrganizationFixture({
  1379. access: ['org:billing'],
  1380. });
  1381. const subscription = SubscriptionFixture({
  1382. plan: 'am1_f',
  1383. organization,
  1384. totalLicenses: 1,
  1385. usedLicenses: 2,
  1386. });
  1387. SubscriptionStore.set(organization.slug, subscription);
  1388. const mockForceTrial = MockApiClient.addMockResponse({
  1389. method: 'POST',
  1390. url: `/organizations/${organization.slug}/over-member-limit-trial/`,
  1391. body: {},
  1392. });
  1393. render(<GSBanner organization={organization} />, {
  1394. organization,
  1395. });
  1396. await waitFor(() => {
  1397. expect(mockForceTrial).toHaveBeenCalledWith(
  1398. `/organizations/${organization.slug}/over-member-limit-trial/`,
  1399. expect.objectContaining({
  1400. method: 'POST',
  1401. })
  1402. );
  1403. });
  1404. expect(openForcedTrialModal).toHaveBeenCalled();
  1405. });
  1406. it('forced trial automatically starts for restricted integration', async function () {
  1407. const organization = OrganizationFixture({
  1408. access: ['org:billing'],
  1409. });
  1410. SubscriptionStore.set(
  1411. organization.slug,
  1412. SubscriptionFixture({
  1413. plan: 'am1_f',
  1414. organization,
  1415. totalLicenses: 1,
  1416. usedLicenses: 1,
  1417. hasRestrictedIntegration: true,
  1418. })
  1419. );
  1420. const mockForceTrial = MockApiClient.addMockResponse({
  1421. method: 'POST',
  1422. url: `/organizations/${organization.slug}/restricted-integration-trial/`,
  1423. body: {},
  1424. });
  1425. render(<GSBanner organization={organization} />, {
  1426. organization,
  1427. });
  1428. await waitFor(() => {
  1429. expect(mockForceTrial).toHaveBeenCalledWith(
  1430. `/organizations/${organization.slug}/restricted-integration-trial/`,
  1431. expect.objectContaining({
  1432. method: 'POST',
  1433. })
  1434. );
  1435. });
  1436. expect(openForcedTrialModal).toHaveBeenCalled();
  1437. });
  1438. it('open the forced trial modal', async function () {
  1439. const now = moment();
  1440. const organization = OrganizationFixture({
  1441. slug: 'forced-trial',
  1442. });
  1443. SubscriptionStore.set(
  1444. organization.slug,
  1445. SubscriptionFixture({
  1446. organization,
  1447. hasDismissedForcedTrialNotice: false,
  1448. plan: 'am1_t',
  1449. trialEnd: now.add(14, 'day').toString(),
  1450. isForcedTrial: true,
  1451. isTrial: true,
  1452. })
  1453. );
  1454. render(<GSBanner organization={organization} />, {
  1455. organization,
  1456. });
  1457. await waitFor(() => expect(openForcedTrialModal).toHaveBeenCalled());
  1458. });
  1459. it('does not open forced trial modal if dismissed', async function () {
  1460. const now = moment();
  1461. const organization = OrganizationFixture({
  1462. slug: 'forced-trial',
  1463. });
  1464. SubscriptionStore.set(
  1465. organization.slug,
  1466. SubscriptionFixture({
  1467. organization,
  1468. hasDismissedForcedTrialNotice: true,
  1469. plan: 'am1_t',
  1470. trialEnd: now.add(14, 'day').toString(),
  1471. isForcedTrial: true,
  1472. isTrial: true,
  1473. })
  1474. );
  1475. render(<GSBanner organization={organization} />, {
  1476. organization,
  1477. });
  1478. await act(tick);
  1479. expect(openForcedTrialModal).not.toHaveBeenCalled();
  1480. });
  1481. it('activates the first available promotion', async function () {
  1482. const now = moment();
  1483. const organization = OrganizationFixture({
  1484. slug: 'promotion-platform',
  1485. });
  1486. const promotionData = {
  1487. availablePromotions: [
  1488. {
  1489. endDate: null,
  1490. name: 'Lorem Ipsum',
  1491. slug: 'lorem-ipsum',
  1492. startDate: now.add(-14, 'day'),
  1493. timeLimit: 'null',
  1494. autoOptIn: true,
  1495. },
  1496. ],
  1497. };
  1498. MockApiClient.addMockResponse({
  1499. method: 'POST',
  1500. url: `/organizations/${organization.slug}/promotions/trigger-check/`,
  1501. body: promotionData,
  1502. });
  1503. const activatePromoEndpoint = MockApiClient.addMockResponse({
  1504. method: 'POST',
  1505. url: `/organizations/${organization.slug}/promotions/lorem-ipsum/claim/`,
  1506. body: {},
  1507. });
  1508. SubscriptionStore.set(
  1509. organization.slug,
  1510. SubscriptionFixture({
  1511. organization,
  1512. plan: 'am1_team',
  1513. })
  1514. );
  1515. render(<GSBanner organization={organization} />, {
  1516. organization,
  1517. });
  1518. await waitFor(() => {
  1519. expect(activatePromoEndpoint).toHaveBeenCalledWith(
  1520. `/organizations/${organization.slug}/promotions/lorem-ipsum/claim/`,
  1521. expect.objectContaining({
  1522. method: 'POST',
  1523. })
  1524. );
  1525. });
  1526. });
  1527. it("doesn't activate non auto-opt-in promos", async function () {
  1528. const now = moment();
  1529. const organization = OrganizationFixture({
  1530. slug: 'promotion-platform',
  1531. });
  1532. const promotionData = {
  1533. availablePromotions: [
  1534. {
  1535. endDate: null,
  1536. name: 'Lorem Ipsum',
  1537. slug: 'lorem-ipsum',
  1538. startDate: now.add(-14, 'day'),
  1539. timeLimit: 'null',
  1540. autoOptIn: false,
  1541. },
  1542. ],
  1543. };
  1544. MockApiClient.addMockResponse({
  1545. method: 'POST',
  1546. url: `/organizations/${organization.slug}/promotions/trigger-check/`,
  1547. body: promotionData,
  1548. });
  1549. const activatePromoEndpoint = MockApiClient.addMockResponse({
  1550. method: 'POST',
  1551. url: `/organizations/${organization.slug}/promotions/lorem-ipsum/claim/`,
  1552. body: {},
  1553. });
  1554. SubscriptionStore.set(
  1555. organization.slug,
  1556. SubscriptionFixture({
  1557. organization,
  1558. plan: 'am1_team',
  1559. })
  1560. );
  1561. render(<GSBanner organization={organization} />, {
  1562. organization,
  1563. });
  1564. await waitFor(() => {
  1565. expect(activatePromoEndpoint).not.toHaveBeenCalledWith(
  1566. `/organizations/${organization.slug}/promotions/lorem-ipsum/claim/`,
  1567. expect.objectContaining({
  1568. method: 'POST',
  1569. })
  1570. );
  1571. });
  1572. });
  1573. it('shows correct past due modal and banner for billing admins', async function () {
  1574. const organization = OrganizationFixture({
  1575. slug: 'past-due',
  1576. access: ['org:billing'],
  1577. });
  1578. const subscription = SubscriptionFixture({
  1579. organization,
  1580. plan: 'am1_team',
  1581. isPastDue: true,
  1582. });
  1583. SubscriptionStore.set(organization.slug, subscription);
  1584. render(<GSBanner organization={organization} />, {organization});
  1585. renderGlobalModal();
  1586. expect(await screen.findByTestId('banner-alert-past-due')).toBeInTheDocument();
  1587. expect(
  1588. screen.getByRole('button', {name: /update payment information/i})
  1589. ).toHaveAttribute(
  1590. 'href',
  1591. `/settings/past-due/billing/details/?referrer=banner-billing-failure`
  1592. );
  1593. expect(await screen.findByTestId('modal-past-due')).toBeInTheDocument();
  1594. expect(screen.getByTestId('modal-continue-button')).toBeInTheDocument();
  1595. expect(
  1596. screen.getByText(
  1597. 'There was an issue with your payment. Update your payment information to ensure uniterrupted access to Sentry.'
  1598. )
  1599. ).toBeInTheDocument();
  1600. await userEvent.click(screen.getByTestId('modal-continue-button'));
  1601. expect(browserHistory.push).toHaveBeenCalledWith(
  1602. `/settings/past-due/billing/details/?referrer=banner-billing-failure`
  1603. );
  1604. });
  1605. it('shows past due modal and banner for non-billing users', async function () {
  1606. const organization = OrganizationFixture({
  1607. slug: 'past-due-4',
  1608. access: ['org:read'],
  1609. });
  1610. const subscription = SubscriptionFixture({
  1611. organization,
  1612. plan: 'am1_team',
  1613. isPastDue: true,
  1614. });
  1615. SubscriptionStore.set(organization.slug, subscription);
  1616. render(<GSBanner organization={organization} />, {organization});
  1617. renderGlobalModal();
  1618. expect(await screen.findByTestId('banner-alert-past-due')).toBeInTheDocument();
  1619. expect(await screen.findByTestId('modal-past-due')).toBeInTheDocument();
  1620. expect(screen.getByTestId('modal-continue-button')).toBeInTheDocument();
  1621. });
  1622. it('does not show past due modal for users without access', async function () {
  1623. const organization = OrganizationFixture({
  1624. slug: 'past-due-4',
  1625. access: [],
  1626. });
  1627. const subscription = SubscriptionFixture({
  1628. organization,
  1629. plan: 'am1_team',
  1630. isPastDue: true,
  1631. });
  1632. SubscriptionStore.set(organization.slug, subscription);
  1633. render(<GSBanner organization={organization} />, {organization});
  1634. renderGlobalModal();
  1635. await act(tick);
  1636. expect(screen.queryByTestId('modal-past-due')).not.toBeInTheDocument();
  1637. expect(screen.queryByTestId('modal-continue-button')).not.toBeInTheDocument();
  1638. });
  1639. it('shows specific banner text just for cron overages', async function () {
  1640. const organization = OrganizationFixture({access: ['org:billing']});
  1641. const subscription = SubscriptionFixture({
  1642. organization,
  1643. plan: 'am2_team',
  1644. categories: {
  1645. errors: MetricHistoryFixture({usageExceeded: false}),
  1646. transactions: MetricHistoryFixture({usageExceeded: false}),
  1647. replays: MetricHistoryFixture({usageExceeded: false}),
  1648. attachments: MetricHistoryFixture({usageExceeded: false}),
  1649. monitorSeats: MetricHistoryFixture({usageExceeded: true}),
  1650. profileDuration: MetricHistoryFixture({usageExceeded: false}),
  1651. },
  1652. canSelfServe: true,
  1653. });
  1654. SubscriptionStore.set(organization.slug, subscription);
  1655. render(<GSBanner organization={organization} />, {organization});
  1656. expect(
  1657. await screen.findByText(
  1658. "We can't enable additional Cron Monitors because you don't have a sufficient on-demand budget."
  1659. )
  1660. ).toBeInTheDocument();
  1661. expect(await screen.findByRole('button', {name: 'Update Plan'})).toBeInTheDocument();
  1662. });
  1663. it('shows specific banner text just for uptime overages', async function () {
  1664. const organization = OrganizationFixture({access: ['org:billing']});
  1665. const subscription = SubscriptionFixture({
  1666. organization,
  1667. plan: 'am2_team',
  1668. categories: {
  1669. errors: MetricHistoryFixture({usageExceeded: false}),
  1670. transactions: MetricHistoryFixture({usageExceeded: false}),
  1671. replays: MetricHistoryFixture({usageExceeded: false}),
  1672. attachments: MetricHistoryFixture({usageExceeded: false}),
  1673. monitorSeats: MetricHistoryFixture({usageExceeded: false}),
  1674. profileDuration: MetricHistoryFixture({usageExceeded: false}),
  1675. uptime: MetricHistoryFixture({usageExceeded: true}),
  1676. },
  1677. canSelfServe: true,
  1678. });
  1679. SubscriptionStore.set(organization.slug, subscription);
  1680. render(<GSBanner organization={organization} />, {organization});
  1681. expect(
  1682. await screen.findByText(
  1683. "We can't enable additional Uptime Monitors because you don't have a sufficient on-demand budget."
  1684. )
  1685. ).toBeInTheDocument();
  1686. expect(
  1687. await screen.findByRole('button', {name: 'Increase Reserved Limits'})
  1688. ).toBeInTheDocument();
  1689. });
  1690. it('does not show past due modal for enterprise orgs', async function () {
  1691. const organization = OrganizationFixture({
  1692. slug: 'past-due-3',
  1693. access: ['org:billing'],
  1694. });
  1695. const subscription = InvoicedSubscriptionFixture({
  1696. organization,
  1697. plan: 'am2_business_ent',
  1698. isPastDue: true,
  1699. canSelfServe: false,
  1700. });
  1701. SubscriptionStore.set(organization.slug, subscription);
  1702. render(<GSBanner organization={organization} />, {organization});
  1703. renderGlobalModal();
  1704. await act(tick);
  1705. expect(screen.queryByTestId('banner-alert-past-due')).not.toBeInTheDocument();
  1706. expect(
  1707. screen.queryByRole('button', {name: /update payment information/i})
  1708. ).not.toBeInTheDocument();
  1709. expect(
  1710. screen.queryByRole('button', {name: /update billing details/i})
  1711. ).not.toBeInTheDocument();
  1712. });
  1713. it('shows overage notification banner for the spans category', async function () {
  1714. const organization = OrganizationFixture();
  1715. const subscription = SubscriptionFixture({
  1716. organization,
  1717. plan: 'am1_t',
  1718. categories: {
  1719. errors: MetricHistoryFixture({usageExceeded: false}),
  1720. transactions: MetricHistoryFixture({usageExceeded: false}),
  1721. replays: MetricHistoryFixture({usageExceeded: false}),
  1722. attachments: MetricHistoryFixture({usageExceeded: false}),
  1723. monitorSeats: MetricHistoryFixture({usageExceeded: false}),
  1724. spans: MetricHistoryFixture({usageExceeded: true}),
  1725. },
  1726. canSelfServe: true,
  1727. });
  1728. SubscriptionStore.set(organization.slug, subscription);
  1729. render(<GSBanner organization={organization} />, {organization});
  1730. expect(await screen.findByTestId('overage-banner-span')).toBeInTheDocument();
  1731. expect(screen.getByText('span')).toBeInTheDocument();
  1732. });
  1733. it('shows overage notification banner for multiple categories including spans', async function () {
  1734. const organization = OrganizationFixture();
  1735. const subscription = SubscriptionFixture({
  1736. organization,
  1737. plan: 'am1_t',
  1738. categories: {
  1739. errors: MetricHistoryFixture({usageExceeded: true}),
  1740. transactions: MetricHistoryFixture({usageExceeded: true}),
  1741. spans: MetricHistoryFixture({usageExceeded: true}),
  1742. replays: MetricHistoryFixture({usageExceeded: false}),
  1743. attachments: MetricHistoryFixture({usageExceeded: true}),
  1744. monitorSeats: MetricHistoryFixture({usageExceeded: true}),
  1745. uptime: MetricHistoryFixture({usageExceeded: true}),
  1746. },
  1747. canSelfServe: true,
  1748. });
  1749. SubscriptionStore.set(organization.slug, subscription);
  1750. render(<GSBanner organization={organization} />, {organization});
  1751. expect(
  1752. await screen.findByTestId(
  1753. 'overage-banner-error-transaction-attachment-monitorSeat-span-uptime'
  1754. )
  1755. ).toBeInTheDocument();
  1756. expect(screen.getByText('span')).toBeInTheDocument();
  1757. });
  1758. it('does not show overage notification banner for spans if overage notifications are disabled', async function () {
  1759. const organization = OrganizationFixture({
  1760. access: ['org:billing'],
  1761. slug: 'another-slug-2',
  1762. });
  1763. const subscription = SubscriptionFixture({
  1764. organization,
  1765. plan: 'am1_team',
  1766. categories: {
  1767. spans: MetricHistoryFixture({usageExceeded: true}),
  1768. },
  1769. canSelfServe: true,
  1770. hasOverageNotificationsDisabled: true,
  1771. });
  1772. SubscriptionStore.set(organization.slug, subscription);
  1773. render(<GSBanner organization={organization} />, {organization});
  1774. await act(tick);
  1775. expect(
  1776. screen.queryByRole('button', {name: /increase reserved limits/i})
  1777. ).not.toBeInTheDocument();
  1778. });
  1779. it('shows overage warning banner for spans', async function () {
  1780. const organization = OrganizationFixture({
  1781. access: ['org:billing'],
  1782. slug: 'another-slug-1',
  1783. });
  1784. const subscription = SubscriptionFixture({
  1785. organization,
  1786. plan: 'am1_team',
  1787. categories: {
  1788. errors: MetricHistoryFixture({sentUsageWarning: false}),
  1789. spans: MetricHistoryFixture({sentUsageWarning: true}),
  1790. replays: MetricHistoryFixture({usageExceeded: false}),
  1791. attachments: MetricHistoryFixture({sentUsageWarning: false}),
  1792. monitorSeats: MetricHistoryFixture({sentUsageWarning: false}),
  1793. },
  1794. canSelfServe: true,
  1795. });
  1796. SubscriptionStore.set(organization.slug, subscription);
  1797. render(<GSBanner organization={organization} />, {organization});
  1798. expect(
  1799. await screen.findByRole('button', {name: /increase reserved limits/i})
  1800. ).toBeInTheDocument();
  1801. });
  1802. it('does not show overage warning banner for spans if on-demand is set', async function () {
  1803. const organization = OrganizationFixture({
  1804. access: ['org:billing'],
  1805. slug: 'another-slug-1',
  1806. });
  1807. const subscription = SubscriptionFixture({
  1808. organization,
  1809. plan: 'am1_team',
  1810. categories: {spans: MetricHistoryFixture({sentUsageWarning: true})},
  1811. canSelfServe: true,
  1812. onDemandMaxSpend: 10,
  1813. });
  1814. SubscriptionStore.set(organization.slug, subscription);
  1815. render(<GSBanner organization={organization} />, {organization});
  1816. await act(tick);
  1817. expect(
  1818. screen.queryByRole('button', {name: /increase reserved limits/i})
  1819. ).not.toBeInTheDocument();
  1820. });
  1821. it('does not show overage notification banner for spans if active product trial', async function () {
  1822. const organization = OrganizationFixture();
  1823. const subscription = SubscriptionFixture({
  1824. organization,
  1825. plan: 'am3_team',
  1826. categories: {spans: MetricHistoryFixture({usageExceeded: true})},
  1827. canSelfServe: true,
  1828. productTrials: [
  1829. {
  1830. category: DataCategory.SPANS,
  1831. isStarted: true,
  1832. reasonCode: 1001,
  1833. startDate: moment().utc().subtract(10, 'days').format(),
  1834. endDate: moment().utc().add(20, 'days').format(),
  1835. },
  1836. ],
  1837. });
  1838. SubscriptionStore.set(organization.slug, subscription);
  1839. const {container} = render(<GSBanner organization={organization} />, {organization});
  1840. await act(tick);
  1841. expect(container).toBeEmptyDOMElement();
  1842. });
  1843. it('shows overage notification banner for spans even if other category is on active trial', async function () {
  1844. const organization = OrganizationFixture();
  1845. const subscription = SubscriptionFixture({
  1846. organization,
  1847. plan: 'am3_team',
  1848. categories: {
  1849. spans: MetricHistoryFixture({usageExceeded: true}),
  1850. replays: MetricHistoryFixture({usageExceeded: true}),
  1851. },
  1852. canSelfServe: true,
  1853. productTrials: [
  1854. {
  1855. category: DataCategory.REPLAYS,
  1856. isStarted: true,
  1857. reasonCode: 1001,
  1858. startDate: moment().utc().subtract(10, 'days').format(),
  1859. endDate: moment().utc().add(20, 'days').format(),
  1860. },
  1861. ],
  1862. });
  1863. SubscriptionStore.set(organization.slug, subscription);
  1864. render(<GSBanner organization={organization} />, {organization});
  1865. expect(await screen.findByTestId('overage-banner-span')).toBeInTheDocument();
  1866. expect(screen.queryByTestId('overage-banner-replay')).not.toBeInTheDocument();
  1867. });
  1868. it('shows overage warning banner for profileDuration', async function () {
  1869. const organization = OrganizationFixture({
  1870. access: ['org:billing'],
  1871. slug: 'another-slug-1',
  1872. });
  1873. const subscription = SubscriptionFixture({
  1874. organization,
  1875. plan: 'am1_team',
  1876. categories: {
  1877. errors: MetricHistoryFixture({sentUsageWarning: false}),
  1878. spans: MetricHistoryFixture({sentUsageWarning: false}),
  1879. profileDuration: MetricHistoryFixture({sentUsageWarning: true}), // Warning sent
  1880. replays: MetricHistoryFixture({usageExceeded: false}),
  1881. attachments: MetricHistoryFixture({sentUsageWarning: false}),
  1882. monitorSeats: MetricHistoryFixture({sentUsageWarning: false}),
  1883. },
  1884. canSelfServe: true,
  1885. });
  1886. SubscriptionStore.set(organization.slug, subscription);
  1887. render(<GSBanner organization={organization} />, {organization});
  1888. expect(
  1889. await screen.findByRole('button', {name: /increase reserved limits/i})
  1890. ).toBeInTheDocument();
  1891. });
  1892. });