diff --git a/static/app/components/quickTrace/index.tsx b/static/app/components/quickTrace/index.tsx index 28629a1e48e53f..b3ee9a264f4e3a 100644 --- a/static/app/components/quickTrace/index.tsx +++ b/static/app/components/quickTrace/index.tsx @@ -18,7 +18,6 @@ import {backend, frontend, mobile, serverless} from 'sentry/data/platformCategor import {IconFire} from 'sentry/icons'; import {t, tct, tn} from 'sentry/locale'; import type {Event} from 'sentry/types/event'; -import type {OrganizationSummary} from 'sentry/types/organization'; import {trackAnalytics} from 'sentry/utils/analytics'; import {getDocsPlatform} from 'sentry/utils/docs'; import getDuration from 'sentry/utils/duration/getDuration'; @@ -35,6 +34,7 @@ import Projects from 'sentry/utils/projects'; const FRONTEND_PLATFORMS: string[] = [...frontend, ...mobile]; const BACKEND_PLATFORMS: string[] = [...backend, ...serverless]; +import type {Organization} from 'sentry/types/organization'; import {generateLinkToEventInTraceView} from 'sentry/utils/discover/urls'; import { @@ -67,7 +67,7 @@ type QuickTraceProps = Pick< > & { event: Event; location: Location; - organization: OrganizationSummary; + organization: Organization; quickTrace: QuickTraceType; }; @@ -288,18 +288,14 @@ export default function QuickTrace({ return {nodes}; } -function handleNode(key: string, organization: OrganizationSummary) { +function handleNode(key: string, organization: Organization) { trackAnalytics('quick_trace.node.clicked', { organization: organization.id, node_key: key, }); } -function handleDropdownItem( - key: string, - organization: OrganizationSummary, - extra: boolean -) { +function handleDropdownItem(key: string, organization: Organization, extra: boolean) { const eventKey = extra ? 'quick_trace.dropdown.clicked_extra' : 'quick_trace.dropdown.clicked'; @@ -316,7 +312,7 @@ type EventNodeSelectorProps = { events: QuickTraceEvent[]; location: Location; nodeKey: keyof typeof TOOLTIP_PREFIX; - organization: OrganizationSummary; + organization: Organization; text: React.ReactNode; traceSlug: string; transactionDest: TransactionDestination; @@ -410,10 +406,7 @@ function EventNodeSelector({ projectSlug: events[0].project_slug, timestamp: events[0].timestamp, location, - organization: { - slug: organization.slug, - features: organization.features, - }, + organization, transactionName: events[0].transaction, type: transactionDest, }); @@ -484,10 +477,7 @@ function EventNodeSelector({ projectSlug: event.project_slug, eventId: event.event_id, location, - organization: { - slug: organization.slug, - features: organization.features, - }, + organization, type: transactionDest, transactionName: event.transaction, }); @@ -525,7 +515,7 @@ function EventNodeSelector({ type DropdownNodeProps = { anchor: 'left' | 'right'; event: TraceError | QuickTraceEvent | TracePerformanceIssue; - organization: OrganizationSummary; + organization: Organization; allowDefaultEvent?: boolean; onSelect?: (eventKey: any) => void; subtext?: string; diff --git a/static/app/components/quickTrace/utils.tsx b/static/app/components/quickTrace/utils.tsx index 22dc490743e77e..6f9676ef9f8323 100644 --- a/static/app/components/quickTrace/utils.tsx +++ b/static/app/components/quickTrace/utils.tsx @@ -3,7 +3,7 @@ import type {Location, LocationDescriptor} from 'history'; import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse'; import {ALL_ACCESS_PROJECTS} from 'sentry/constants/pageFilters'; import type {Event} from 'sentry/types/event'; -import type {OrganizationSummary} from 'sentry/types/organization'; +import type {Organization} from 'sentry/types/organization'; import {defined} from 'sentry/utils'; import EventView from 'sentry/utils/discover/eventView'; import { @@ -32,7 +32,7 @@ export type TransactionDestination = 'discover' | 'performance'; export function generateIssueEventTarget( event: TraceError | TracePerformanceIssue, - organization: OrganizationSummary, + organization: Organization, referrer?: string ): LocationDescriptor { const queryParams = referrer ? '?referrer=' + referrer : ''; @@ -41,7 +41,7 @@ export function generateIssueEventTarget( function generateDiscoverEventTarget( event: EventLite | TraceError | TracePerformanceIssue, - organization: OrganizationSummary, + organization: Organization, location: Location, referrer?: string ): LocationDescriptor { @@ -67,7 +67,7 @@ function generateDiscoverEventTarget( export function generateSingleErrorTarget( event: TraceError | TracePerformanceIssue, - organization: OrganizationSummary, + organization: Organization, location: Location, destination: ErrorDestination, referrer?: string @@ -84,7 +84,7 @@ export function generateSingleErrorTarget( export function generateMultiTransactionsTarget( currentEvent: Event, events: EventLite[], - organization: OrganizationSummary, + organization: Organization, groupType: 'Ancestor' | 'Children' | 'Descendant' ): LocationDescriptor { const queryResults = new MutableSearch([]); @@ -134,7 +134,7 @@ export function getEventTimestamp(event: Event): string | number | undefined { export function generateTraceTarget( event: Event, - organization: OrganizationSummary, + organization: Organization, location: Location, source?: string ): LocationDescriptor { diff --git a/static/app/types/organization.tsx b/static/app/types/organization.tsx index fcc0eb85885f1e..0d714cdbac3cd0 100644 --- a/static/app/types/organization.tsx +++ b/static/app/types/organization.tsx @@ -48,7 +48,8 @@ export interface Organization extends OrganizationSummary { allowJoinRequests: boolean; allowSharedIssues: boolean; attachmentsRole: string; - availableRoles: {id: string; name: string}[]; // Deprecated, use orgRoleList + /** @deprecated use orgRoleList instead. */ + availableRoles: {id: string; name: string}[]; dataScrubber: boolean; dataScrubberDefaults: boolean; debugFilesRole: string; @@ -79,6 +80,12 @@ export interface Organization extends OrganizationSummary { trustedRelays: Relay[]; desiredSampleRate?: number | null; effectiveSampleRate?: number | null; + extraOptions?: { + traces: { + checkSpanExtractionDate: boolean; + spansExtractionDate: number; + }; + }; orgRole?: string; planSampleRate?: number | null; } diff --git a/static/app/utils/discover/urls.tsx b/static/app/utils/discover/urls.tsx index e136d8a80fcf10..15d210add131ad 100644 --- a/static/app/utils/discover/urls.tsx +++ b/static/app/utils/discover/urls.tsx @@ -57,7 +57,7 @@ export function generateLinkToEventInTraceView({ }: { eventId: string; location: Location; - organization: Pick; + organization: Organization; projectSlug: string; timestamp: string | number; traceSlug: string; diff --git a/static/app/views/performance/traceDetails/TraceDetailsRouting.tsx b/static/app/views/performance/traceDetails/TraceDetailsRouting.tsx index 6d2274a9fee5af..384c04d3e8c729 100644 --- a/static/app/views/performance/traceDetails/TraceDetailsRouting.tsx +++ b/static/app/views/performance/traceDetails/TraceDetailsRouting.tsx @@ -7,7 +7,7 @@ import {browserHistory} from 'sentry/utils/browserHistory'; import {useLocation} from 'sentry/utils/useLocation'; import useOrganization from 'sentry/utils/useOrganization'; -import {getTraceDetailsUrl} from './utils'; +import {getTraceDetailsUrl, shouldForceRouteToOldView} from './utils'; type Props = { children: JSX.Element; @@ -25,7 +25,10 @@ function TraceDetailsRouting(props: Props) { return children; } - if (organization.features.includes('trace-view-v1')) { + if ( + organization.features.includes('trace-view-v1') && + !shouldForceRouteToOldView(organization, getEventTimestamp(event)) + ) { if (event?.groupID && event?.eventID) { const issuesLocation = `/organizations/${organization.slug}/issues/${event.groupID}/events/${event.eventID}`; browserHistory.replace({ diff --git a/static/app/views/performance/traceDetails/utils.tsx b/static/app/views/performance/traceDetails/utils.tsx index e4c4a5a44ebd48..001c9d8f3f17a6 100644 --- a/static/app/views/performance/traceDetails/utils.tsx +++ b/static/app/views/performance/traceDetails/utils.tsx @@ -1,7 +1,7 @@ import type {Location, LocationDescriptorObject} from 'history'; import {PAGE_URL_PARAM} from 'sentry/constants/pageFilters'; -import type {Organization, OrganizationSummary} from 'sentry/types'; +import type {Organization} from 'sentry/types'; import {getTimeStampFromTableDateField} from 'sentry/utils/dates'; import type { EventLite, @@ -29,7 +29,7 @@ export function getTraceDetailsUrl({ }: { dateSelection; location: Location; - organization: Pick; + organization: Organization; traceSlug: string; demo?: string; eventId?: string; @@ -46,6 +46,17 @@ export function getTraceDetailsUrl({ [PAGE_URL_PARAM.PAGE_END]: end, }; + const oldTraceUrl = { + pathname: normalizeUrl( + `/organizations/${organization.slug}/performance/trace/${traceSlug}/` + ), + query: queryParams, + }; + + if (shouldForceRouteToOldView(organization, timestamp)) { + return oldTraceUrl; + } + if (organization.features.includes('trace-view-v1')) { if (spanId) { queryParams.node = [`span-${spanId}`, `txn-${eventId}`]; @@ -68,12 +79,28 @@ export function getTraceDetailsUrl({ queryParams.limit = DEFAULT_TRACE_ROWS_LIMIT; } - return { - pathname: normalizeUrl( - `/organizations/${organization.slug}/performance/trace/${traceSlug}/` - ), - query: queryParams, - }; + return oldTraceUrl; +} + +/** + * Single tenant, on-premise etc. users may not have span extraction enabled. + * + * This code can be removed at the time we're sure all STs have rolled out span extraction. + */ +export function shouldForceRouteToOldView( + organization: Organization, + timestamp: string | number | undefined +) { + const usableTimestamp = getTimeStampFromTableDateField(timestamp); + if (!usableTimestamp) { + // Timestamps must always be provided for the new view, if it doesn't exist, fall back to the old view. + return true; + } + + return ( + organization.extraOptions?.traces.checkSpanExtractionDate && + organization.extraOptions?.traces.spansExtractionDate <= usableTimestamp + ); } function transactionVisitor() {