Skip to content

Commit 9fb87db

Browse files
TkDodogeorge-sentry
authored andcommitted
fix(apiOptions): prevent empty options from being included in queryKey (#112370)
this enables some sort of fuzzy matching for invalidation
1 parent 64f9fb1 commit 9fb87db

File tree

2 files changed

+40
-5
lines changed

2 files changed

+40
-5
lines changed

static/app/utils/api/apiOptions.spec.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,35 @@ describe('apiOptions', () => {
4040
]);
4141
});
4242

43+
it('should not include options in queryKey when all values are undefined', () => {
44+
const options = apiOptions.as<unknown>()('/api-tokens/$tokenId/', {
45+
staleTime: 0,
46+
path: {tokenId: '123'},
47+
query: undefined,
48+
method: undefined,
49+
});
50+
51+
expect(options.queryKey).toEqual([
52+
{infinite: false, version: 'v2'},
53+
'/api-tokens/123/',
54+
]);
55+
});
56+
57+
it('should strip undefined values from options in queryKey', () => {
58+
const options = apiOptions.as<unknown>()('/api-tokens/$tokenId/', {
59+
staleTime: 0,
60+
path: {tokenId: '123'},
61+
query: {cursor: 'abc'},
62+
method: undefined,
63+
});
64+
65+
expect(options.queryKey).toEqual([
66+
{infinite: false, version: 'v2'},
67+
'/api-tokens/123/',
68+
{query: {cursor: 'abc'}},
69+
]);
70+
});
71+
4372
it('should stringify number path params', () => {
4473
const options = apiOptions.as<unknown>()('/api-tokens/$tokenId/', {
4574
staleTime: 0,

static/app/utils/api/apiOptions.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,17 @@ import {parseLinkHeader} from 'sentry/utils/parseLinkHeader';
1616

1717
type KnownApiUrls = KnownGetsentryApiUrls | KnownSentryApiUrls;
1818

19-
type Options = QueryKeyEndpointOptions & {staleTime: number};
19+
type Options = QueryKeyEndpointOptions & {staleTime: number | 'static'};
2020

2121
type PathParamOptions<TApiPath extends string> =
2222
ExtractPathParams<TApiPath> extends never
2323
? {path?: never}
2424
: {path: Record<ExtractPathParams<TApiPath>, string | number> | SkipToken};
2525

26+
function stripUndefinedValues(obj: Record<string, unknown>): Record<string, unknown> {
27+
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined));
28+
}
29+
2630
const selectJson = <TData>(data: ApiResponse<TData>) => data.json;
2731

2832
export const selectJsonWithHeaders = <TData>(
@@ -43,11 +47,12 @@ function _apiOptions<
4347
: [Options & PathParamOptions<TApiPath>]
4448
) {
4549
const url = getApiUrl(path, ...([{path: pathParams}] as OptionalPathParams<TApiPath>));
50+
const strippedOptions = stripUndefinedValues(options);
4651

4752
return queryOptions({
4853
queryKey:
49-
Object.keys(options).length > 0
50-
? ([{infinite: false, version: 'v2'}, url, options] as ApiQueryKey)
54+
Object.keys(strippedOptions).length > 0
55+
? ([{infinite: false, version: 'v2'}, url, strippedOptions] as ApiQueryKey)
5156
: ([{infinite: false, version: 'v2'}, url] as ApiQueryKey),
5257
queryFn: pathParams === skipToken ? skipToken : apiFetch<TActualData>,
5358
enabled: pathParams !== skipToken,
@@ -77,11 +82,12 @@ function _apiOptionsInfinite<
7782
: [Options & PathParamOptions<TApiPath>]
7883
) {
7984
const url = getApiUrl(path, ...([{path: pathParams}] as OptionalPathParams<TApiPath>));
85+
const strippedOptions = stripUndefinedValues(options);
8086

8187
return infiniteQueryOptions({
8288
queryKey:
83-
Object.keys(options).length > 0
84-
? ([{infinite: true, version: 'v2'}, url, options] as InfiniteApiQueryKey)
89+
Object.keys(strippedOptions).length > 0
90+
? ([{infinite: true, version: 'v2'}, url, strippedOptions] as InfiniteApiQueryKey)
8591
: ([{infinite: true, version: 'v2'}, url] as InfiniteApiQueryKey),
8692
queryFn: pathParams === skipToken ? skipToken : apiFetchInfinite<TActualData>,
8793
getPreviousPageParam: parsePageParam('previous'),

0 commit comments

Comments
 (0)