Skip to content

Commit

Permalink
feat(suspect-spans): Add empty spans tab (#28315)
Browse files Browse the repository at this point in the history
This adds a new empty tab for the suspect spans feature behind the
`performance-suspect-spans-view` flag.
  • Loading branch information
Zylphrex authored Sep 20, 2021
1 parent a424267 commit 392b09e
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 3 deletions.
7 changes: 7 additions & 0 deletions static/app/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1405,6 +1405,13 @@ function routes() {
}
component={SafeLazyLoad}
/>
<Route
path="/organizations/:orgId/performance/summary/spans/"
componentPromise={() =>
import('app/views/performance/transactionSummary/transactionSpans')
}
component={SafeLazyLoad}
/>
</Route>
<Route
path="/organizations/:orgId/performance/vitaldetail/"
Expand Down
19 changes: 19 additions & 0 deletions static/app/views/performance/transactionSummary/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import Breadcrumb from 'app/views/performance/breadcrumb';
import {getCurrentLandingDisplay, LandingDisplayField} from '../landing/utils';

import {eventsRouteWithQuery} from './transactionEvents/utils';
import {spansRouteWithQuery} from './transactionSpans/utils';
import {tagsRouteWithQuery} from './transactionTags/utils';
import {vitalsRouteWithQuery} from './transactionVitals/utils';
import Tab from './tabs';
Expand All @@ -47,6 +48,10 @@ const TAB_ANALYTICS: Partial<Record<Tab, AnalyticInfo>> = {
eventKey: 'performance_views.events.events_tab_clicked',
eventName: 'Performance Views: Events tab clicked',
},
[Tab.Spans]: {
eventKey: 'performance_views.spans.spans_tab_clicked',
eventName: 'Performance Views: Spans tab clicked',
},
};

type Props = {
Expand Down Expand Up @@ -219,6 +224,7 @@ class TransactionHeader extends React.Component<Props> {
const summaryTarget = transactionSummaryRouteWithQuery(routeQuery);
const tagsTarget = tagsRouteWithQuery(routeQuery);
const eventsTarget = eventsRouteWithQuery(routeQuery);
const spansTarget = spansRouteWithQuery(routeQuery);

return (
<Layout.Header>
Expand Down Expand Up @@ -271,6 +277,19 @@ class TransactionHeader extends React.Component<Props> {
{t('All Events')}
</ListLink>
</Feature>
<Feature
organization={organization}
features={['organizations:performance-suspect-spans-view']}
>
<ListLink
data-test-id="spans-tab"
to={spansTarget}
isActive={() => currentTab === Tab.Spans}
onClick={this.trackTabClick(Tab.Spans)}
>
{t('Spans')}
</ListLink>
</Feature>
</StyledNavTabs>
</React.Fragment>
</Layout.Header>
Expand Down
1 change: 1 addition & 0 deletions static/app/views/performance/transactionSummary/tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ enum Tab {
WebVitals,
Tags,
Events,
Spans,
}

export default Tab;
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {Location} from 'history';

import {Organization} from 'app/types';
import EventView from 'app/utils/discover/eventView';

type Props = {
location: Location;
organization: Organization;
eventView: EventView;
};

function SpansContent(_props: Props) {
return <p>spans</p>;
}

export default SpansContent;
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import {Location} from 'history';

import {t} from 'app/locale';
import {Organization, Project} from 'app/types';
import EventView from 'app/utils/discover/eventView';
import {decodeScalar} from 'app/utils/queryString';
import {MutableSearch} from 'app/utils/tokenizeSearch';
import withOrganization from 'app/utils/withOrganization';
import withProjects from 'app/utils/withProjects';

import PageLayout from '../pageLayout';
import Tab from '../tabs';

import SpansContent from './content';

type Props = {
location: Location;
organization: Organization;
projects: Project[];
};

function TransactionSpans(props: Props) {
const {location, organization, projects} = props;

return (
<PageLayout
location={location}
organization={organization}
projects={projects}
tab={Tab.Spans}
getDocumentTitle={getDocumentTitle}
generateEventView={generateEventView}
childComponent={SpansContent}
/>
);
}

function generateEventView(location: Location, transactionName: string): EventView {
const query = decodeScalar(location.query.query, '');
const conditions = new MutableSearch(query);
// TODO: what should this event type be?
conditions
.setFilterValues('event.type', ['transaction'])
.setFilterValues('transaction', [transactionName]);

return EventView.fromNewQueryWithLocation(
{
id: undefined,
version: 2,
name: transactionName,
fields: ['count()'],
query: conditions.formatString(),
projects: [],
},
location
);
}

function getDocumentTitle(transactionName: string): string {
const hasTransactionName =
typeof transactionName === 'string' && String(transactionName).trim().length > 0;

if (hasTransactionName) {
return [String(transactionName).trim(), t('Performance')].join(' - ');
}

return [t('Summary'), t('Performance')].join(' - ');
}

export default withProjects(withOrganization(TransactionSpans));
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {Query} from 'history';

export function generateSpansRoute({orgSlug}: {orgSlug: String}): string {
return `/organizations/${orgSlug}/performance/summary/spans/`;
}

export function spansRouteWithQuery({
orgSlug,
transaction,
projectID,
query,
}: {
orgSlug: string;
transaction: string;
query: Query;
projectID?: string | string[];
}) {
const pathname = generateSpansRoute({
orgSlug,
});

return {
pathname,
query: {
transaction,
project: projectID,
environment: query.environment,
statsPeriod: query.statsPeriod,
start: query.start,
end: query.end,
query: query.query,
},
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ function TransactionVitals(props: Props) {
);
}

function getDocumentTitle(transactionName): string {
function getDocumentTitle(transactionName: string): string {
const hasTransactionName =
typeof transactionName === 'string' && String(transactionName).trim().length > 0;

Expand Down
38 changes: 36 additions & 2 deletions tests/js/spec/views/performance/transactionSummary/header.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,21 @@ import EventView from 'app/utils/discover/eventView';
import TransactionHeader from 'app/views/performance/transactionSummary/header';
import Tab from 'app/views/performance/transactionSummary/tabs';

function initializeData(opts?: {platform?: string}) {
type InitialOpts = {
features?: string[];
platform?: string;
};

function initializeData(opts?: InitialOpts) {
const {features, platform} = opts ?? {};
// @ts-expect-error
const project = TestStubs.Project({platform: opts?.platform});
const project = TestStubs.Project({platform});
// @ts-expect-error
const organization = TestStubs.Organization({
projects: [project],
features,
});

const initialData = initializeOrg({
organization,
router: {
Expand Down Expand Up @@ -182,4 +190,30 @@ describe('Performance > Transaction Summary Header', function () {

expect(wrapper.find('ListLink[data-test-id="web-vitals-tab"]').exists()).toBeFalsy();
});

it('should render spans tab with feature', async function () {
const {project, organization, router, eventView} = initializeData({
features: ['performance-suspect-spans-view'],
});

wrapper = mountWithTheme(
<TransactionHeader
eventView={eventView}
location={router.location}
organization={organization}
projects={[project]}
projectId={project.id}
transactionName="transaction_name"
currentTab={Tab.TransactionSummary}
hasWebVitals="yes"
handleIncompatibleQuery={() => {}}
/>
);

// @ts-expect-error
await tick();
wrapper.update();

expect(wrapper.find('ListLink[data-test-id="spans-tab"]').exists()).toBeTruthy();
});
});

0 comments on commit 392b09e

Please sign in to comment.