useProjectSampleCounts.tsx 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  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 interface ProjectSampleCount {
  20. count: number;
  21. ownCount: number;
  22. project: Project;
  23. subProjects: Array<{count: number; slug: string}>;
  24. }
  25. export type ProjectionSamplePeriod = '24h' | '30d';
  26. export function useProjectSampleCounts({period}: {period: ProjectionSamplePeriod}) {
  27. const {projects, fetching} = useProjects();
  28. const {data, isPending, isError, refetch} = useMetricsQuery(
  29. metricsQuery,
  30. {
  31. datetime: {
  32. start: null,
  33. end: null,
  34. utc: true,
  35. period,
  36. },
  37. environments: [],
  38. projects: [],
  39. },
  40. {
  41. includeSeries: false,
  42. interval: period === '24h' ? '1h' : '1d',
  43. }
  44. );
  45. const queryResult = data?.data?.[0];
  46. const projectBySlug = useMemo(
  47. () =>
  48. projects.reduce(
  49. (acc, project) => {
  50. acc[project.slug] = project;
  51. return acc;
  52. },
  53. {} as Record<string, Project>
  54. ),
  55. [projects]
  56. );
  57. const projectById = useMemo(
  58. () =>
  59. projects.reduce(
  60. (acc, project) => {
  61. acc[project.id] = project;
  62. return acc;
  63. },
  64. {} as Record<string, Project>
  65. ),
  66. [projects]
  67. );
  68. const projectEntries = useMemo(() => {
  69. const map = new Map<
  70. string,
  71. {
  72. count: number;
  73. ownCount: number;
  74. slug: string;
  75. subProjects: Array<{count: number; slug: string}>;
  76. }
  77. >();
  78. for (const row of queryResult ?? []) {
  79. const project = row.by.project && projectBySlug[row.by.project];
  80. const subProject =
  81. row.by.target_project_id && projectById[row.by.target_project_id];
  82. const rowValue = row.totals;
  83. if (!project || !subProject) {
  84. continue;
  85. }
  86. const existingEntry = map.get(project.slug) ?? {
  87. count: 0,
  88. ownCount: 0,
  89. slug: project.slug,
  90. subProjects: [],
  91. };
  92. existingEntry.count += rowValue;
  93. if (subProject && subProject.id === project.id) {
  94. existingEntry.ownCount = rowValue;
  95. } else {
  96. existingEntry.subProjects.push({
  97. count: rowValue,
  98. slug: subProject.slug,
  99. });
  100. }
  101. map.set(project.slug, existingEntry);
  102. }
  103. return map;
  104. }, [projectById, projectBySlug, queryResult]);
  105. const items = useMemo(
  106. () =>
  107. projectEntries
  108. .entries()
  109. .map<ProjectSampleCount>(([key, value]) => {
  110. return {
  111. project: projectBySlug[key],
  112. count: value.count,
  113. ownCount: value.ownCount,
  114. subProjects: value.subProjects.toSorted((a, b) => b.count - a.count),
  115. };
  116. })
  117. .toArray(),
  118. [projectBySlug, projectEntries]
  119. );
  120. return {data: items, isPending: fetching || isPending, isError, refetch};
  121. }