import {browserHistory} from 'react-router'; import styled from '@emotion/styled'; import type {Location} from 'history'; import omit from 'lodash/omit'; import {Button} from 'sentry/components/button'; import {CompactSelect} from 'sentry/components/compactSelect'; import SearchBar from 'sentry/components/events/searchBar'; 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 {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {Organization, Project} from 'sentry/types'; import {trackAnalytics} from 'sentry/utils/analytics'; import type EventView from 'sentry/utils/discover/eventView'; import type {WebVital} from 'sentry/utils/fields'; import {decodeScalar} from 'sentry/utils/queryString'; import projectSupportsReplay from 'sentry/utils/replays/projectSupportsReplay'; import {useRoutes} from 'sentry/utils/useRoutes'; import { platformToPerformanceType, ProjectPerformanceType, } from 'sentry/views/performance/utils'; import type {SpanOperationBreakdownFilter} from '../filter'; import Filter, {filterToSearchConditions} from '../filter'; import type {SetStateAction} from '../types'; import EventsTable from './eventsTable'; import type {EventsDisplayFilterName} from './utils'; import {getEventsFilterOptions} from './utils'; type Props = { eventView: EventView; eventsDisplayFilterName: EventsDisplayFilterName; location: Location; onChangeEventsDisplayFilter: (eventsDisplayFilterName: EventsDisplayFilterName) => void; onChangeSpanOperationBreakdownFilter: (newFilter: SpanOperationBreakdownFilter) => void; organization: Organization; projectId: string; projects: Project[]; setError: SetStateAction; spanOperationBreakdownFilter: SpanOperationBreakdownFilter; transactionName: string; percentileValues?: Record; webVital?: WebVital; }; export const TRANSACTIONS_LIST_TITLES: Readonly = [ t('event id'), t('user'), t('operation duration'), t('total duration'), t('trace id'), t('timestamp'), ]; function EventsContent(props: Props) { const { location, organization, eventView: originalEventView, transactionName, spanOperationBreakdownFilter, webVital, setError, projectId, projects, } = props; const routes = useRoutes(); const eventView = originalEventView.clone(); const transactionsListTitles = TRANSACTIONS_LIST_TITLES.slice(); const project = projects.find(p => p.id === projectId); const fields = [...eventView.fields]; if (webVital) { transactionsListTitles.splice(3, 0, webVital); } const spanOperationBreakdownConditions = filterToSearchConditions( spanOperationBreakdownFilter, location ); if (spanOperationBreakdownConditions) { eventView.query = `${eventView.query} ${spanOperationBreakdownConditions}`.trim(); transactionsListTitles.splice(2, 1, t('%s duration', spanOperationBreakdownFilter)); } const platform = platformToPerformanceType(projects, eventView.project); if (platform === ProjectPerformanceType.BACKEND) { const userIndex = transactionsListTitles.indexOf('user'); if (userIndex > 0) { transactionsListTitles.splice(userIndex + 1, 0, 'http.method'); fields.splice(userIndex + 1, 0, {field: 'http.method'}); } } if ( organization.features.includes('profiling') && project && // only show for projects that already sent a profile // once we have a more compact design we will show this for // projects that support profiling as well project.hasProfiles ) { transactionsListTitles.push(t('profile')); fields.push({field: 'profile.id'}); } if ( organization.features.includes('session-replay') && project && projectSupportsReplay(project) ) { transactionsListTitles.push(t('replay')); fields.push({field: 'replayId'}); } eventView.fields = fields; return ( ); } function Search(props: Props) { const { eventView, location, organization, spanOperationBreakdownFilter, onChangeSpanOperationBreakdownFilter, eventsDisplayFilterName, onChangeEventsDisplayFilter, percentileValues, } = props; const handleSearch = (query: string) => { const queryParams = normalizeDateTimeParams({ ...(location.query || {}), query, }); // do not propagate pagination when making a new search const searchQueryParams = omit(queryParams, 'cursor'); browserHistory.push({ pathname: location.pathname, query: searchQueryParams, }); }; const query = decodeScalar(location.query.query, ''); const eventsFilterOptions = getEventsFilterOptions( spanOperationBreakdownFilter, percentileValues ); const handleDiscoverButtonClick = () => { trackAnalytics('performance_views.all_events.open_in_discover', { organization, }); }; return ( onChangeEventsDisplayFilter(opt.value)} options={Object.entries(eventsFilterOptions).map(([name, filter]) => ({ value: name as EventsDisplayFilterName, label: filter.label, }))} /> ); } const FilterActions = styled('div')` display: grid; gap: ${space(2)}; margin-bottom: ${space(2)}; @media (min-width: ${p => p.theme.breakpoints.small}) { grid-template-columns: repeat(4, min-content); } @media (min-width: ${p => p.theme.breakpoints.xlarge}) { grid-template-columns: auto auto 1fr auto auto; } `; const StyledSearchBar = styled(SearchBar)` @media (min-width: ${p => p.theme.breakpoints.small}) { order: 1; grid-column: 1/6; } @media (min-width: ${p => p.theme.breakpoints.xlarge}) { order: initial; grid-column: auto; } `; export default EventsContent;