diff --git a/static/app/views/issueList/overview.spec.tsx b/static/app/views/issueList/overview.spec.tsx index e29641529cbff9..79175b0f2d7171 100644 --- a/static/app/views/issueList/overview.spec.tsx +++ b/static/app/views/issueList/overview.spec.tsx @@ -17,7 +17,11 @@ import {TagStore} from 'sentry/stores/tagStore'; import {localStorageWrapper} from 'sentry/utils/localStorage'; import * as parseLinkHeaderModule from 'sentry/utils/parseLinkHeader'; import IssueListOverview from 'sentry/views/issueList/overview'; -import {DEFAULT_QUERY} from 'sentry/views/issueList/utils'; +import { + DEFAULT_QUERY, + getStoredIssueSort, + IssueSortOptions, +} from 'sentry/views/issueList/utils'; const DEFAULT_LINKS_HEADER = '; rel="previous"; results="false"; cursor="1443575731:0:1", ' + @@ -350,6 +354,32 @@ describe('IssueList', () => { }); }); + describe('sort persistence', () => { + it('does not persist sort to localStorage without the recommended-sort feature', async () => { + render(, {organization, initialRouterConfig}); + + await userEvent.click(await screen.findByRole('button', {name: 'Last Seen'})); + await userEvent.click(screen.getByRole('option', {name: 'Events'})); + + // Writing while the feature is off would leave a stale value that overrides + // the Recommended default once the flag is enabled. + expect(getStoredIssueSort(organization.slug)).toBeNull(); + }); + + it('persists sort to localStorage with the recommended-sort feature', async () => { + const featureOrg = OrganizationFixture({ + ...organization, + features: ['issue-stream-recommended-sort'], + }); + render(, {organization: featureOrg, initialRouterConfig}); + + await userEvent.click(await screen.findByRole('button', {name: 'Recommended'})); + await userEvent.click(screen.getByRole('option', {name: 'Events'})); + + expect(getStoredIssueSort(featureOrg.slug)).toBe(IssueSortOptions.FREQ); + }); + }); + describe('transitionTo', () => { it('pushes to history when query is updated', async () => { MockApiClient.addMockResponse({ diff --git a/static/app/views/issueList/overview.tsx b/static/app/views/issueList/overview.tsx index 3a54176b1011f1..6d4f4816bd19e7 100644 --- a/static/app/views/issueList/overview.tsx +++ b/static/app/views/issueList/overview.tsx @@ -69,6 +69,8 @@ import { DEFAULT_ISSUE_STREAM_SORT, DEFAULT_QUERY, FOR_REVIEW_QUERIES, + getStoredIssueSort, + setStoredIssueSort, isForReviewQuery, IssueSortOptions, Query, @@ -213,10 +215,13 @@ function IssueListOverviewInner({ const query = defined(location.query.query) ? (decodeScalar(location.query.query) ?? '') : initialQuery; - const sort = decodeScalar( - location.query.sort, - DEFAULT_ISSUE_STREAM_SORT - ) as IssueSortOptions; + const hasRecommendedSort = organization.features.includes( + 'issue-stream-recommended-sort' + ); + const defaultSort = hasRecommendedSort + ? (getStoredIssueSort(organization.slug) ?? IssueSortOptions.RECOMMENDED) + : DEFAULT_ISSUE_STREAM_SORT; + const sort = decodeScalar(location.query.sort, defaultSort) as IssueSortOptions; const getGroupStatsPeriod = useCallback((): string => { const currentPeriod = decodeScalar( @@ -248,7 +253,7 @@ function IssueListOverviewInner({ params.start = getUtcDateString(params.start); } - if (sort !== DEFAULT_ISSUE_STREAM_SORT) { + if (sort !== IssueSortOptions.DATE) { params.sort = sort; } @@ -690,6 +695,9 @@ function IssueListOverviewInner({ organization, sort: newSort, }); + if (hasRecommendedSort) { + setStoredIssueSort(organization.slug, newSort as IssueSortOptions); + } transitionTo({sort: newSort}); }; diff --git a/static/app/views/issueList/utils.tsx b/static/app/views/issueList/utils.tsx index f06d0dc67f2bc0..eb8ba44dab7e14 100644 --- a/static/app/views/issueList/utils.tsx +++ b/static/app/views/issueList/utils.tsx @@ -4,6 +4,7 @@ import {t} from 'sentry/locale'; import type {Event} from 'sentry/types/event'; import type {Group} from 'sentry/types/group'; import type {Organization} from 'sentry/types/organization'; +import {localStorageWrapper} from 'sentry/utils/localStorage'; export const DEFAULT_QUERY = 'is:unresolved issue.priority:[high, medium]'; @@ -85,6 +86,24 @@ export const FOR_REVIEW_QUERIES: string[] = [Query.FOR_REVIEW]; export const SAVED_SEARCHES_SIDEBAR_OPEN_LOCALSTORAGE_KEY = 'issue-stream-saved-searches-sidebar-open'; +const ISSUE_STREAM_SORT_LOCALSTORAGE_KEY = 'issue-stream-sort'; + +function makeSortStorageKey(orgSlug: string): string { + return `${ISSUE_STREAM_SORT_LOCALSTORAGE_KEY}:${orgSlug}`; +} + +export function getStoredIssueSort(orgSlug: string): IssueSortOptions | null { + const value = localStorageWrapper.getItem(makeSortStorageKey(orgSlug)); + if (value && Object.values(IssueSortOptions).includes(value as IssueSortOptions)) { + return value as IssueSortOptions; + } + return null; +} + +export function setStoredIssueSort(orgSlug: string, sort: IssueSortOptions): void { + localStorageWrapper.setItem(makeSortStorageKey(orgSlug), sort); +} + export function createIssueLink({ organization, data,