123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254 |
- import {useEffect} from 'react';
- import {browserHistory} from 'react-router';
- import {Location} from 'history';
- import {loadOrganizationTags} from 'sentry/actionCreators/tags';
- import {t} from 'sentry/locale';
- import {Organization, PageFilters, Project} from 'sentry/types';
- import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent';
- import {useDiscoverQuery} from 'sentry/utils/discover/discoverQuery';
- import EventView from 'sentry/utils/discover/eventView';
- import {Column, isAggregateField, QueryFieldValue} from 'sentry/utils/discover/fields';
- import {WebVital} from 'sentry/utils/fields';
- import {MEPSettingProvider} from 'sentry/utils/performance/contexts/metricsEnhancedSetting';
- import {removeHistogramQueryStrings} from 'sentry/utils/performance/histogram';
- import {decodeScalar} from 'sentry/utils/queryString';
- import {MutableSearch} from 'sentry/utils/tokenizeSearch';
- import useApi from 'sentry/utils/useApi';
- import withOrganization from 'sentry/utils/withOrganization';
- import withPageFilters from 'sentry/utils/withPageFilters';
- import withProjects from 'sentry/utils/withProjects';
- import {addRoutePerformanceContext} from '../../utils';
- import {
- decodeFilterFromLocation,
- filterToLocationQuery,
- SpanOperationBreakdownFilter,
- } from '../filter';
- import PageLayout, {ChildProps} from '../pageLayout';
- import Tab from '../tabs';
- import {
- PERCENTILE as VITAL_PERCENTILE,
- VITAL_GROUPS,
- } from '../transactionVitals/constants';
- import {ZOOM_END, ZOOM_START} from './latencyChart/utils';
- import SummaryContent from './content';
- // Used to cast the totals request to numbers
- // as React.ReactText
- type TotalValues = Record<string, number>;
- type Props = {
- location: Location;
- organization: Organization;
- projects: Project[];
- selection: PageFilters;
- };
- function TransactionOverview(props: Props) {
- const api = useApi();
- const {location, selection, organization, projects} = props;
- useEffect(() => {
- loadOrganizationTags(api, organization.slug, selection);
- addRoutePerformanceContext(selection);
- trackAdvancedAnalyticsEvent('performance_views.transaction_summary.view', {
- organization,
- });
- }, [selection, organization, api]);
- return (
- <MEPSettingProvider>
- <PageLayout
- location={location}
- organization={organization}
- projects={projects}
- tab={Tab.TransactionSummary}
- getDocumentTitle={getDocumentTitle}
- generateEventView={generateEventView}
- childComponent={OverviewContentWrapper}
- />
- </MEPSettingProvider>
- );
- }
- function OverviewContentWrapper(props: ChildProps) {
- const {
- location,
- organization,
- eventView,
- projectId,
- transactionName,
- transactionThreshold,
- transactionThresholdMetric,
- } = props;
- const useEvents = organization.features.includes(
- 'performance-frontend-use-events-endpoint'
- );
- const queryData = useDiscoverQuery({
- eventView: getTotalsEventView(organization, eventView),
- orgSlug: organization.slug,
- location,
- transactionThreshold,
- transactionThresholdMetric,
- referrer: 'api.performance.transaction-summary',
- useEvents,
- });
- const {data: tableData, isLoading, error} = queryData;
- const spanOperationBreakdownFilter = decodeFilterFromLocation(location);
- const onChangeFilter = (newFilter: SpanOperationBreakdownFilter) => {
- trackAdvancedAnalyticsEvent('performance_views.filter_dropdown.selection', {
- organization,
- action: newFilter as string,
- });
- const nextQuery: Location['query'] = {
- ...removeHistogramQueryStrings(location, [ZOOM_START, ZOOM_END]),
- ...filterToLocationQuery(newFilter),
- };
- if (newFilter === SpanOperationBreakdownFilter.None) {
- delete nextQuery.breakdown;
- }
- browserHistory.push({
- pathname: location.pathname,
- query: nextQuery,
- });
- };
- const totals: TotalValues | null =
- (tableData?.data?.[0] as {[k: string]: number}) ?? null;
- return (
- <SummaryContent
- location={location}
- organization={organization}
- eventView={eventView}
- projectId={projectId}
- transactionName={transactionName}
- isLoading={isLoading}
- error={error}
- totalValues={totals}
- onChangeFilter={onChangeFilter}
- spanOperationBreakdownFilter={spanOperationBreakdownFilter}
- />
- );
- }
- function getDocumentTitle(transactionName: string): string {
- const hasTransactionName =
- typeof transactionName === 'string' && String(transactionName).trim().length > 0;
- if (hasTransactionName) {
- return [String(transactionName).trim(), t('Performance')].join(' - ');
- }
- return [t('Summary'), t('Performance')].join(' - ');
- }
- function generateEventView({
- location,
- organization,
- transactionName,
- }: {
- location: Location;
- organization: Organization;
- transactionName: string;
- }): EventView {
- // Use the user supplied query but overwrite any transaction or event type
- // conditions they applied.
- const query = decodeScalar(location.query.query, '');
- const conditions = new MutableSearch(query);
- conditions.setFilterValues('event.type', ['transaction']);
- conditions.setFilterValues('transaction', [transactionName]);
- Object.keys(conditions.filters).forEach(field => {
- if (isAggregateField(field)) {
- conditions.removeFilter(field);
- }
- });
- const fields = ['id', 'user.display', 'transaction.duration', 'trace', 'timestamp'];
- if (organization.features.includes('session-replay-ui')) {
- fields.push('replayId');
- }
- return EventView.fromNewQueryWithLocation(
- {
- id: undefined,
- version: 2,
- name: transactionName,
- fields,
- query: conditions.formatString(),
- projects: [],
- },
- location
- );
- }
- function getTotalsEventView(
- _organization: Organization,
- eventView: EventView
- ): EventView {
- const vitals = VITAL_GROUPS.map(({vitals: vs}) => vs).reduce((keys: WebVital[], vs) => {
- vs.forEach(vital => keys.push(vital));
- return keys;
- }, []);
- const totalsColumns: QueryFieldValue[] = [
- {
- kind: 'function',
- function: ['p95', '', undefined, undefined],
- },
- {
- kind: 'function',
- function: ['count', '', undefined, undefined],
- },
- {
- kind: 'function',
- function: ['count_unique', 'user', undefined, undefined],
- },
- {
- kind: 'function',
- function: ['failure_rate', '', undefined, undefined],
- },
- {
- kind: 'function',
- function: ['tpm', '', undefined, undefined],
- },
- {
- kind: 'function',
- function: ['count_miserable', 'user', undefined, undefined],
- },
- {
- kind: 'function',
- function: ['user_misery', '', undefined, undefined],
- },
- {
- kind: 'function',
- function: ['apdex', '', undefined, undefined],
- },
- ];
- return eventView.withColumns([
- ...totalsColumns,
- ...vitals.map(
- vital =>
- ({
- kind: 'function',
- function: ['percentile', vital, VITAL_PERCENTILE.toString(), undefined],
- } as Column)
- ),
- ]);
- }
- export default withPageFilters(withProjects(withOrganization(TransactionOverview)));
|