Skip to content

Commit e77edb2

Browse files
wedamijaclaude
andcommitted
ref(flags): Remove organizations:minute-resolution-sessions feature flag
This feature flag had `default=True` and was always on for all users. Remove the flag registration, all frontend feature checks, and clean up backend tests that unnecessarily wrapped with this feature. The `getSessionsInterval` utility now always uses high-fidelity (sub-hour) resolution for recent data, which was the existing behavior for all users. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 7b31e0a commit e77edb2

File tree

10 files changed

+102
-158
lines changed

10 files changed

+102
-158
lines changed

src/sentry/features/temporary.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,6 @@ def register_temporary_features(manager: FeatureManager) -> None:
175175
manager.add("organizations:mep-use-default-tags", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False)
176176
# Enable flamegraph visualization for MetricKit hang diagnostic stack traces
177177
manager.add("organizations:metrickit-flamegraph", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True)
178-
# Enable Session Stats down to a minute resolution
179-
manager.add("organizations:minute-resolution-sessions", OrganizationFeature, FeatureHandlerStrategy.INTERNAL, default=True, api_expose=True)
180178
# Enables higher limit for alert rules
181179
manager.add("organizations:more-fast-alerts", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False)
182180
manager.add("organizations:more-slow-alerts", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False)

static/app/components/charts/sessionsRequest.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ export class SessionsRequest extends Component<Props, State> {
8282
query,
8383
groupBy,
8484
interval,
85-
organization,
8685
} = this.props;
8786

8887
return {
@@ -96,10 +95,7 @@ export class SessionsRequest extends Component<Props, State> {
9695
end,
9796
interval: interval
9897
? interval
99-
: getSessionsInterval(
100-
{start, end, period: statsPeriod},
101-
{highFidelity: organization.features.includes('minute-resolution-sessions')}
102-
),
98+
: getSessionsInterval({start, end, period: statsPeriod}),
10399
};
104100
}
105101

