useResourcesQuery.ts 5.3 KB

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