Repository: getsentry/sentry
Pull request: feat(insights): Tag mobile vitals module as new
Adds a New Tag to the Mobile Vitals module

Fixes https://github.com/getsentry/sentry/issues/84868

<img width="399" alt="image" src="https://github.com/user-attachments/assets/c0ebdc5e-b4b4-47aa-9006-ddddd538d91d" />


Problem statement:
Add (NEW) badge + description
- Add a (NEW) badge to the Mobile Vitals tab
OR
- Add a blue banner that says "App Starts, Screen Loads & (NEW) Screen Renders have been merged into a cohesive view under Mobile Vitals" with a link

Hints from issue discussion:
Routing to @getsentry/product-owners-insights for [triage](https://develop.sentry.dev/processing-tickets/#3-triage) ⏲️

Commit message: Improve tests and simply tab component

Patch to review:
diff --git a/static/app/views/insights/mobile/screens/settings.ts b/static/app/views/insights/mobile/screens/settings.ts
index 93cb5b9506311e..995e1cfd7af762 100644
--- a/static/app/views/insights/mobile/screens/settings.ts
+++ b/static/app/views/insights/mobile/screens/settings.ts
@@ -1,10 +1,12 @@
 import {t} from 'sentry/locale';
 
 export const MODULE_TITLE = t('Mobile Vitals');
-// TBD, existing screen load modules uses 'screens' already
+
 export const BASE_URL = 'mobile-vitals';
 
-export const MODULE_DESCRIPTION = t("Improve your application's responsiveness.");
+export const MODULE_DESCRIPTION = t(
+  'Get insights into key performance metrics of your mobile app.'
+);
 
 export const MODULE_FEATURE = t('insights-mobile-screens-module');
 
diff --git a/static/app/views/insights/pages/domainViewHeader.spec.tsx b/static/app/views/insights/pages/domainViewHeader.spec.tsx
index 8c659c007300f4..2f3288db1aee00 100644
--- a/static/app/views/insights/pages/domainViewHeader.spec.tsx
+++ b/static/app/views/insights/pages/domainViewHeader.spec.tsx
@@ -15,7 +15,7 @@ const useLocationMock = jest.mocked(useLocation);
 
 describe('DomainViewHeader', function () {
   const organization = OrganizationFixture({
-    features: ['insights-entry-points'],
+    features: ['insights-entry-points', 'insights-mobile-screens-module'],
   });
 
   beforeEach(() => {
@@ -105,4 +105,21 @@ describe('DomainViewHeader', function () {
     expect(screen.queryByRole('tab', {name: 'Overview'})).not.toBeInTheDocument();
     expect(screen.getByRole('tab', {name: 'Network Requests'})).toBeInTheDocument();
   });
+
+  it('renders a new badge only for mobile vitals', () => {
+    render(
+      <DomainViewHeader
+        domainBaseUrl="domainBaseUrl"
+        domainTitle="domainTitle"
+        modules={[ModuleName.HTTP, ModuleName.MOBILE_VITALS]}
+        selectedModule={undefined}
+        hasOverviewPage={false}
+      />
+    );
+    expect(screen.getByRole('tab', {name: 'Mobile Vitals New'})).toBeInTheDocument();
+    expect(screen.getByRole('tab', {name: 'Network Requests'})).toBeInTheDocument();
+    expect(
+      screen.queryByRole('tab', {name: 'Network Requests New'})
+    ).not.toBeInTheDocument();
+  });
 });
diff --git a/static/app/views/insights/pages/domainViewHeader.tsx b/static/app/views/insights/pages/domainViewHeader.tsx
index d3e49ca87888ae..2f06019e15e737 100644
--- a/static/app/views/insights/pages/domainViewHeader.tsx
+++ b/static/app/views/insights/pages/domainViewHeader.tsx
@@ -1,6 +1,7 @@
 import {Fragment} from 'react';
 import styled from '@emotion/styled';
 
+import Tag from 'sentry/components/badge/tag';
 import {Breadcrumbs, type Crumb} from 'sentry/components/breadcrumbs';
 import ButtonBar from 'sentry/components/buttonBar';
 import FeedbackWidgetButton from 'sentry/components/feedback/widget/feedbackWidgetButton';
@@ -9,6 +10,7 @@ import {extractSelectionParameters} from 'sentry/components/organizations/pageFi
 import {TabList} from 'sentry/components/tabs';
 import type {TabListItemProps} from 'sentry/components/tabs/item';
 import {IconBusiness} from 'sentry/icons';
+import {t} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
 import {useLocation} from 'sentry/utils/useLocation';
 import useOrganization from 'sentry/utils/useOrganization';
@@ -18,7 +20,11 @@ import {
   useModuleURLBuilder,
 } from 'sentry/views/insights/common/utils/useModuleURL';
 import {OVERVIEW_PAGE_TITLE} from 'sentry/views/insights/pages/settings';
-import {isModuleEnabled, isModuleVisible} from 'sentry/views/insights/pages/utils';
+import {
+  isModuleConsideredNew,
+  isModuleEnabled,
+  isModuleVisible,
+} from 'sentry/views/insights/pages/utils';
 import type {ModuleName} from 'sentry/views/insights/types';
 
 export type Props = {
@@ -123,19 +129,21 @@ function TabLabel({moduleName}: TabLabelProps) {
   const moduleTitles = useModuleTitles();
   const organization = useOrganization();
   const showBusinessIcon = !isModuleEnabled(moduleName, organization);
-  if (showBusinessIcon) {
+
+  if (showBusinessIcon || isModuleConsideredNew(moduleName)) {
     return (
-      <TabWithIconContainer>
+      <TabContainer>
         {moduleTitles[moduleName]}
-        <IconBusiness />
-      </TabWithIconContainer>
+        {isModuleConsideredNew(moduleName) && <Tag>{t('New')}</Tag>}
+        {showBusinessIcon && <IconBusiness />}
+      </TabContainer>
     );
   }
 
-  return <Fragment>{moduleTitles[moduleName]}</Fragment>;
+  return moduleTitles[moduleName];
 }
 
-const TabWithIconContainer = styled('div')`
+const TabContainer = styled('div')`
   display: inline-flex;
   align-items: center;
   text-align: left;
diff --git a/static/app/views/insights/pages/utils.ts b/static/app/views/insights/pages/utils.ts
index 7f388f626f256c..dd64b954398a29 100644
--- a/static/app/views/insights/pages/utils.ts
+++ b/static/app/views/insights/pages/utils.ts
@@ -4,6 +4,7 @@ import type {DomainView} from 'sentry/views/insights/pages/useFilters';
 import {
   MODULE_FEATURE_MAP,
   MODULE_FEATURE_VISIBLE_MAP,
+  MODULES_CONSIDERED_NEW,
 } from 'sentry/views/insights/settings';
 import type {ModuleName} from 'sentry/views/insights/types';
 
@@ -13,6 +14,9 @@ export const isModuleEnabled = (module: ModuleName, organization: Organization)
 export const isModuleVisible = (module: ModuleName, organization: Organization) =>
   MODULE_FEATURE_VISIBLE_MAP[module].every(f => organization.features.includes(f));
 
+export const isModuleConsideredNew = (module: ModuleName) =>
+  MODULES_CONSIDERED_NEW.has(module);
+
 export const getModuleView = (module: ModuleName): DomainView => {
   if (DOMAIN_VIEW_MODULES.backend.includes(module)) {
     return 'backend';
diff --git a/static/app/views/insights/settings.ts b/static/app/views/insights/settings.ts
index 32503831a9dfa0..16fa41d7adc9b3 100644
--- a/static/app/views/insights/settings.ts
+++ b/static/app/views/insights/settings.ts
@@ -234,3 +234,10 @@ export const MODULE_FEATURE_VISIBLE_MAP: Record<ModuleName, string[]> = {
   [ModuleName.SESSIONS]: ['insights-entry-points', ...SESSIONS_MODULE_VISIBLE_FEATURES],
   [ModuleName.OTHER]: ['insights-entry-points'],
 };
+
+/**
+ * Modules that are considered "new", e.g. used to show a badge on the tab.
+ */
+export const MODULES_CONSIDERED_NEW: Set<ModuleName> = new Set([
+  ModuleName.MOBILE_VITALS,
+]);


Code review comments:
diff_hunk: @@ -123,19 +129,21 @@ function TabLabel({moduleName}: TabLabelProps) {
   const moduleTitles = useModuleTitles();
   const organization = useOrganization();
   const showBusinessIcon = !isModuleEnabled(moduleName, organization);
-  if (showBusinessIcon) {
+
+  if (showBusinessIcon || isModuleConsideredNew(moduleName)) {
     return (
-      <TabWithIconContainer>
+      <TabContainer>
         {moduleTitles[moduleName]}
-        <IconBusiness />
-      </TabWithIconContainer>
+        {isModuleConsideredNew(moduleName) && <Tag>{t('New')}</Tag>}
path: static/app/views/insights/pages/domainViewHeader.tsx
text: @user1:
I think this'd be better using [`<Badge type="new"`](https://sentry.sentry.io/stories/?name=app%2Fcomponents%2Fbadge%2Fbadge.stories.tsx&query=badge) 👀 

@author:
Thanks for pointing out, looks prettier now too :)