Skip to content

Commit 7374d6e

Browse files
MichaelSun48andrewshie-sentry
authored andcommitted
ref(shared-views): Use queryclient cache instead of state, rewire with new endpoints (#89441)
This PR makes a couple of major refactors to the new Issue Views Navigation component family in preparation for the launch of shared views. The same refactors have **not** been applied to the legacy issue views component since it will be completely deleted with the release of issue view sharing. Here's a high level overview of the changes: * **QueryClient cache is now the single source of truth for starred views state:** Previously, I used a react useState that consumed the views from the QueryClient cache, which was essentially a middleman that provided 0 value. * **All entrypoints to `PUT` `/group-search-views/` have been rewired:** All issue view mutations in the new nav have been rewired to use the new CRUD endpoints created. * **Unsaved Changes no longer persist across navigation:** Clicking away from your view with unsaved changes will now discard those changes automatically. This saves us from a lot of state management and complexity.
1 parent ced92ef commit 7374d6e

20 files changed

+403
-576
lines changed

static/app/views/issueList/issueViews/createIssueViewFromUrl.tsx

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,10 @@ import {
77
decodeList,
88
decodeScalar,
99
} from 'sentry/utils/queryString';
10-
import type {GroupSearchView} from 'sentry/views/issueList/types';
10+
import type {IssueViewParams} from 'sentry/views/issueList/issueViews/issueViews';
1111
import {IssueSortOptions} from 'sentry/views/issueList/utils';
1212

13-
export function createIssueViewFromUrl({
14-
query,
15-
}: {
16-
query: Query;
17-
}): Pick<
18-
GroupSearchView,
19-
'query' | 'querySort' | 'projects' | 'environments' | 'timeFilters'
20-
> {
13+
export function createIssueViewFromUrl({query}: {query: Query}): IssueViewParams {
2114
return {
2215
query: typeof query.query === 'string' ? query.query : '',
2316
querySort:

static/app/views/issueList/issueViews/issueViewsList/issueViewsList.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@ import {
2020
makeFetchGroupSearchViewsKey,
2121
useFetchGroupSearchViews,
2222
} from 'sentry/views/issueList/queries/useFetchGroupSearchViews';
23+
import {makeFetchStarredGroupSearchViewsKey} from 'sentry/views/issueList/queries/useFetchStarredGroupSearchViews';
2324
import {
2425
type GroupSearchView,
2526
GroupSearchViewCreatedBy,
2627
GroupSearchViewSort,
28+
type StarredGroupSearchView,
2729
} from 'sentry/views/issueList/types';
2830

2931
type IssueViewSectionProps = {
@@ -78,13 +80,33 @@ function IssueViewSection({createdBy, limit, cursorQueryParam}: IssueViewSection
7880
view.id === variables.id ? {...view, starred: variables.starred} : view
7981
);
8082
});
83+
if (variables.starred) {
84+
setApiQueryData<StarredGroupSearchView[]>(
85+
queryClient,
86+
makeFetchStarredGroupSearchViewsKey({orgSlug: organization.slug}),
87+
data => [...(data ?? []), variables.view]
88+
);
89+
} else {
90+
setApiQueryData<StarredGroupSearchView[]>(
91+
queryClient,
92+
makeFetchStarredGroupSearchViewsKey({orgSlug: organization.slug}),
93+
data => data?.filter(view => view.id !== variables.id)
94+
);
95+
}
8196
},
8297
onError: (_error, variables) => {
8398
setApiQueryData<GroupSearchView[]>(queryClient, tableQueryKey, data => {
8499
return data?.map(view =>
85100
view.id === variables.id ? {...view, starred: !variables.starred} : view
86101
);
87102
});
103+
if (variables.starred) {
104+
setApiQueryData<StarredGroupSearchView[]>(
105+
queryClient,
106+
makeFetchStarredGroupSearchViewsKey({orgSlug: organization.slug}),
107+
data => data?.filter(view => view.id !== variables.id)
108+
);
109+
}
88110
},
89111
});
90112

static/app/views/issueList/issueViews/useIssueViewUnsavedChanges.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,12 @@ export function useIssueViewUnsavedChanges() {
2424

2525
return {
2626
hasUnsavedChanges,
27+
changedParams: {
28+
query: !isEqual(currentViewData.query, viewFromUrl.query),
29+
querySort: !isEqual(currentViewData.querySort, viewFromUrl.querySort),
30+
projects: !isEqual(currentViewData.projects, viewFromUrl.projects),
31+
environments: !isEqual(currentViewData.environments, viewFromUrl.environments),
32+
timeFilters: !isEqual(currentViewData.timeFilters, viewFromUrl.timeFilters),
33+
},
2734
};
2835
}

