useSpanList.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. import {Location} from 'history';
  2. import omit from 'lodash/omit';
  3. import {defined} from 'sentry/utils';
  4. import EventView from 'sentry/utils/discover/eventView';
  5. import type {Sort} from 'sentry/utils/discover/fields';
  6. import {DiscoverDatasets} from 'sentry/utils/discover/types';
  7. import {useLocation} from 'sentry/utils/useLocation';
  8. import {ModuleName, SpanMetricsFields} from 'sentry/views/starfish/types';
  9. import {useWrappedDiscoverQuery} from 'sentry/views/starfish/utils/useSpansQuery';
  10. import {NULL_SPAN_CATEGORY} from 'sentry/views/starfish/views/webServiceView/spanGroupBreakdownContainer';
  11. const {
  12. SPAN_SELF_TIME,
  13. SPAN_DESCRIPTION,
  14. SPAN_GROUP,
  15. SPAN_OP,
  16. SPAN_DOMAIN,
  17. SPAN_ACTION,
  18. SPAN_MODULE,
  19. } = SpanMetricsFields;
  20. const SPAN_FILTER_KEYS = [
  21. SPAN_OP,
  22. SPAN_DOMAIN,
  23. SPAN_ACTION,
  24. `!${SPAN_MODULE}`,
  25. '!span.category',
  26. ];
  27. export type SpanMetrics = {
  28. 'http_error_count()': number;
  29. 'http_error_count_percent_change()': number;
  30. 'p95(span.self_time)': number;
  31. 'percentile_percent_change(span.self_time, 0.95)': number;
  32. 'span.description': string;
  33. 'span.domain': string;
  34. 'span.group': string;
  35. 'span.op': string;
  36. 'sps()': number;
  37. 'sps_percent_change()': number;
  38. 'sum(span.self_time)': number;
  39. 'time_spent_percentage()': number;
  40. 'time_spent_percentage(local)': number;
  41. };
  42. export const useSpanList = (
  43. moduleName: ModuleName,
  44. transaction?: string,
  45. method?: string,
  46. spanCategory?: string,
  47. sorts?: Sort[],
  48. limit?: number,
  49. referrer = 'use-span-list',
  50. cursor?: string
  51. ) => {
  52. const location = useLocation();
  53. const eventView = getEventView(
  54. moduleName,
  55. location,
  56. transaction,
  57. method,
  58. spanCategory,
  59. sorts
  60. );
  61. const {isLoading, data, meta, pageLinks} = useWrappedDiscoverQuery<SpanMetrics[]>({
  62. eventView,
  63. initialData: [],
  64. limit,
  65. referrer,
  66. cursor,
  67. });
  68. return {isLoading, data, meta, pageLinks};
  69. };
  70. function getEventView(
  71. moduleName: ModuleName,
  72. location: Location,
  73. transaction?: string,
  74. method?: string,
  75. spanCategory?: string,
  76. sorts?: Sort[]
  77. ) {
  78. const query = buildEventViewQuery(
  79. moduleName,
  80. location,
  81. transaction,
  82. method,
  83. spanCategory
  84. )
  85. .filter(Boolean)
  86. .join(' ');
  87. const fields = [
  88. SPAN_OP,
  89. SPAN_GROUP,
  90. SPAN_DESCRIPTION,
  91. SPAN_DOMAIN,
  92. 'sps()',
  93. 'sps_percent_change()',
  94. `sum(${SPAN_SELF_TIME})`,
  95. `p95(${SPAN_SELF_TIME})`,
  96. `percentile_percent_change(${SPAN_SELF_TIME}, 0.95)`,
  97. 'http_error_count()',
  98. 'http_error_count_percent_change()',
  99. ];
  100. if (defined(transaction)) {
  101. fields.push('time_spent_percentage(local)');
  102. } else {
  103. fields.push('time_spent_percentage()');
  104. }
  105. const eventView = EventView.fromNewQueryWithLocation(
  106. {
  107. name: '',
  108. query,
  109. fields,
  110. dataset: DiscoverDatasets.SPANS_METRICS,
  111. version: 2,
  112. },
  113. omit(location, 'span.category', 'http.method')
  114. );
  115. if (sorts) {
  116. eventView.sorts = sorts;
  117. }
  118. return eventView;
  119. }
  120. function buildEventViewQuery(
  121. moduleName: ModuleName,
  122. location: Location,
  123. transaction?: string,
  124. method?: string,
  125. spanCategory?: string
  126. ) {
  127. const {query} = location;
  128. const result = Object.keys(query)
  129. .filter(key => SPAN_FILTER_KEYS.includes(key))
  130. .filter(key => Boolean(query[key]))
  131. .map(key => {
  132. const value = query[key];
  133. const isArray = Array.isArray(value);
  134. if (key === '!span.category' && isArray && value.includes('db')) {
  135. // When omitting database spans, explicitly allow `db.redis` spans, because
  136. // we're not including those spans in the database category
  137. const categoriesAsideFromDatabase = value.filter(v => v !== 'db');
  138. return `(!span.category:db OR ${SPAN_OP}:db.redis) !span.category:[${categoriesAsideFromDatabase.join(
  139. ','
  140. )}]`;
  141. }
  142. return `${key}:${isArray ? `[${value}]` : value}`;
  143. });
  144. result.push(`has:${SPAN_DESCRIPTION}`);
  145. if (moduleName !== ModuleName.ALL) {
  146. result.push(`${SPAN_MODULE}:${moduleName}`);
  147. }
  148. if (moduleName === ModuleName.DB) {
  149. result.push(`!${SPAN_OP}:db.redis`);
  150. }
  151. if (defined(spanCategory)) {
  152. if (spanCategory === NULL_SPAN_CATEGORY) {
  153. result.push(`!has:span.category`);
  154. } else if (spanCategory !== 'Other') {
  155. result.push(`span.category:${spanCategory}`);
  156. }
  157. }
  158. if (transaction) {
  159. result.push(`transaction:${transaction}`);
  160. }
  161. if (method) {
  162. result.push(`transaction.method:${method}`);
  163. }
  164. return result;
  165. }