Files
2024-02-15 17:01:50 +00:00

213 lines
6.9 KiB
TypeScript

import {Fragment} from 'react';
import {browserHistory} from 'react-router';
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 {space} from 'sentry/styles/space';
import type {Organization} from 'sentry/types';
import {defined} from 'sentry/utils';
import {trackAnalytics} from 'sentry/utils/analytics';
import DiscoverQuery from 'sentry/utils/discover/discoverQuery';
import type EventView from 'sentry/utils/discover/eventView';
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 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<string | undefined>;
transactionName: string;
};
function SpansContent(props: Props) {
const {location, organization, eventView, projectId, transactionName} = props;
const query = decodeScalar(location.query.query, '');
function handleChange(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,
});
};
}
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();
return (
<Layout.Main fullWidth>
<FilterActions>
<OpsFilter
location={location}
eventView={eventView}
organization={organization}
handleOpChange={handleChange('spanOp')}
transactionName={transactionName}
/>
<PageFilterBar condensed>
<EnvironmentPageFilter />
<DatePageFilter
maxPickableDays={SPAN_RETENTION_DAYS}
relativeOptions={SPAN_RELATIVE_PERIODS}
/>
</PageFilterBar>
<StyledSearchBar
organization={organization}
projectIds={eventView.project}
query={query}
fields={eventView.fields}
onSearch={handleChange('query')}
/>
<CompactSelect
value={sort.field}
options={SPAN_SORT_OPTIONS.map(opt => ({value: opt.field, label: opt.label}))}
onChange={opt => handleChange('sort')(opt.value)}
triggerProps={{prefix: sort.prefix}}
triggerLabel={sort.label}
/>
</FilterActions>
<DiscoverQuery
eventView={totalsView}
orgSlug={organization.slug}
location={location}
referrer="api.performance.transaction-spans"
cursor="0:0:1"
noPagination
>
{({tableData}) => {
const totals: SpansTotalValues | null =
(tableData?.data?.[0] as SpansTotalValues | undefined) ?? null;
return (
<SuspectSpansQuery
location={location}
orgSlug={organization.slug}
eventView={spansView}
limit={10}
perSuspect={0}
spanOps={defined(spanOp) ? [spanOp] : []}
spanGroups={defined(spanGroup) ? [spanGroup] : []}
>
{({suspectSpans, isLoading, pageLinks}) => (
<Fragment>
<VisuallyCompleteWithData
id="TransactionSpans-SuspectSpansTable"
hasData={!!suspectSpans?.length}
isLoading={isLoading}
>
<SuspectSpansTable
location={location}
organization={organization}
transactionName={transactionName}
project={projects.find(p => p.id === projectId)}
isLoading={isLoading}
suspectSpans={suspectSpans ?? []}
totals={totals}
sort={sort.field}
/>
</VisuallyCompleteWithData>
<Pagination pageLinks={pageLinks ?? null} />
</Fragment>
)}
</SuspectSpansQuery>
);
}}
</DiscoverQuery>
</Layout.Main>
);
}
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 StyledSearchBar = styled(SearchBar)`
@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;