Skip to content

Commit

Permalink
ref(billing): Iterate through categories for quota notifs FE (#80037)
Browse files Browse the repository at this point in the history
  • Loading branch information
isabellaenriquez authored Nov 7, 2024
1 parent 08c2cfa commit 9c5bfff
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 114 deletions.
11 changes: 11 additions & 0 deletions static/app/constants/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ export const DATA_CATEGORY_INFO = {
titleName: t('Errors'),
productName: t('Error Monitoring'),
uid: 1,
isBilledCategory: true,
},
[DataCategoryExact.TRANSACTION]: {
name: DataCategoryExact.TRANSACTION,
Expand All @@ -265,6 +266,7 @@ export const DATA_CATEGORY_INFO = {
titleName: t('Transactions'),
productName: t('Performance Monitoring'),
uid: 2,
isBilledCategory: true,
},
[DataCategoryExact.ATTACHMENT]: {
name: DataCategoryExact.ATTACHMENT,
Expand All @@ -274,6 +276,7 @@ export const DATA_CATEGORY_INFO = {
titleName: t('Attachments'),
productName: t('Attachments'),
uid: 4,
isBilledCategory: true,
},
[DataCategoryExact.PROFILE]: {
name: DataCategoryExact.PROFILE,
Expand All @@ -283,6 +286,7 @@ export const DATA_CATEGORY_INFO = {
titleName: t('Profiles'),
productName: t('Continuous Profiling'),
uid: 6,
isBilledCategory: false,
},
[DataCategoryExact.REPLAY]: {
name: DataCategoryExact.REPLAY,
Expand All @@ -292,6 +296,7 @@ export const DATA_CATEGORY_INFO = {
titleName: t('Session Replays'),
productName: t('Session Replay'),
uid: 7,
isBilledCategory: true,
},
[DataCategoryExact.TRANSACTION_PROCESSED]: {
name: DataCategoryExact.TRANSACTION_PROCESSED,
Expand All @@ -301,6 +306,7 @@ export const DATA_CATEGORY_INFO = {
titleName: t('Transactions'),
productName: t('Performance Monitoring'),
uid: 8,
isBilledCategory: false,
},
[DataCategoryExact.TRANSACTION_INDEXED]: {
name: DataCategoryExact.TRANSACTION_INDEXED,
Expand All @@ -310,6 +316,7 @@ export const DATA_CATEGORY_INFO = {
titleName: t('Indexed Transactions'),
productName: t('Performance Monitoring'),
uid: 9,
isBilledCategory: false,
},
[DataCategoryExact.MONITOR]: {
name: DataCategoryExact.MONITOR,
Expand All @@ -319,6 +326,7 @@ export const DATA_CATEGORY_INFO = {
titleName: t('Monitor Check-Ins'),
productName: t('Cron Monitoring'),
uid: 10,
isBilledCategory: false,
},
[DataCategoryExact.SPAN]: {
name: DataCategoryExact.SPAN,
Expand All @@ -328,6 +336,7 @@ export const DATA_CATEGORY_INFO = {
titleName: t('Spans'),
productName: t('Tracing'),
uid: 12,
isBilledCategory: true,
},
[DataCategoryExact.MONITOR_SEAT]: {
name: DataCategoryExact.MONITOR_SEAT,
Expand All @@ -337,6 +346,7 @@ export const DATA_CATEGORY_INFO = {
titleName: t('Cron Monitors'),
productName: t('Cron Monitoring'),
uid: 13,
isBilledCategory: true,
},
[DataCategoryExact.PROFILE_DURATION]: {
name: DataCategoryExact.PROFILE_DURATION,
Expand All @@ -346,6 +356,7 @@ export const DATA_CATEGORY_INFO = {
titleName: t('Profile Hours'),
productName: t('Continuous Profiling'),
uid: 17,
isBilledCategory: false, // TODO(Continuous Profiling GA): make true for launch to show spend notification toggle
},
} as const satisfies Record<DataCategoryExact, DataCategoryInfo>;

Expand Down
1 change: 1 addition & 0 deletions static/app/types/core.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export enum DataCategoryExact {
export interface DataCategoryInfo {
apiName: string;
displayName: string;
isBilledCategory: boolean;
name: DataCategoryExact;
plural: string;
productName: string;
Expand Down
112 changes: 23 additions & 89 deletions static/app/views/settings/account/notifications/fields2.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import {Fragment} from 'react';
import upperFirst from 'lodash/upperFirst';

import type {Field} from 'sentry/components/forms/types';
import ExternalLink from 'sentry/components/links/externalLink';
import QuestionTooltip from 'sentry/components/questionTooltip';
import {DATA_CATEGORY_INFO} from 'sentry/constants';
import {t, tct} from 'sentry/locale';
import {getDocsLinkForEventType} from 'sentry/views/settings/account/notifications/utils';

Expand Down Expand Up @@ -139,6 +141,26 @@ export const NOTIFICATION_SETTING_FIELDS: Record<string, Field> = {
},
};

const CATEGORY_QUOTA_FIELDS = Object.values(DATA_CATEGORY_INFO)
.filter(categoryInfo => categoryInfo.isBilledCategory)
.map(categoryInfo => {
return {
name: 'quota' + upperFirst(categoryInfo.plural),
label: categoryInfo.titleName,
help: tct(
`Receive notifications about your [displayName] quotas. [learnMore:Learn more]`,
{
displayName: categoryInfo.displayName,
learnMore: <ExternalLink href={getDocsLinkForEventType(categoryInfo.name)} />,
}
),
choices: [
['always', t('On')],
['never', t('Off')],
] as const,
};
});

// TODO(isabella): Once spend vis notifs are GA, remove this
// partial field definition for quota sub-categories
export const QUOTA_FIELDS = [
Expand All @@ -151,95 +173,7 @@ export const QUOTA_FIELDS = [
['never', t('100%')],
] as const,
},
{
name: 'quotaErrors',
label: t('Errors'),
help: tct('Receive notifications about your error quotas. [learnMore:Learn more]', {
learnMore: <ExternalLink href={getDocsLinkForEventType('error')} />,
}),
choices: [
['always', t('On')],
['never', t('Off')],
] as const,
},
{
name: 'quotaTransactions',
label: t('Transactions'),
help: tct(
'Receive notifications about your transaction quota. [learnMore:Learn more]',
{
learnMore: <ExternalLink href={getDocsLinkForEventType('transaction')} />,
}
),
choices: [
['always', t('On')],
['never', t('Off')],
] as const,
},
{
name: 'quotaSpans',
label: t('Spans'),
help: tct('Receive notifications about your spans quotas. [learnMore:Learn more]', {
learnMore: <ExternalLink href={getDocsLinkForEventType('span')} />,
}),
choices: [
['always', t('On')],
['never', t('Off')],
] as const,
},
{
name: 'quotaReplays',
label: t('Replays'),
help: tct('Receive notifications about your replay quotas. [learnMore:Learn more]', {
learnMore: <ExternalLink href={getDocsLinkForEventType('replay')} />,
}),
choices: [
['always', t('On')],
['never', t('Off')],
] as const,
},
{
name: 'quotaAttachments',
label: t('Attachments'),
help: tct(
'Receive notifications about your attachment quota. [learnMore:Learn more]',
{
learnMore: <ExternalLink href={getDocsLinkForEventType('attachment')} />,
}
),
choices: [
['always', t('On')],
['never', t('Off')],
] as const,
},
{
name: 'quotaMonitorSeats',
label: t('Cron Monitors'),
help: tct(
'Receive notifications about your cron monitor quotas. [learnMore:Learn more]',
{
learnMore: <ExternalLink href={getDocsLinkForEventType('monitorSeat')} />,
}
),
choices: [
['always', t('On')],
['never', t('Off')],
] as const,
},
{
name: 'quotaProfileDuration',
label: t('Continuous Profiling'),
help: tct(
'Receive notifications about your continuous profiling quota. [learnMore:Learn more]',
{
learnMore: <ExternalLink href={getDocsLinkForEventType('profileDuration')} />,
}
),
choices: [
['always', t('On')],
['never', t('Off')],
] as const,
},
...CATEGORY_QUOTA_FIELDS,
{
name: 'quotaSpendAllocations',
label: (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ describe('NotificationSettingsByType', function () {
expect(projectsMock).toHaveBeenCalledTimes(1);
});

it('renders all the quota subcatories', async function () {
it('renders all the quota subcategories', async function () {
renderComponent({notificationType: 'quota'});

// check for all the quota subcategories
Expand All @@ -161,7 +161,7 @@ describe('NotificationSettingsByType', function () {
).toBeInTheDocument();
expect(screen.getByText('Errors')).toBeInTheDocument();
expect(screen.getByText('Transactions')).toBeInTheDocument();
expect(screen.getByText('Replays')).toBeInTheDocument();
expect(screen.getByText('Session Replays')).toBeInTheDocument();
expect(screen.getByText('Attachments')).toBeInTheDocument();
expect(screen.getByText('Spend Allocations')).toBeInTheDocument();
expect(screen.queryByText('Spans')).not.toBeInTheDocument();
Expand Down Expand Up @@ -330,10 +330,10 @@ describe('NotificationSettingsByType', function () {

expect(screen.getByText('Errors')).toBeInTheDocument();
expect(screen.getByText('Spans')).toBeInTheDocument();
expect(screen.getByText('Replays')).toBeInTheDocument();
expect(screen.getByText('Session Replays')).toBeInTheDocument();
expect(screen.getByText('Attachments')).toBeInTheDocument();
expect(screen.getByText('Spend Allocations')).toBeInTheDocument();
expect(screen.getByText('Continuous Profiling')).toBeInTheDocument();
expect(screen.queryByText('Continuous Profiling')).not.toBeInTheDocument(); // TODO(Continuous Profiling GA): should be in document
expect(screen.queryByText('Transactions')).not.toBeInTheDocument();

const editSettingMock = MockApiClient.addMockResponse({
Expand All @@ -349,7 +349,7 @@ describe('NotificationSettingsByType', function () {
});

// toggle spans quota notifications off
await selectEvent.select(screen.getAllByText('On')[2], 'Off');
await selectEvent.select(screen.getAllByText('On')[4], 'Off');

expect(editSettingMock).toHaveBeenCalledTimes(1);
expect(editSettingMock).toHaveBeenCalledWith(
Expand Down Expand Up @@ -379,11 +379,11 @@ describe('NotificationSettingsByType', function () {

expect(screen.getByText('Errors')).toBeInTheDocument();
expect(screen.getByText('Spans')).toBeInTheDocument();
expect(screen.getByText('Replays')).toBeInTheDocument();
expect(screen.getByText('Session Replays')).toBeInTheDocument();
expect(screen.getByText('Attachments')).toBeInTheDocument();
expect(screen.getByText('Spend Allocations')).toBeInTheDocument();
expect(screen.getByText('Transactions')).toBeInTheDocument();
expect(screen.getByText('Continuous Profiling')).toBeInTheDocument();
expect(screen.queryByText('Continuous Profiling')).not.toBeInTheDocument(); // TODO(Continuous Profiling GA): should be in document
});

it('spend notifications on org with am1 org only', async function () {
Expand All @@ -399,7 +399,7 @@ describe('NotificationSettingsByType', function () {
expect(await screen.getAllByText('Spend Notifications').length).toEqual(2);

expect(screen.getByText('Errors')).toBeInTheDocument();
expect(screen.getByText('Replays')).toBeInTheDocument();
expect(screen.getByText('Session Replays')).toBeInTheDocument();
expect(screen.getByText('Attachments')).toBeInTheDocument();
expect(screen.getByText('Spend Allocations')).toBeInTheDocument();
expect(screen.getByText('Transactions')).toBeInTheDocument();
Expand All @@ -420,10 +420,10 @@ describe('NotificationSettingsByType', function () {

expect(screen.getByText('Errors')).toBeInTheDocument();
expect(screen.getByText('Spans')).toBeInTheDocument();
expect(screen.getByText('Replays')).toBeInTheDocument();
expect(screen.getByText('Session Replays')).toBeInTheDocument();
expect(screen.getByText('Attachments')).toBeInTheDocument();
expect(screen.getByText('Spend Allocations')).toBeInTheDocument();
expect(screen.getByText('Continuous Profiling')).toBeInTheDocument();
expect(screen.queryByText('Continuous Profiling')).not.toBeInTheDocument(); // TODO(Continuous Profiling GA): should be in document
expect(screen.queryByText('Transactions')).not.toBeInTheDocument();

const editSettingMock = MockApiClient.addMockResponse({
Expand All @@ -439,7 +439,7 @@ describe('NotificationSettingsByType', function () {
});

// toggle spans quota notifications off
await selectEvent.select(screen.getAllByText('On')[1], 'Off');
await selectEvent.select(screen.getAllByText('On')[3], 'Off');

expect(editSettingMock).toHaveBeenCalledTimes(1);
expect(editSettingMock).toHaveBeenCalledWith(
Expand Down
22 changes: 8 additions & 14 deletions static/app/views/settings/account/notifications/utils.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {DataCategoryExact} from 'sentry/types/core';
import type {OrganizationSummary} from 'sentry/types/organization';
import type {Project} from 'sentry/types/project';
import {NOTIFICATION_SETTINGS_PATHNAMES} from 'sentry/views/settings/account/notifications/constants';
Expand Down Expand Up @@ -37,29 +38,22 @@ export const groupByOrganization = (
* Returns a link to docs on explaining how to manage quotas for that event type
*/
export function getDocsLinkForEventType(
event:
| 'error'
| 'transaction'
| 'attachment'
| 'replay'
| 'monitorSeat'
| 'span'
| 'profileDuration'
event: DataCategoryExact | string // TODO(isabella): get rid of strings after removing need for backward compatibility on gs
) {
switch (event) {
case 'transaction':
case DataCategoryExact.TRANSACTION || 'transaction':
// For pre-AM3 plans prior to June 11th, 2024
return 'https://docs.sentry.io/pricing/quotas/legacy-manage-transaction-quota/';
case 'span':
case DataCategoryExact.SPAN || 'span':
// For post-AM3 plans after June 11th, 2024
return 'https://docs.sentry.io/pricing/quotas/manage-transaction-quota/';
case 'attachment':
case DataCategoryExact.ATTACHMENT || 'attachment':
return 'https://docs.sentry.io/product/accounts/quotas/manage-attachments-quota/#2-rate-limiting';
case 'replay':
case DataCategoryExact.REPLAY || 'replay':
return 'https://docs.sentry.io/product/session-replay/';
case 'monitorSeat':
case DataCategoryExact.MONITOR_SEAT || 'monitorSeat':
return 'https://docs.sentry.io/product/crons/';
case 'profileDuration':
case DataCategoryExact.PROFILE_DURATION || 'profileDuration':
return 'https://docs.sentry.io/product/explore/profiling/';
default:
return 'https://docs.sentry.io/product/accounts/quotas/manage-event-stream-guide/#common-workflows-for-managing-your-event-stream';
Expand Down

0 comments on commit 9c5bfff

Please sign in to comment.