From 6ed64e472691adc8779ffc3a8e927b9f390e48ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Wed, 3 Jun 2026 16:05:12 -0400 Subject: [PATCH 1/5] ref(overlay): import NODE_ENV/defined from leaves to break import cycle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `overlay` and `useHoverOverlay` were pulled into the frontend type-import strongly-connected component (SCC) solely by value-imports from the `sentry/constants` and `sentry/utils` god-barrels: - `NODE_ENV` now comes from the `sentry/constants/env` leaf - `defined` is extracted to a `sentry/utils/defined` leaf (the `sentry/utils` barrel re-exports it, so existing consumers are untouched) With those two modules out of the cycle, `core/tooltip` — one of the most widely-imported core components — and its dependents leave the SCC too. Largest SCC drops 1867 -> 1770. Co-Authored-By: Claude Opus 4.8 --- static/app/components/overlay.tsx | 4 ++-- static/app/utils.tsx | 6 ++---- static/app/utils/defined.tsx | 3 +++ static/app/utils/useHoverOverlay.tsx | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) create mode 100644 static/app/utils/defined.tsx diff --git a/static/app/components/overlay.tsx b/static/app/components/overlay.tsx index 78f37090867db8..94dbb523018b73 100644 --- a/static/app/components/overlay.tsx +++ b/static/app/components/overlay.tsx @@ -6,8 +6,8 @@ import {motion, useIsPresent} from 'framer-motion'; import type {OverlayArrowProps} from 'sentry/components/overlayArrow'; import {OverlayArrow} from 'sentry/components/overlayArrow'; -import {NODE_ENV} from 'sentry/constants'; -import {defined} from 'sentry/utils'; +import {NODE_ENV} from 'sentry/constants/env'; +import {defined} from 'sentry/utils/defined'; import {PanelProvider} from 'sentry/utils/panelProvider'; type OriginPoint = Partial<{x: number; y: number}>; diff --git a/static/app/utils.tsx b/static/app/utils.tsx index 7ef5fb24d8b3df..d67da57276aac2 100644 --- a/static/app/utils.tsx +++ b/static/app/utils.tsx @@ -7,6 +7,8 @@ import { } from 'sentry/utils/fields'; import {appendTagCondition} from 'sentry/utils/queryString'; +export {defined} from 'sentry/utils/defined'; + /** * Replaces slug special chars with a space */ @@ -14,10 +16,6 @@ export function explodeSlug(slug: string): string { return slug.replace(/[-_]+/g, ' ').trim(); } -export function defined(item: T): item is Exclude { - return item !== undefined && item !== null; -} - export function escape(str: string): string { return str .replace(/&/g, '&') diff --git a/static/app/utils/defined.tsx b/static/app/utils/defined.tsx new file mode 100644 index 00000000000000..fffcd14dad6689 --- /dev/null +++ b/static/app/utils/defined.tsx @@ -0,0 +1,3 @@ +export function defined(item: T): item is Exclude { + return item !== undefined && item !== null; +} diff --git a/static/app/utils/useHoverOverlay.tsx b/static/app/utils/useHoverOverlay.tsx index 2e2b74369690dc..072f1574c8c292 100644 --- a/static/app/utils/useHoverOverlay.tsx +++ b/static/app/utils/useHoverOverlay.tsx @@ -16,7 +16,7 @@ import {usePopper} from 'react-popper'; import {useTheme} from '@emotion/react'; import {mergeProps, mergeRefs} from '@react-aria/utils'; -import {NODE_ENV} from 'sentry/constants'; +import {NODE_ENV} from 'sentry/constants/env'; import type {Theme} from 'sentry/utils/theme'; function makeDefaultPopperModifiers(arrowElement: HTMLElement | null, offset: number) { From 31be7662abe194f41eb3a3956f1da0337ffbc957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Thu, 4 Jun 2026 11:19:55 -0400 Subject: [PATCH 2/5] test(overlay): mock NODE_ENV from constants/env leaf --- static/app/utils/useHoverOverlay.timing.spec.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/static/app/utils/useHoverOverlay.timing.spec.tsx b/static/app/utils/useHoverOverlay.timing.spec.tsx index 54d2c9f662bcb9..77fb17bdc6d354 100644 --- a/static/app/utils/useHoverOverlay.timing.spec.tsx +++ b/static/app/utils/useHoverOverlay.timing.spec.tsx @@ -5,8 +5,8 @@ import {act, fireEvent, render, screen} from 'sentry-test/reactTestingLibrary'; // Disable the NODE_ENV === 'test' instant-open bypass for this file so we can // drive the real state machine with fake timers. The rest of the tooltip test // suite keeps the bypass and does not need to be rewritten. -jest.mock('sentry/constants', () => ({ - ...jest.requireActual('sentry/constants'), +jest.mock('sentry/constants/env', () => ({ + ...jest.requireActual('sentry/constants/env'), NODE_ENV: 'production', })); From 4770651d2d3d1b866b4eaa2c8e8013014867170f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Thu, 4 Jun 2026 11:52:13 -0400 Subject: [PATCH 3/5] ref(types): define IntervalPeriod without importing charts/utils getInterval returns string, so IntervalPeriod is equivalent to a plain string. Importing it pulled the foundational types/core module into the frontend import cycle; defining the alias directly removes that edge. --- static/app/types/core.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/static/app/types/core.tsx b/static/app/types/core.tsx index cc897b896f7774..53b75bba3977b9 100644 --- a/static/app/types/core.tsx +++ b/static/app/types/core.tsx @@ -6,8 +6,6 @@ */ import type {MenuListItemProps} from '@sentry/scraps/menuListItem'; -import type {getInterval} from 'sentry/components/charts/utils'; - export type {Scope} from 'sentry/constants/scopes'; export {DataCategory, DataCategoryExact} from 'sentry/types/dataCategory'; export type {DataCategoryInfo} from 'sentry/types/dataCategory'; @@ -79,7 +77,7 @@ export enum Outcome { DROPPED = 'dropped', // this is not a real outcome coming from the server } -export type IntervalPeriod = ReturnType; +export type IntervalPeriod = string; /** * Represents a pinned page filter sentinel value From 35b94a4c921229ed46b7ae160e2c4630ecdd169f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Thu, 4 Jun 2026 14:07:30 -0400 Subject: [PATCH 4/5] ref(types): move IntervalPeriod to organizationStats Rather than hardcoding `IntervalPeriod = string` in the foundational `types/core` module, relocate the type to `organizationStats/types`, where all four of its consumers live. It is purely an organizationStats usage-stats concept and was never used elsewhere. Living there, it can keep `ReturnType` cycle-free, since organizationStats already depends on `charts/utils`. Core sheds the type entirely instead of just swapping the import for a hardcoded `string`. Co-Authored-By: Claude Opus 4.8 --- static/app/types/core.tsx | 2 -- static/app/views/organizationStats/mapSeriesToChart.ts | 4 ++-- static/app/views/organizationStats/types.tsx | 3 +++ static/app/views/organizationStats/usageChart/index.tsx | 3 ++- static/app/views/organizationStats/usageChart/utils.tsx | 3 ++- static/app/views/organizationStats/usageStatsOrg.tsx | 9 ++------- 6 files changed, 11 insertions(+), 13 deletions(-) diff --git a/static/app/types/core.tsx b/static/app/types/core.tsx index 53b75bba3977b9..be9de64632f3d1 100644 --- a/static/app/types/core.tsx +++ b/static/app/types/core.tsx @@ -77,8 +77,6 @@ export enum Outcome { DROPPED = 'dropped', // this is not a real outcome coming from the server } -export type IntervalPeriod = string; - /** * Represents a pinned page filter sentinel value */ diff --git a/static/app/views/organizationStats/mapSeriesToChart.ts b/static/app/views/organizationStats/mapSeriesToChart.ts index 786fdd0da86aaf..6d64f6dd563381 100644 --- a/static/app/views/organizationStats/mapSeriesToChart.ts +++ b/static/app/views/organizationStats/mapSeriesToChart.ts @@ -3,12 +3,12 @@ import startCase from 'lodash/startCase'; import moment from 'moment-timezone'; import type {TooltipSubLabel} from 'sentry/components/charts/components/tooltip'; -import type {DataCategory, IntervalPeriod} from 'sentry/types/core'; +import type {DataCategory} from 'sentry/types/core'; import {Outcome} from 'sentry/types/core'; import {getDateFromMoment} from './usageChart/utils'; import {getReasonGroupName} from './getReasonGroupName'; -import type {UsageSeries, UsageStat} from './types'; +import type {IntervalPeriod, UsageSeries, UsageStat} from './types'; import type {ChartStats} from './usageChart'; import {SeriesTypes} from './usageChart'; import {formatUsageWithUnits, getFormatUsageOptions} from './utils'; diff --git a/static/app/views/organizationStats/types.tsx b/static/app/views/organizationStats/types.tsx index 621032e14a029d..30247f52fc48e5 100644 --- a/static/app/views/organizationStats/types.tsx +++ b/static/app/views/organizationStats/types.tsx @@ -1,5 +1,8 @@ +import type {getInterval} from 'sentry/components/charts/utils'; import type {SeriesApi} from 'sentry/types/organization'; +export type IntervalPeriod = ReturnType; + /** * Raw response from API endpoint */ diff --git a/static/app/views/organizationStats/usageChart/index.tsx b/static/app/views/organizationStats/usageChart/index.tsx index 2c2deb5ae15889..ccf9779c9a0eda 100644 --- a/static/app/views/organizationStats/usageChart/index.tsx +++ b/static/app/views/organizationStats/usageChart/index.tsx @@ -14,10 +14,11 @@ import {Placeholder} from 'sentry/components/placeholder'; import {DATA_CATEGORY_INFO} from 'sentry/constants'; import {IconWarning} from 'sentry/icons'; import {t} from 'sentry/locale'; -import type {DataCategory, IntervalPeriod, SelectValue} from 'sentry/types/core'; +import type {DataCategory, SelectValue} from 'sentry/types/core'; import {parsePeriodToHours} from 'sentry/utils/duration/parsePeriodToHours'; import {statsPeriodToDays} from 'sentry/utils/duration/statsPeriodToDays'; import type {Theme} from 'sentry/utils/theme'; +import type {IntervalPeriod} from 'sentry/views/organizationStats/types'; import {formatUsageWithUnits} from 'sentry/views/organizationStats/utils'; import {getTooltipFormatter, getXAxisDates, getXAxisLabelVisibility} from './utils'; diff --git a/static/app/views/organizationStats/usageChart/utils.tsx b/static/app/views/organizationStats/usageChart/utils.tsx index a06b2a474fb305..31f45ca85bd027 100644 --- a/static/app/views/organizationStats/usageChart/utils.tsx +++ b/static/app/views/organizationStats/usageChart/utils.tsx @@ -1,9 +1,10 @@ import moment from 'moment-timezone'; import {parseStatsPeriod} from 'sentry/components/pageFilters/parse'; -import type {DataCategory, IntervalPeriod} from 'sentry/types/core'; +import type {DataCategory} from 'sentry/types/core'; import {shouldUse24Hours} from 'sentry/utils/dates'; import {parsePeriodToHours} from 'sentry/utils/duration/parsePeriodToHours'; +import type {IntervalPeriod} from 'sentry/views/organizationStats/types'; import {formatUsageWithUnits} from 'sentry/views/organizationStats/utils'; /** diff --git a/static/app/views/organizationStats/usageStatsOrg.tsx b/static/app/views/organizationStats/usageStatsOrg.tsx index 5622963011883d..59e102d8df4ed4 100644 --- a/static/app/views/organizationStats/usageStatsOrg.tsx +++ b/static/app/views/organizationStats/usageStatsOrg.tsx @@ -20,12 +20,7 @@ import {ScoreCard} from 'sentry/components/scoreCard'; import {DEFAULT_STATS_PERIOD} from 'sentry/constants'; import {IconSettings} from 'sentry/icons'; import {t, tct} from 'sentry/locale'; -import type { - DataCategory, - DataCategoryExact, - DataCategoryInfo, - IntervalPeriod, -} from 'sentry/types/core'; +import type {DataCategory, DataCategoryExact, DataCategoryInfo} from 'sentry/types/core'; import type {Organization} from 'sentry/types/organization'; import {trackAnalytics} from 'sentry/utils/analytics'; import {getApiUrl} from 'sentry/utils/api/getApiUrl'; @@ -45,7 +40,7 @@ import { getTooltipFormatter, } from './usageChart/utils'; import {mapSeriesToChart} from './mapSeriesToChart'; -import type {UsageSeries} from './types'; +import type {IntervalPeriod, UsageSeries} from './types'; import type {ChartStats, UsageChartProps} from './usageChart'; import { CHART_OPTIONS_DATA_TRANSFORM, From 0696f39e549edc5f9b538d910fe4af2a03d4714e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Thu, 4 Jun 2026 18:55:35 -0400 Subject: [PATCH 5/5] ref(types): drop stray defined re-export from utils barrel --- static/app/utils.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/static/app/utils.tsx b/static/app/utils.tsx index a1a42c3b5546a9..1b2b375f0da691 100644 --- a/static/app/utils.tsx +++ b/static/app/utils.tsx @@ -1,5 +1,3 @@ -export {defined} from 'sentry/utils/defined'; - /** * Replaces slug special chars with a space */