From dc61802c58d2b806357c906d478819bab93ba2b8 Mon Sep 17 00:00:00 2001 From: Copilot Date: Thu, 30 Apr 2026 08:55:46 +0700 Subject: [PATCH 1/2] feat(ui): extend self-hosted max pickable days Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../timeRangeSelector/index.spec.tsx | 26 +++++++++ static/app/constants/index.tsx | 1 + .../profiling/hooks/useRelativeDateTime.tsx | 4 +- static/app/utils/useDatePageFilterProps.tsx | 1 + static/app/utils/useMaxPickableDays.spec.tsx | 52 +++++++++++++++++- static/app/utils/useMaxPickableDays.tsx | 53 +++++++++++++++---- .../views/alerts/rules/crons/details.spec.tsx | 21 +++++++- .../app/views/alerts/rules/crons/details.tsx | 4 +- .../views/explore/conversations/layout.tsx | 5 +- .../views/explore/conversations/overview.tsx | 7 +-- .../views/insights/crons/views/overview.tsx | 4 +- .../streamline/eventMissingBanner.spec.tsx | 31 +++++++++++ .../streamline/eventMissingBanner.tsx | 11 ++-- .../issueDetails/streamline/header/header.tsx | 9 ++-- .../useGroupDefaultStatsPeriod.tsx | 9 ++-- 15 files changed, 205 insertions(+), 33 deletions(-) diff --git a/static/app/components/timeRangeSelector/index.spec.tsx b/static/app/components/timeRangeSelector/index.spec.tsx index 342763ff2f4c..88b160676f42 100644 --- a/static/app/components/timeRangeSelector/index.spec.tsx +++ b/static/app/components/timeRangeSelector/index.spec.tsx @@ -254,6 +254,32 @@ describe('TimeRangeSelector', () => { expect(screen.queryByRole('option', {name: 'Last 90 days'})).not.toBeInTheDocument(); }); + it('shows a 180-day default option for self-hosted flagged organizations', async () => { + ConfigStore.set('isSelfHosted', true); + + render(getComponent(), { + organization: OrganizationFixture({ + features: ['open-membership', 'visibility-increased-max-pickable-days'], + }), + }); + + await userEvent.click(screen.getByRole('button', {expanded: false})); + + expect(screen.getByRole('option', {name: 'Last 180 days'})).toBeInTheDocument(); + }); + + it('does not show a 180-day default option on SaaS', async () => { + render(getComponent(), { + organization: OrganizationFixture({ + features: ['open-membership', 'visibility-increased-max-pickable-days'], + }), + }); + + await userEvent.click(screen.getByRole('button', {expanded: false})); + + expect(screen.queryByRole('option', {name: 'Last 180 days'})).not.toBeInTheDocument(); + }); + it('respects maxPickableDays for arbitrary time ranges', async () => { renderComponent({maxPickableDays: 30}); diff --git a/static/app/constants/index.tsx b/static/app/constants/index.tsx index 3cc45e5e12f7..d2860cb00109 100644 --- a/static/app/constants/index.tsx +++ b/static/app/constants/index.tsx @@ -270,6 +270,7 @@ export const DEFAULT_RELATIVE_PERIODS = { '14d': t('Last 14 days'), '30d': t('Last 30 days'), '90d': t('Last 90 days'), + '180d': t('Last 180 days'), }; const DEFAULT_STATS_INFO = { diff --git a/static/app/utils/profiling/hooks/useRelativeDateTime.tsx b/static/app/utils/profiling/hooks/useRelativeDateTime.tsx index 17bf17187b4c..a1cb56bfdb2b 100644 --- a/static/app/utils/profiling/hooks/useRelativeDateTime.tsx +++ b/static/app/utils/profiling/hooks/useRelativeDateTime.tsx @@ -2,6 +2,7 @@ import {useMemo} from 'react'; import {useTimezone} from 'sentry/components/timezoneProvider'; import type {PageFilters} from 'sentry/types/core'; +import {useDefaultMaxPickableDays} from 'sentry/utils/useMaxPickableDays'; const DAY = 24 * 60 * 60 * 1000; @@ -17,13 +18,14 @@ export function useRelativeDateTime({ retentionDays, }: UseRelativeDateTimeOptions) { const timezone = useTimezone(); + const defaultMaxPickableDays = useDefaultMaxPickableDays(); const anchorTime = anchor * 1000; // Make sure to memo this. Otherwise, each re-render will have // a different min/max date time, causing the query to refetch. const maxDateTime = useMemo(() => Date.now(), []); - const minDateTime = maxDateTime - (retentionDays ?? 90) * DAY; + const minDateTime = maxDateTime - (retentionDays ?? defaultMaxPickableDays) * DAY; const beforeTime = anchorTime - relativeDays * DAY; const beforeDateTime = diff --git a/static/app/utils/useDatePageFilterProps.tsx b/static/app/utils/useDatePageFilterProps.tsx index e71d5085518b..cef142e2200c 100644 --- a/static/app/utils/useDatePageFilterProps.tsx +++ b/static/app/utils/useDatePageFilterProps.tsx @@ -24,6 +24,7 @@ export function useDatePageFilterProps({ [14, '14d', t('Last 14 days')], [30, '30d', t('Last 30 days')], [90, '90d', t('Last 90 days')], + [180, '180d', t('Last 180 days')], ]; // find the relative options that should be enabled based on the maxPickableDays diff --git a/static/app/utils/useMaxPickableDays.spec.tsx b/static/app/utils/useMaxPickableDays.spec.tsx index b2abdb03ed48..e4ea55549df5 100644 --- a/static/app/utils/useMaxPickableDays.spec.tsx +++ b/static/app/utils/useMaxPickableDays.spec.tsx @@ -1,12 +1,40 @@ +import {ConfigFixture} from 'sentry-fixture/config'; import {OrganizationFixture} from 'sentry-fixture/organization'; import {renderHookWithProviders} from 'sentry-test/reactTestingLibrary'; +import {ConfigStore} from 'sentry/stores/configStore'; import {DataCategory} from 'sentry/types/core'; -import {useMaxPickableDays} from './useMaxPickableDays'; +import { + getDefaultMaxPickableDays, + INCREASED_MAX_PICKABLE_DAYS, + useMaxPickableDays, +} from './useMaxPickableDays'; describe('useMaxPickableDays', () => { + beforeEach(() => { + ConfigStore.loadInitialData(ConfigFixture()); + }); + + it('returns 180 for self-hosted flagged organizations', () => { + ConfigStore.set('isSelfHosted', true); + + expect( + getDefaultMaxPickableDays( + OrganizationFixture({features: ['visibility-increased-max-pickable-days']}) + ) + ).toBe(INCREASED_MAX_PICKABLE_DAYS); + }); + + it('returns 90 for SaaS organizations even with the flag', () => { + expect( + getDefaultMaxPickableDays( + OrganizationFixture({features: ['visibility-increased-max-pickable-days']}) + ) + ).toBe(90); + }); + it('returns 90/90 for transactions', () => { const {result} = renderHookWithProviders(() => useMaxPickableDays({ @@ -79,6 +107,28 @@ describe('useMaxPickableDays', () => { }); }); + it('returns 180/180 days for trace metrics on self-hosted with the flag', () => { + ConfigStore.set('isSelfHosted', true); + + const {result} = renderHookWithProviders( + () => + useMaxPickableDays({ + dataCategories: [DataCategory.TRACE_METRICS], + }), + { + organization: OrganizationFixture({ + features: ['visibility-increased-max-pickable-days'], + }), + } + ); + + expect(result.current).toEqual({ + defaultPeriod: '24h', + maxPickableDays: 180, + maxUpgradableDays: 180, + }); + }); + it('returns 30/30 days for logs', () => { const {result} = renderHookWithProviders(() => useMaxPickableDays({ diff --git a/static/app/utils/useMaxPickableDays.tsx b/static/app/utils/useMaxPickableDays.tsx index 9ff8d8d23f14..252f0e4bbbd6 100644 --- a/static/app/utils/useMaxPickableDays.tsx +++ b/static/app/utils/useMaxPickableDays.tsx @@ -4,11 +4,16 @@ import {HookOrDefault} from 'sentry/components/hookOrDefault'; import type {DatePageFilterProps} from 'sentry/components/pageFilters/date/datePageFilter'; import {MAX_PICKABLE_DAYS} from 'sentry/constants'; import {t} from 'sentry/locale'; +import {ConfigStore} from 'sentry/stores/configStore'; import {HookStore} from 'sentry/stores/hookStore'; import {DataCategory} from 'sentry/types/core'; import type {Organization} from 'sentry/types/organization'; import {useOrganization} from 'sentry/utils/useOrganization'; +export const INCREASED_MAX_PICKABLE_DAYS = 180; +export const INCREASED_MAX_PICKABLE_DAYS_FEATURE = + 'visibility-increased-max-pickable-days'; + /** * This returns the default max pickable days for the current organization. * @@ -22,9 +27,29 @@ export function useDefaultMaxPickableDays(): number { } function useDefaultMaxPickableDaysImpl() { + const organization = useOrganization({allowNull: true}); + return getDefaultMaxPickableDays(organization); +} + +export function getDefaultMaxPickableDays( + organization: Organization | null | undefined +): number { + if (isIncreasedMaxPickableDaysEnabled(organization)) { + return INCREASED_MAX_PICKABLE_DAYS; + } + return MAX_PICKABLE_DAYS; } +export function isIncreasedMaxPickableDaysEnabled( + organization: Organization | null | undefined +): boolean { + return ( + ConfigStore.get('isSelfHosted') && + organization?.features.includes(INCREASED_MAX_PICKABLE_DAYS_FEATURE) + ); +} + export interface MaxPickableDaysOptions { /** * The maximum number of days the user is allowed to pick on the date page filter @@ -97,17 +122,25 @@ export function getMaxPickableDays( dataCategory: DataCategory, organization: Organization ): MaxPickableDaysOptions { + const defaultMaxPickableDays = getDefaultMaxPickableDays(organization); + const hasIncreasedMaxPickableDays = isIncreasedMaxPickableDaysEnabled(organization); + switch (dataCategory) { case DataCategory.SPANS: case DataCategory.SPANS_INDEXED: { - const maxPickableDays = organization.features.includes( - 'visibility-explore-range-high' - ) - ? MAX_PICKABLE_DAYS - : 30; + let maxPickableDays = 30; + + if (hasIncreasedMaxPickableDays) { + maxPickableDays = defaultMaxPickableDays; + } else if (organization.features.includes('visibility-explore-range-high')) { + maxPickableDays = MAX_PICKABLE_DAYS; + } + return { maxPickableDays, - maxUpgradableDays: MAX_PICKABLE_DAYS, + maxUpgradableDays: hasIncreasedMaxPickableDays + ? defaultMaxPickableDays + : MAX_PICKABLE_DAYS, upsellFooter: SpansUpsellFooter, }; } @@ -115,8 +148,8 @@ export function getMaxPickableDays( case DataCategory.LOG_BYTE: case DataCategory.LOG_ITEM: return { - maxPickableDays: 30, - maxUpgradableDays: 30, + maxPickableDays: hasIncreasedMaxPickableDays ? defaultMaxPickableDays : 30, + maxUpgradableDays: hasIncreasedMaxPickableDays ? defaultMaxPickableDays : 30, defaultPeriod: '24h', }; case DataCategory.PROFILE_CHUNKS: @@ -126,8 +159,8 @@ export function getMaxPickableDays( case DataCategory.TRANSACTIONS: case DataCategory.REPLAYS: return { - maxPickableDays: MAX_PICKABLE_DAYS, - maxUpgradableDays: MAX_PICKABLE_DAYS, + maxPickableDays: defaultMaxPickableDays, + maxUpgradableDays: defaultMaxPickableDays, }; default: throw new Error( diff --git a/static/app/views/alerts/rules/crons/details.spec.tsx b/static/app/views/alerts/rules/crons/details.spec.tsx index eafc90706da3..2f59829e3be9 100644 --- a/static/app/views/alerts/rules/crons/details.spec.tsx +++ b/static/app/views/alerts/rules/crons/details.spec.tsx @@ -3,7 +3,9 @@ import {MonitorFixture} from 'sentry-fixture/monitor'; import {OrganizationFixture} from 'sentry-fixture/organization'; import {ProjectFixture} from 'sentry-fixture/project'; -import {render, screen} from 'sentry-test/reactTestingLibrary'; +import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary'; + +import {ConfigStore} from 'sentry/stores/configStore'; import MonitorDetails from 'sentry/views/alerts/rules/crons/details'; @@ -21,6 +23,7 @@ describe('Monitor Details', () => { beforeEach(() => { MockApiClient.clearMockResponses(); + ConfigStore.set('isSelfHosted', false); MockApiClient.addMockResponse({ url: `/projects/${organization.slug}/${project.slug}/monitors/${monitor.slug}/`, body: {...monitor}, @@ -88,4 +91,20 @@ describe('Monitor Details', () => { ) ).toBeInTheDocument(); }); + + it('shows a 180-day filter option for self-hosted flagged organizations', async () => { + ConfigStore.set('isSelfHosted', true); + + render(, { + organization: OrganizationFixture({ + features: ['visibility-increased-max-pickable-days'], + }), + initialRouterConfig, + }); + + await screen.findByText(monitor.slug, {exact: false}); + await userEvent.click(screen.getByTestId('page-filter-timerange-selector')); + + expect(screen.getByRole('option', {name: 'Last 180 days'})).toBeInTheDocument(); + }); }); diff --git a/static/app/views/alerts/rules/crons/details.tsx b/static/app/views/alerts/rules/crons/details.tsx index fae6e15f0371..c0f634b1ac13 100644 --- a/static/app/views/alerts/rules/crons/details.tsx +++ b/static/app/views/alerts/rules/crons/details.tsx @@ -19,6 +19,7 @@ import {t} from 'sentry/locale'; import {useApiQuery} from 'sentry/utils/queryClient'; import {useApi} from 'sentry/utils/useApi'; import {useLocation} from 'sentry/utils/useLocation'; +import {useDefaultMaxPickableDays} from 'sentry/utils/useMaxPickableDays'; import {useOrganization} from 'sentry/utils/useOrganization'; import {useParams} from 'sentry/utils/useParams'; import {DetailsSidebar} from 'sentry/views/insights/crons/components/detailsSidebar'; @@ -43,6 +44,7 @@ function hasLastCheckIn(monitor: Monitor) { export default function MonitorDetails() { const api = useApi(); const organization = useOrganization(); + const maxPickableDays = useDefaultMaxPickableDays(); const queryClient = useQueryClient(); const params = useParams<{monitorSlug: string; projectId: string}>(); const location = useLocation(); @@ -127,7 +129,7 @@ export default function MonitorDetails() { - + @@ -52,7 +53,7 @@ function ConversationsLayoutContent() { - + diff --git a/static/app/views/explore/conversations/overview.tsx b/static/app/views/explore/conversations/overview.tsx index bd81ee85f8c2..0a9df2c2f2ad 100644 --- a/static/app/views/explore/conversations/overview.tsx +++ b/static/app/views/explore/conversations/overview.tsx @@ -16,6 +16,7 @@ import { import {SearchQueryBuilderProvider} from 'sentry/components/searchQueryBuilder/context'; import {trackAnalytics} from 'sentry/utils/analytics'; import {useDatePageFilterProps} from 'sentry/utils/useDatePageFilterProps'; +import {useDefaultMaxPickableDays} from 'sentry/utils/useMaxPickableDays'; import {useOrganization} from 'sentry/utils/useOrganization'; import {SchemaHintsList} from 'sentry/views/explore/components/schemaHints/schemaHintsList'; import {SchemaHintsSources} from 'sentry/views/explore/components/schemaHints/schemaHintsUtils'; @@ -28,7 +29,6 @@ import {useSpanItemAttributes} from 'sentry/views/explore/contexts/traceItemAttr import {ConversationsTable} from 'sentry/views/explore/conversations/components/conversationsTable'; import {useShowConversationOnboarding} from 'sentry/views/explore/conversations/hooks/useShowConversationOnboarding'; import {ConversationOnboarding} from 'sentry/views/explore/conversations/onboarding'; -import {MAX_PICKABLE_DAYS} from 'sentry/views/explore/conversations/settings'; import {AgentSelector} from 'sentry/views/insights/common/components/agentSelector'; import {useDefaultToAllProjects} from 'sentry/views/insights/common/utils/useDefaultToAllProjects'; import {useTableCursor} from 'sentry/views/insights/pages/agents/hooks/useTableCursor'; @@ -38,9 +38,10 @@ const DISABLE_AGGREGATES: never[] = []; function ConversationsOverviewPage() { const organization = useOrganization(); + const maxPickableDays = useDefaultMaxPickableDays(); const datePageFilterProps = useDatePageFilterProps({ - maxPickableDays: MAX_PICKABLE_DAYS, - maxUpgradableDays: MAX_PICKABLE_DAYS, + maxPickableDays, + maxUpgradableDays: maxPickableDays, }); useDefaultToAllProjects(); const { diff --git a/static/app/views/insights/crons/views/overview.tsx b/static/app/views/insights/crons/views/overview.tsx index 6d0c235e0881..5c91a783c8ec 100644 --- a/static/app/views/insights/crons/views/overview.tsx +++ b/static/app/views/insights/crons/views/overview.tsx @@ -33,6 +33,7 @@ import {selectJsonWithHeaders} from 'sentry/utils/api/apiOptions'; import {decodeList, decodeScalar} from 'sentry/utils/queryString'; import {useRouteAnalyticsEventNames} from 'sentry/utils/routeAnalytics/useRouteAnalyticsEventNames'; import {useRouteAnalyticsParams} from 'sentry/utils/routeAnalytics/useRouteAnalyticsParams'; +import {useDefaultMaxPickableDays} from 'sentry/utils/useMaxPickableDays'; import {useLocation} from 'sentry/utils/useLocation'; import {useNavigate} from 'sentry/utils/useNavigate'; import {useOrganization} from 'sentry/utils/useOrganization'; @@ -54,6 +55,7 @@ const CronsListPageHeader = HookOrDefault({ function CronsOverview() { const organization = useOrganization(); + const maxPickableDays = useDefaultMaxPickableDays(); const navigate = useNavigate(); const location = useLocation(); const {guideVisible} = useCronsUpsertGuideState(); @@ -174,7 +176,7 @@ function CronsOverview() { - + { + beforeEach(() => { + ConfigStore.loadInitialData(ConfigFixture()); + }); + it('renders elements for known event IDs', () => { const initialRouterConfig = { location: { @@ -49,4 +57,27 @@ describe('EventMissingBanner', () => { // Image expect(screen.getByAltText('Compass illustration')).toBeInTheDocument(); }); + + it('uses the self-hosted increased max pickable days for recommended events', () => { + ConfigStore.set('isSelfHosted', true); + + const initialRouterConfig = { + location: { + pathname: '/organizations/org-slug/issues/group-1/events/abc123/', + }, + route: '/organizations/:orgId/issues/:groupId/events/:eventId/', + }; + + render(, { + initialRouterConfig, + organization: OrganizationFixture({ + features: ['visibility-increased-max-pickable-days'], + }), + }); + + expect(screen.getByRole('link', {name: 'View recommended event'})).toHaveAttribute( + 'href', + '/organizations/org-slug/issues/group-1/events/recommended/?statsPeriod=180d' + ); + }); }); diff --git a/static/app/views/issueDetails/streamline/eventMissingBanner.tsx b/static/app/views/issueDetails/streamline/eventMissingBanner.tsx index 62cff2a5465d..25ee86d94b71 100644 --- a/static/app/views/issueDetails/streamline/eventMissingBanner.tsx +++ b/static/app/views/issueDetails/streamline/eventMissingBanner.tsx @@ -5,9 +5,9 @@ import compassImage from 'sentry-images/spot/onboarding-compass.svg'; import {Flex} from '@sentry/scraps/layout'; import {Link} from '@sentry/scraps/link'; -import {MAX_PICKABLE_DAYS} from 'sentry/constants'; import {t, tct} from 'sentry/locale'; import {HookStore} from 'sentry/stores/hookStore'; +import {useDefaultMaxPickableDays} from 'sentry/utils/useMaxPickableDays'; import {useLocation} from 'sentry/utils/useLocation'; import {useOrganization} from 'sentry/utils/useOrganization'; import {useParams} from 'sentry/utils/useParams'; @@ -17,6 +17,7 @@ import {useDefaultIssueEvent} from 'sentry/views/issueDetails/utils'; export function EventMissingBanner() { const location = useLocation(); const organization = useOrganization(); + const defaultMaxPickableDays = useDefaultMaxPickableDays(); const defaultEventId = useDefaultIssueEvent(); const {groupId, eventId: eventIdParam} = useParams<{ eventId: string; @@ -25,9 +26,9 @@ export function EventMissingBanner() { const eventId = eventIdParam ?? defaultEventId; const retentionHook = HookStore.get('react-hook:use-get-max-retention-days')[0]; - const useGetMaxRetentionDays = retentionHook ?? (() => MAX_PICKABLE_DAYS); - const maxRetentionDays = useGetMaxRetentionDays(); - const statsPeriod = maxRetentionDays ? `${maxRetentionDays}d` : '30d'; + const useGetMaxRetentionDays = retentionHook ?? (() => defaultMaxPickableDays); + const maxRetentionDays = useGetMaxRetentionDays() ?? defaultMaxPickableDays; + const statsPeriod = `${maxRetentionDays}d`; const baseUrl = `/organizations/${organization.slug}/issues/${groupId}/events`; const isReservedEventId = RESERVED_EVENT_IDS.has(eventId); @@ -44,7 +45,7 @@ export function EventMissingBanner() { to={{ pathname: `${baseUrl}/recommended/`, query: { - statsPeriod: `${MAX_PICKABLE_DAYS}d`, + statsPeriod, ...(location.query.project ? {project: location.query.project} : {}), }, }} diff --git a/static/app/views/issueDetails/streamline/header/header.tsx b/static/app/views/issueDetails/streamline/header/header.tsx index 823f5a3d5811..88185f375b1a 100644 --- a/static/app/views/issueDetails/streamline/header/header.tsx +++ b/static/app/views/issueDetails/streamline/header/header.tsx @@ -17,7 +17,6 @@ import {useFeedbackSDKIntegration} from 'sentry/components/feedbackButton/useFee import {getBadgeProperties} from 'sentry/components/group/inboxBadges/statusBadge'; import {UnhandledTag} from 'sentry/components/group/inboxBadges/unhandledTag'; import {TourElement} from 'sentry/components/tours/components'; -import {MAX_PICKABLE_DAYS} from 'sentry/constants'; import {t, tct} from 'sentry/locale'; import {HookStore} from 'sentry/stores/hookStore'; import type {Event} from 'sentry/types/event'; @@ -27,6 +26,7 @@ import type {Project} from 'sentry/types/project'; import {getMessage, getTitle} from 'sentry/utils/events'; import {getConfigForIssueType} from 'sentry/utils/issueTypeConfig'; import {useLocation} from 'sentry/utils/useLocation'; +import {useDefaultMaxPickableDays} from 'sentry/utils/useMaxPickableDays'; import {useOrganization} from 'sentry/utils/useOrganization'; import {GroupActions} from 'sentry/views/issueDetails/actions/index'; import {Divider} from 'sentry/views/issueDetails/divider'; @@ -59,15 +59,16 @@ interface GroupHeaderProps { export function StreamlinedGroupHeader({event, group, project}: GroupHeaderProps) { const location = useLocation(); const organization = useOrganization(); + const defaultMaxPickableDays = useDefaultMaxPickableDays(); const {baseUrl} = useGroupDetailsRoute(); const {sort: _sort, ...query} = location.query; const {count: eventCount, userCount} = group; const useGetMaxRetentionDays = HookStore.get('react-hook:use-get-max-retention-days')[0] ?? - (() => MAX_PICKABLE_DAYS); - const maxRetentionDays = useGetMaxRetentionDays(); - const userCountPeriod = maxRetentionDays ? `(${maxRetentionDays}d)` : '(30d)'; + (() => defaultMaxPickableDays); + const maxRetentionDays = useGetMaxRetentionDays() ?? defaultMaxPickableDays; + const userCountPeriod = `(${maxRetentionDays}d)`; const {title: primaryTitle, subtitle} = getTitle(group); const secondaryTitle = getMessage(group); const isComplete = group.status === 'resolved' || group.status === 'ignored'; diff --git a/static/app/views/issueDetails/useGroupDefaultStatsPeriod.tsx b/static/app/views/issueDetails/useGroupDefaultStatsPeriod.tsx index 2dc16f534339..a1d9c94d8559 100644 --- a/static/app/views/issueDetails/useGroupDefaultStatsPeriod.tsx +++ b/static/app/views/issueDetails/useGroupDefaultStatsPeriod.tsx @@ -1,11 +1,11 @@ import moment from 'moment-timezone'; -import {MAX_PICKABLE_DAYS} from 'sentry/constants'; import {HookStore} from 'sentry/stores/hookStore'; import type {Group} from 'sentry/types/group'; import type {Project} from 'sentry/types/project'; import type {getPeriod} from 'sentry/utils/duration/getPeriod'; import {getConfigForIssueType} from 'sentry/utils/issueTypeConfig'; +import {useDefaultMaxPickableDays} from 'sentry/utils/useMaxPickableDays'; function getDaysSinceDateRoundedUp(date: string): number { const dateWithTime = moment(new Date(date)).startOf('day'); @@ -37,10 +37,11 @@ export function useGroupDefaultStatsPeriod( group: Group | undefined | null, project: Project ): UseGroupDefaultStatsPeriodResult { + const defaultMaxPickableDays = useDefaultMaxPickableDays(); const useGetMaxRetentionDays = HookStore.get('react-hook:use-get-max-retention-days')[0] ?? - (() => MAX_PICKABLE_DAYS); - const maxRetentionDays = useGetMaxRetentionDays(); + (() => defaultMaxPickableDays); + const maxRetentionDays = useGetMaxRetentionDays() ?? defaultMaxPickableDays; let isMaxRetention = false; if (!group) { @@ -63,7 +64,7 @@ export function useGroupDefaultStatsPeriod( if (!maxRetentionDays) { isMaxRetention = true; - return {statsPeriod: '30d', isMaxRetention}; + return {statsPeriod: `${defaultMaxPickableDays}d`, isMaxRetention}; } const clampedRetentionDays = Math.min(maxRetentionDays, daysSinceFirstSeen); From a2b4572cedca5740c7951ccb35563325c6555ba9 Mon Sep 17 00:00:00 2001 From: Copilot Date: Thu, 30 Apr 2026 09:12:16 +0700 Subject: [PATCH 2/2] fix(ui): coerce self-hosted max pickable flag to boolean Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- static/app/utils/useMaxPickableDays.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/static/app/utils/useMaxPickableDays.tsx b/static/app/utils/useMaxPickableDays.tsx index 252f0e4bbbd6..8af172261871 100644 --- a/static/app/utils/useMaxPickableDays.tsx +++ b/static/app/utils/useMaxPickableDays.tsx @@ -44,9 +44,9 @@ export function getDefaultMaxPickableDays( export function isIncreasedMaxPickableDaysEnabled( organization: Organization | null | undefined ): boolean { - return ( + return Boolean( ConfigStore.get('isSelfHosted') && - organization?.features.includes(INCREASED_MAX_PICKABLE_DAYS_FEATURE) + organization?.features.includes(INCREASED_MAX_PICKABLE_DAYS_FEATURE) ); }