import React from 'react'; import styled from '@emotion/styled'; import Alert from 'sentry/components/alert'; import ProjectAvatar from 'sentry/components/avatar/projectAvatar'; 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 ExternalLink from 'sentry/components/links/externalLink'; import {DatePageFilter} from 'sentry/components/organizations/datePageFilter'; import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter'; import PageFilterBar from 'sentry/components/organizations/pageFilterBar'; import {t, tct} 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 { EMPTY_OPTION_VALUE, escapeFilterValue, MutableSearch, } from 'sentry/utils/tokenizeSearch'; import useLocationQuery from 'sentry/utils/url/useLocationQuery'; import {useLocation} from 'sentry/utils/useLocation'; import useProjects from 'sentry/utils/useProjects'; import {DurationChart} from 'sentry/views/performance/http/charts/durationChart'; import {ResponseRateChart} from 'sentry/views/performance/http/charts/responseRateChart'; import {ThroughputChart} from 'sentry/views/performance/http/charts/throughputChart'; import {DomainStatusLink} from 'sentry/views/performance/http/components/domainStatusLink'; import {HTTPSamplesPanel} from 'sentry/views/performance/http/httpSamplesPanel'; import {Referrer} from 'sentry/views/performance/http/referrers'; import { BASE_FILTERS, MODULE_DOC_LINK, NULL_DOMAIN_DESCRIPTION, } from 'sentry/views/performance/http/settings'; import { DomainTransactionsTable, isAValidSort, } from 'sentry/views/performance/http/tables/domainTransactionsTable'; import {MetricReadout} from 'sentry/views/performance/metricReadout'; import * as ModuleLayout from 'sentry/views/performance/moduleLayout'; import {ModulePageProviders} from 'sentry/views/performance/modulePageProviders'; import {useModuleBreadcrumbs} from 'sentry/views/performance/utils/useModuleBreadcrumbs'; import {useSynchronizeCharts} from 'sentry/views/starfish/components/chart'; 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 {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'; type Query = { aggregate?: string; domain?: string; }; export function HTTPDomainSummaryPage() { const location = useLocation(); const {projects} = useProjects(); // 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 {domain, project: projectId} = useLocationQuery({ fields: { project: decodeScalar, domain: decodeScalar, }, }); const project = projects.find(p => projectId === p.id); const filters: SpanMetricsQueryFilters = { ...BASE_FILTERS, 'span.domain': domain === '' ? EMPTY_OPTION_VALUE : escapeFilterValue(domain), }; const cursor = decodeScalar(location.query?.[QueryParameterNames.TRANSACTIONS_CURSOR]); const {data: domainMetrics, isLoading: areDomainMetricsLoading} = useSpanMetrics( { search: MutableSearch.fromQueryObject(filters), fields: [ `${SpanFunction.SPM}()`, `avg(${SpanMetricsField.SPAN_SELF_TIME})`, `sum(${SpanMetricsField.SPAN_SELF_TIME})`, 'http_response_rate(3)', 'http_response_rate(4)', 'http_response_rate(5)', `${SpanFunction.TIME_SPENT_PERCENTAGE}()`, ], }, Referrer.DOMAIN_SUMMARY_METRICS_RIBBON ); const { isLoading: isThroughputDataLoading, data: throughputData, error: throughputError, } = useSpanMetricsSeries( { search: MutableSearch.fromQueryObject(filters), yAxis: ['spm()'], }, Referrer.DOMAIN_SUMMARY_THROUGHPUT_CHART ); const { isLoading: isDurationDataLoading, data: durationData, error: durationError, } = useSpanMetricsSeries( { search: MutableSearch.fromQueryObject(filters), yAxis: [`avg(${SpanMetricsField.SPAN_SELF_TIME})`], }, Referrer.DOMAIN_SUMMARY_DURATION_CHART ); const { isLoading: isResponseCodeDataLoading, data: responseCodeData, error: responseCodeError, } = useSpanMetricsSeries( { search: MutableSearch.fromQueryObject(filters), yAxis: ['http_response_rate(3)', 'http_response_rate(4)', 'http_response_rate(5)'], }, Referrer.DOMAIN_SUMMARY_RESPONSE_CODE_CHART ); const { isLoading: isTransactionsListLoading, data: transactionsList, meta: transactionsListMeta, error: transactionsListError, pageLinks: transactionsListPageLinks, } = useSpanMetrics( { search: MutableSearch.fromQueryObject(filters), fields: [ 'project.id', 'transaction', 'transaction.method', 'spm()', 'http_response_rate(3)', 'http_response_rate(4)', 'http_response_rate(5)', 'avg(span.self_time)', 'sum(span.self_time)', 'time_spent_percentage()', ], sorts: [sort], limit: TRANSACTIONS_TABLE_ROW_COUNT, cursor, }, Referrer.DOMAIN_SUMMARY_TRANSACTIONS_LIST ); useSynchronizeCharts([!isThroughputDataLoading && !isDurationDataLoading]); const crumbs = useModuleBreadcrumbs('http'); return ( {project && } {domain || NULL_DOMAIN_DESCRIPTION} {domain === '' && ( {tct( '"Unknown Domain" entries can be caused by instrumentation errors. Please refer to our [link] for more information.', { link: documentation, } )} )} ); } const DEFAULT_SORT = { field: 'time_spent_percentage()' as const, kind: 'desc' as const, }; const TRANSACTIONS_TABLE_ROW_COUNT = 20; const HeaderContainer = styled('div')` display: flex; justify-content: space-between; flex-wrap: wrap; `; const MetricsRibbon = styled('div')` display: flex; flex-wrap: wrap; gap: ${space(4)}; `; function PageWithProviders() { return ( ); } export default PageWithProviders;