123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186 |
- 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 <ExploreContent {...props} />;
- }
- return <Content {...props} />;
- }
- 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 (
- <SentryDocumentTitle title={t('Traces')} orgSlug={organization.slug}>
- <PageFiltersContainer
- defaultSelection={{
- datetime: {start: null, end: null, utc: null, period: DEFAULT_STATS_PERIOD},
- }}
- >
- <Layout.Page>
- <Layout.Header>
- <Layout.HeaderContent>
- <HeaderContentBar>
- <Layout.Title>
- {t('Traces')}
- <PageHeadingQuestionTooltip
- docsUrl={TRACE_EXPLORER_DOCS_URL}
- title={t(
- 'Traces lets you search for individual spans that make up a trace, linked by a trace id.'
- )}
- />
- <FeatureBadge type="beta" />
- </Layout.Title>
- <FeedbackWidgetButton />
- </HeaderContentBar>
- </Layout.HeaderContent>
- </Layout.Header>
- <Layout.Body>
- <LayoutMain fullWidth>
- <PageFilterBar condensed>
- <ProjectPageFilter />
- <EnvironmentPageFilter />
- <DatePageFilter defaultPeriod="2h" />
- </PageFilterBar>
- {isError && typeof tracesQuery.error?.responseJSON?.detail === 'string' ? (
- <StyledAlert type="error" showIcon>
- {tracesQuery.error?.responseJSON?.detail}
- </StyledAlert>
- ) : null}
- <TracesSearchBar
- queries={queries}
- handleSearch={handleSearch}
- handleClearSearch={handleClearSearch}
- />
- <ModuleLayout.Full>
- <TracesChart />
- </ModuleLayout.Full>
- <TracesTable
- isEmpty={isEmpty}
- isError={isError}
- isLoading={isLoading}
- queries={queries}
- data={data}
- />
- </LayoutMain>
- </Layout.Body>
- </Layout.Page>
- </PageFiltersContainer>
- </SentryDocumentTitle>
- );
- }
- 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;
- `;
|