useResourcesQuery.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. import {useDiscoverQuery} from 'sentry/utils/discover/discoverQuery';
  2. import EventView from 'sentry/utils/discover/eventView';
  3. import {DiscoverDatasets} from 'sentry/utils/discover/types';
  4. import {EMPTY_OPTION_VALUE} from 'sentry/utils/tokenizeSearch';
  5. import {useLocation} from 'sentry/utils/useLocation';
  6. import useOrganization from 'sentry/utils/useOrganization';
  7. import usePageFilters from 'sentry/utils/usePageFilters';
  8. import {
  9. FONT_FILE_EXTENSIONS,
  10. IMAGE_FILE_EXTENSIONS,
  11. } from 'sentry/views/insights/browser/resources/constants';
  12. import {ResourceSpanOps} from 'sentry/views/insights/browser/resources/types';
  13. import type {ModuleFilters} from 'sentry/views/insights/browser/resources/utils/useResourceFilters';
  14. import {useResourceModuleFilters} from 'sentry/views/insights/browser/resources/utils/useResourceFilters';
  15. import type {ValidSort} from 'sentry/views/insights/browser/resources/utils/useResourceSort';
  16. import {SpanFunction, SpanMetricsField} from 'sentry/views/insights/types';
  17. const {
  18. SPAN_DOMAIN,
  19. SPAN_GROUP,
  20. SPAN_DESCRIPTION,
  21. SPAN_OP,
  22. SPAN_SELF_TIME,
  23. RESOURCE_RENDER_BLOCKING_STATUS,
  24. HTTP_RESPONSE_CONTENT_LENGTH,
  25. PROJECT_ID,
  26. FILE_EXTENSION,
  27. USER_GEO_SUBREGION,
  28. } = SpanMetricsField;
  29. const {TIME_SPENT_PERCENTAGE} = SpanFunction;
  30. type Props = {
  31. referrer: string;
  32. sort: ValidSort;
  33. cursor?: string;
  34. defaultResourceTypes?: string[];
  35. limit?: number;
  36. query?: string;
  37. };
  38. export const DEFAULT_RESOURCE_FILTERS = ['!span.description:"browser-extension://*"'];
  39. export const getResourcesEventViewQuery = (
  40. resourceFilters: Partial<ModuleFilters>,
  41. defaultResourceTypes: string[] | undefined
  42. ): string[] => {
  43. return [
  44. ...DEFAULT_RESOURCE_FILTERS,
  45. ...(resourceFilters.transaction
  46. ? [`transaction:"${resourceFilters.transaction}"`]
  47. : []),
  48. ...(resourceFilters[USER_GEO_SUBREGION]
  49. ? [`user.geo.subregion:[${resourceFilters[USER_GEO_SUBREGION]}]`]
  50. : []),
  51. ...getDomainFilter(resourceFilters[SPAN_DOMAIN]),
  52. ...(resourceFilters[RESOURCE_RENDER_BLOCKING_STATUS]
  53. ? [
  54. `${RESOURCE_RENDER_BLOCKING_STATUS}:${resourceFilters[RESOURCE_RENDER_BLOCKING_STATUS]}`,
  55. ]
  56. : []),
  57. ...getResourceTypeFilter(resourceFilters[SPAN_OP], defaultResourceTypes),
  58. ];
  59. };
  60. export const useResourcesQuery = ({
  61. sort,
  62. defaultResourceTypes,
  63. query,
  64. limit,
  65. cursor,
  66. referrer,
  67. }: Props) => {
  68. const pageFilters = usePageFilters();
  69. const location = useLocation();
  70. const resourceFilters = useResourceModuleFilters();
  71. const {slug: orgSlug} = useOrganization();
  72. const queryConditions = [
  73. ...(!query ? getResourcesEventViewQuery(resourceFilters, defaultResourceTypes) : []),
  74. query,
  75. ];
  76. // TODO - we should be using metrics data here
  77. const eventView = EventView.fromNewQueryWithPageFilters(
  78. {
  79. fields: [
  80. SPAN_DESCRIPTION,
  81. SPAN_OP,
  82. 'count()',
  83. `avg(${SPAN_SELF_TIME})`,
  84. 'spm()',
  85. SPAN_GROUP,
  86. `avg(${HTTP_RESPONSE_CONTENT_LENGTH})`,
  87. 'project.id',
  88. `${TIME_SPENT_PERCENTAGE}()`,
  89. `sum(${SPAN_SELF_TIME})`,
  90. ],
  91. name: 'Resource module - resource table',
  92. query: queryConditions.join(' '),
  93. orderby: '-count',
  94. version: 2,
  95. dataset: DiscoverDatasets.SPANS_METRICS,
  96. },
  97. pageFilters.selection
  98. );
  99. if (sort) {
  100. eventView.sorts = [sort];
  101. }
  102. const result = useDiscoverQuery({
  103. eventView,
  104. limit: limit ?? 100,
  105. location,
  106. orgSlug,
  107. options: {
  108. refetchOnWindowFocus: false,
  109. },
  110. cursor,
  111. referrer,
  112. });
  113. const data = result?.data?.data.map(row => ({
  114. [SPAN_OP]: row[SPAN_OP].toString() as `resource.${string}`,
  115. [SPAN_DESCRIPTION]: row[SPAN_DESCRIPTION].toString(),
  116. ['avg(span.self_time)']: row[`avg(${SPAN_SELF_TIME})`] as number,
  117. 'count()': row['count()'] as number,
  118. 'spm()': row['spm()'] as number,
  119. [SPAN_GROUP]: row[SPAN_GROUP].toString(),
  120. [PROJECT_ID]: row[PROJECT_ID] as number,
  121. [`avg(http.response_content_length)`]: row[
  122. `avg(${HTTP_RESPONSE_CONTENT_LENGTH})`
  123. ] as number,
  124. [`time_spent_percentage()`]: row[`${TIME_SPENT_PERCENTAGE}()`] as number,
  125. ['count_unique(transaction)']: row['count_unique(transaction)'] as number,
  126. [`sum(span.self_time)`]: row[`sum(${SPAN_SELF_TIME})`] as number,
  127. }));
  128. return {...result, data: data || []};
  129. };
  130. export const getDomainFilter = (selectedDomain: string | undefined) => {
  131. if (!selectedDomain) {
  132. return [];
  133. }
  134. if (selectedDomain === EMPTY_OPTION_VALUE) {
  135. return [`!has:${SPAN_DOMAIN}`];
  136. }
  137. return [`${SPAN_DOMAIN}:"${selectedDomain}"`];
  138. };
  139. const SPAN_OP_FILTER = {
  140. [ResourceSpanOps.SCRIPT]: [`${SPAN_OP}:${ResourceSpanOps.SCRIPT}`],
  141. [ResourceSpanOps.CSS]: [`${FILE_EXTENSION}:css`],
  142. [ResourceSpanOps.FONT]: [`${FILE_EXTENSION}:[${FONT_FILE_EXTENSIONS.join(',')}]`],
  143. [ResourceSpanOps.IMAGE]: [
  144. `${FILE_EXTENSION}:[${IMAGE_FILE_EXTENSIONS.join(',')}]`,
  145. `${SPAN_OP}:resource.img`,
  146. ],
  147. };
  148. export const getResourceTypeFilter = (
  149. selectedSpanOp: string | undefined,
  150. defaultResourceTypes: string[] | undefined
  151. ) => {
  152. let resourceFilter: string[] = [`${SPAN_OP}:resource.*`];
  153. if (selectedSpanOp) {
  154. resourceFilter = [SPAN_OP_FILTER[selectedSpanOp].join(' OR ')];
  155. } else if (defaultResourceTypes) {
  156. resourceFilter = [
  157. defaultResourceTypes.map(type => SPAN_OP_FILTER[type].join(' OR ')).join(' OR '),
  158. ];
  159. }
  160. return ['(', ...resourceFilter, ')'];
  161. };