useResourcesQuery.ts 4.7 KB

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