Skip to content

Commit a3db9c1

Browse files
authored
feat(explore): Add hooks to get the explore query options (#76180)
These hooks will fetch the current state of the user specified options.
1 parent 73d18f4 commit a3db9c1

8 files changed

+429
-0
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import {createMemoryHistory, Route, Router, RouterContext} from 'react-router';
2+
3+
import {act, render} from 'sentry-test/reactTestingLibrary';
4+
5+
import {useResultMode} from 'sentry/views/explore/hooks/useResultsMode';
6+
import {RouteContext} from 'sentry/views/routeContext';
7+
8+
describe('useResultMode', function () {
9+
it('allows changing results mode', function () {
10+
let resultMode, setResultMode;
11+
12+
function TestPage() {
13+
[resultMode, setResultMode] = useResultMode();
14+
return null;
15+
}
16+
17+
const memoryHistory = createMemoryHistory();
18+
19+
render(
20+
<Router
21+
history={memoryHistory}
22+
render={props => {
23+
return (
24+
<RouteContext.Provider value={props}>
25+
<RouterContext {...props} />
26+
</RouteContext.Provider>
27+
);
28+
}}
29+
>
30+
<Route path="/" component={TestPage} />
31+
</Router>
32+
);
33+
34+
expect(resultMode).toEqual('samples'); // default
35+
36+
act(() => setResultMode('aggregate'));
37+
expect(resultMode).toEqual('aggregate');
38+
39+
act(() => setResultMode('samples'));
40+
expect(resultMode).toEqual('samples');
41+
});
42+
});
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import {useCallback, useMemo} from 'react';
2+
import type {Location} from 'history';
3+
4+
import {decodeScalar} from 'sentry/utils/queryString';
5+
import {useLocation} from 'sentry/utils/useLocation';
6+
import {useNavigate} from 'sentry/utils/useNavigate';
7+
8+
interface Options {
9+
location: Location;
10+
navigate: ReturnType<typeof useNavigate>;
11+
}
12+
13+
export type ResultMode = 'samples' | 'aggregate';
14+
15+
export function useResultMode(): [ResultMode, (newMode: ResultMode) => void] {
16+
const location = useLocation();
17+
const navigate = useNavigate();
18+
const options = {location, navigate};
19+
20+
return useResultModeImpl(options);
21+
}
22+
23+
function useResultModeImpl({
24+
location,
25+
navigate,
26+
}: Options): [ResultMode, (newMode: ResultMode) => void] {
27+
const resultMode: ResultMode = useMemo(() => {
28+
const rawMode = decodeScalar(location.query.mode);
29+
if (rawMode === 'aggregate') {
30+
return 'aggregate' as const;
31+
}
32+
return 'samples' as const;
33+
}, [location.query.mode]);
34+
35+
const setResultMode = useCallback(
36+
(newMode: ResultMode) => {
37+
navigate({
38+
...location,
39+
query: {
40+
...location.query,
41+
mode: newMode,
42+
},
43+
});
44+
},
45+
[location, navigate]
46+
);
47+
48+
return [resultMode, setResultMode];
49+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import {createMemoryHistory, Route, Router, RouterContext} from 'react-router';
2+
3+
import {act, render} from 'sentry-test/reactTestingLibrary';
4+
5+
import {useSampleFields} from 'sentry/views/explore/hooks/useSampleFields';
6+
import {RouteContext} from 'sentry/views/routeContext';
7+
8+
describe('useSampleFields', function () {
9+
it('allows changing sample fields', function () {
10+
let sampleFields, setSampleFields;
11+
12+
function TestPage() {
13+
[sampleFields, setSampleFields] = useSampleFields();
14+
return null;
15+
}
16+
17+
const memoryHistory = createMemoryHistory();
18+
19+
render(
20+
<Router
21+
history={memoryHistory}
22+
render={props => {
23+
return (
24+
<RouteContext.Provider value={props}>
25+
<RouterContext {...props} />
26+
</RouteContext.Provider>
27+
);
28+
}}
29+
>
30+
<Route path="/" component={TestPage} />
31+
</Router>
32+
);
33+
34+
expect(sampleFields).toEqual([
35+
'project',
36+
'id',
37+
'span.op',
38+
'span.description',
39+
'span.duration',
40+
'timestamp',
41+
]); // default
42+
43+
act(() => setSampleFields(['foo', 'bar']));
44+
expect(sampleFields).toEqual(['foo', 'bar']);
45+
46+
act(() => setSampleFields([]));
47+
expect(sampleFields).toEqual([
48+
'project',
49+
'id',
50+
'span.op',
51+
'span.description',
52+
'span.duration',
53+
'timestamp',
54+
]); // default
55+
});
56+
});
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import {useCallback, useMemo} from 'react';
2+
import type {Location} from 'history';
3+
4+
import {decodeList} from 'sentry/utils/queryString';
5+
import {useLocation} from 'sentry/utils/useLocation';
6+
import {useNavigate} from 'sentry/utils/useNavigate';
7+
8+
interface Options {
9+
location: Location;
10+
navigate: ReturnType<typeof useNavigate>;
11+
}
12+
13+
export type Field = string;
14+
15+
export function useSampleFields(): [Field[], (fields: Field[]) => void] {
16+
const location = useLocation();
17+
const navigate = useNavigate();
18+
const options = {location, navigate};
19+
20+
return useSampleFieldsImpl(options);
21+
}
22+
23+
function useSampleFieldsImpl({
24+
location,
25+
navigate,
26+
}: Options): [Field[], (fields: Field[]) => void] {
27+
const sampleFields = useMemo(() => {
28+
const fields = decodeList(location.query.field);
29+
30+
if (fields.length) {
31+
return fields;
32+
}
33+
34+
return ['project', 'id', 'span.op', 'span.description', 'span.duration', 'timestamp'];
35+
}, [location.query.field]);
36+
37+
const setSampleFields = useCallback(
38+
(fields: Field[]) => {
39+
navigate({
40+
...location,
41+
query: {
42+
...location.query,
43+
field: fields,
44+
},
45+
});
46+
},
47+
[location, navigate]
48+
);
49+
50+
return [sampleFields, setSampleFields];
51+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import {createMemoryHistory, Route, Router, RouterContext} from 'react-router';
2+
3+
import {act, render} from 'sentry-test/reactTestingLibrary';
4+
5+
import {useSort} from 'sentry/views/explore/hooks/useSort';
6+
import {RouteContext} from 'sentry/views/routeContext';
7+
8+
describe('useSort', function () {
9+
it('allows changing sort', function () {
10+
let sort, setSort;
11+
12+
const fields = ['id', 'timestamp'];
13+
14+
function TestPage() {
15+
[sort, setSort] = useSort({fields});
16+
return null;
17+
}
18+
19+
const memoryHistory = createMemoryHistory();
20+
21+
render(
22+
<Router
23+
history={memoryHistory}
24+
render={props => {
25+
return (
26+
<RouteContext.Provider value={props}>
27+
<RouterContext {...props} />
28+
</RouteContext.Provider>
29+
);
30+
}}
31+
>
32+
<Route path="/" component={TestPage} />
33+
</Router>
34+
);
35+
36+
expect(sort).toEqual({
37+
direction: 'desc',
38+
field: 'timestamp',
39+
}); // default
40+
41+
act(() =>
42+
setSort({
43+
direction: 'asc',
44+
field: 'timestamp',
45+
})
46+
);
47+
expect(sort).toEqual({
48+
direction: 'asc',
49+
field: 'timestamp',
50+
});
51+
52+
act(() =>
53+
setSort({
54+
direction: 'desc',
55+
field: 'id',
56+
})
57+
);
58+
expect(sort).toEqual({
59+
direction: 'desc',
60+
field: 'id',
61+
});
62+
63+
act(() =>
64+
setSort({
65+
direction: 'asc',
66+
field: 'foo',
67+
})
68+
);
69+
expect(sort).toEqual({
70+
direction: 'asc',
71+
field: 'id',
72+
});
73+
});
74+
});
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import {useCallback, useMemo} from 'react';
2+
import type {Location} from 'history';
3+
4+
import {decodeScalar} from 'sentry/utils/queryString';
5+
import {useLocation} from 'sentry/utils/useLocation';
6+
import {useNavigate} from 'sentry/utils/useNavigate';
7+
8+
import type {Field} from './useSampleFields';
9+
10+
interface Options {
11+
fields: Field[];
12+
}
13+
14+
export type Direction = 'asc' | 'desc';
15+
export type Sort = {
16+
direction: Direction;
17+
field: Field;
18+
};
19+
20+
export function useSort(props): [Sort, (newSort: Sort) => void] {
21+
const location = useLocation();
22+
const navigate = useNavigate();
23+
const options = {location, navigate, ...props};
24+
25+
return useSortImpl(options);
26+
}
27+
28+
interface ImplOptions extends Options {
29+
location: Location;
30+
navigate: ReturnType<typeof useNavigate>;
31+
}
32+
33+
function useSortImpl({
34+
fields,
35+
location,
36+
navigate,
37+
}: ImplOptions): [Sort, (newSort: Sort) => void] {
38+
const rawSort = decodeScalar(location.query.sort);
39+
40+
const direction: Direction = !rawSort || rawSort.startsWith('-') ? 'desc' : 'asc';
41+
42+
const field: Field = useMemo(() => {
43+
let f = rawSort;
44+
if (direction === 'desc') {
45+
f = f?.substring(1);
46+
}
47+
f = f || 'timestamp';
48+
if (fields.length && !fields.includes(f)) {
49+
f = fields[0];
50+
}
51+
return f;
52+
}, [rawSort, direction, fields]);
53+
54+
const sort: Sort = useMemo(() => {
55+
return {
56+
direction,
57+
field,
58+
};
59+
}, [direction, field]);
60+
61+
const setSort = useCallback(
62+
(newSort: Sort) => {
63+
const formatted =
64+
newSort.direction === 'desc' ? `-${newSort.field}` : newSort.field;
65+
navigate({
66+
...location,
67+
query: {
68+
...location.query,
69+
sort: formatted,
70+
},
71+
});
72+
},
73+
[location, navigate]
74+
);
75+
76+
return [sort, setSort];
77+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import {createMemoryHistory, Route, Router, RouterContext} from 'react-router';
2+
3+
import {act, render} from 'sentry-test/reactTestingLibrary';
4+
5+
import {useUserQuery} from 'sentry/views/explore/hooks/useUserQuery';
6+
import {RouteContext} from 'sentry/views/routeContext';
7+
8+
describe('useUserQuery', function () {
9+
it('allows changing user query', function () {
10+
let userQuery, setUserQuery;
11+
12+
function TestPage() {
13+
[userQuery, setUserQuery] = useUserQuery();
14+
return null;
15+
}
16+
17+
const memoryHistory = createMemoryHistory();
18+
19+
render(
20+
<Router
21+
history={memoryHistory}
22+
render={props => {
23+
return (
24+
<RouteContext.Provider value={props}>
25+
<RouterContext {...props} />
26+
</RouteContext.Provider>
27+
);
28+
}}
29+
>
30+
<Route path="/" component={TestPage} />
31+
</Router>
32+
);
33+
34+
expect(userQuery).toEqual(''); // default
35+
36+
act(() => setUserQuery('foo:bar'));
37+
expect(userQuery).toEqual('foo:bar');
38+
});
39+
});

0 commit comments

Comments
 (0)