static/app/components/charts/useSessionsRequest.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,7 @@ export function useSessionsRequest({
4545
end,
4646
interval: interval
4747
? interval
48-
: getSessionsInterval(
49-
{start, end, period: statsPeriod},
50-
{highFidelity: organization.features.includes('minute-resolution-sessions')}
51-
),
48+
: getSessionsInterval({start, end, period: statsPeriod}),
5249
};
5350

5451
const sessionQuery = useApiQuery<SessionApiResponse>(

static/app/utils/sessions.spec.tsx

Lines changed: 38 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -192,80 +192,52 @@ describe('utils/sessions', () => {
192192
});
193193

194194
describe('getSessionsInterval', () => {
195-
describe('with high fidelity', () => {
196-
it('>= 60 days', () => {
197-
expect(getSessionsInterval({period: '60d'}, {highFidelity: true})).toBe('1d');
198-
});
199-
200-
it('>= 30 days', () => {
201-
expect(getSessionsInterval({period: '30d'}, {highFidelity: true})).toBe('4h');
202-
});
203-
204-
it('14 days', () => {
205-
expect(getSessionsInterval({period: '14d'}, {highFidelity: true})).toBe('1h');
206-
});
207-
208-
it('>= 6 hours', () => {
209-
expect(getSessionsInterval({period: '6h'}, {highFidelity: true})).toBe('1h');
210-
});
211-
212-
it('between 6 hours and 30 minutes', () => {
213-
expect(getSessionsInterval({period: '31m'}, {highFidelity: true})).toBe('5m');
214-
});
215-
216-
it('less or equal to 30 minutes', () => {
217-
expect(getSessionsInterval({period: '30m'}, {highFidelity: true})).toBe('1m');
218-
});
219-
220-
it('less or equal to 10 minutes', () => {
221-
expect(
222-
getSessionsInterval(
223-
{start: '2021-10-08T12:00:00Z', end: '2021-10-08T12:05:00.000Z'},
224-
{highFidelity: true}
225-
)
226-
).toBe('10s');
227-
});
195+
it('>= 60 days', () => {
196+
expect(getSessionsInterval({period: '60d'})).toBe('1d');
197+
expect(
198+
getSessionsInterval({
199+
start: '2021-07-19T15:14:23Z',
200+
end: '2021-10-19T15:14:23Z',
201+
})
202+
).toBe('1d');
203+
});
228204

229-
it('ignores high fidelity flag if start is older than 30d', () => {
230-
expect(
231-
getSessionsInterval(
232-
{start: '2017-09-15T02:41:20Z', end: '2017-09-15T02:42:20Z'},
233-
{highFidelity: true}
234-
)
235-
).toBe('1h');
236-
});
205+
it('>= 30 days', () => {
206+
expect(getSessionsInterval({period: '30d'})).toBe('4h');
237207
});
238208

239-
describe('with low fidelity', () => {
240-
it('>= 60 days', () => {
241-
expect(getSessionsInterval({period: '60d'})).toBe('1d');
242-
expect(
243-
getSessionsInterval(
244-
{start: '2021-07-19T15:14:23Z', end: '2021-10-19T15:14:23Z'},
245-
{highFidelity: true}
246-
)
247-
).toBe('1d');
248-
});
209+
it('14 days', () => {
210+
expect(getSessionsInterval({period: '14d'})).toBe('1h');
211+
});
249212

250-
it('>= 30 days', () => {
251-
expect(getSessionsInterval({period: '30d'})).toBe('4h');
252-
});
213+
it('>= 6 hours', () => {
214+
expect(getSessionsInterval({period: '6h'})).toBe('1h');
215+
});
253216

254-
it('14 days', () => {
255-
expect(getSessionsInterval({period: '14d'})).toBe('1h');
256-
});
217+
it('between 6 hours and 30 minutes', () => {
218+
expect(getSessionsInterval({period: '31m'})).toBe('5m');
219+
});
257220

258-
it('>= 6 hours', () => {
259-
expect(getSessionsInterval({period: '6h'})).toBe('1h');
260-
});
221+
it('less or equal to 30 minutes', () => {
222+
expect(getSessionsInterval({period: '30m'})).toBe('1m');
223+
});
261224

262-
it('between 6 hours and 30 minutes', () => {
263-
expect(getSessionsInterval({period: '31m'})).toBe('1h');
264-
});
225+
it('less or equal to 10 minutes', () => {
226+
expect(
227+
getSessionsInterval({
228+
start: '2021-10-08T12:00:00Z',
229+
end: '2021-10-08T12:05:00.000Z',
230+
})
231+
).toBe('10s');
232+
});
265233

266-
it('less or equal to 30 minutes', () => {
267-
expect(getSessionsInterval({period: '30m'})).toBe('1h');
268-
});
234+
it('falls back to 1h if start is older than 30d', () => {
235+
expect(
236+
getSessionsInterval({
237+
start: '2017-09-15T02:41:20Z',
238+
end: '2017-09-15T02:42:20Z',
239+
})
240+
).toBe('1h');
269241
});
270242
});
271243

static/app/utils/sessions.tsx

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -257,20 +257,14 @@ export function initSessionsChart(theme: Theme) {
257257

258258
type GetSessionsIntervalOptions = {
259259
dailyInterval?: boolean;
260-
highFidelity?: boolean;
261260
};
262261

263262
export function getSessionsInterval(
264263
datetimeObj: DateTimeObject,
265-
{highFidelity, dailyInterval}: GetSessionsIntervalOptions = {}
264+
{dailyInterval}: GetSessionsIntervalOptions = {}
266265
) {
267266
const diffInMinutes = getDiffInMinutes(datetimeObj);
268267

269-
if (moment(datetimeObj.start).isSameOrBefore(moment().subtract(30, 'days'))) {
270-
// we cannot use sub-hour session resolution on buckets older than 30 days
271-
highFidelity = false;
272-
}
273-
274268
if (dailyInterval === true && diffInMinutes > TWENTY_FOUR_HOURS) {
275269
return '1d';
276270
}
@@ -287,22 +281,23 @@ export function getSessionsInterval(
287281
return '1h';
288282
}
289283

290-
// limit on backend for sub-hour session resolution is set to six hours
291-
if (highFidelity) {
292-
if (diffInMinutes <= MINUTES_THRESHOLD_TO_DISPLAY_SECONDS) {
293-
// This only works for metrics-based session stats.
294-
// Backend will silently replace with '1m' for session-based stats.
295-
return '10s';
296-
}
284+
// we cannot use sub-hour session resolution on buckets older than 30 days
285+
if (moment(datetimeObj.start).isSameOrBefore(moment().subtract(30, 'days'))) {
286+
return '1h';
287+
}
297288

298-
if (diffInMinutes <= 30) {
299-
return '1m';
300-
}
289+
// limit on backend for sub-hour session resolution is set to six hours
290+
if (diffInMinutes <= MINUTES_THRESHOLD_TO_DISPLAY_SECONDS) {
291+
// This only works for metrics-based session stats.
292+
// Backend will silently replace with '1m' for session-based stats.
293+
return '10s';
294+
}
301295

302-
return '5m';
296+
if (diffInMinutes <= 30) {
297+
return '1m';
303298
}
304299

305-
return '1h';
300+
return '5m';
306301
}
307302

308303
// Sessions API can only round intervals to the closest hour - this is especially problematic when using sub-hour resolution.

static/app/views/projectDetail/charts/projectSessionsAnrRequest.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ export function ProjectSessionsAnrRequest({
5454
const baseParams = {
5555
field: [yAxis, 'count_unique(user)'],
5656
interval: getSessionsInterval(datetime, {
57-
highFidelity: organization.features.includes('minute-resolution-sessions'),
5857
dailyInterval: true,
5958
}),
6059
project: projects[0],

static/app/views/projectDetail/charts/projectSessionsChartRequest.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -200,15 +200,13 @@ class ProjectSessionsChartRequest extends Component<
200200
}
201201

202202
queryParams({shouldFetchWithPrevious = false}): Record<string, any> {
203-
const {selection, query, organization} = this.props;
203+
const {selection, query} = this.props;
204204
const {datetime, projects, environments: environment} = selection;
205205

206206
const baseParams = {
207207
field: this.field,
208208
groupBy: this.isCrashFreeRate ? undefined : 'session.status',
209-
interval: getSessionsInterval(datetime, {
210-
highFidelity: organization.features.includes('minute-resolution-sessions'),
211-
}),
209+
interval: getSessionsInterval(datetime),
212210
project: projects[0],
213211
environment,
214212
query,

static/app/views/releases/list/releasesAdoptionChart.tsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -75,20 +75,16 @@ export function ReleasesAdoptionChart({
7575

7676
const diffInMinutes = getDiffInMinutes(datetimeObj);
7777

78-
// use high fidelity intervals when available
79-
// limit on backend is set to six hour
80-
if (
81-
organization.features.includes('minute-resolution-sessions') &&
82-
diffInMinutes < 360
83-
) {
78+
// limit on backend is set to six hours
79+
if (diffInMinutes < 360) {
8480
return '10m';
8581
}
8682

8783
if (diffInMinutes >= ONE_WEEK) {
8884
return '1d';
8985
}
9086
return '1h';
91-
}, [organization, location]);
87+
}, [location]);
9288

9389
const getReleasesSeries = (response: SessionApiResponse | null) => {
9490
// If there are many releases, display releases with the highest number of sessions

tests/sentry/api/serializers/test_organization.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ def test_simple(self) -> None:
8989
"integrations-ticket-rules",
9090
"integrations-vercel",
9191
"invite-members",
92-
"minute-resolution-sessions",
9392
"open-membership",
9493
"relay",
9594
"session-replay-ui",

tests/snuba/api/endpoints/test_organization_sessions.py

Lines changed: 43 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -307,61 +307,55 @@ def test_no_projects(self) -> None:
307307

308308
@freeze_time(MOCK_DATETIME_PLUS_TEN_MINUTES)
309309
def test_minute_resolution(self) -> None:
310-
with self.feature("organizations:minute-resolution-sessions"):
311-
response = self.do_request(
312-
{
313-
"project": [self.project1.id, self.project2.id],
314-
"statsPeriod": "30m",
315-
"interval": "10m",
316-
"field": ["sum(session)"],
317-
}
318-
)
319-
assert response.status_code == 200, response.content
310+
response = self.do_request(
311+
{
312+
"project": [self.project1.id, self.project2.id],
313+
"statsPeriod": "30m",
314+
"interval": "10m",
315+
"field": ["sum(session)"],
316+
}
317+
)
318+
assert response.status_code == 200, response.content
320319

321-
ten_min = 10 * 60
322-
expected_start = adjust_start(
323-
MOCK_DATETIME.replace(hour=12, minute=0, second=0), ten_min
324-
)
325-
expected_end = adjust_end(MOCK_DATETIME.replace(hour=12, minute=38, second=0), ten_min)
326-
327-
assert result_sorted(response.data) == {
328-
"start": expected_start.strftime(SNUBA_TIME_FORMAT),
329-
"end": expected_end.strftime(SNUBA_TIME_FORMAT),
330-
"query": "",
331-
"intervals": [
332-
*[
333-
MOCK_DATETIME.replace(hour=12, minute=min, second=0).strftime(
334-
SNUBA_TIME_FORMAT
335-
)
336-
for min in [0, 10, 20, 30]
337-
],
338-
],
339-
"groups": [
340-
{
341-
"by": {},
342-
"series": {"sum(session)": [2, 1, 1, 0]},
343-
"totals": {"sum(session)": 4},
344-
}
320+
ten_min = 10 * 60
321+
expected_start = adjust_start(MOCK_DATETIME.replace(hour=12, minute=0, second=0), ten_min)
322+
expected_end = adjust_end(MOCK_DATETIME.replace(hour=12, minute=38, second=0), ten_min)
323+
324+
assert result_sorted(response.data) == {
325+
"start": expected_start.strftime(SNUBA_TIME_FORMAT),
326+
"end": expected_end.strftime(SNUBA_TIME_FORMAT),
327+
"query": "",
328+
"intervals": [
329+
*[
330+
MOCK_DATETIME.replace(hour=12, minute=min, second=0).strftime(SNUBA_TIME_FORMAT)
331+
for min in [0, 10, 20, 30]
345332
],
346-
}
333+
],
334+
"groups": [
335+
{
336+
"by": {},
337+
"series": {"sum(session)": [2, 1, 1, 0]},
338+
"totals": {"sum(session)": 4},
339+
}
340+
],
341+
}
347342

348343
@freeze_time(MOCK_DATETIME_PLUS_TEN_MINUTES)
349344
def test_10s_resolution(self) -> None:
350-
with self.feature("organizations:minute-resolution-sessions"):
351-
response = self.do_request(
352-
{
353-
"project": [self.project1.id],
354-
"statsPeriod": "1m",
355-
"interval": "10s",
356-
"field": ["sum(session)"],
357-
}
358-
)
359-
assert response.status_code == 200, response.content
345+
response = self.do_request(
346+
{
347+
"project": [self.project1.id],
348+
"statsPeriod": "1m",
349+
"interval": "10s",
350+
"field": ["sum(session)"],
351+
}
352+
)
353+
assert response.status_code == 200, response.content
360354

361-
# With the metrics backend, we should get exactly what we asked for,
362-
# 6 intervals with 10 second length. However, since we add both the
363-
# starting and ending interval we get 7 intervals.
364-
assert len(response.data["intervals"]) == 7
355+
# With the metrics backend, we should get exactly what we asked for,
356+
# 6 intervals with 10 second length. However, since we add both the
357+
# starting and ending interval we get 7 intervals.
358+
assert len(response.data["intervals"]) == 7
365359

366360
@freeze_time(MOCK_DATETIME)
367361
def test_filter_projects(self) -> None:

0 commit comments

Comments
 (0)