useProjectSampleCounts.tsx 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. import {useMemo} from 'react';
  2. import type {MetricsQueryApiResponse} from 'sentry/types/metrics';
  3. import type {Project} from 'sentry/types/project';
  4. import {useApiQuery} from 'sentry/utils/queryClient';
  5. import useOrganization from 'sentry/utils/useOrganization';
  6. import useProjects from 'sentry/utils/useProjects';
  7. export interface ProjectSampleCount {
  8. count: number;
  9. ownCount: number;
  10. project: Project;
  11. subProjects: Array<{count: number; slug: string}>;
  12. }
  13. export type ProjectionSamplePeriod = '24h' | '30d';
  14. export function useProjectSampleCounts({period}: {period: ProjectionSamplePeriod}) {
  15. const organization = useOrganization();
  16. const {projects, fetching} = useProjects();
  17. const {data, isPending, isError, refetch} = useApiQuery<MetricsQueryApiResponse>(
  18. [
  19. `/organizations/${organization.slug}/sampling/project-root-counts/`,
  20. {
  21. query: {
  22. statsPeriod: period,
  23. },
  24. },
  25. ],
  26. {
  27. staleTime: 0,
  28. }
  29. );
  30. const queryResult = data?.data?.[0];
  31. const projectBySlug = useMemo(
  32. () =>
  33. projects.reduce(
  34. (acc, project) => {
  35. acc[project.slug] = project;
  36. return acc;
  37. },
  38. {} as Record<string, Project>
  39. ),
  40. [projects]
  41. );
  42. const projectById = useMemo(
  43. () =>
  44. projects.reduce(
  45. (acc, project) => {
  46. acc[project.id] = project;
  47. return acc;
  48. },
  49. {} as Record<string, Project>
  50. ),
  51. [projects]
  52. );
  53. const projectEntries = useMemo(() => {
  54. const map = new Map<
  55. string,
  56. {
  57. count: number;
  58. ownCount: number;
  59. slug: string;
  60. subProjects: Array<{count: number; slug: string}>;
  61. }
  62. >();
  63. for (const row of queryResult ?? []) {
  64. const project = row.by.project && projectBySlug[row.by.project];
  65. const subProject =
  66. row.by.target_project_id && projectById[row.by.target_project_id];
  67. const rowValue = row.totals;
  68. if (!project || !subProject) {
  69. continue;
  70. }
  71. const existingEntry = map.get(project.slug) ?? {
  72. count: 0,
  73. ownCount: 0,
  74. slug: project.slug,
  75. subProjects: [],
  76. };
  77. existingEntry.count += rowValue;
  78. if (subProject && subProject.id === project.id) {
  79. existingEntry.ownCount = rowValue;
  80. } else {
  81. existingEntry.subProjects.push({
  82. count: rowValue,
  83. slug: subProject.slug,
  84. });
  85. }
  86. map.set(project.slug, existingEntry);
  87. }
  88. return map;
  89. }, [projectById, projectBySlug, queryResult]);
  90. const items = useMemo(
  91. () =>
  92. Array.from(projectEntries.entries()).map<ProjectSampleCount>(([key, value]) => {
  93. return {
  94. project: projectBySlug[key],
  95. count: value.count,
  96. ownCount: value.ownCount,
  97. subProjects: value.subProjects.toSorted((a, b) => b.count - a.count),
  98. };
  99. }),
  100. [projectBySlug, projectEntries]
  101. );
  102. return {data: items, isPending: fetching || isPending, isError, refetch};
  103. }