logsPageParams.tsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  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 type {Sort} from 'sentry/utils/discover/fields';
  6. import {createDefinedContext} from 'sentry/utils/performance/contexts/utils';
  7. import {decodeScalar} from 'sentry/utils/queryString';
  8. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  9. import {useLocation} from 'sentry/utils/useLocation';
  10. import {useNavigate} from 'sentry/utils/useNavigate';
  11. import {getLogFieldsFromLocation} from 'sentry/views/explore/contexts/logs/fields';
  12. import {
  13. getLogSortBysFromLocation,
  14. updateLocationWithLogSortBys,
  15. } from 'sentry/views/explore/contexts/logs/sortBys';
  16. const LOGS_QUERY_KEY = 'logsQuery'; // Logs may exist on other pages.
  17. const LOGS_CURSOR_KEY = 'logsCursor';
  18. interface LogsPageParams {
  19. readonly cursor: string;
  20. readonly fields: string[];
  21. readonly search: MutableSearch;
  22. readonly sortBys: Sort[];
  23. }
  24. type LogPageParamsUpdate = Partial<LogsPageParams>;
  25. const [_LogsPageParamsProvider, useLogsPageParams, LogsPageParamsContext] =
  26. createDefinedContext<LogsPageParams>({
  27. name: 'LogsPageParamsContext',
  28. });
  29. export function LogsPageParamsProvider({children}: {children: React.ReactNode}) {
  30. const location = useLocation();
  31. const logsQuery = decodeLogsQuery(location);
  32. const search = new MutableSearch(logsQuery);
  33. const fields = getLogFieldsFromLocation(location);
  34. const sortBys = getLogSortBysFromLocation(location, fields);
  35. const cursor = getLogCursorFromLocation(location);
  36. return (
  37. <LogsPageParamsContext.Provider value={{fields, search, sortBys, cursor}}>
  38. {children}
  39. </LogsPageParamsContext.Provider>
  40. );
  41. }
  42. const decodeLogsQuery = (location: Location): string => {
  43. if (!location.query || !location.query[LOGS_QUERY_KEY]) {
  44. return '';
  45. }
  46. const queryParameter = location.query[LOGS_QUERY_KEY];
  47. return decodeScalar(queryParameter, '').trim();
  48. };
  49. function setLogsPageParams(location: Location, pageParams: LogPageParamsUpdate) {
  50. const target: Location = {...location, query: {...location.query}};
  51. updateNullableLocation(target, LOGS_QUERY_KEY, pageParams.search?.formatString());
  52. updateNullableLocation(target, LOGS_CURSOR_KEY, pageParams.cursor);
  53. updateLocationWithLogSortBys(target, pageParams.sortBys, LOGS_CURSOR_KEY);
  54. return target;
  55. }
  56. /**
  57. * Allows updating a location field, removing it if the value is null.
  58. *
  59. * Return true if the location field was updated, in case of side effects.
  60. */
  61. function updateNullableLocation(
  62. location: Location,
  63. key: string,
  64. value: string | string[] | null | undefined
  65. ): boolean {
  66. if (defined(value) && location.query[key] !== value) {
  67. location.query[key] = value;
  68. return true;
  69. }
  70. if (value === null && location.query[key]) {
  71. delete location.query[key];
  72. return true;
  73. }
  74. return false;
  75. }
  76. function useSetLogsPageParams() {
  77. const location = useLocation();
  78. const navigate = useNavigate();
  79. return useCallback(
  80. (pageParams: LogPageParamsUpdate) => {
  81. const target = setLogsPageParams(location, pageParams);
  82. navigate(target);
  83. },
  84. [location, navigate]
  85. );
  86. }
  87. export function useLogsSearch(): MutableSearch {
  88. const {search} = useLogsPageParams();
  89. return search;
  90. }
  91. export function useSetLogsQuery() {
  92. const setPageParams = useSetLogsPageParams();
  93. return useCallback(
  94. (query: string) => {
  95. setPageParams({search: new MutableSearch(query)});
  96. },
  97. [setPageParams]
  98. );
  99. }
  100. export function useSetLogsSearch() {
  101. const setPageParams = useSetLogsPageParams();
  102. return useCallback(
  103. (search: MutableSearch) => {
  104. setPageParams({search});
  105. },
  106. [setPageParams]
  107. );
  108. }
  109. export function useLogsSortBys() {
  110. const {sortBys} = useLogsPageParams();
  111. return sortBys;
  112. }
  113. export function useLogsFields() {
  114. const {fields} = useLogsPageParams();
  115. return fields;
  116. }
  117. export function useLogsCursor() {
  118. const {cursor} = useLogsPageParams();
  119. return cursor;
  120. }
  121. export function useSetLogsCursor() {
  122. const setPageParams = useSetLogsPageParams();
  123. return useCallback<CursorHandler>(
  124. cursor => {
  125. setPageParams({cursor});
  126. },
  127. [setPageParams]
  128. );
  129. }
  130. export function useSetLogsSortBys() {
  131. const setPageParams = useSetLogsPageParams();
  132. const currentPageSortBys = useLogsSortBys();
  133. return useCallback(
  134. (desiredSortBys: ToggleableSortBy[]) => {
  135. const targetSortBys: Sort[] = desiredSortBys.map(desiredSortBy => {
  136. const currentSortBy = currentPageSortBys.find(
  137. s => s.field === desiredSortBy.field
  138. );
  139. const reverseDirection = currentSortBy?.kind === 'asc' ? 'desc' : 'asc';
  140. return {
  141. field: desiredSortBy.field,
  142. kind:
  143. desiredSortBy.kind ??
  144. reverseDirection ??
  145. desiredSortBy.defaultDirection ??
  146. 'desc',
  147. };
  148. });
  149. setPageParams({sortBys: targetSortBys});
  150. },
  151. [setPageParams, currentPageSortBys]
  152. );
  153. }
  154. function getLogCursorFromLocation(location: Location): string {
  155. if (!location.query || !location.query[LOGS_CURSOR_KEY]) {
  156. return '';
  157. }
  158. return decodeScalar(location.query[LOGS_CURSOR_KEY], '');
  159. }
  160. interface ToggleableSortBy {
  161. field: string;
  162. defaultDirection?: 'asc' | 'desc'; // Defaults to descending if not provided.
  163. kind?: 'asc' | 'desc';
  164. }