facetInsights.tsx 5.7 KB

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