Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit a31483e

Browse files
committedMar 18, 2025·
feat(billing): Add continuous profiling to spend notifications user prefs page
1 parent afe9c5e commit a31483e

File tree

5 files changed

+68
-16
lines changed

5 files changed

+68
-16
lines changed
 

‎src/sentry/features/permanent.py

+3
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ def register_permanent_features(manager: FeatureManager):
108108
"organizations:team-roles": True,
109109
# Enable the uptime monitoring features
110110
"organizations:uptime": True,
111+
# Feature flag for continuous profiling billing-related features.
112+
# Separate from organizations:continuous-profiling feature flag.
113+
"organizations:continuous-profiling-billing": False,
111114
# Signals that the organization supports the on demand metrics prefill.
112115
"organizations:on-demand-metrics-prefill": False,
113116
# Metrics: Enable ingestion and storage of custom metrics. See custom-metrics for UI.

‎static/app/constants/index.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ export const DEFAULT_RELATIVE_PERIODS_PAGE_FILTER = {
256256
};
257257

258258
// https://github.com/getsentry/relay/blob/master/relay-base-schema/src/data_category.rs
259-
export const DATA_CATEGORY_INFO = {
259+
export const DATA_CATEGORY_INFO: Record<DataCategoryExact, DataCategoryInfo> = {
260260
[DataCategoryExact.ERROR]: {
261261
name: DataCategoryExact.ERROR,
262262
apiName: 'error',
@@ -375,7 +375,7 @@ export const DATA_CATEGORY_INFO = {
375375
titleName: t('Profile Hours'),
376376
productName: t('Continuous Profiling'),
377377
uid: 17,
378-
isBilledCategory: false, // TODO(Continuous Profiling GA): make true for launch to show spend notification toggle
378+
isBilledCategory: true,
379379
},
380380
[DataCategoryExact.PROFILE_DURATION_UI]: {
381381
name: DataCategoryExact.PROFILE_DURATION_UI,
@@ -385,7 +385,7 @@ export const DATA_CATEGORY_INFO = {
385385
titleName: t('UI Profile Hours'),
386386
productName: t('UI Profiling'),
387387
uid: 25,
388-
isBilledCategory: false, // TODO(Continuous Profiling GA): make true for launch to show spend notification toggle
388+
isBilledCategory: true,
389389
},
390390
[DataCategoryExact.UPTIME]: {
391391
name: DataCategoryExact.UPTIME,

‎static/app/views/settings/account/notifications/notificationSettingsByType.spec.tsx

+54-12
Original file line numberDiff line numberDiff line change
@@ -318,9 +318,13 @@ describe('NotificationSettingsByType', function () {
318318
});
319319

320320
it('spend notifications on org with am3 with spend visibility notifications', async function () {
321-
const organization = OrganizationFixture();
322-
organization.features.push('spend-visibility-notifications');
323-
organization.features.push('am3-tier');
321+
const organization = OrganizationFixture({
322+
features: [
323+
'spend-visibility-notifications',
324+
'am3-tier',
325+
'continuous-profiling-billing',
326+
],
327+
});
324328
renderComponent({
325329
notificationType: 'quota',
326330
organizations: [organization],
@@ -333,7 +337,8 @@ describe('NotificationSettingsByType', function () {
333337
expect(screen.getByText('Session Replays')).toBeInTheDocument();
334338
expect(screen.getByText('Attachments')).toBeInTheDocument();
335339
expect(screen.getByText('Spend Allocations')).toBeInTheDocument();
336-
expect(screen.queryByText('Continuous Profiling')).not.toBeInTheDocument(); // TODO(Continuous Profiling GA): should be in document
340+
expect(screen.getByText('Profile Hours', {exact: true})).toBeInTheDocument();
341+
expect(screen.getByText('UI Profile Hours', {exact: true})).toBeInTheDocument();
337342
expect(screen.queryByText('Transactions')).not.toBeInTheDocument();
338343

339344
const editSettingMock = MockApiClient.addMockResponse({
@@ -366,9 +371,13 @@ describe('NotificationSettingsByType', function () {
366371
});
367372

368373
it('spend notifications on org with am3 and org without am3', async function () {
369-
const organization = OrganizationFixture();
370-
organization.features.push('spend-visibility-notifications');
371-
organization.features.push('am3-tier');
374+
const organization = OrganizationFixture({
375+
features: [
376+
'spend-visibility-notifications',
377+
'am3-tier',
378+
'continuous-profiling-billing',
379+
],
380+
});
372381
const otherOrganization = OrganizationFixture();
373382
renderComponent({
374383
notificationType: 'quota',
@@ -383,13 +392,15 @@ describe('NotificationSettingsByType', function () {
383392
expect(screen.getByText('Attachments')).toBeInTheDocument();
384393
expect(screen.getByText('Spend Allocations')).toBeInTheDocument();
385394
expect(screen.getByText('Transactions')).toBeInTheDocument();
386-
expect(screen.queryByText('Continuous Profiling')).not.toBeInTheDocument(); // TODO(Continuous Profiling GA): should be in document
395+
expect(screen.getByText('Profile Hours', {exact: true})).toBeInTheDocument();
396+
expect(screen.getByText('UI Profile Hours', {exact: true})).toBeInTheDocument();
387397
});
388398

389399
it('spend notifications on org with am1 org only', async function () {
390400
const organization = OrganizationFixture();
391401
organization.features.push('spend-visibility-notifications');
392402
organization.features.push('am1-tier');
403+
organization.features.push('continuous-profiling-billing');
393404
const otherOrganization = OrganizationFixture();
394405
renderComponent({
395406
notificationType: 'quota',
@@ -403,13 +414,15 @@ describe('NotificationSettingsByType', function () {
403414
expect(screen.getByText('Attachments')).toBeInTheDocument();
404415
expect(screen.getByText('Spend Allocations')).toBeInTheDocument();
405416
expect(screen.getByText('Transactions')).toBeInTheDocument();
406-
expect(screen.queryByText('Continuous Profiling')).not.toBeInTheDocument();
417+
expect(screen.queryByText('Profile Hours', {exact: true})).not.toBeInTheDocument();
418+
expect(screen.queryByText('UI Profile Hours', {exact: true})).not.toBeInTheDocument();
407419
expect(screen.queryByText('Spans')).not.toBeInTheDocument();
408420
});
409421

410422
it('spend notifications on org with am3 without spend visibility notifications', async function () {
411-
const organization = OrganizationFixture();
412-
organization.features.push('am3-tier');
423+
const organization = OrganizationFixture({
424+
features: ['am3-tier', 'continuous-profiling-billing'],
425+
});
413426
renderComponent({
414427
notificationType: 'quota',
415428
organizations: [organization],
@@ -423,7 +436,8 @@ describe('NotificationSettingsByType', function () {
423436
expect(screen.getByText('Session Replays')).toBeInTheDocument();
424437
expect(screen.getByText('Attachments')).toBeInTheDocument();
425438
expect(screen.getByText('Spend Allocations')).toBeInTheDocument();
426-
expect(screen.queryByText('Continuous Profiling')).not.toBeInTheDocument(); // TODO(Continuous Profiling GA): should be in document
439+
expect(screen.getByText('Profile Hours', {exact: true})).toBeInTheDocument();
440+
expect(screen.getByText('UI Profile Hours', {exact: true})).toBeInTheDocument();
427441
expect(screen.queryByText('Transactions')).not.toBeInTheDocument();
428442

429443
const editSettingMock = MockApiClient.addMockResponse({
@@ -454,4 +468,32 @@ describe('NotificationSettingsByType', function () {
454468
})
455469
);
456470
});
471+
472+
it('should not show Profile Hours when continuous-profiling-billing is not enabled', async function () {
473+
const organization = OrganizationFixture({
474+
features: [
475+
'spend-visibility-notifications',
476+
'am3-tier',
477+
// No continuous-profiling-billing feature
478+
],
479+
});
480+
renderComponent({
481+
notificationType: 'quota',
482+
organizations: [organization],
483+
});
484+
485+
expect(await screen.getAllByText('Spend Notifications').length).toBe(2);
486+
487+
// These should be present
488+
expect(screen.getByText('Errors')).toBeInTheDocument();
489+
expect(screen.getByText('Spans')).toBeInTheDocument();
490+
expect(screen.getByText('Session Replays')).toBeInTheDocument();
491+
expect(screen.getByText('Attachments')).toBeInTheDocument();
492+
expect(screen.getByText('Spend Allocations')).toBeInTheDocument();
493+
494+
// These should NOT be present
495+
expect(screen.queryByText('Profile Hours', {exact: true})).not.toBeInTheDocument();
496+
expect(screen.queryByText('UI Profile Hours', {exact: true})).not.toBeInTheDocument();
497+
expect(screen.queryByText('Transactions')).not.toBeInTheDocument();
498+
});
457499
});

‎static/app/views/settings/account/notifications/notificationSettingsByType.tsx

+7-1
Original file line numberDiff line numberDiff line change
@@ -199,9 +199,15 @@ class NotificationSettingsByTypeV2 extends DeprecatedAsyncComponent<Props, State
199199
organization.features?.includes('am2-tier')
200200
);
201201

202+
// Check if any organization has the continuous-profiling-billing feature flag
203+
const hasOrgWithContinuousProfilingBilling = organizations.some(organization =>
204+
organization.features?.includes('continuous-profiling-billing')
205+
);
206+
202207
const excludeTransactions = hasOrgWithAm3 && !hasOrgWithoutAm3;
203208
const includeSpans = hasOrgWithAm3;
204-
const includeProfileDuration = hasOrgWithAm2 || hasOrgWithAm3;
209+
const includeProfileDuration =
210+
(hasOrgWithAm2 || hasOrgWithAm3) && hasOrgWithContinuousProfilingBilling;
205211

206212
// if a quota notification is not disabled, add in our dependent fields
207213
// but do not show the top level controller

‎static/app/views/settings/account/notifications/utils.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export function getDocsLinkForEventType(
5656
case DataCategoryExact.MONITOR_SEAT:
5757
return 'https://docs.sentry.io/product/crons/';
5858
case DataCategoryExact.PROFILE_DURATION:
59+
case DataCategoryExact.PROFILE_DURATION_UI:
5960
return 'https://docs.sentry.io/product/explore/profiling/';
6061
case DataCategoryExact.UPTIME:
6162
return 'https://docs.sentry.io/product/alerts/uptime-monitoring/';

0 commit comments

Comments
 (0)
Please sign in to comment.