static/app/views/issueList/mutations/useCreateGroupSearchView.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import {
77
} from 'sentry/utils/queryClient';
88
import useApi from 'sentry/utils/useApi';
99
import useOrganization from 'sentry/utils/useOrganization';
10-
import {makeFetchGroupSearchViewsKey} from 'sentry/views/issueList/queries/useFetchGroupSearchViews';
11-
import type {GroupSearchView} from 'sentry/views/issueList/types';
10+
import {makeFetchStarredGroupSearchViewsKey} from 'sentry/views/issueList/queries/useFetchStarredGroupSearchViews';
11+
import type {GroupSearchView, StarredGroupSearchView} from 'sentry/views/issueList/types';
1212

1313
interface CreateGroupSearchViewData
1414
extends Partial<
@@ -36,9 +36,9 @@ export function useCreateGroupSearchView(
3636
...options,
3737
onSuccess: (data, variables, context) => {
3838
if (variables.starred) {
39-
setApiQueryData<GroupSearchView[]>(
39+
setApiQueryData<StarredGroupSearchView[]>(
4040
queryClient,
41-
makeFetchGroupSearchViewsKey({
41+
makeFetchStarredGroupSearchViewsKey({
4242
orgSlug: organization.slug,
4343
}),
4444
existingViews => [...(existingViews ?? []), data]
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import {addErrorMessage} from 'sentry/actionCreators/indicator';
2+
import {t} from 'sentry/locale';
3+
import {
4+
setApiQueryData,
5+
useMutation,
6+
type UseMutationOptions,
7+
useQueryClient,
8+
} from 'sentry/utils/queryClient';
9+
import type RequestError from 'sentry/utils/requestError/requestError';
10+
import useApi from 'sentry/utils/useApi';
11+
import useOrganization from 'sentry/utils/useOrganization';
12+
import {makeFetchGroupSearchViewKey} from 'sentry/views/issueList/queries/useFetchGroupSearchView';
13+
import {makeFetchStarredGroupSearchViewsKey} from 'sentry/views/issueList/queries/useFetchStarredGroupSearchViews';
14+
import type {GroupSearchView, StarredGroupSearchView} from 'sentry/views/issueList/types';
15+
16+
type DeleteGroupSearchViewVariables = {
17+
id: string;
18+
};
19+
export const useDeleteGroupSearchView = (
20+
options: Omit<
21+
UseMutationOptions<GroupSearchView, RequestError, DeleteGroupSearchViewVariables>,
22+
'mutationFn'
23+
> = {}
24+
) => {
25+
const api = useApi({persistInFlight: true});
26+
const queryClient = useQueryClient();
27+
const organization = useOrganization();
28+
29+
return useMutation<GroupSearchView, RequestError, DeleteGroupSearchViewVariables>({
30+
...options,
31+
mutationFn: ({id}: DeleteGroupSearchViewVariables) =>
32+
api.requestPromise(
33+
`/organizations/${organization.slug}/group-search-views/${id}/`,
34+
{
35+
method: 'DELETE',
36+
}
37+
),
38+
onSuccess: (data, parameters, context) => {
39+
// Invalidate the view in cache
40+
queryClient.invalidateQueries({
41+
queryKey: makeFetchGroupSearchViewKey({
42+
orgSlug: organization.slug,
43+
id: parameters.id,
44+
}),
45+
});
46+
47+
// Update any matching starred views in cache
48+
setApiQueryData<StarredGroupSearchView[]>(
49+
queryClient,
50+
makeFetchStarredGroupSearchViewsKey({orgSlug: organization.slug}),
51+
oldGroupSearchViews => {
52+
return oldGroupSearchViews?.filter(view => view.id !== parameters.id) ?? [];
53+
}
54+
);
55+
options.onSuccess?.(data, parameters, context);
56+
},
57+
onError: (error, variables, context) => {
58+
addErrorMessage(t('Failed to delete view'));
59+
options.onError?.(error, variables, context);
60+
},
61+
});
62+
};

static/app/views/issueList/mutations/useUpdateGroupSearchView.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import type RequestError from 'sentry/utils/requestError/requestError';
1010
import useApi from 'sentry/utils/useApi';
1111
import useOrganization from 'sentry/utils/useOrganization';
1212
import {makeFetchGroupSearchViewKey} from 'sentry/views/issueList/queries/useFetchGroupSearchView';
13-
import {makeFetchGroupSearchViewsKey} from 'sentry/views/issueList/queries/useFetchGroupSearchViews';
14-
import type {GroupSearchView} from 'sentry/views/issueList/types';
13+
import {makeFetchStarredGroupSearchViewsKey} from 'sentry/views/issueList/queries/useFetchStarredGroupSearchViews';
14+
import type {GroupSearchView, StarredGroupSearchView} from 'sentry/views/issueList/types';
1515

1616
type UpdateGroupSearchViewVariables = Pick<
1717
GroupSearchView,
@@ -47,9 +47,9 @@ export const useUpdateGroupSearchView = (
4747
);
4848

4949
// Update any matching starred views in cache
50-
setApiQueryData<GroupSearchView[]>(
50+
setApiQueryData<StarredGroupSearchView[]>(
5151
queryClient,
52-
makeFetchGroupSearchViewsKey({orgSlug: organization.slug}),
52+
makeFetchStarredGroupSearchViewsKey({orgSlug: organization.slug}),
5353
oldGroupSearchViews => {
5454
return (
5555
oldGroupSearchViews?.map(view => {

static/app/views/issueList/mutations/useUpdateGroupSearchViewStarred.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
import type RequestError from 'sentry/utils/requestError/requestError';
99
import useApi from 'sentry/utils/useApi';
1010
import useOrganization from 'sentry/utils/useOrganization';
11-
import {makeFetchGroupSearchViewsKey} from 'sentry/views/issueList/queries/useFetchGroupSearchViews';
11+
import {makeFetchStarredGroupSearchViewsKey} from 'sentry/views/issueList/queries/useFetchStarredGroupSearchViews';
1212
import type {GroupSearchView} from 'sentry/views/issueList/types';
1313

1414
type UpdateGroupSearchViewStarredVariables = {
@@ -45,7 +45,7 @@ export const useUpdateGroupSearchViewStarred = (
4545
},
4646
onSettled: (...args) => {
4747
queryClient.invalidateQueries({
48-
queryKey: makeFetchGroupSearchViewsKey({orgSlug: organization.slug}),
48+
queryKey: makeFetchStarredGroupSearchViewsKey({orgSlug: organization.slug}),
4949
});
5050
options.onSettled?.(...args);
5151
},

static/app/views/issueList/mutations/useUpdateGroupSearchViewStarredOrder.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import {
1010
import type RequestError from 'sentry/utils/requestError/requestError';
1111
import useApi from 'sentry/utils/useApi';
1212
import {makeFetchGroupSearchViewsKey} from 'sentry/views/issueList/queries/useFetchGroupSearchViews';
13-
import type {GroupSearchView} from 'sentry/views/issueList/types';
13+
import {makeFetchStarredGroupSearchViewsKey} from 'sentry/views/issueList/queries/useFetchStarredGroupSearchViews';
14+
import type {GroupSearchView, StarredGroupSearchView} from 'sentry/views/issueList/types';
1415

1516
type UpdateGroupSearchViewStarredOrderVariables = {
1617
orgSlug: string;
@@ -40,9 +41,9 @@ export const useUpdateGroupSearchViewStarredOrder = () => {
4041
.map(id => groupSearchViews.find(view => parseInt(view.id, 10) === id))
4142
.filter(defined);
4243

43-
setApiQueryData<GroupSearchView[]>(
44+
setApiQueryData<StarredGroupSearchView[]>(
4445
queryClient,
45-
makeFetchGroupSearchViewsKey({orgSlug: parameters.orgSlug}),
46+
makeFetchStarredGroupSearchViewsKey({orgSlug: parameters.orgSlug}),
4647
newViewsOrder
4748
);
4849
},

static/app/views/issueList/mutations/useUpdateGroupSearchViews.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ import {
88
} from 'sentry/utils/queryClient';
99
import type RequestError from 'sentry/utils/requestError/requestError';
1010
import useApi from 'sentry/utils/useApi';
11-
import {makeFetchGroupSearchViewsKey} from 'sentry/views/issueList/queries/useFetchGroupSearchViews';
11+
import {makeFetchStarredGroupSearchViewsKey} from 'sentry/views/issueList/queries/useFetchStarredGroupSearchViews';
1212
import type {
1313
GroupSearchView,
14+
StarredGroupSearchView,
1415
UpdateGroupSearchViewPayload,
1516
} from 'sentry/views/issueList/types';
1617

@@ -36,9 +37,9 @@ export const useUpdateGroupSearchViews = (
3637
data: {views: groupSearchViews},
3738
}),
3839
onSuccess: (groupSearchViews, parameters, context) => {
39-
setApiQueryData<GroupSearchView[]>(
40+
setApiQueryData<StarredGroupSearchView[]>(
4041
queryClient,
41-
makeFetchGroupSearchViewsKey({orgSlug: parameters.orgSlug}),
42+
makeFetchStarredGroupSearchViewsKey({orgSlug: parameters.orgSlug}),
4243
groupSearchViews // Update the cache with the new groupSearchViews
4344
);
4445
options.onSuccess?.(groupSearchViews, parameters, context);

static/app/views/nav/secondary/sections/issues/issueViews/issueViewAddViewButton.tsx

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,22 @@ import LoadingIndicator from 'sentry/components/loadingIndicator';
66
import {IconAdd} from 'sentry/icons';
77
import {t} from 'sentry/locale';
88
import {space} from 'sentry/styles/space';
9+
import {trackAnalytics} from 'sentry/utils/analytics';
910
import normalizeUrl from 'sentry/utils/url/normalizeUrl';
1011
import {useNavigate} from 'sentry/utils/useNavigate';
1112
import useOrganization from 'sentry/utils/useOrganization';
1213
import {
1314
DEFAULT_ENVIRONMENTS,
1415
DEFAULT_TIME_FILTERS,
1516
} from 'sentry/views/issueList/issueViews/issueViews';
16-
import {useUpdateGroupSearchViews} from 'sentry/views/issueList/mutations/useUpdateGroupSearchViews';
17+
import {useCreateGroupSearchView} from 'sentry/views/issueList/mutations/useCreateGroupSearchView';
1718
import {useFetchStarredGroupSearchViews} from 'sentry/views/issueList/queries/useFetchStarredGroupSearchViews';
1819
import {IssueSortOptions} from 'sentry/views/issueList/utils';
1920
import {useNavContext} from 'sentry/views/nav/context';
2021
import useDefaultProject from 'sentry/views/nav/secondary/sections/issues/issueViews/useDefaultProject';
2122
import type {NavLayout} from 'sentry/views/nav/types';
2223

23-
export function IssueViewAddViewButton({baseUrl}: {baseUrl: string}) {
24+
export function IssueViewAddViewButton() {
2425
const navigate = useNavigate();
2526
const organization = useOrganization();
2627

@@ -33,35 +34,31 @@ export function IssueViewAddViewButton({baseUrl}: {baseUrl: string}) {
3334
orgSlug: organization.slug,
3435
});
3536

36-
const {mutate: updateViews} = useUpdateGroupSearchViews({
37-
onSuccess: data => {
38-
if (data?.length) {
39-
navigate(
40-
normalizeUrl({
41-
pathname: `${baseUrl}/views/${data.at(-1)!.id}/`,
42-
})
43-
);
44-
setIsLoading(false);
45-
}
37+
const {mutate: createIssueView} = useCreateGroupSearchView({
38+
onSuccess: (data, variables) => {
39+
setIsLoading(false);
40+
navigate(
41+
normalizeUrl(`/organizations/${organization.slug}/issues/views/${data.id}/`)
42+
);
43+
44+
trackAnalytics('issue_views.created', {
45+
organization,
46+
starred: variables.starred ?? false,
47+
});
4648
},
4749
});
4850

4951
const handleOnAddView = () => {
5052
if (starredGroupSearchViews) {
5153
setIsLoading(true);
52-
updateViews({
53-
groupSearchViews: [
54-
...starredGroupSearchViews,
55-
{
56-
name: 'New View',
57-
query: 'is:unresolved',
58-
querySort: IssueSortOptions.DATE,
59-
projects: defaultProject,
60-
environments: DEFAULT_ENVIRONMENTS,
61-
timeFilters: DEFAULT_TIME_FILTERS,
62-
},
63-
],
64-
orgSlug: organization.slug,
54+
createIssueView({
55+
name: 'New View',
56+
query: 'is:unresolved',
57+
querySort: IssueSortOptions.DATE,
58+
projects: defaultProject,
59+
environments: DEFAULT_ENVIRONMENTS,
60+
timeFilters: DEFAULT_TIME_FILTERS,
61+
starred: true,
6562
});
6663
}
6764
};

0 commit comments

Comments
 (0)