import {Fragment} from 'react'; import styled from '@emotion/styled'; import * as qs from 'query-string'; import GridEditable, {GridColumnHeader} from 'sentry/components/gridEditable'; import Link from 'sentry/components/links/link'; import {CHART_PALETTE} from 'sentry/constants/chartPalette'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import {Series} from 'sentry/types/echarts'; import EventView from 'sentry/utils/discover/eventView'; import { DiscoverQueryProps, useGenericDiscoverQuery, } from 'sentry/utils/discover/genericDiscoverQuery'; import {MutableSearch} from 'sentry/utils/tokenizeSearch'; import {useLocation} from 'sentry/utils/useLocation'; import useOrganization from 'sentry/utils/useOrganization'; import {TAG_EXPLORER_COLUMN_ORDER} from 'sentry/views/performance/transactionSummary/transactionOverview/tagExplorer'; import Sparkline from 'sentry/views/starfish/components/sparkline'; import { OverflowEllipsisTextContainer, TextAlignLeft, } from 'sentry/views/starfish/modules/APIModule/endpointTable'; type Props = { eventView: EventView; }; type DataRow = { p50: Series; tagKey: string; tagValue: string; throughput: Series; tpmCorrelation: string; }; const COLUMN_ORDER = [ { key: 'tagKey', name: 'Key', width: 300, }, { key: 'tagValue', name: 'Value', width: 200, }, { key: 'p50', name: 'p50(duration)', width: 200, }, { key: 'throughput', name: 'tpm', width: 200, }, { key: 'tpmCorrelation', name: 'tpm correlation', width: 200, }, ]; function transformSeries(name, data): Series { return { seriesName: name, data: data.map(datum => { return {name: datum[0], value: datum[1][0].count}; }), }; } export function FacetInsights({eventView}: Props) { const location = useLocation(); const organization = useOrganization(); const facetStatsEventView = eventView.clone(); facetStatsEventView.fields = TAG_EXPLORER_COLUMN_ORDER; const sortedFacetStatsEventView = facetStatsEventView.withSorts([ { field: 'sumdelta', kind: 'desc', }, ]); function renderBodyCell(column: GridColumnHeader, row: DataRow): React.ReactNode { if (column.key === 'throughput' || column.key === 'p50') { return ( ); } if (column.key === 'tagValue') { const query = new MutableSearch(eventView.query); let queryFilter = ''; query.tokens.forEach(value => { if (value.key) { queryFilter = queryFilter.concat(' ', `${value.key}:${value.value}`); } }); queryFilter = queryFilter.concat(' ', `${row.tagKey}:${row.tagValue}`); return ( {row[column.key]} ); } return {row[column.key]}; } const {isLoading, data} = useGenericDiscoverQuery({ route: 'events-facets-stats', eventView, location, orgSlug: organization.slug, getRequestPayload: () => ({ ...sortedFacetStatsEventView.getEventsAPIPayload(location), aggregateColumn: 'transaction.duration', }), }); if (isLoading) { return null; } const transformedData: DataRow[] = []; const totals = data?.totals; const keys = Object.keys(totals); let showCorrelation = false; for (let index = 0; index < keys.length; index++) { const element = keys[index]; const tpmCorrelation = categorizeCorrelation(totals[element].sum_correlation); if (tpmCorrelation !== NO_CORRELATION) { showCorrelation = true; transformedData.push({ tagKey: element.split(',')[0], tagValue: element.split(',')[1], throughput: transformSeries('throughput', data![element]['count()'].data), p50: transformSeries('p50', data![element]['p75(transaction.duration)'].data), tpmCorrelation, }); } } if (showCorrelation === false) { return null; } return ( {t('Correlations')} renderBodyCell(column, row), }} /> ); } const NO_CORRELATION = 'no/low correlation'; function categorizeCorrelation(correlation: number): string { if (correlation >= 0.8) { return 'very highly correlated'; } if (correlation >= 0.6) { return 'highly correlated'; } if (correlation >= 0.4) { return 'correlated'; } return NO_CORRELATION; } const SubHeader = styled('h3')` color: ${p => p.theme.gray300}; font-size: ${p => p.theme.fontSizeLarge}; margin: 0; margin-bottom: ${space(1)}; `;