import {Fragment} from 'react'; import type {RouteComponentProps} from 'react-router'; import styled from '@emotion/styled'; import {Breadcrumbs} from 'sentry/components/breadcrumbs'; import ButtonBar from 'sentry/components/buttonBar'; import FeedbackWidgetButton from 'sentry/components/feedback/widget/feedbackWidgetButton'; import * as Layout from 'sentry/components/layouts/thirds'; import {DatePageFilter} from 'sentry/components/organizations/datePageFilter'; import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter'; import PageFilterBar from 'sentry/components/organizations/pageFilterBar'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import {DurationUnit, RateUnit} from 'sentry/utils/discover/fields'; import {decodeScalar, decodeSorts} from 'sentry/utils/queryString'; import {MutableSearch} from 'sentry/utils/tokenizeSearch'; import {useLocation} from 'sentry/utils/useLocation'; import useOrganization from 'sentry/utils/useOrganization'; import {normalizeUrl} from 'sentry/utils/withDomainRequired'; import {DurationChart} from 'sentry/views/performance/database/durationChart'; import {isAValidSort} from 'sentry/views/performance/database/queriesTable'; import {QueryTransactionsTable} from 'sentry/views/performance/database/queryTransactionsTable'; import {BASE_URL} from 'sentry/views/performance/database/settings'; import {ThroughputChart} from 'sentry/views/performance/database/throughputChart'; import {useSelectedDurationAggregate} from 'sentry/views/performance/database/useSelectedDurationAggregate'; import {MetricReadout} from 'sentry/views/performance/metricReadout'; import * as ModuleLayout from 'sentry/views/performance/moduleLayout'; import {ModulePageProviders} from 'sentry/views/performance/modulePageProviders'; import {useDatabaseModuleURL} from 'sentry/views/performance/utils/useModuleURL'; import {useSynchronizeCharts} from 'sentry/views/starfish/components/chart'; import {DatabaseSpanDescription} from 'sentry/views/starfish/components/spanDescription'; import {getTimeSpentExplanation} from 'sentry/views/starfish/components/tableCells/timeSpentCell'; import {useSpanMetrics} from 'sentry/views/starfish/queries/useDiscover'; import {useSpanMetricsSeries} from 'sentry/views/starfish/queries/useDiscoverSeries'; import type {SpanMetricsQueryFilters} from 'sentry/views/starfish/types'; import {ModuleName, SpanFunction, SpanMetricsField} from 'sentry/views/starfish/types'; import {QueryParameterNames} from 'sentry/views/starfish/views/queryParameters'; import {DataTitles, getThroughputTitle} from 'sentry/views/starfish/views/spans/types'; import {SampleList} from 'sentry/views/starfish/views/spanSummaryPage/sampleList'; type Query = { transaction: string; transactionMethod: string; [QueryParameterNames.TRANSACTIONS_SORT]: string; aggregate?: string; }; type Props = RouteComponentProps; export function DatabaseSpanSummaryPage({params}: Props) { const moduleURL = useDatabaseModuleURL(); const organization = useOrganization(); const location = useLocation(); const [selectedAggregate] = useSelectedDurationAggregate(); const {groupId} = params; const {transaction, transactionMethod} = location.query; const filters: SpanMetricsQueryFilters = { 'span.group': groupId, }; const cursor = decodeScalar(location.query?.[QueryParameterNames.TRANSACTIONS_CURSOR]); // TODO: Fetch sort information using `useLocationQuery` const sortField = decodeScalar(location.query?.[QueryParameterNames.TRANSACTIONS_SORT]); const sort = decodeSorts(sortField).filter(isAValidSort).at(0) ?? DEFAULT_SORT; const {data, isLoading: areSpanMetricsLoading} = useSpanMetrics( { search: MutableSearch.fromQueryObject(filters), fields: [ SpanMetricsField.SPAN_OP, SpanMetricsField.SPAN_DESCRIPTION, SpanMetricsField.SPAN_ACTION, SpanMetricsField.SPAN_DOMAIN, 'count()', `${SpanFunction.SPM}()`, `sum(${SpanMetricsField.SPAN_SELF_TIME})`, `avg(${SpanMetricsField.SPAN_SELF_TIME})`, `${SpanFunction.TIME_SPENT_PERCENTAGE}()`, `${SpanFunction.HTTP_ERROR_COUNT}()`, ], enabled: Boolean(groupId), }, 'api.starfish.span-summary-page-metrics' ); const spanMetrics = data[0] ?? {}; const { isLoading: isTransactionsListLoading, data: transactionsList, meta: transactionsListMeta, error: transactionsListError, pageLinks: transactionsListPageLinks, } = useSpanMetrics( { search: MutableSearch.fromQueryObject(filters), fields: [ 'transaction', 'transaction.method', 'spm()', `sum(${SpanMetricsField.SPAN_SELF_TIME})`, `avg(${SpanMetricsField.SPAN_SELF_TIME})`, 'time_spent_percentage()', `${SpanFunction.HTTP_ERROR_COUNT}()`, ], sorts: [sort], limit: TRANSACTIONS_TABLE_ROW_COUNT, cursor, }, 'api.starfish.span-transaction-metrics' ); const span = { ...spanMetrics, [SpanMetricsField.SPAN_GROUP]: groupId, } as { [SpanMetricsField.SPAN_OP]: string; [SpanMetricsField.SPAN_DESCRIPTION]: string; [SpanMetricsField.SPAN_ACTION]: string; [SpanMetricsField.SPAN_DOMAIN]: string[]; [SpanMetricsField.SPAN_GROUP]: string; }; const { isLoading: isThroughputDataLoading, data: throughputData, error: throughputError, } = useSpanMetricsSeries( { search: MutableSearch.fromQueryObject(filters), yAxis: ['spm()'], enabled: Boolean(groupId), }, 'api.starfish.span-summary-page-metrics-chart' ); const { isLoading: isDurationDataLoading, data: durationData, error: durationError, } = useSpanMetricsSeries( { search: MutableSearch.fromQueryObject(filters), yAxis: [`${selectedAggregate}(${SpanMetricsField.SPAN_SELF_TIME})`], enabled: Boolean(groupId), }, 'api.starfish.span-summary-page-metrics-chart' ); useSynchronizeCharts([!isThroughputDataLoading && !isDurationDataLoading]); return ( {t('Query Summary')} {groupId && ( )} {span && ( )} ); } const DEFAULT_SORT = { kind: 'desc' as const, field: 'time_spent_percentage()' as const, }; const TRANSACTIONS_TABLE_ROW_COUNT = 25; const ChartContainer = styled('div')` display: grid; gap: 0; grid-template-columns: 1fr; @media (min-width: ${p => p.theme.breakpoints.small}) { grid-template-columns: 1fr 1fr; gap: ${space(2)}; } `; const HeaderContainer = styled('div')` display: flex; justify-content: space-between; flex-wrap: wrap; `; const DescriptionContainer = styled(ModuleLayout.Full)` line-height: 1.2; `; const MetricsRibbon = styled('div')` display: flex; flex-wrap: wrap; gap: ${space(4)}; `; function PageWithProviders(props) { return ( ); } export default PageWithProviders;