123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291 |
- import {browserHistory} from 'react-router';
- import {Location} from 'history';
- import * as Layout from 'sentry/components/layouts/thirds';
- import LoadingIndicator from 'sentry/components/loadingIndicator';
- import {t} from 'sentry/locale';
- import {Organization, Project} from 'sentry/types';
- import {trackAnalyticsEvent} from 'sentry/utils/analytics';
- import DiscoverQuery from 'sentry/utils/discover/discoverQuery';
- import EventView from 'sentry/utils/discover/eventView';
- import {
- isAggregateField,
- SPAN_OP_BREAKDOWN_FIELDS,
- SPAN_OP_RELATIVE_BREAKDOWN_FIELD,
- } from 'sentry/utils/discover/fields';
- import {WebVital} from 'sentry/utils/fields';
- import {removeHistogramQueryStrings} from 'sentry/utils/performance/histogram';
- import {decodeScalar} from 'sentry/utils/queryString';
- import {MutableSearch} from 'sentry/utils/tokenizeSearch';
- import withOrganization from 'sentry/utils/withOrganization';
- import withProjects from 'sentry/utils/withProjects';
- import {
- decodeFilterFromLocation,
- filterToLocationQuery,
- SpanOperationBreakdownFilter,
- } from '../filter';
- import PageLayout, {ChildProps} from '../pageLayout';
- import Tab from '../tabs';
- import {ZOOM_END, ZOOM_START} from '../transactionOverview/latencyChart/utils';
- import EventsContent from './content';
- import {
- decodeEventsDisplayFilterFromLocation,
- EventsDisplayFilterName,
- filterEventsDisplayToLocationQuery,
- getEventsFilterOptions,
- getPercentilesEventView,
- mapPercentileValues,
- } from './utils';
- type PercentileValues = Record<EventsDisplayFilterName, number>;
- type Props = {
- location: Location;
- organization: Organization;
- projects: Project[];
- };
- function TransactionEvents(props: Props) {
- const {location, organization, projects} = props;
- return (
- <PageLayout
- location={location}
- organization={organization}
- projects={projects}
- tab={Tab.Events}
- getDocumentTitle={getDocumentTitle}
- generateEventView={generateEventView}
- childComponent={EventsContentWrapper}
- />
- );
- }
- function EventsContentWrapper(props: ChildProps) {
- const {location, organization, eventView, transactionName, setError} = props;
- const eventsDisplayFilterName = decodeEventsDisplayFilterFromLocation(location);
- const spanOperationBreakdownFilter = decodeFilterFromLocation(location);
- const webVital = getWebVital(location);
- const totalEventsView = eventView.clone();
- const percentilesView = getPercentilesEventView(eventView);
- totalEventsView.sorts = [];
- totalEventsView.fields = [{field: 'count()', width: -1}];
- const getFilteredEventView = (percentiles: PercentileValues) => {
- const filter = getEventsFilterOptions(spanOperationBreakdownFilter, percentiles)[
- eventsDisplayFilterName
- ];
- const filteredEventView = eventView?.clone();
- if (filteredEventView && filter?.query) {
- const query = new MutableSearch(filteredEventView.query);
- filter.query.forEach(item => query.setFilterValues(item[0], [item[1]]));
- filteredEventView.query = query.formatString();
- }
- return filteredEventView;
- };
- const onChangeSpanOperationBreakdownFilter = (
- newFilter: SpanOperationBreakdownFilter
- ) => {
- trackAnalyticsEvent({
- eventName: 'Performance Views: Transaction Events Ops Breakdown Filter Dropdown',
- eventKey: 'performance_views.transactionEvents.ops_filter_dropdown.selection',
- organization_id: parseInt(organization.id, 10),
- action: newFilter as string,
- });
- // Check to see if the current table sort matches the EventsDisplayFilter.
- // If it does, we can re-sort using the new SpanOperationBreakdownFilter
- const eventsFilterOptionSort = getEventsFilterOptions(spanOperationBreakdownFilter)[
- eventsDisplayFilterName
- ].sort;
- const currentSort = eventView?.sorts?.[0];
- let sortQuery = {};
- if (
- eventsFilterOptionSort?.kind === currentSort?.kind &&
- eventsFilterOptionSort?.field === currentSort?.field
- ) {
- sortQuery = filterEventsDisplayToLocationQuery(eventsDisplayFilterName, newFilter);
- }
- const nextQuery: Location['query'] = {
- ...removeHistogramQueryStrings(location, [ZOOM_START, ZOOM_END]),
- ...filterToLocationQuery(newFilter),
- ...sortQuery,
- };
- if (newFilter === SpanOperationBreakdownFilter.None) {
- delete nextQuery.breakdown;
- }
- browserHistory.push({
- pathname: location.pathname,
- query: nextQuery,
- });
- };
- const onChangeEventsDisplayFilter = (newFilterName: EventsDisplayFilterName) => {
- trackAnalyticsEvent({
- eventName: 'Performance Views: Transaction Events Display Filter Dropdown',
- eventKey: 'performance_views.transactionEvents.display_filter_dropdown.selection',
- organization_id: parseInt(organization.id, 10),
- action: newFilterName as string,
- });
- const nextQuery: Location['query'] = {
- ...removeHistogramQueryStrings(location, [ZOOM_START, ZOOM_END]),
- ...filterEventsDisplayToLocationQuery(newFilterName, spanOperationBreakdownFilter),
- };
- if (newFilterName === EventsDisplayFilterName.p100) {
- delete nextQuery.showTransaction;
- }
- browserHistory.push({
- pathname: location.pathname,
- query: nextQuery,
- });
- };
- return (
- <DiscoverQuery
- eventView={totalEventsView}
- orgSlug={organization.slug}
- location={location}
- setError={error => setError(error?.message)}
- referrer="api.performance.transaction-summary"
- cursor="0:0:0"
- useEvents
- >
- {({isLoading: isTotalEventsLoading, tableData: table}) => {
- const totalEventCount: string =
- table?.data[0]?.['count()']?.toLocaleString() || '';
- return (
- <DiscoverQuery
- eventView={percentilesView}
- orgSlug={organization.slug}
- location={location}
- referrer="api.performance.transaction-events"
- useEvents
- >
- {({isLoading, tableData}) => {
- if (isTotalEventsLoading || isLoading) {
- return (
- <Layout.Main fullWidth>
- <LoadingIndicator />
- </Layout.Main>
- );
- }
- const percentileData = tableData?.data?.[0];
- const percentiles = mapPercentileValues(percentileData);
- const filteredEventView = getFilteredEventView(percentiles);
- return (
- <EventsContent
- totalEventCount={totalEventCount}
- location={location}
- organization={organization}
- eventView={filteredEventView}
- transactionName={transactionName}
- spanOperationBreakdownFilter={spanOperationBreakdownFilter}
- onChangeSpanOperationBreakdownFilter={
- onChangeSpanOperationBreakdownFilter
- }
- eventsDisplayFilterName={eventsDisplayFilterName}
- onChangeEventsDisplayFilter={onChangeEventsDisplayFilter}
- percentileValues={percentiles}
- webVital={webVital}
- setError={setError}
- />
- );
- }}
- </DiscoverQuery>
- );
- }}
- </DiscoverQuery>
- );
- }
- function getDocumentTitle(transactionName: string): string {
- const hasTransactionName =
- typeof transactionName === 'string' && String(transactionName).trim().length > 0;
- if (hasTransactionName) {
- return [String(transactionName).trim(), t('Events')].join(' \u2014 ');
- }
- return [t('Summary'), t('Events')].join(' \u2014 ');
- }
- function getWebVital(location: Location): WebVital | undefined {
- const webVital = decodeScalar(location.query.webVital, '') as WebVital;
- if (Object.values(WebVital).includes(webVital)) {
- return webVital;
- }
- return undefined;
- }
- function generateEventView({
- location,
- organization,
- transactionName,
- }: {
- location: Location;
- organization: Organization;
- transactionName: string;
- }): EventView {
- 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);
- }
- });
- // Default fields for relative span view
- const fields = [
- 'id',
- 'user.display',
- SPAN_OP_RELATIVE_BREAKDOWN_FIELD,
- 'transaction.duration',
- 'trace',
- 'timestamp',
- ];
- if (organization.features.includes('session-replay-ui')) {
- fields.push('replayId');
- }
- const breakdown = decodeFilterFromLocation(location);
- if (breakdown !== SpanOperationBreakdownFilter.None) {
- fields.splice(2, 1, `spans.${breakdown}`);
- } else {
- fields.push(...SPAN_OP_BREAKDOWN_FIELDS);
- }
- const webVital = getWebVital(location);
- if (webVital) {
- fields.splice(3, 0, webVital);
- }
- return EventView.fromNewQueryWithLocation(
- {
- id: undefined,
- version: 2,
- name: transactionName,
- fields,
- query: conditions.formatString(),
- projects: [],
- orderby: decodeScalar(location.query.sort, '-timestamp'),
- },
- location
- );
- }
- export default withProjects(withOrganization(TransactionEvents));
|