facetInsights.tsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. import * as qs from 'query-string';
  2. import GridEditable, {GridColumnHeader} from 'sentry/components/gridEditable';
  3. import Link from 'sentry/components/links/link';
  4. import Placeholder from 'sentry/components/placeholder';
  5. import {CHART_PALETTE} from 'sentry/constants/chartPalette';
  6. import {Series} from 'sentry/types/echarts';
  7. import EventView from 'sentry/utils/discover/eventView';
  8. import {
  9. DiscoverQueryProps,
  10. useGenericDiscoverQuery,
  11. } from 'sentry/utils/discover/genericDiscoverQuery';
  12. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  13. import {useLocation} from 'sentry/utils/useLocation';
  14. import useOrganization from 'sentry/utils/useOrganization';
  15. import {TAG_EXPLORER_COLUMN_ORDER} from 'sentry/views/performance/transactionSummary/transactionOverview/tagExplorer';
  16. import Sparkline from 'sentry/views/starfish/components/sparkline';
  17. import {
  18. OverflowEllipsisTextContainer,
  19. TextAlignLeft,
  20. } from 'sentry/views/starfish/modules/APIModule/endpointTable';
  21. type Props = {
  22. eventView: EventView;
  23. };
  24. type DataRow = {
  25. p50: Series;
  26. tagKey: string;
  27. tagValue: string;
  28. throughput: Series;
  29. tpmCorrelation: string;
  30. };
  31. const COLUMN_ORDER = [
  32. {
  33. key: 'tagKey',
  34. name: 'Key',
  35. width: 300,
  36. },
  37. {
  38. key: 'tagValue',
  39. name: 'Value',
  40. width: 200,
  41. },
  42. {
  43. key: 'p50',
  44. name: 'p50(duration)',
  45. width: 200,
  46. },
  47. {
  48. key: 'throughput',
  49. name: 'tpm',
  50. width: 200,
  51. },
  52. {
  53. key: 'tpmCorrelation',
  54. name: 'tpm correlation',
  55. width: 200,
  56. },
  57. ];
  58. function transformSeries(name, data): Series {
  59. return {
  60. seriesName: name,
  61. data: data.map(datum => {
  62. return {name: datum[0], value: datum[1][0].count};
  63. }),
  64. };
  65. }
  66. export function FacetInsights({eventView}: Props) {
  67. const location = useLocation();
  68. const organization = useOrganization();
  69. const facetStatsEventView = eventView.clone();
  70. facetStatsEventView.fields = TAG_EXPLORER_COLUMN_ORDER;
  71. const sortedFacetStatsEventView = facetStatsEventView.withSorts([
  72. {
  73. field: 'sumdelta',
  74. kind: 'desc',
  75. },
  76. ]);
  77. function renderBodyCell(column: GridColumnHeader, row: DataRow): React.ReactNode {
  78. if (column.key === 'throughput' || column.key === 'p50') {
  79. return (
  80. <Sparkline
  81. color={column.key === 'throughput' ? CHART_PALETTE[3][0] : CHART_PALETTE[3][1]}
  82. series={row[column.key]}
  83. width={column.width ? column.width - column.width / 5 : undefined}
  84. />
  85. );
  86. }
  87. if (column.key === 'tagValue') {
  88. const query = new MutableSearch(eventView.query);
  89. let queryFilter = '';
  90. query.tokens.forEach(value => {
  91. if (value.key) {
  92. queryFilter = queryFilter.concat(' ', `${value.key}:${value.value}`);
  93. }
  94. });
  95. queryFilter = queryFilter.concat(' ', `${row.tagKey}:${row.tagValue}`);
  96. return (
  97. <OverflowEllipsisTextContainer>
  98. <Link
  99. to={`/discover/homepage/?${qs.stringify({
  100. ...eventView.generateQueryStringObject(),
  101. query: queryFilter,
  102. field: [
  103. 'title',
  104. 'p50(transaction.duration)',
  105. 'p75(transaction.duration)',
  106. 'p95(transaction.duration)',
  107. ],
  108. yAxis: 'count()',
  109. })}`}
  110. >
  111. {row[column.key]}
  112. </Link>
  113. </OverflowEllipsisTextContainer>
  114. );
  115. }
  116. return <TextAlignLeft>{row[column.key]}</TextAlignLeft>;
  117. }
  118. const {isLoading, data} = useGenericDiscoverQuery<any, DiscoverQueryProps>({
  119. route: 'events-facets-stats',
  120. eventView,
  121. location,
  122. orgSlug: organization.slug,
  123. getRequestPayload: () => ({
  124. ...sortedFacetStatsEventView.getEventsAPIPayload(location),
  125. aggregateColumn: 'transaction.duration',
  126. }),
  127. });
  128. if (isLoading) {
  129. return <Placeholder height="400px" />;
  130. }
  131. const transformedData: DataRow[] = [];
  132. const totals = data?.totals;
  133. const keys = Object.keys(totals);
  134. for (let index = 0; index < keys.length; index++) {
  135. const element = keys[index];
  136. transformedData.push({
  137. tagKey: element.split(',')[0],
  138. tagValue: element.split(',')[1],
  139. throughput: transformSeries('throughput', data![element]['count()'].data),
  140. p50: transformSeries('p50', data![element]['p75(transaction.duration)'].data),
  141. tpmCorrelation: categorizeCorrelation(totals[element].sum_correlation),
  142. });
  143. }
  144. return (
  145. <GridEditable
  146. isLoading={isLoading}
  147. data={transformedData}
  148. columnOrder={COLUMN_ORDER}
  149. columnSortBy={[]}
  150. location={location}
  151. grid={{
  152. renderBodyCell: (column: GridColumnHeader, row: DataRow) =>
  153. renderBodyCell(column, row),
  154. }}
  155. />
  156. );
  157. }
  158. function categorizeCorrelation(correlation: number): string {
  159. if (correlation >= 0.8) {
  160. return 'very highly correlated';
  161. }
  162. if (correlation >= 0.6) {
  163. return 'highly correlated';
  164. }
  165. if (correlation >= 0.4) {
  166. return 'correlated';
  167. }
  168. return 'no/low correlation';
  169. }