import {Fragment, useCallback, useMemo} from 'react'; import styled from '@emotion/styled'; import type {Location} from 'history'; import omit from 'lodash/omit'; 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 Pagination from 'sentry/components/pagination'; import {SpanSearchQueryBuilder} from 'sentry/components/performance/spanSearchQueryBuilder'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {Organization} from 'sentry/types/organization'; import {defined} from 'sentry/utils'; import {trackAnalytics} from 'sentry/utils/analytics'; import {browserHistory} from 'sentry/utils/browserHistory'; import DiscoverQuery from 'sentry/utils/discover/discoverQuery'; import type EventView from 'sentry/utils/discover/eventView'; import {DiscoverDatasets} from 'sentry/utils/discover/types'; import SuspectSpansQuery from 'sentry/utils/performance/suspectSpans/suspectSpansQuery'; import {VisuallyCompleteWithData} from 'sentry/utils/performanceForSentry'; import {decodeScalar} from 'sentry/utils/queryString'; import useProjects from 'sentry/utils/useProjects'; import SpanMetricsTable from 'sentry/views/performance/transactionSummary/transactionSpans/spanMetricsTable'; import {useSpanMetricsFieldSupportedTags} from 'sentry/views/performance/utils/useSpanFieldSupportedTags'; import type {SetStateAction} from '../types'; import OpsFilter from './opsFilter'; import SuspectSpansTable from './suspectSpansTable'; import type {SpanSort, SpansTotalValues} from './types'; import { getSuspectSpanSortFromEventView, getTotalsView, SPAN_RELATIVE_PERIODS, SPAN_RETENTION_DAYS, SPAN_SORT_OPTIONS, SPAN_SORT_TO_FIELDS, } from './utils'; const ANALYTICS_VALUES = { spanOp: (organization: Organization, value: string | undefined) => trackAnalytics('performance_views.spans.change_op', { organization, operation_name: value, }), sort: (organization: Organization, value: string | undefined) => trackAnalytics('performance_views.spans.change_sort', { organization, sort_column: value, }), }; type Props = { eventView: EventView; location: Location; organization: Organization; projectId: string; setError: SetStateAction; transactionName: string; }; function SpansContent(props: Props) { const {location, organization, eventView, projectId, transactionName} = props; const query = decodeScalar(location.query.query, ''); const handleChange = useCallback( (key: string) => { return function (value: string | undefined) { ANALYTICS_VALUES[key]?.(organization, value); const queryParams = normalizeDateTimeParams({ ...(location.query || {}), [key]: value, }); // do not propagate pagination when making a new search const toOmit = ['cursor']; if (!defined(value)) { toOmit.push(key); } const searchQueryParams = omit(queryParams, toOmit); browserHistory.push({ ...location, query: searchQueryParams, }); }; }, [location, organization] ); const spanOp = decodeScalar(location.query.spanOp); const spanGroup = decodeScalar(location.query.spanGroup); const sort = getSuspectSpanSortFromEventView(eventView); const spansView = getSpansEventView(eventView, sort.field); const totalsView = getTotalsView(eventView); const {projects} = useProjects(); const onSearch = useMemo(() => handleChange('query'), [handleChange]); const projectIds = useMemo(() => eventView.project.slice(), [eventView]); const hasNewSpansUIFlag = organization.features.includes('performance-spans-new-ui') && organization.features.includes('insights-initial-modules'); // TODO: Remove this flag when the feature is GA'd and replace the old content entirely if (hasNewSpansUIFlag) { return ; } return ( {organization.features.includes('search-query-builder-performance') ? ( ) : ( )} ({value: opt.field, label: opt.label}))} onChange={opt => handleChange('sort')(opt.value)} triggerProps={{prefix: sort.prefix}} triggerLabel={sort.label} /> {({tableData}) => { const totals: SpansTotalValues | null = (tableData?.data?.[0] as SpansTotalValues | undefined) ?? null; return ( {({suspectSpans, isLoading, pageLinks}) => ( p.id === projectId)} isLoading={isLoading} suspectSpans={suspectSpans ?? []} totals={totals} sort={sort.field} /> )} ); }} ); } // TODO: Temporary component while we make the switch to spans only. Will fully replace the old Spans tab when GA'd function SpansContentV2(props: Props) { const {location, organization, eventView, projectId, transactionName} = props; const supportedTags = useSpanMetricsFieldSupportedTags(); const {projects} = useProjects(); const project = projects.find(p => p.id === projectId); const spansQuery = decodeScalar(location.query.spansQuery, ''); const handleChange = useCallback( (key: string) => { return function (value: string | undefined) { ANALYTICS_VALUES[key]?.(organization, value); const queryParams = normalizeDateTimeParams({ ...(location.query || {}), [key]: value, }); // do not propagate pagination when making a new search const toOmit = ['cursor']; if (!defined(value)) { toOmit.push(key); } const searchQueryParams = omit(queryParams, toOmit); browserHistory.push({ ...location, query: searchQueryParams, }); }; }, [location, organization] ); const onSearch = useMemo(() => handleChange('spansQuery'), [handleChange]); const projectIds = useMemo(() => eventView.project.slice(), [eventView]); return ( {organization.features.includes('search-query-builder-performance') ? ( ) : ( )} ); } function getSpansEventView(eventView: EventView, sort: SpanSort): EventView { eventView = eventView.clone(); const fields = SPAN_SORT_TO_FIELDS[sort]; eventView.fields = fields ? fields.map(field => ({field})) : []; return eventView; } 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(3, min-content); } @media (min-width: ${p => p.theme.breakpoints.xlarge}) { grid-template-columns: auto auto 1fr auto; } `; const StyledSearchBarWrapper = styled('div')` @media (min-width: ${p => p.theme.breakpoints.small}) { order: 1; grid-column: 1/5; } @media (min-width: ${p => p.theme.breakpoints.xlarge}) { order: initial; grid-column: auto; } `; export default SpansContent;