useReplaysCount.tsx 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. import {useCallback, useMemo, useState} from 'react';
  2. import {DateString, IssueCategory, Organization} from 'sentry/types';
  3. import {ApiQueryKey, useApiQuery} from 'sentry/utils/queryClient';
  4. import toArray from 'sentry/utils/toArray';
  5. type DateTime = {
  6. end: DateString;
  7. start: DateString;
  8. };
  9. type Options = {
  10. organization: Organization;
  11. datetime?: DateTime;
  12. extraConditions?: string;
  13. groupIds?: string | string[];
  14. issueCategory?: IssueCategory;
  15. replayIds?: string | string[];
  16. transactionNames?: string | string[];
  17. };
  18. type CountState = Record<string, undefined | number>;
  19. function useReplaysCount({
  20. issueCategory,
  21. groupIds,
  22. organization,
  23. replayIds,
  24. transactionNames,
  25. extraConditions,
  26. datetime,
  27. }: Options) {
  28. const [lastData, setLastData] = useState<CountState>({});
  29. const filterUnseen = useCallback(
  30. (ids: string | string[]) => {
  31. return toArray(ids).filter(id => !(id in lastData));
  32. },
  33. [lastData]
  34. );
  35. const zeroCounts = useMemo(() => {
  36. const gIds = toArray(groupIds || []);
  37. const txnNames = toArray(transactionNames || []);
  38. const rIds = toArray(replayIds || []);
  39. return [...gIds, ...txnNames, ...rIds].reduce<CountState>((record, key) => {
  40. record[key] = 0;
  41. return record;
  42. }, {});
  43. }, [groupIds, replayIds, transactionNames]);
  44. const queryField = useMemo(() => {
  45. const fieldsProvided = [
  46. groupIds !== undefined,
  47. transactionNames !== undefined,
  48. replayIds !== undefined,
  49. ].filter(Boolean);
  50. if (fieldsProvided.length === 0) {
  51. throw new Error(
  52. 'Missing one of: groupIds|transactionNames|replayIds in useReplaysCount()'
  53. );
  54. }
  55. if (fieldsProvided.length > 1) {
  56. throw new Error(
  57. 'Unable to query more than one of: groupIds|transactionNames|replayIDs simultaneously in useReplaysCount()'
  58. );
  59. }
  60. if (groupIds && groupIds.length) {
  61. const groupsToFetch = filterUnseen(groupIds);
  62. if (groupsToFetch.length) {
  63. return {
  64. field: 'issue.id' as const,
  65. conditions: `issue.id:[${groupsToFetch.join(',')}]`,
  66. };
  67. }
  68. return null;
  69. }
  70. if (replayIds && replayIds.length) {
  71. const replaysToFetch = filterUnseen(replayIds);
  72. if (replaysToFetch.length) {
  73. return {
  74. field: 'replay_id' as const,
  75. conditions: `replay_id:[${replaysToFetch.join(',')}]`,
  76. };
  77. }
  78. return null;
  79. }
  80. if (transactionNames && transactionNames.length) {
  81. const txnsToFetch = filterUnseen(transactionNames);
  82. if (txnsToFetch.length) {
  83. return {
  84. field: 'transaction' as const,
  85. conditions: `transaction:[${txnsToFetch.map(t => `"${t}"`).join(',')}]`,
  86. };
  87. }
  88. return null;
  89. }
  90. return null;
  91. }, [filterUnseen, groupIds, replayIds, transactionNames]);
  92. const hasSessionReplay = organization.features.includes('session-replay');
  93. const {data, isFetched} = useApiQuery<CountState>(
  94. makeReplayCountsQueryKey({
  95. organization,
  96. conditions: [queryField?.conditions ?? '', extraConditions ?? ''],
  97. datetime,
  98. issueCategory,
  99. }),
  100. {
  101. staleTime: Infinity,
  102. enabled: Boolean(queryField) && hasSessionReplay,
  103. }
  104. );
  105. return useMemo(() => {
  106. if (isFetched) {
  107. const merged = {
  108. ...zeroCounts,
  109. ...lastData,
  110. ...data,
  111. };
  112. setLastData(merged);
  113. return merged;
  114. }
  115. return {
  116. ...lastData,
  117. ...data,
  118. };
  119. }, [isFetched, zeroCounts, lastData, data]);
  120. }
  121. function makeReplayCountsQueryKey({
  122. conditions,
  123. datetime,
  124. issueCategory,
  125. organization,
  126. }: {
  127. conditions: string[];
  128. datetime: undefined | DateTime;
  129. issueCategory: undefined | IssueCategory;
  130. organization: Organization;
  131. }): ApiQueryKey {
  132. return [
  133. `/organizations/${organization.slug}/replay-count/`,
  134. {
  135. query: {
  136. query: conditions.filter(Boolean).join(' ').trim(),
  137. data_source: getDatasource(issueCategory),
  138. project: -1,
  139. ...(datetime ? {...datetime, statsPeriod: undefined} : {statsPeriod: '14d'}),
  140. },
  141. },
  142. ];
  143. }
  144. function getDatasource(issueCategory: undefined | IssueCategory) {
  145. switch (issueCategory) {
  146. case IssueCategory.PERFORMANCE:
  147. return 'search_issues';
  148. default:
  149. return 'discover';
  150. }
  151. }
  152. export default useReplaysCount;