import {useCallback, useMemo} from 'react'; import styled from '@emotion/styled'; import {Alert} from 'sentry/components/alert'; import FeatureBadge from 'sentry/components/badge/featureBadge'; import FeedbackWidgetButton from 'sentry/components/feedback/widget/feedbackWidgetButton'; 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 PageFiltersContainer from 'sentry/components/organizations/pageFilters/container'; import {ProjectPageFilter} from 'sentry/components/organizations/projectPageFilter'; import {PageHeadingQuestionTooltip} from 'sentry/components/pageHeadingQuestionTooltip'; import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import {browserHistory} from 'sentry/utils/browserHistory'; import {decodeInteger} from 'sentry/utils/queryString'; import {useLocation} from 'sentry/utils/useLocation'; import useOrganization from 'sentry/utils/useOrganization'; import {ExploreContent} from 'sentry/views/explore/content'; import * as ModuleLayout from 'sentry/views/insights/common/components/moduleLayout'; import {usePageParams} from './hooks/usePageParams'; import {useTraces} from './hooks/useTraces'; import {TracesChart} from './tracesChart'; import {TracesSearchBar} from './tracesSearchBar'; import {TracesTable} from './tracesTable'; import {normalizeTraces} from './utils'; const TRACE_EXPLORER_DOCS_URL = 'https://docs.sentry.io/product/explore/traces/'; const DEFAULT_STATS_PERIOD = '24h'; const DEFAULT_PER_PAGE = 50; export default function Wrapper(props) { const location = useLocation(); const organization = useOrganization(); if ( location.query.view !== 'trace' && organization.features.includes('visibility-explore-view') ) { return ; } return ; } function Content() { const location = useLocation(); const organization = useOrganization(); const limit = useMemo(() => { return decodeInteger(location.query.perPage, DEFAULT_PER_PAGE); }, [location.query.perPage]); const {queries} = usePageParams(location); const handleSearch = useCallback( (searchIndex: number, searchQuery: string) => { const newQueries = [...queries]; if (newQueries.length === 0) { // In the odd case someone wants to add search bars before any query has been made, we add both the default one shown and a new one. newQueries[0] = ''; } newQueries[searchIndex] = searchQuery; browserHistory.push({ ...location, query: { ...location.query, cursor: undefined, query: typeof searchQuery === 'string' ? newQueries : queries, }, }); }, [location, queries] ); const handleClearSearch = useCallback( (searchIndex: number) => { const newQueries = [...queries]; if (typeof newQueries[searchIndex] !== undefined) { delete newQueries[searchIndex]; browserHistory.push({ ...location, query: { ...location.query, cursor: undefined, query: newQueries, }, }); return true; } return false; }, [location, queries] ); const tracesQuery = useTraces({ limit, query: queries, }); const isLoading = tracesQuery.isFetching; const isError = !isLoading && tracesQuery.isError; const isEmpty = !isLoading && !isError && (tracesQuery?.data?.data?.length ?? 0) === 0; const rawData = !isLoading && !isError ? tracesQuery?.data?.data : undefined; const data = normalizeTraces(rawData); return ( {t('Traces')} {isError && typeof tracesQuery.error?.responseJSON?.detail === 'string' ? ( {tracesQuery.error?.responseJSON?.detail} ) : null} ); } const HeaderContentBar = styled('div')` display: flex; align-items: center; justify-content: space-between; flex-direction: row; `; const LayoutMain = styled(Layout.Main)` display: flex; flex-direction: column; gap: ${space(2)}; `; const StyledAlert = styled(Alert)` margin-bottom: 0; `;