logsPageParams.tsx 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. import {useCallback} from 'react';
  2. import type {Location} from 'history';
  3. import type {CursorHandler} from 'sentry/components/pagination';
  4. import {defined} from 'sentry/utils';
  5. import {decodeProjects} from 'sentry/utils/discover/eventView';
  6. import type {Sort} from 'sentry/utils/discover/fields';
  7. import {createDefinedContext} from 'sentry/utils/performance/contexts/utils';
  8. import {decodeScalar} from 'sentry/utils/queryString';
  9. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  10. import {useLocation} from 'sentry/utils/useLocation';
  11. import {useNavigate} from 'sentry/utils/useNavigate';
  12. import {
  13. defaultLogFields,
  14. getLogFieldsFromLocation,
  15. } from 'sentry/views/explore/contexts/logs/fields';
  16. import {
  17. getLogSortBysFromLocation,
  18. logsTimestampDescendingSortBy,
  19. updateLocationWithLogSortBys,
  20. } from 'sentry/views/explore/contexts/logs/sortBys';
  21. import {OurLogKnownFieldKey} from 'sentry/views/explore/logs/types';
  22. const LOGS_QUERY_KEY = 'logsQuery'; // Logs may exist on other pages.
  23. const LOGS_CURSOR_KEY = 'logsCursor';
  24. export const LOGS_FIELDS_KEY = 'logsFields';
  25. interface LogsPageParams {
  26. readonly cursor: string;
  27. readonly fields: string[];
  28. readonly isTableEditingFrozen: boolean | undefined;
  29. readonly search: MutableSearch;
  30. readonly sortBys: Sort[];
  31. /**
  32. * The base search, which doesn't appear in the URL or the search bar, used for adding traceid etc..
  33. */
  34. readonly baseSearch?: MutableSearch;
  35. /**
  36. * If provided, ignores the project in the location and uses the provided project IDs.
  37. * Useful for cross-project traces when project is in the location.
  38. */
  39. readonly projectIds?: number[];
  40. }
  41. type LogPageParamsUpdate = Partial<LogsPageParams>;
  42. const [_LogsPageParamsProvider, _useLogsPageParams, LogsPageParamsContext] =
  43. createDefinedContext<LogsPageParams>({
  44. name: 'LogsPageParamsContext',
  45. });
  46. export interface LogsPageParamsProviderProps {
  47. children: React.ReactNode;
  48. isOnEmbeddedView?: boolean;
  49. limitToTraceId?: string;
  50. }
  51. export function LogsPageParamsProvider({
  52. children,
  53. limitToTraceId,
  54. isOnEmbeddedView,
  55. }: LogsPageParamsProviderProps) {
  56. const location = useLocation();
  57. const logsQuery = decodeLogsQuery(location);
  58. const search = new MutableSearch(logsQuery);
  59. const baseSearch = limitToTraceId
  60. ? new MutableSearch('').addFilterValues(OurLogKnownFieldKey.TRACE_ID, [
  61. limitToTraceId,
  62. ])
  63. : undefined;
  64. const isTableEditingFrozen = isOnEmbeddedView;
  65. const fields = isTableEditingFrozen
  66. ? defaultLogFields()
  67. : getLogFieldsFromLocation(location);
  68. const sortBys = isTableEditingFrozen
  69. ? [logsTimestampDescendingSortBy]
  70. : getLogSortBysFromLocation(location, fields);
  71. const projectIds = isOnEmbeddedView ? [-1] : decodeProjects(location);
  72. const cursor = getLogCursorFromLocation(location);
  73. return (
  74. <LogsPageParamsContext.Provider
  75. value={{
  76. fields,
  77. search,
  78. sortBys,
  79. cursor,
  80. isTableEditingFrozen,
  81. baseSearch,
  82. projectIds,
  83. }}
  84. >
  85. {children}
  86. </LogsPageParamsContext.Provider>
  87. );
  88. }
  89. export const useLogsPageParams = _useLogsPageParams;
  90. const decodeLogsQuery = (location: Location): string => {
  91. if (!location.query || !location.query[LOGS_QUERY_KEY]) {
  92. return '';
  93. }
  94. const queryParameter = location.query[LOGS_QUERY_KEY];
  95. return decodeScalar(queryParameter, '').trim();
  96. };
  97. function setLogsPageParams(location: Location, pageParams: LogPageParamsUpdate) {
  98. const target: Location = {...location, query: {...location.query}};
  99. updateNullableLocation(target, LOGS_QUERY_KEY, pageParams.search?.formatString());
  100. updateNullableLocation(target, LOGS_CURSOR_KEY, pageParams.cursor);
  101. updateNullableLocation(target, LOGS_FIELDS_KEY, pageParams.fields);
  102. if (!pageParams.isTableEditingFrozen) {
  103. updateLocationWithLogSortBys(target, pageParams.sortBys, LOGS_CURSOR_KEY);
  104. }
  105. return target;
  106. }
  107. /**
  108. * Allows updating a location field, removing it if the value is null.
  109. *
  110. * Return true if the location field was updated, in case of side effects.
  111. */
  112. function updateNullableLocation(
  113. location: Location,
  114. key: string,
  115. value: string | string[] | null | undefined
  116. ): boolean {
  117. if (defined(value) && location.query[key] !== value) {
  118. location.query[key] = value;
  119. return true;
  120. }
  121. if (value === null && location.query[key]) {
  122. delete location.query[key];
  123. return true;
  124. }
  125. return false;
  126. }
  127. export function useSetLogsPageParams() {
  128. const location = useLocation();
  129. const navigate = useNavigate();
  130. return useCallback(
  131. (pageParams: LogPageParamsUpdate) => {
  132. const target = setLogsPageParams(location, pageParams);
  133. navigate(target);
  134. },
  135. [location, navigate]
  136. );
  137. }
  138. export function useLogsSearch(): MutableSearch {
  139. const {search} = useLogsPageParams();
  140. return search;
  141. }
  142. export function useLogsBaseSearch(): MutableSearch | undefined {
  143. const {baseSearch} = useLogsPageParams();
  144. return baseSearch;
  145. }
  146. export function useSetLogsQuery() {
  147. const setPageParams = useSetLogsPageParams();
  148. return useCallback(
  149. (query: string) => {
  150. setPageParams({search: new MutableSearch(query)});
  151. },
  152. [setPageParams]
  153. );
  154. }
  155. export function useSetLogsSearch() {
  156. const setPageParams = useSetLogsPageParams();
  157. return useCallback(
  158. (search: MutableSearch) => {
  159. setPageParams({search});
  160. },
  161. [setPageParams]
  162. );
  163. }
  164. export function useLogsIsTableEditingFrozen() {
  165. const {isTableEditingFrozen} = useLogsPageParams();
  166. return isTableEditingFrozen;
  167. }
  168. export function useLogsSortBys() {
  169. const {sortBys} = useLogsPageParams();
  170. return sortBys;
  171. }
  172. export function useLogsFields() {
  173. const {fields} = useLogsPageParams();
  174. return fields;
  175. }
  176. export function useLogsProjectIds() {
  177. const {projectIds} = useLogsPageParams();
  178. return projectIds;
  179. }
  180. export function useSetLogsFields() {
  181. const setPageParams = useSetLogsPageParams();
  182. return useCallback(
  183. (fields: string[]) => {
  184. setPageParams({fields});
  185. },
  186. [setPageParams]
  187. );
  188. }
  189. export function useLogsCursor() {
  190. const {cursor} = useLogsPageParams();
  191. return cursor;
  192. }
  193. export function useSetLogsCursor() {
  194. const setPageParams = useSetLogsPageParams();
  195. return useCallback<CursorHandler>(
  196. cursor => {
  197. setPageParams({cursor});
  198. },
  199. [setPageParams]
  200. );
  201. }
  202. export function useSetLogsSortBys() {
  203. const setPageParams = useSetLogsPageParams();
  204. const currentPageSortBys = useLogsSortBys();
  205. return useCallback(
  206. (desiredSortBys: ToggleableSortBy[]) => {
  207. const targetSortBys: Sort[] = desiredSortBys.map(desiredSortBy => {
  208. const currentSortBy = currentPageSortBys.find(
  209. s => s.field === desiredSortBy.field
  210. );
  211. const reverseDirection = currentSortBy?.kind === 'asc' ? 'desc' : 'asc';
  212. return {
  213. field: desiredSortBy.field,
  214. kind:
  215. desiredSortBy.kind ??
  216. reverseDirection ??
  217. desiredSortBy.defaultDirection ??
  218. 'desc',
  219. };
  220. });
  221. setPageParams({sortBys: targetSortBys});
  222. },
  223. [setPageParams, currentPageSortBys]
  224. );
  225. }
  226. function getLogCursorFromLocation(location: Location): string {
  227. if (!location.query || !location.query[LOGS_CURSOR_KEY]) {
  228. return '';
  229. }
  230. return decodeScalar(location.query[LOGS_CURSOR_KEY], '');
  231. }
  232. interface ToggleableSortBy {
  233. field: string;
  234. defaultDirection?: 'asc' | 'desc'; // Defaults to descending if not provided.
  235. kind?: 'asc' | 'desc';
  236. }