From 392b09e02144b37c5142abf1ccef75f75d675430 Mon Sep 17 00:00:00 2001 From: Tony Xiao Date: Mon, 20 Sep 2021 10:22:18 -0400 Subject: [PATCH] feat(suspect-spans): Add empty spans tab (#28315) This adds a new empty tab for the suspect spans feature behind the `performance-suspect-spans-view` flag. --- static/app/routes.tsx | 7 ++ .../performance/transactionSummary/header.tsx | 19 +++++ .../performance/transactionSummary/tabs.tsx | 1 + .../transactionSpans/content.tsx | 16 +++++ .../transactionSpans/index.tsx | 70 +++++++++++++++++++ .../transactionSpans/utils.tsx | 34 +++++++++ .../transactionVitals/index.tsx | 2 +- .../transactionSummary/header.spec.tsx | 38 +++++++++- 8 files changed, 184 insertions(+), 3 deletions(-) create mode 100644 static/app/views/performance/transactionSummary/transactionSpans/content.tsx create mode 100644 static/app/views/performance/transactionSummary/transactionSpans/index.tsx create mode 100644 static/app/views/performance/transactionSummary/transactionSpans/utils.tsx diff --git a/static/app/routes.tsx b/static/app/routes.tsx index 879988a2548d1c..446ecaedd8fcf2 100644 --- a/static/app/routes.tsx +++ b/static/app/routes.tsx @@ -1405,6 +1405,13 @@ function routes() { } component={SafeLazyLoad} /> + + import('app/views/performance/transactionSummary/transactionSpans') + } + component={SafeLazyLoad} + /> > = { 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 = { @@ -219,6 +224,7 @@ class TransactionHeader extends React.Component { const summaryTarget = transactionSummaryRouteWithQuery(routeQuery); const tagsTarget = tagsRouteWithQuery(routeQuery); const eventsTarget = eventsRouteWithQuery(routeQuery); + const spansTarget = spansRouteWithQuery(routeQuery); return ( @@ -271,6 +277,19 @@ class TransactionHeader extends React.Component { {t('All Events')} + + currentTab === Tab.Spans} + onClick={this.trackTabClick(Tab.Spans)} + > + {t('Spans')} + + diff --git a/static/app/views/performance/transactionSummary/tabs.tsx b/static/app/views/performance/transactionSummary/tabs.tsx index 113f5c87373eb7..f0bcb714bd56b8 100644 --- a/static/app/views/performance/transactionSummary/tabs.tsx +++ b/static/app/views/performance/transactionSummary/tabs.tsx @@ -3,6 +3,7 @@ enum Tab { WebVitals, Tags, Events, + Spans, } export default Tab; diff --git a/static/app/views/performance/transactionSummary/transactionSpans/content.tsx b/static/app/views/performance/transactionSummary/transactionSpans/content.tsx new file mode 100644 index 00000000000000..7f970dd8ca3c8e --- /dev/null +++ b/static/app/views/performance/transactionSummary/transactionSpans/content.tsx @@ -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

spans

; +} + +export default SpansContent; diff --git a/static/app/views/performance/transactionSummary/transactionSpans/index.tsx b/static/app/views/performance/transactionSummary/transactionSpans/index.tsx new file mode 100644 index 00000000000000..f1eb0ff499e043 --- /dev/null +++ b/static/app/views/performance/transactionSummary/transactionSpans/index.tsx @@ -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 ( + + ); +} + +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)); diff --git a/static/app/views/performance/transactionSummary/transactionSpans/utils.tsx b/static/app/views/performance/transactionSummary/transactionSpans/utils.tsx new file mode 100644 index 00000000000000..28401a8cd213c9 --- /dev/null +++ b/static/app/views/performance/transactionSummary/transactionSpans/utils.tsx @@ -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, + }, + }; +} diff --git a/static/app/views/performance/transactionSummary/transactionVitals/index.tsx b/static/app/views/performance/transactionSummary/transactionVitals/index.tsx index 9984e54e9d0067..5480ca62ce2667 100644 --- a/static/app/views/performance/transactionSummary/transactionVitals/index.tsx +++ b/static/app/views/performance/transactionSummary/transactionVitals/index.tsx @@ -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; diff --git a/tests/js/spec/views/performance/transactionSummary/header.spec.tsx b/tests/js/spec/views/performance/transactionSummary/header.spec.tsx index 0f1aea87cc4d95..48cc45c0b2179e 100644 --- a/tests/js/spec/views/performance/transactionSummary/header.spec.tsx +++ b/tests/js/spec/views/performance/transactionSummary/header.spec.tsx @@ -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: { @@ -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( + {}} + /> + ); + + // @ts-expect-error + await tick(); + wrapper.update(); + + expect(wrapper.find('ListLink[data-test-id="spans-tab"]').exists()).toBeTruthy(); + }); });