facetInsights.tsx 5.5 KB

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