useProjectSampleCounts.tsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. import {useMemo} from 'react';
  2. import type {MRI} from 'sentry/types/metrics';
  3. import type {Project} from 'sentry/types/project';
  4. import {
  5. type MetricsQueryApiQueryParams,
  6. useMetricsQuery,
  7. } from 'sentry/utils/metrics/useMetricsQuery';
  8. import useProjects from 'sentry/utils/useProjects';
  9. const SPANS_COUNT_METRIC: MRI = `c:spans/count_per_root_project@none`;
  10. const metricsQuery: MetricsQueryApiQueryParams[] = [
  11. {
  12. mri: SPANS_COUNT_METRIC,
  13. aggregation: 'sum',
  14. name: 'spans',
  15. groupBy: ['project', 'target_project_id'],
  16. orderBy: 'desc',
  17. },
  18. ];
  19. export function useProjectSampleCounts({period}: {period: '24h' | '30d'}) {
  20. const {projects, fetching} = useProjects();
  21. const {data, isPending, isError, refetch} = useMetricsQuery(
  22. metricsQuery,
  23. {
  24. datetime: {
  25. start: null,
  26. end: null,
  27. utc: true,
  28. period,
  29. },
  30. environments: [],
  31. projects: [],
  32. },
  33. {
  34. includeSeries: false,
  35. interval: period === '24h' ? '1h' : '1d',
  36. }
  37. );
  38. const queryResult = data?.data?.[0];
  39. const projectBySlug = useMemo(
  40. () =>
  41. projects.reduce(
  42. (acc, project) => {
  43. acc[project.slug] = project;
  44. return acc;
  45. },
  46. {} as Record<string, Project>
  47. ),
  48. [projects]
  49. );
  50. const projectById = useMemo(
  51. () =>
  52. projects.reduce(
  53. (acc, project) => {
  54. acc[project.id] = project;
  55. return acc;
  56. },
  57. {} as Record<string, Project>
  58. ),
  59. [projects]
  60. );
  61. const projectEntries = useMemo(() => {
  62. const map = new Map<
  63. string,
  64. {
  65. count: number;
  66. ownCount: number;
  67. slug: string;
  68. subProjects: Array<{count: number; slug: string}>;
  69. }
  70. >();
  71. for (const row of queryResult ?? []) {
  72. const project = row.by.project && projectBySlug[row.by.project];
  73. const subProject =
  74. row.by.target_project_id && projectById[row.by.target_project_id];
  75. const rowValue = row.totals;
  76. if (!project || !subProject) {
  77. continue;
  78. }
  79. const existingEntry = map.get(project.slug) ?? {
  80. count: 0,
  81. ownCount: 0,
  82. slug: project.slug,
  83. subProjects: [],
  84. };
  85. existingEntry.count += rowValue;
  86. if (subProject && subProject.id === project.id) {
  87. existingEntry.ownCount = rowValue;
  88. } else {
  89. existingEntry.subProjects.push({
  90. count: rowValue,
  91. slug: subProject.slug,
  92. });
  93. }
  94. map.set(project.slug, existingEntry);
  95. }
  96. return map;
  97. }, [projectById, projectBySlug, queryResult]);
  98. const items = useMemo(
  99. () =>
  100. projectEntries
  101. .entries()
  102. .map(([key, value]) => {
  103. return {
  104. id: key,
  105. project: projectBySlug[key],
  106. count: value.count,
  107. ownCount: value.ownCount,
  108. // This is a placeholder value to satisfy typing
  109. // the actual value is calculated in the balanceSampleRate function
  110. sampleRate: 1,
  111. subProjects: value.subProjects.toSorted((a, b) => b.count - a.count),
  112. };
  113. })
  114. .toArray(),
  115. [projectBySlug, projectEntries]
  116. );
  117. return {data: items, isPending: fetching || isPending, isError, refetch};
  118. }