groupStatsProvider.tsx 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. import {createContext, useContext, useEffect, useState} from 'react';
  2. import * as Sentry from '@sentry/react';
  3. import {dropUndefinedKeys} from '@sentry/utils';
  4. import * as reactQuery from '@tanstack/react-query';
  5. import {ApiResult} from 'sentry/api';
  6. import type {Group, GroupStats, Organization, PageFilters} from 'sentry/types';
  7. import {getUtcDateString} from 'sentry/utils/dates';
  8. import {UseQueryResult} from 'sentry/utils/queryClient';
  9. import RequestError from 'sentry/utils/requestError/requestError';
  10. import useApi from 'sentry/utils/useApi';
  11. function getEndpointParams(
  12. p: Pick<GroupStatsProviderProps, 'selection' | 'period' | 'query' | 'groupIds'>
  13. ): StatEndpointParams {
  14. const params: StatEndpointParams = {
  15. project: p.selection.projects,
  16. environment: p.selection.environments,
  17. groupStatsPeriod: p.period,
  18. query: p.query,
  19. groups: p.groupIds,
  20. ...p.selection.datetime,
  21. };
  22. if (p.selection.datetime.period) {
  23. delete params.period;
  24. params.statsPeriod = p.selection.datetime.period;
  25. }
  26. if (params.end) {
  27. params.end = getUtcDateString(params.end);
  28. }
  29. if (params.start) {
  30. params.start = getUtcDateString(params.start);
  31. }
  32. return dropUndefinedKeys(params);
  33. }
  34. const GroupStatsContext = createContext<UseQueryResult<
  35. Record<string, GroupStats>
  36. > | null>(null);
  37. export function useGroupStats(group: Group): GroupStats {
  38. const ctx = useContext(GroupStatsContext);
  39. if (!ctx) {
  40. return group;
  41. }
  42. return ctx.data?.[group.id] ?? group;
  43. }
  44. interface StatEndpointParams extends Partial<PageFilters['datetime']> {
  45. environment: string[];
  46. groups: Group['id'][];
  47. project: number[];
  48. cursor?: string;
  49. expand?: string | string[];
  50. groupStatsPeriod?: string | null;
  51. page?: number | string;
  52. query?: string | undefined;
  53. sort?: string;
  54. statsPeriod?: string | null;
  55. }
  56. export type GroupStatsQuery = UseQueryResult<Record<string, GroupStats>, RequestError>;
  57. export interface GroupStatsProviderProps {
  58. children: React.ReactNode;
  59. groupIds: Group['id'][];
  60. organization: Organization;
  61. period: string;
  62. selection: PageFilters;
  63. onStatsQuery?: (query: GroupStatsQuery) => void;
  64. query?: string;
  65. }
  66. export function GroupStatsProvider(props: GroupStatsProviderProps) {
  67. const api = useApi();
  68. const [groupStats, setGroupStats] = useState<Record<string, GroupStats>>({});
  69. const queryFn = (): Promise<Record<string, GroupStats>> => {
  70. const promise = api
  71. .requestPromise<true>(`/organizations/${props.organization.slug}/issues-stats/`, {
  72. method: 'GET',
  73. query: getEndpointParams({
  74. selection: props.selection,
  75. period: props.period,
  76. query: props.query,
  77. groupIds: props.groupIds,
  78. }),
  79. includeAllArgs: true,
  80. })
  81. .then((resp: ApiResult<GroupStats[]>): Record<string, GroupStats> => {
  82. const map: Record<string, GroupStats> = {...groupStats};
  83. if (!resp || !Array.isArray(resp[0])) {
  84. return map;
  85. }
  86. for (const stat of resp[0]) {
  87. map[stat.id] = stat;
  88. }
  89. setGroupStats(map);
  90. return map;
  91. })
  92. .catch(e => {
  93. Sentry.captureException(e);
  94. return {};
  95. });
  96. return promise;
  97. };
  98. const statsQuery = reactQuery.useQuery<Record<string, GroupStats>, RequestError>(
  99. [
  100. `/organizations/${props.organization.slug}/issues-stats/`,
  101. props.selection,
  102. props.period,
  103. props.query,
  104. props.groupIds,
  105. ],
  106. queryFn,
  107. {
  108. enabled: props.groupIds.length > 0,
  109. staleTime: Infinity,
  110. }
  111. );
  112. const onStatsQuery = props.onStatsQuery;
  113. useEffect(() => {
  114. onStatsQuery?.(statsQuery);
  115. // We only want to fire the observer when the status changes
  116. // eslint-disable-next-line react-hooks/exhaustive-deps
  117. }, [statsQuery.status, onStatsQuery]);
  118. return (
  119. // @ts-expect-error we are overriding data with the stored version
  120. <GroupStatsContext.Provider value={{...statsQuery, data: groupStats}}>
  121. {props.children}
  122. </GroupStatsContext.Provider>
  123. );
  124. }