diff --git a/static/app/views/insights/browser/resources/views/resourceSummaryPage.tsx b/static/app/views/insights/browser/resources/views/resourceSummaryPage.tsx index 42be31a6d00aa6..ac29bf8cd3699c 100644 --- a/static/app/views/insights/browser/resources/views/resourceSummaryPage.tsx +++ b/static/app/views/insights/browser/resources/views/resourceSummaryPage.tsx @@ -22,6 +22,7 @@ import {ModuleBodyUpsellHook} from 'sentry/views/insights/common/components/modu import {ToolRibbon} from 'sentry/views/insights/common/components/ribbon'; import {useSpanMetrics} from 'sentry/views/insights/common/queries/useDiscover'; import {useModuleURL} from 'sentry/views/insights/common/utils/useModuleURL'; +import {useSamplesDrawer} from 'sentry/views/insights/common/utils/useSamplesDrawer'; import SubregionSelector from 'sentry/views/insights/common/views/spans/selectors/subregionSelector'; import {SampleList} from 'sentry/views/insights/common/views/spanSummaryPage/sampleList'; import {FrontendHeader} from 'sentry/views/insights/pages/frontend/frontendPageHeader'; @@ -43,6 +44,20 @@ function ResourceSummary() { const {groupId} = useParams(); const filters = useResourceModuleFilters(); const selectedSpanOp = filters[SPAN_OP]; + + useSamplesDrawer({ + Component: ( + + ), + moduleName: ModuleName.RESOURCE, + requiredParams: ['transaction'], + }); + const {data, isPending} = useSpanMetrics( { search: MutableSearch.fromQueryObject({ @@ -135,15 +150,6 @@ function ResourceSummary() { - - - - diff --git a/static/app/views/insights/browser/webVitals/components/pageOverviewWebVitalsDetailPanel.tsx b/static/app/views/insights/browser/webVitals/components/pageOverviewWebVitalsDetailPanel.tsx index 6391caaadb6e95..da1e27eef34426 100644 --- a/static/app/views/insights/browser/webVitals/components/pageOverviewWebVitalsDetailPanel.tsx +++ b/static/app/views/insights/browser/webVitals/components/pageOverviewWebVitalsDetailPanel.tsx @@ -2,6 +2,7 @@ import {useMemo} from 'react'; import styled from '@emotion/styled'; import type {LineChartSeries} from 'sentry/components/charts/lineChart'; +import {DrawerHeader} from 'sentry/components/globalDrawer/components'; import type { GridColumnHeader, GridColumnOrder, @@ -38,7 +39,7 @@ import type { } from 'sentry/views/insights/browser/webVitals/types'; import decodeBrowserTypes from 'sentry/views/insights/browser/webVitals/utils/queryParameterDecoders/browserType'; import useProfileExists from 'sentry/views/insights/browser/webVitals/utils/useProfileExists'; -import DetailPanel from 'sentry/views/insights/common/components/detailPanel'; +import {SampleDrawerBody} from 'sentry/views/insights/common/components/sampleDrawerBody'; import {SpanIndexedField, type SubregionCode} from 'sentry/views/insights/types'; import {TraceViewSources} from 'sentry/views/performance/newTraceDetails/traceHeader/breadcrumbs'; import {generateReplayLink} from 'sentry/views/performance/transactionSummary/utils'; @@ -77,9 +78,7 @@ const inpSort: GridColumnSortBy = { export function PageOverviewWebVitalsDetailPanel({ webVital, - onClose, }: { - onClose: () => void; webVital: WebVitals | null; }) { const location = useLocation(); @@ -367,7 +366,9 @@ export function PageOverviewWebVitalsDetailPanel({ return ( - + + + {webVital && ( - + ); } diff --git a/static/app/views/insights/browser/webVitals/components/webVitalDescription.tsx b/static/app/views/insights/browser/webVitals/components/webVitalDescription.tsx index 4a4d89b1153ec2..ba6923d0d0dce8 100644 --- a/static/app/views/insights/browser/webVitals/components/webVitalDescription.tsx +++ b/static/app/views/insights/browser/webVitals/components/webVitalDescription.tsx @@ -273,7 +273,6 @@ const Value = styled('h2')` const WebVitalName = styled('h4')` margin-bottom: ${space(1)}; - margin-top: 40px; max-width: 400px; ${p => p.theme.overflowEllipsis} `; diff --git a/static/app/views/insights/browser/webVitals/components/webVitalsDetailPanel.spec.tsx b/static/app/views/insights/browser/webVitals/components/webVitalsDetailPanel.spec.tsx index 5129c77ae406c2..8baa6071b00bf6 100644 --- a/static/app/views/insights/browser/webVitals/components/webVitalsDetailPanel.spec.tsx +++ b/static/app/views/insights/browser/webVitals/components/webVitalsDetailPanel.spec.tsx @@ -58,7 +58,7 @@ describe('WebVitalsDetailPanel', function () { }); it('renders correctly with empty results', async () => { - render( undefined} webVital="lcp" />, { + render(, { organization, }); await waitForElementToBeRemoved(() => screen.queryByTestId('loading-indicator')); diff --git a/static/app/views/insights/browser/webVitals/components/webVitalsDetailPanel.tsx b/static/app/views/insights/browser/webVitals/components/webVitalsDetailPanel.tsx index 9cad88eedddd59..195b2336c86038 100644 --- a/static/app/views/insights/browser/webVitals/components/webVitalsDetailPanel.tsx +++ b/static/app/views/insights/browser/webVitals/components/webVitalsDetailPanel.tsx @@ -2,6 +2,7 @@ import {useEffect, useMemo} from 'react'; import styled from '@emotion/styled'; import type {LineChartSeries} from 'sentry/components/charts/lineChart'; +import {DrawerHeader} from 'sentry/components/globalDrawer/components'; import type { GridColumnHeader, GridColumnOrder, @@ -34,7 +35,7 @@ import type { WebVitals, } from 'sentry/views/insights/browser/webVitals/types'; import decodeBrowserTypes from 'sentry/views/insights/browser/webVitals/utils/queryParameterDecoders/browserType'; -import DetailPanel from 'sentry/views/insights/common/components/detailPanel'; +import {SampleDrawerBody} from 'sentry/views/insights/common/components/sampleDrawerBody'; import {SpanIndexedField, type SubregionCode} from 'sentry/views/insights/types'; type Column = GridColumnHeader; @@ -51,13 +52,7 @@ const sort: GridColumnSortBy = {key: 'count()', order: 'desc'}; const MAX_ROWS = 10; -export function WebVitalsDetailPanel({ - webVital, - onClose, -}: { - onClose: () => void; - webVital: WebVitals | null; -}) { +export function WebVitalsDetailPanel({webVital}: {webVital: WebVitals | null}) { const location = useLocation(); const organization = useOrganization(); const browserTypes = decodeBrowserTypes(location.query[SpanIndexedField.BROWSER_NAME]); @@ -133,8 +128,6 @@ export function WebVitalsDetailPanel({ seriesName: webVital ?? '', }; - const detailKey = webVital; - useEffect(() => { if (webVital !== null) { trackAnalytics('insight.vital.vital_sidebar_opened', { @@ -243,7 +236,9 @@ export function WebVitalsDetailPanel({ return ( - + + + {webVital && ( - + ); } diff --git a/static/app/views/insights/browser/webVitals/views/pageOverview.tsx b/static/app/views/insights/browser/webVitals/views/pageOverview.tsx index edf82257080230..a2027d26c0e96b 100644 --- a/static/app/views/insights/browser/webVitals/views/pageOverview.tsx +++ b/static/app/views/insights/browser/webVitals/views/pageOverview.tsx @@ -1,4 +1,4 @@ -import React, {Fragment, useMemo, useState} from 'react'; +import React, {Fragment, useEffect, useMemo, useState} from 'react'; import styled from '@emotion/styled'; import omit from 'lodash/omit'; @@ -32,6 +32,7 @@ import {ModulePageFilterBar} from 'sentry/views/insights/common/components/modul import {ModulePageProviders} from 'sentry/views/insights/common/components/modulePageProviders'; import {ModuleBodyUpsellHook} from 'sentry/views/insights/common/components/moduleUpsellHookWrapper'; import {useModuleURL} from 'sentry/views/insights/common/utils/useModuleURL'; +import {useWebVitalsDrawer} from 'sentry/views/insights/common/utils/useWebVitalsDrawer'; import {FrontendHeader} from 'sentry/views/insights/pages/frontend/frontendPageHeader'; import {useDomainViewFilters} from 'sentry/views/insights/pages/useFilters'; import { @@ -106,6 +107,25 @@ export function PageOverview() { const {data: projectScores, isPending: isProjectScoresLoading} = useProjectWebVitalsScoresQuery({transaction, browserTypes, subregions}); + const {openVitalsDrawer} = useWebVitalsDrawer({ + Component: , + webVital: state.webVital, + onClose: () => { + router.replace({ + pathname: router.location.pathname, + query: omit(router.location.query, 'webVital'), + }); + + setState({...state, webVital: null}); + }, + }); + + useEffect(() => { + if (state.webVital) { + openVitalsDrawer(); + } + }); + if (transaction === undefined) { // redirect user to webvitals landing page window.location.href = moduleURL; @@ -239,16 +259,6 @@ export function PageOverview() { )} - { - router.replace({ - pathname: router.location.pathname, - query: omit(router.location.query, 'webVital'), - }); - setState({...state, webVital: null}); - }} - /> ); diff --git a/static/app/views/insights/browser/webVitals/views/webVitalsLandingPage.tsx b/static/app/views/insights/browser/webVitals/views/webVitalsLandingPage.tsx index 2e2b17cde8fa77..088440c2f36a45 100644 --- a/static/app/views/insights/browser/webVitals/views/webVitalsLandingPage.tsx +++ b/static/app/views/insights/browser/webVitals/views/webVitalsLandingPage.tsx @@ -1,6 +1,5 @@ -import React, {Fragment, useState} from 'react'; +import React, {Fragment, useEffect, useState} from 'react'; import styled from '@emotion/styled'; -import omit from 'lodash/omit'; import Alert from 'sentry/components/alert'; import {Button} from 'sentry/components/button'; @@ -11,7 +10,6 @@ import {t, tct} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import {decodeList} from 'sentry/utils/queryString'; import {useLocation} from 'sentry/utils/useLocation'; -import useRouter from 'sentry/utils/useRouter'; import BrowserTypeSelector from 'sentry/views/insights/browser/webVitals/components/browserTypeSelector'; import {PerformanceScoreChart} from 'sentry/views/insights/browser/webVitals/components/charts/performanceScoreChart'; import {PagePerformanceTable} from 'sentry/views/insights/browser/webVitals/components/tables/pagePerformanceTable'; @@ -26,6 +24,7 @@ import {ModulePageFilterBar} from 'sentry/views/insights/common/components/modul import {ModulePageProviders} from 'sentry/views/insights/common/components/modulePageProviders'; import {ModulesOnboarding} from 'sentry/views/insights/common/components/modulesOnboarding'; import {ModuleBodyUpsellHook} from 'sentry/views/insights/common/components/moduleUpsellHookWrapper'; +import {useWebVitalsDrawer} from 'sentry/views/insights/common/utils/useWebVitalsDrawer'; import {FrontendHeader} from 'sentry/views/insights/pages/frontend/frontendPageHeader'; import { ModuleName, @@ -38,8 +37,6 @@ const WEB_VITALS_COUNT = 5; export function WebVitalsLandingPage() { const location = useLocation(); - const router = useRouter(); - const [state, setState] = useState<{webVital: WebVitals | null}>({ webVital: (location.query.webVital as WebVitals) ?? null, }); @@ -61,6 +58,20 @@ export function WebVitalsLandingPage() { ? undefined : getWebVitalScoresFromTableDataRow(projectScores?.data?.[0]); + const {openVitalsDrawer} = useWebVitalsDrawer({ + Component: , + webVital: state.webVital, + onClose: () => { + setState({webVital: null}); + }, + }); + + useEffect(() => { + if (state.webVital) { + openVitalsDrawer(); + } + }); + return ( @@ -130,16 +141,6 @@ export function WebVitalsLandingPage() { - { - router.replace({ - pathname: router.location.pathname, - query: omit(router.location.query, 'webVital'), - }); - setState({...state, webVital: null}); - }} - /> ); } diff --git a/static/app/views/insights/cache/components/samplePanel.tsx b/static/app/views/insights/cache/components/samplePanel.tsx index cccee628a406c5..1a83b52b61bf1b 100644 --- a/static/app/views/insights/cache/components/samplePanel.tsx +++ b/static/app/views/insights/cache/components/samplePanel.tsx @@ -1,21 +1,16 @@ -import {Fragment, useCallback} from 'react'; -import styled from '@emotion/styled'; +import {Fragment} from 'react'; import keyBy from 'lodash/keyBy'; -import * as qs from 'query-string'; -import ProjectAvatar from 'sentry/components/avatar/projectAvatar'; import {Button} from 'sentry/components/button'; import {CompactSelect} from 'sentry/components/compactSelect'; -import Link from 'sentry/components/links/link'; +import {DrawerHeader} from 'sentry/components/globalDrawer/components'; import {SpanSearchQueryBuilder} from 'sentry/components/performance/spanSearchQueryBuilder'; import {t} from 'sentry/locale'; -import {space} from 'sentry/styles/space'; import {trackAnalytics} from 'sentry/utils/analytics'; import {DurationUnit, RateUnit, SizeUnit} from 'sentry/utils/discover/fields'; import {PageAlertProvider} from 'sentry/utils/performance/contexts/pageAlert'; import {decodeScalar} from 'sentry/utils/queryString'; import {MutableSearch} from 'sentry/utils/tokenizeSearch'; -import normalizeUrl from 'sentry/utils/url/normalizeUrl'; import useLocationQuery from 'sentry/utils/url/useLocationQuery'; import {useLocation} from 'sentry/utils/useLocation'; import {useNavigate} from 'sentry/utils/useNavigate'; @@ -27,7 +22,6 @@ import {TransactionDurationChart} from 'sentry/views/insights/cache/components/c import {SpanSamplesTable} from 'sentry/views/insights/cache/components/tables/spanSamplesTable'; import {Referrer} from 'sentry/views/insights/cache/referrers'; import {BASE_FILTERS} from 'sentry/views/insights/cache/settings'; -import DetailPanel from 'sentry/views/insights/common/components/detailPanel'; import {MetricReadout} from 'sentry/views/insights/common/components/metricReadout'; import * as ModuleLayout from 'sentry/views/insights/common/components/moduleLayout'; import {ReadoutRibbon} from 'sentry/views/insights/common/components/ribbon'; @@ -45,7 +39,6 @@ import { getThroughputTitle, } from 'sentry/views/insights/common/views/spans/types'; import {useDebouncedState} from 'sentry/views/insights/http/utils/useDebouncedState'; -import {useDomainViewFilters} from 'sentry/views/insights/pages/useFilters'; import { MetricsFields, type MetricsQueryFilters, @@ -57,7 +50,9 @@ import { SpanMetricsField, type SpanMetricsQueryFilters, } from 'sentry/views/insights/types'; -import {getTransactionSummaryBaseUrl} from 'sentry/views/performance/transactionSummary/utils'; + +import {SampleDrawerBody} from '../../common/components/sampleDrawerBody'; +import {SampleDrawerHeaderTransaction} from '../../common/components/sampleDrawerHeaderTransaction'; // This is similar to http sample table, its difficult to use the generic span samples sidebar as we require a bunch of custom things. export function CacheSamplePanel() { @@ -65,7 +60,6 @@ export function CacheSamplePanel() { const location = useLocation(); const organization = useOrganization(); const {selection} = usePageFilters(); - const {view} = useDomainViewFilters(); const query = useLocationQuery({ fields: { @@ -98,13 +92,6 @@ export function CacheSamplePanel() { }); }; - // `detailKey` controls whether the panel is open. If all required properties are ailable, concat them to make a key, otherwise set to `undefined` and hide the panel - const detailKey = query.transaction - ? [query.transaction].filter(Boolean).join(':') - : undefined; - - const isPanelOpen = Boolean(detailKey); - const filters: SpanMetricsQueryFilters = { ...BASE_FILTERS, transaction: query.transaction, @@ -130,7 +117,6 @@ export function CacheSamplePanel() { `sum(${SpanMetricsField.SPAN_SELF_TIME})`, `avg(${SpanMetricsField.CACHE_ITEM_SIZE})`, ], - enabled: isPanelOpen, }, Referrer.SAMPLES_CACHE_METRICS_RIBBON ); @@ -142,7 +128,6 @@ export function CacheSamplePanel() { transaction: query.transaction, } satisfies MetricsQueryFilters), fields: [`avg(${MetricsFields.TRANSACTION_DURATION})`], - enabled: isPanelOpen && Boolean(query.transaction), }, Referrer.SAMPLES_CACHE_TRANSACTION_DURATION ); @@ -177,7 +162,6 @@ export function CacheSamplePanel() { ], sorts: [SPAN_SAMPLES_SORT], limit, - enabled: isPanelOpen, }, Referrer.SAMPLES_CACHE_SPAN_SAMPLES ); @@ -239,26 +223,6 @@ export function CacheSamplePanel() { }); }; - const handleClose = () => { - navigate({ - pathname: location.pathname, - query: { - ...location.query, - transaction: undefined, - transactionMethod: undefined, - }, - }); - }; - - const handleOpen = useCallback(() => { - if (query.transaction) { - trackAnalytics('performance_views.sample_spans.opened', { - organization, - source: ModuleName.CACHE, - }); - } - }, [organization, query.transaction]); - const handleRefetch = () => { refetchCacheHits(); refetchCacheMisses(); @@ -266,36 +230,15 @@ export function CacheSamplePanel() { return ( - + + + + + - - - {project && ( - - )} - - <Link - to={normalizeUrl( - `${getTransactionSummaryBaseUrl(organization.slug, view)}?${qs.stringify( - { - project: query.project, - transaction: query.transaction, - } - )}` - )} - > - {query.transaction} - </Link> - - - - - + ); } @@ -463,26 +406,3 @@ const CACHE_STATUS_OPTIONS = [ label: t('Miss'), }, ]; - -const SpanSummaryProjectAvatar = styled(ProjectAvatar)` - padding-right: ${space(1)}; -`; - -// TODO - copy of static/app/views/starfish/views/spanSummaryPage/sampleList/index.tsx -const HeaderContainer = styled('div')` - display: grid; - grid-template-rows: auto auto auto; - align-items: center; - - @media (min-width: ${p => p.theme.breakpoints.small}) { - grid-template-rows: auto; - grid-template-columns: auto 1fr; - } -`; - -const Title = styled('h4')` - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - margin: 0; -`; diff --git a/static/app/views/insights/cache/views/cacheLandingPage.spec.tsx b/static/app/views/insights/cache/views/cacheLandingPage.spec.tsx index ccfc9f03d685ff..bb42a27f484dad 100644 --- a/static/app/views/insights/cache/views/cacheLandingPage.spec.tsx +++ b/static/app/views/insights/cache/views/cacheLandingPage.spec.tsx @@ -93,29 +93,6 @@ describe('CacheLandingPage', function () { await waitForElementToBeRemoved(() => screen.queryAllByTestId('loading-indicator')); - expect(requestMocks.missRateChart).toHaveBeenCalledWith( - `/organizations/${organization.slug}/events-stats/`, - expect.objectContaining({ - method: 'GET', - query: { - cursor: undefined, - dataset: 'spansMetrics', - environment: [], - excludeOther: 0, - field: [], - interval: '30m', - orderby: undefined, - partial: 1, - per_page: 50, - project: [], - query: 'span.op:[cache.get_item,cache.get] project.id:1', - referrer: 'api.performance.cache.samples-cache-hit-miss-chart', - statsPeriod: '10d', - topEvents: undefined, - yAxis: 'cache_miss_rate()', - }, - }) - ); expect(requestMocks.throughputChart).toHaveBeenCalledWith( `/organizations/${organization.slug}/events-stats/`, expect.objectContaining({ diff --git a/static/app/views/insights/cache/views/cacheLandingPage.tsx b/static/app/views/insights/cache/views/cacheLandingPage.tsx index 987d44bed346d9..f959dd2a381a2d 100644 --- a/static/app/views/insights/cache/views/cacheLandingPage.tsx +++ b/static/app/views/insights/cache/views/cacheLandingPage.tsx @@ -40,6 +40,8 @@ import {DataTitles} from 'sentry/views/insights/common/views/spans/types'; import {BackendHeader} from 'sentry/views/insights/pages/backend/backendPageHeader'; import {ModuleName, SpanFunction, SpanMetricsField} from 'sentry/views/insights/types'; +import {useSamplesDrawer} from '../../common/utils/useSamplesDrawer'; + const {CACHE_MISS_RATE} = SpanFunction; const {CACHE_ITEM_SIZE} = SpanMetricsField; @@ -65,6 +67,12 @@ export function CacheLandingPage() { const sort = decodeSorts(sortField).filter(isAValidSort).at(0) ?? DEFAULT_SORT; const cursor = decodeScalar(location.query?.[QueryParameterNames.TRANSACTIONS_CURSOR]); + useSamplesDrawer({ + Component: , + moduleName: ModuleName.CACHE, + requiredParams: ['transaction'], + }); + const { isPending: isCacheMissRateLoading, data: cacheMissRateData, @@ -216,7 +224,6 @@ export function CacheLandingPage() { - ); } diff --git a/static/app/views/insights/common/components/sampleDrawerBody.tsx b/static/app/views/insights/common/components/sampleDrawerBody.tsx new file mode 100644 index 00000000000000..38bc7ba5f8f898 --- /dev/null +++ b/static/app/views/insights/common/components/sampleDrawerBody.tsx @@ -0,0 +1,16 @@ +import styled from '@emotion/styled'; + +import {DrawerBody} from 'sentry/components/globalDrawer/components'; +import {space} from 'sentry/styles/space'; + +export const SampleDrawerBody = styled(DrawerBody)` + flex-grow: 1; + overflow: auto; + overscroll-behavior: contain; + /* Move the scrollbar to the left edge */ + scroll-margin: 0 ${space(2)}; + direction: rtl; + * { + direction: ltr; + } +`; diff --git a/static/app/views/insights/common/components/sampleDrawerHeaderTransaction.spec.tsx b/static/app/views/insights/common/components/sampleDrawerHeaderTransaction.spec.tsx new file mode 100644 index 00000000000000..48d7f4de543c40 --- /dev/null +++ b/static/app/views/insights/common/components/sampleDrawerHeaderTransaction.spec.tsx @@ -0,0 +1,67 @@ +import {ProjectFixture} from 'sentry-fixture/project'; + +import {render, screen} from 'sentry-test/reactTestingLibrary'; +import {textWithMarkupMatcher} from 'sentry-test/utils'; + +import {SampleDrawerHeaderTransaction} from './sampleDrawerHeaderTransaction'; + +describe('SampleDrawerHeaderTransaction', () => { + it('Links to the transaction summary page', () => { + const project = ProjectFixture(); + + render(); + + const $link = screen.getByRole('link'); + expect($link).toHaveAccessibleName('/issues'); + expect($link).toHaveAttribute( + 'href', + '/organizations/org-slug/performance/summary?project=project-slug&transaction=%2Fissues' + ); + }); + + it('Shows transaction method', () => { + const project = ProjectFixture(); + + render( + + ); + + const $link = screen.getByRole('link'); + expect($link).toHaveAccessibleName('GET /issues'); + }); + + it('Strips duplicate transaction method', () => { + const project = ProjectFixture(); + + render( + + ); + + const $link = screen.getByRole('link'); + expect($link).toHaveAccessibleName('GET /issues'); + }); + + it('Shows a prefix', () => { + const project = ProjectFixture(); + + render( + + ); + + expect( + screen.getByText(textWithMarkupMatcher('Producer:tasks.deliver_mail')) + ).toBeInTheDocument(); + }); +}); diff --git a/static/app/views/insights/common/components/sampleDrawerHeaderTransaction.tsx b/static/app/views/insights/common/components/sampleDrawerHeaderTransaction.tsx new file mode 100644 index 00000000000000..522f184de7a009 --- /dev/null +++ b/static/app/views/insights/common/components/sampleDrawerHeaderTransaction.tsx @@ -0,0 +1,94 @@ +import {Link} from 'react-router-dom'; +import styled from '@emotion/styled'; +import * as qs from 'query-string'; + +import ProjectAvatar from 'sentry/components/avatar/projectAvatar'; +import {space} from 'sentry/styles/space'; +import type {Project} from 'sentry/types/project'; +import useOrganization from 'sentry/utils/useOrganization'; +import {getTransactionSummaryBaseUrl} from 'sentry/views/performance/transactionSummary/utils'; + +import {useDomainViewFilters} from '../../pages/useFilters'; + +interface SampleDrawerHeaderProps { + transaction: string; + project?: Project; + subtitle?: string; + transactionMethod?: string; +} + +export function SampleDrawerHeaderTransaction(props: SampleDrawerHeaderProps) { + const organization = useOrganization(); + + const {project, subtitle, transaction, transactionMethod} = props; + const {view} = useDomainViewFilters(); + + const label = + transaction && transactionMethod && !transaction.startsWith(transactionMethod) + ? `${transactionMethod} ${transaction}` + : transaction; + + return ( + + {project && ( + + )} + + {subtitle ? ( + + {subtitle} + {DELIMITER} + + ) : null} + + {project ? ( + + {label} + + ) : ( + {label} + )} + + ); +} + +const DELIMITER = ':'; + +const Bar = styled('h4')` + display: flex; + align-items: center; + gap: ${space(1)}; + padding: 0; + margin: 0; + + font-size: ${p => p.theme.fontSizeMedium}; + font-weight: ${p => p.theme.fontWeightNormal}; + + overflow: hidden; +`; + +const Deemphasize = styled('span')` + color: ${p => p.theme.gray300}; +`; + +const TruncatedLink = styled(Link)` + ${p => p.theme.overflowEllipsis} +`; + +const TruncatedSpan = styled('span')` + ${p => p.theme.overflowEllipsis} +`; diff --git a/static/app/views/insights/common/utils/useMobileVitalsDrawer.tsx b/static/app/views/insights/common/utils/useMobileVitalsDrawer.tsx new file mode 100644 index 00000000000000..5055f4ca14a0fc --- /dev/null +++ b/static/app/views/insights/common/utils/useMobileVitalsDrawer.tsx @@ -0,0 +1,34 @@ +import {useCallback} from 'react'; + +import useDrawer from 'sentry/components/globalDrawer'; +import {t} from 'sentry/locale'; + +import type {VitalItem} from '../../mobile/screens/utils'; + +interface UseMobileVitalsDrawerProps { + Component: React.ReactNode; + onClose: () => void; + vital?: VitalItem; +} + +export function useMobileVitalsDrawer({ + Component, + vital, + onClose, +}: UseMobileVitalsDrawerProps) { + const {openDrawer, isDrawerOpen} = useDrawer(); + + const openVitalsDrawer = useCallback(() => { + if (!vital || isDrawerOpen) { + return; + } + + openDrawer(() => Component, { + ariaLabel: t('%s Details', vital.title), + onClose, + transitionProps: {stiffness: 1000}, + }); + }, [openDrawer, isDrawerOpen, onClose, Component, vital]); + + return {openVitalsDrawer}; +} diff --git a/static/app/views/insights/common/utils/useSamplesDrawer.tsx b/static/app/views/insights/common/utils/useSamplesDrawer.tsx new file mode 100644 index 00000000000000..68c3f7346a0b91 --- /dev/null +++ b/static/app/views/insights/common/utils/useSamplesDrawer.tsx @@ -0,0 +1,100 @@ +import {useCallback, useEffect} from 'react'; +import styled from '@emotion/styled'; +import type {Location} from 'history'; + +import useDrawer from 'sentry/components/globalDrawer'; +import {t} from 'sentry/locale'; +import {trackAnalytics} from 'sentry/utils/analytics'; +import {useLocation} from 'sentry/utils/useLocation'; +import {useNavigate} from 'sentry/utils/useNavigate'; +import useOrganization from 'sentry/utils/useOrganization'; + +import type {ModuleName} from '../../types'; + +interface UseSamplesDrawerProps { + Component: React.ReactNode; + moduleName: ModuleName; + requiredParams: [string, ...string[]]; +} + +export function useSamplesDrawer({ + Component, + moduleName, + requiredParams, +}: UseSamplesDrawerProps): void { + const organization = useOrganization(); + const {openDrawer, closeDrawer, isDrawerOpen} = useDrawer(); + const navigate = useNavigate(); + const location = useLocation(); + + const onClose = useCallback(() => { + navigate({ + query: { + ...location.query, + transaction: undefined, + transactionMethod: undefined, + spanGroup: undefined, + spanOp: undefined, + query: undefined, + responseCodeClass: undefined, + panel: undefined, + statusClass: undefined, + spanSearchQuery: undefined, + traceStatus: undefined, + retryCount: undefined, + }, + }); + }, [navigate, location.query]); + + const shouldCloseOnLocationChange = useCallback( + (newLocation: Location) => { + return !requiredParams.some(paramName => Boolean(newLocation.query[paramName])); + }, + [requiredParams] + ); + + const openSamplesDrawer = useCallback(() => { + if (isDrawerOpen) { + return; + } + + trackAnalytics('performance_views.sample_spans.opened', { + organization, + source: moduleName, + }); + + openDrawer(() => {Component}, { + ariaLabel: t('Samples'), + onClose, + transitionProps: {stiffness: 1000}, + shouldCloseOnLocationChange, + shouldCloseOnInteractOutside: () => false, + }); + }, [ + openDrawer, + isDrawerOpen, + onClose, + shouldCloseOnLocationChange, + Component, + organization, + moduleName, + ]); + + const shouldDrawerOpen = requiredParams.every(paramName => + Boolean(location.query[paramName]) + ); + + useEffect(() => { + if (shouldDrawerOpen) { + openSamplesDrawer(); + } else { + closeDrawer(); + } + }, [shouldDrawerOpen, openSamplesDrawer, closeDrawer]); +} + +const FullHeightWrapper = styled('div')` + height: 100%; + display: flex; + flex-direction: column; +`; diff --git a/static/app/views/insights/common/utils/useWebVitalsDrawer.tsx b/static/app/views/insights/common/utils/useWebVitalsDrawer.tsx new file mode 100644 index 00000000000000..91dbe451f0a96f --- /dev/null +++ b/static/app/views/insights/common/utils/useWebVitalsDrawer.tsx @@ -0,0 +1,34 @@ +import {useCallback} from 'react'; + +import useDrawer from 'sentry/components/globalDrawer'; +import {t} from 'sentry/locale'; + +import type {WebVitals} from '../../browser/webVitals/types'; + +interface UseWebVitalsDrawerProps { + Component: React.ReactNode; + onClose: () => void; + webVital: WebVitals | null; +} + +export function useWebVitalsDrawer({ + Component, + webVital, + onClose, +}: UseWebVitalsDrawerProps) { + const {openDrawer, isDrawerOpen} = useDrawer(); + + const openVitalsDrawer = useCallback(() => { + if (!webVital || isDrawerOpen) { + return; + } + + openDrawer(() => Component, { + ariaLabel: t('%s Details', webVital), + onClose, + transitionProps: {stiffness: 1000}, + }); + }, [openDrawer, isDrawerOpen, onClose, Component, webVital]); + + return {openVitalsDrawer}; +} diff --git a/static/app/views/insights/common/views/spanSummaryPage/sampleList/index.tsx b/static/app/views/insights/common/views/spanSummaryPage/sampleList/index.tsx index 6ef050ebb11c23..330d88cc7ba588 100644 --- a/static/app/views/insights/common/views/spanSummaryPage/sampleList/index.tsx +++ b/static/app/views/insights/common/views/spanSummaryPage/sampleList/index.tsx @@ -1,21 +1,16 @@ import {useCallback, useMemo, useState} from 'react'; import styled from '@emotion/styled'; import debounce from 'lodash/debounce'; -import omit from 'lodash/omit'; -import * as qs from 'query-string'; -import ProjectAvatar from 'sentry/components/avatar/projectAvatar'; +import {DrawerHeader} from 'sentry/components/globalDrawer/components'; import {COL_WIDTH_UNDEFINED} from 'sentry/components/gridEditable'; -import Link from 'sentry/components/links/link'; import {SpanSearchQueryBuilder} from 'sentry/components/performance/spanSearchQueryBuilder'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; -import {trackAnalytics} from 'sentry/utils/analytics'; import {generateLinkToEventInTraceView} from 'sentry/utils/discover/urls'; import {PageAlert, PageAlertProvider} from 'sentry/utils/performance/contexts/pageAlert'; import {decodeScalar} from 'sentry/utils/queryString'; import {MutableSearch} from 'sentry/utils/tokenizeSearch'; -import normalizeUrl from 'sentry/utils/url/normalizeUrl'; import useLocationQuery from 'sentry/utils/url/useLocationQuery'; import {useLocation} from 'sentry/utils/useLocation'; import useOrganization from 'sentry/utils/useOrganization'; @@ -24,7 +19,6 @@ import useProjects from 'sentry/utils/useProjects'; import useRouter from 'sentry/utils/useRouter'; import {DATA_TYPE} from 'sentry/views/insights/browser/resources/settings'; import decodeSubregions from 'sentry/views/insights/browser/resources/utils/queryParameterDecoders/subregions'; -import DetailPanel from 'sentry/views/insights/common/components/detailPanel'; import {DEFAULT_COLUMN_ORDER} from 'sentry/views/insights/common/components/samplesTable/spanSamplesTable'; import DurationChart from 'sentry/views/insights/common/views/spanSummaryPage/sampleList/durationChart'; import SampleInfo from 'sentry/views/insights/common/views/spanSummaryPage/sampleList/sampleInfo'; @@ -37,23 +31,19 @@ import { } from 'sentry/views/insights/types'; import {getTransactionSummaryBaseUrl} from 'sentry/views/performance/transactionSummary/utils'; +import {SampleDrawerBody} from '../../../components/sampleDrawerBody'; +import {SampleDrawerHeaderTransaction} from '../../../components/sampleDrawerHeaderTransaction'; + const {HTTP_RESPONSE_CONTENT_LENGTH, SPAN_DESCRIPTION} = SpanMetricsField; type Props = { groupId: string; moduleName: ModuleName; - onClose?: () => void; referrer?: string; transactionRoute?: string; }; -export function SampleList({ - groupId, - moduleName, - onClose, - transactionRoute, - referrer, -}: Props) { +export function SampleList({groupId, moduleName, transactionRoute, referrer}: Props) { const organization = useOrganization(); const {view} = useDomainViewFilters(); @@ -76,12 +66,6 @@ export function SampleList({ transactionRoute ??= `/${getTransactionSummaryBaseUrl(organization.slug, view, true)}`; - // A a transaction name is required to show the panel, but a transaction - // method is not - const detailKey = transactionName - ? [groupId, transactionName, transactionMethod].filter(Boolean).join(':') - : undefined; - // eslint-disable-next-line react-hooks/exhaustive-deps const debounceSetHighlightedSpanId = useCallback( debounce(id => { @@ -111,27 +95,6 @@ export function SampleList({ }); }; - const onOpenDetailPanel = useCallback(() => { - if (location.query.transaction) { - trackAnalytics('performance_views.sample_spans.opened', { - organization, - source: moduleName, - }); - } - }, [organization, location.query.transaction, moduleName]); - - const label = - transactionMethod && !transactionName.startsWith(transactionMethod) - ? `${transactionMethod} ${transactionName}` - : transactionName; - - const link = normalizeUrl( - `/organizations/${organization.slug}${transactionRoute}?${qs.stringify({ - project: location.query.project, - transaction: transactionName, - })}` - ); - // set additional query filters from the span search bar and the `query` param const spanSearch = new MutableSearch(spanSearchQuery ?? ''); if (location.query.query) { @@ -143,13 +106,6 @@ export function SampleList({ }); } - function defaultOnClose() { - router.replace({ - pathname: router.location.pathname, - query: omit(router.location.query, 'transaction', 'transactionMethod', 'query'), - }); - } - let columnOrder = DEFAULT_COLUMN_ORDER; const additionalFields: SpanIndexedField[] = [ @@ -178,27 +134,15 @@ export function SampleList({ return ( - { - onClose ? onClose() : defaultOnClose(); - }} - onOpen={onOpenDetailPanel} - > - - {project && ( - - )} - - <Link to={link}>{label}</Link> - - + + + + + - + ); } -const SpanSummaryProjectAvatar = styled(ProjectAvatar)` - padding-right: ${space(1)}; -`; - -const HeaderContainer = styled('div')` - width: 100%; - padding-bottom: ${space(2)}; - padding-top: ${space(1)}; - - display: grid; - grid-template-rows: auto auto auto; - align-items: center; - - @media (min-width: ${p => p.theme.breakpoints.small}) { - grid-template-rows: auto; - grid-template-columns: auto 1fr; - } -`; - -const Title = styled('h4')` - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - margin: 0; -`; - const StyledSearchBar = styled('div')` margin: ${space(2)} 0; `; diff --git a/static/app/views/insights/database/views/databaseSpanSummaryPage.tsx b/static/app/views/insights/database/views/databaseSpanSummaryPage.tsx index 387411862788db..722eb4b5c97edd 100644 --- a/static/app/views/insights/database/views/databaseSpanSummaryPage.tsx +++ b/static/app/views/insights/database/views/databaseSpanSummaryPage.tsx @@ -47,6 +47,8 @@ import { } from 'sentry/views/insights/types'; import {TraceViewSources} from 'sentry/views/performance/newTraceDetails/traceHeader/breadcrumbs'; +import {useSamplesDrawer} from '../../common/utils/useSamplesDrawer'; + type Query = { transaction: string; transactionMethod: string; @@ -146,6 +148,18 @@ export function DatabaseSpanSummaryPage({params}: Props) { [SpanMetricsField.SPAN_GROUP]: string; }; + useSamplesDrawer({ + Component: ( + + ), + moduleName: ModuleName.DB, + requiredParams: ['transaction'], + }); + const { isPending: isThroughputDataLoading, data: throughputData, @@ -285,12 +299,6 @@ export function DatabaseSpanSummaryPage({params}: Props) { )} - - diff --git a/static/app/views/insights/http/components/httpSamplesPanel.tsx b/static/app/views/insights/http/components/httpSamplesPanel.tsx index c8aae218f61a92..70d9e14c21cab4 100644 --- a/static/app/views/insights/http/components/httpSamplesPanel.tsx +++ b/static/app/views/insights/http/components/httpSamplesPanel.tsx @@ -1,11 +1,9 @@ -import {Fragment, useCallback} from 'react'; +import {Fragment} from 'react'; import styled from '@emotion/styled'; -import * as qs from 'query-string'; -import ProjectAvatar from 'sentry/components/avatar/projectAvatar'; import {Button} from 'sentry/components/button'; import {CompactSelect} from 'sentry/components/compactSelect'; -import Link from 'sentry/components/links/link'; +import {DrawerHeader} from 'sentry/components/globalDrawer/components'; import {SpanSearchQueryBuilder} from 'sentry/components/performance/spanSearchQueryBuilder'; import {SegmentedControl} from 'sentry/components/segmentedControl'; import {t} from 'sentry/locale'; @@ -26,7 +24,6 @@ import useOrganization from 'sentry/utils/useOrganization'; import usePageFilters from 'sentry/utils/usePageFilters'; import useProjects from 'sentry/utils/useProjects'; import {computeAxisMax} from 'sentry/views/insights/common/components/chart'; -import DetailPanel from 'sentry/views/insights/common/components/detailPanel'; import {MetricReadout} from 'sentry/views/insights/common/components/metricReadout'; import * as ModuleLayout from 'sentry/views/insights/common/components/moduleLayout'; import {ReadoutRibbon} from 'sentry/views/insights/common/components/ribbon'; @@ -54,7 +51,6 @@ import {BASE_FILTERS} from 'sentry/views/insights/http/settings'; import decodePanel from 'sentry/views/insights/http/utils/queryParameterDecoders/panel'; import decodeResponseCodeClass from 'sentry/views/insights/http/utils/queryParameterDecoders/responseCodeClass'; import {useDebouncedState} from 'sentry/views/insights/http/utils/useDebouncedState'; -import {useDomainViewFilters} from 'sentry/views/insights/pages/useFilters'; import { ModuleName, SpanFunction, @@ -63,7 +59,9 @@ import { type SpanMetricsQueryFilters, } from 'sentry/views/insights/types'; import {TraceViewSources} from 'sentry/views/performance/newTraceDetails/traceHeader/breadcrumbs'; -import {getTransactionSummaryBaseUrl} from 'sentry/views/performance/transactionSummary/utils'; + +import {SampleDrawerBody} from '../../common/components/sampleDrawerBody'; +import {SampleDrawerHeaderTransaction} from '../../common/components/sampleDrawerHeaderTransaction'; export function HTTPSamplesPanel() { const navigate = useNavigate(); @@ -83,7 +81,6 @@ export function HTTPSamplesPanel() { }); const organization = useOrganization(); - const {view} = useDomainViewFilters(); const {projects} = useProjects(); const {selection} = usePageFilters(); @@ -293,60 +290,18 @@ export function HTTPSamplesPanel() { } }; - const handleClose = () => { - navigate({ - pathname: location.pathname, - query: { - ...location.query, - transaction: undefined, - transactionMethod: undefined, - }, - }); - }; - - const handleOpen = useCallback(() => { - if (query.transaction) { - trackAnalytics('performance_views.sample_spans.opened', { - organization, - source: ModuleName.HTTP, - }); - } - }, [organization, query.transaction]); - return ( - + + + + + - - - {project && ( - - )} - - <Link - to={`${getTransactionSummaryBaseUrl(organization.slug, view)}?${qs.stringify( - { - project: query.project, - transaction: query.transaction, - } - )}`} - > - {query.transaction && - query.transactionMethod && - !query.transaction.startsWith(query.transactionMethod) - ? `${query.transactionMethod} ${query.transaction}` - : query.transaction} - </Link> - - - - )} - + ); } @@ -570,10 +525,6 @@ const SPAN_SAMPLES_SORT = { kind: 'desc' as const, }; -const SpanSummaryProjectAvatar = styled(ProjectAvatar)` - padding-right: ${space(1)}; -`; - const HTTP_RESPONSE_CODE_CLASS_OPTIONS = [ { value: '', @@ -597,25 +548,6 @@ const HTTP_RESPONSE_CODE_CLASS_OPTIONS = [ }, ]; -// TODO - copy of static/app/views/starfish/views/spanSummaryPage/sampleList/index.tsx -const HeaderContainer = styled('div')` - display: grid; - grid-template-rows: auto auto auto; - align-items: center; - - @media (min-width: ${p => p.theme.breakpoints.small}) { - grid-template-rows: auto; - grid-template-columns: auto 1fr; - } -`; - -const Title = styled('h4')` - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - margin: 0; -`; - const PanelControls = styled('div')` display: flex; justify-content: space-between; diff --git a/static/app/views/insights/http/views/httpDomainSummaryPage.tsx b/static/app/views/insights/http/views/httpDomainSummaryPage.tsx index 33ec1acbb8c306..4c6de0f5c56855 100644 --- a/static/app/views/insights/http/views/httpDomainSummaryPage.tsx +++ b/static/app/views/insights/http/views/httpDomainSummaryPage.tsx @@ -57,6 +57,8 @@ import {useDomainViewFilters} from 'sentry/views/insights/pages/useFilters'; import type {SpanMetricsQueryFilters} from 'sentry/views/insights/types'; import {ModuleName, SpanFunction, SpanMetricsField} from 'sentry/views/insights/types'; +import {useSamplesDrawer} from '../../common/utils/useSamplesDrawer'; + type Query = { aggregate?: string; domain?: string; @@ -81,9 +83,16 @@ export function HTTPDomainSummaryPage() { project: decodeScalar, domain: decodeScalar, [SpanMetricsField.USER_GEO_SUBREGION]: decodeList, + transaction: decodeScalar, }, }); + useSamplesDrawer({ + Component: , + moduleName: ModuleName.HTTP, + requiredParams: ['transaction'], + }); + const project = projects.find(p => projectId === p.id); const filters: SpanMetricsQueryFilters = { ...BASE_FILTERS, @@ -335,8 +344,6 @@ export function HTTPDomainSummaryPage() { - - ); } diff --git a/static/app/views/insights/mobile/appStarts/views/screenSummaryPage.tsx b/static/app/views/insights/mobile/appStarts/views/screenSummaryPage.tsx index 541b63b6967f18..703af59ef8738e 100644 --- a/static/app/views/insights/mobile/appStarts/views/screenSummaryPage.tsx +++ b/static/app/views/insights/mobile/appStarts/views/screenSummaryPage.tsx @@ -21,6 +21,7 @@ import { SECONDARY_RELEASE_ALIAS, } from 'sentry/views/insights/common/components/releaseSelector'; import {ToolRibbon} from 'sentry/views/insights/common/components/ribbon'; +import {useSamplesDrawer} from 'sentry/views/insights/common/utils/useSamplesDrawer'; import {SamplesTables} from 'sentry/views/insights/mobile/appStarts/components/samples'; import { COLD_START_TYPE, @@ -88,7 +89,6 @@ export function ScreenSummaryContentPage() { secondaryRelease, transaction: transactionName, spanGroup, - spanOp, [SpanMetricsField.APP_START_TYPE]: appStartType, } = location.query; @@ -108,6 +108,37 @@ export function ScreenSummaryContentPage() { } }, [location, appStartType, navigate]); + useSamplesDrawer({ + Component: ( + { + navigate( + { + pathname: location.pathname, + query: omit( + location.query, + 'spanGroup', + 'transactionMethod', + 'spanDescription', + 'spanOp' + ), + }, + {replace: true} + ); + }} + /> + ), + moduleName: ModuleName.APP_START, + requiredParams: [ + 'transaction', + 'spanGroup', + 'spanOp', + SpanMetricsField.APP_START_TYPE, + ], + }); + return ( @@ -179,27 +210,6 @@ export function ScreenSummaryContentPage() { - {spanGroup && spanOp && appStartType && ( - { - navigate( - { - pathname: location.pathname, - query: omit( - location.query, - 'spanGroup', - 'transactionMethod', - 'spanDescription', - 'spanOp' - ), - }, - {replace: true} - ); - }} - /> - )} ); } diff --git a/static/app/views/insights/mobile/common/components/spanSamplesPanel.tsx b/static/app/views/insights/mobile/common/components/spanSamplesPanel.tsx index e7fbadb0e3d8c8..a98c96d9a60ec6 100644 --- a/static/app/views/insights/mobile/common/components/spanSamplesPanel.tsx +++ b/static/app/views/insights/mobile/common/components/spanSamplesPanel.tsx @@ -1,21 +1,18 @@ -import {useCallback} from 'react'; import styled from '@emotion/styled'; -import omit from 'lodash/omit'; import * as qs from 'query-string'; import ProjectAvatar from 'sentry/components/avatar/projectAvatar'; +import {DrawerHeader} from 'sentry/components/globalDrawer/components'; import Link from 'sentry/components/links/link'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; -import {trackAnalytics} from 'sentry/utils/analytics'; import {PageAlert, PageAlertProvider} from 'sentry/utils/performance/contexts/pageAlert'; import {decodeScalar} from 'sentry/utils/queryString'; import normalizeUrl from 'sentry/utils/url/normalizeUrl'; import useLocationQuery from 'sentry/utils/url/useLocationQuery'; import {useLocation} from 'sentry/utils/useLocation'; import useOrganization from 'sentry/utils/useOrganization'; -import useRouter from 'sentry/utils/useRouter'; -import DetailPanel from 'sentry/views/insights/common/components/detailPanel'; +import {SampleDrawerBody} from 'sentry/views/insights/common/components/sampleDrawerBody'; import {useReleaseSelection} from 'sentry/views/insights/common/queries/useReleases'; import {SpanSamplesContainer} from 'sentry/views/insights/mobile/common/components/spanSamplesPanelContainer'; import useCrossPlatformProject from 'sentry/views/insights/mobile/common/queries/useCrossPlatformProject'; @@ -33,13 +30,7 @@ type Props = { const PRIMARY_SPAN_QUERY_KEY = 'primarySpanSearchQuery'; const SECONDARY_SPAN_QUERY_KEY = 'secondarySpanSearchQuery'; -export function SpanSamplesPanel({ - groupId, - moduleName, - onClose, - transactionRoute, -}: Props) { - const router = useRouter(); +export function SpanSamplesPanel({groupId, moduleName, transactionRoute}: Props) { const organization = useOrganization(); const {view} = useDomainViewFilters(); @@ -70,24 +61,9 @@ export function SpanSamplesPanel({ const {primaryRelease, secondaryRelease} = useReleaseSelection(); - // A a transaction name is required to show the panel, but a transaction - // method is not - const detailKey = transactionName - ? [groupId, transactionName, transactionMethod].filter(Boolean).join(':') - : undefined; - const {query} = useLocation(); const {project} = useCrossPlatformProject(); - const onOpenDetailPanel = useCallback(() => { - if (query.transaction) { - trackAnalytics('performance_views.sample_spans.opened', { - organization, - source: moduleName, - }); - } - }, [organization, query.transaction, moduleName]); - const label = transactionMethod && !transactionName.startsWith(transactionMethod) ? `${transactionMethod} ${transactionName}` @@ -100,22 +76,11 @@ export function SpanSamplesPanel({ })}` ); - function defaultOnClose() { - router.replace({ - pathname: router.location.pathname, - query: omit(router.location.query, 'transaction', 'transactionMethod'), - }); - } - return ( - { - onClose ? onClose() : defaultOnClose(); - }} - onOpen={onOpenDetailPanel} - > + + + {project && ( - + ); } diff --git a/static/app/views/insights/mobile/screenload/views/screenLoadSpansPage.tsx b/static/app/views/insights/mobile/screenload/views/screenLoadSpansPage.tsx index fc9ec508ddd715..9efee436b53878 100644 --- a/static/app/views/insights/mobile/screenload/views/screenLoadSpansPage.tsx +++ b/static/app/views/insights/mobile/screenload/views/screenLoadSpansPage.tsx @@ -20,6 +20,7 @@ import { SECONDARY_RELEASE_ALIAS, } from 'sentry/views/insights/common/components/releaseSelector'; import {ToolRibbon} from 'sentry/views/insights/common/components/ribbon'; +import {useSamplesDrawer} from 'sentry/views/insights/common/utils/useSamplesDrawer'; import {QueryParameterNames} from 'sentry/views/insights/common/views/queryParameters'; import {SpanSamplesPanel} from 'sentry/views/insights/mobile/common/components/spanSamplesPanel'; import useCrossPlatformProject from 'sentry/views/insights/mobile/common/queries/useCrossPlatformProject'; @@ -78,8 +79,8 @@ function ScreenLoadSpans() { } export function ScreenLoadSpansContent() { - const location = useLocation(); const router = useRouter(); + const location = useLocation(); const { spanGroup, @@ -88,6 +89,23 @@ export function ScreenLoadSpansContent() { transaction: transactionName, } = location.query; + useSamplesDrawer({ + Component: ( + { + router.replace({ + pathname: router.location.pathname, + query: omit(router.location.query, 'spanGroup', 'transactionMethod'), + }); + }} + /> + ), + moduleName: ModuleName.SCREEN_LOAD, + requiredParams: ['transaction', 'spanGroup'], + }); + return ( @@ -179,18 +197,6 @@ export function ScreenLoadSpansContent() { primaryRelease={primaryRelease} secondaryRelease={secondaryRelease} /> - {spanGroup && ( - { - router.replace({ - pathname: router.location.pathname, - query: omit(router.location.query, 'spanGroup', 'transactionMethod'), - }); - }} - /> - )} ); diff --git a/static/app/views/insights/mobile/screens/components/vitalDetailPanel.spec.tsx b/static/app/views/insights/mobile/screens/components/vitalDetailPanel.spec.tsx index 6fd3c2a3b03440..bb3a41bff9d667 100644 --- a/static/app/views/insights/mobile/screens/components/vitalDetailPanel.spec.tsx +++ b/static/app/views/insights/mobile/screens/components/vitalDetailPanel.spec.tsx @@ -1,4 +1,4 @@ -import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary'; +import {render, screen} from 'sentry-test/reactTestingLibrary'; import {DiscoverDatasets} from 'sentry/utils/discover/types'; import {PageAlertProvider} from 'sentry/utils/performance/contexts/pageAlert'; @@ -46,7 +46,7 @@ describe('VitalDetailPanel', () => { test('renders correctly with given props', () => { render( - + ); @@ -64,17 +64,4 @@ describe('VitalDetailPanel', () => { 'https://example.com/platform-docs' ); }); - - test('calls onClose when close action is triggered', async () => { - const onCloseMock = jest.fn(); - render( - - - - ); - - const closeButton = screen.getByLabelText('Close Details'); - await userEvent.click(closeButton); - expect(onCloseMock).toHaveBeenCalled(); - }); }); diff --git a/static/app/views/insights/mobile/screens/components/vitalDetailPanel.tsx b/static/app/views/insights/mobile/screens/components/vitalDetailPanel.tsx index 6ae37bf48a96cf..ba2f939aa4621f 100644 --- a/static/app/views/insights/mobile/screens/components/vitalDetailPanel.tsx +++ b/static/app/views/insights/mobile/screens/components/vitalDetailPanel.tsx @@ -1,12 +1,13 @@ import React from 'react'; import styled from '@emotion/styled'; +import {DrawerHeader} from 'sentry/components/globalDrawer/components'; import ExternalLink from 'sentry/components/links/externalLink'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import {PageAlert, PageAlertProvider} from 'sentry/utils/performance/contexts/pageAlert'; import {PERFORMANCE_SCORE_COLORS} from 'sentry/views/insights/browser/webVitals/utils/performanceScoreColors'; -import DetailPanel from 'sentry/views/insights/common/components/detailPanel'; +import {SampleDrawerBody} from 'sentry/views/insights/common/components/sampleDrawerBody'; import useCrossPlatformProject from 'sentry/views/insights/mobile/common/queries/useCrossPlatformProject'; import { PerformanceScore, @@ -17,9 +18,7 @@ import { export function VitalDetailPanel({ vital, status, - onClose, }: { - onClose: () => void; status: VitalStatus | undefined; vital: VitalItem | undefined; }) { @@ -30,7 +29,9 @@ export function VitalDetailPanel({ return ( - + + + {vital && ( {vital.title} @@ -68,14 +69,13 @@ export function VitalDetailPanel({ )} - + ); } const VitalDetailTitle = styled('h4')` margin-bottom: ${space(1)}; - margin-top: 40px; `; const Badge = styled('div')<{status: string}>` diff --git a/static/app/views/insights/mobile/screens/views/screensLandingPage.tsx b/static/app/views/insights/mobile/screens/views/screensLandingPage.tsx index c4a806c334f592..2cac04d3100870 100644 --- a/static/app/views/insights/mobile/screens/views/screensLandingPage.tsx +++ b/static/app/views/insights/mobile/screens/views/screensLandingPage.tsx @@ -1,4 +1,4 @@ -import {Fragment, useCallback, useState} from 'react'; +import {Fragment, useCallback, useEffect, useState} from 'react'; import styled from '@emotion/styled'; import omit from 'lodash/omit'; @@ -24,6 +24,7 @@ import useOrganization from 'sentry/utils/useOrganization'; import usePageFilters from 'sentry/utils/usePageFilters'; import {ModulePageProviders} from 'sentry/views/insights/common/components/modulePageProviders'; import {ModuleBodyUpsellHook} from 'sentry/views/insights/common/components/moduleUpsellHookWrapper'; +import {useMobileVitalsDrawer} from 'sentry/views/insights/common/utils/useMobileVitalsDrawer'; import useCrossPlatformProject from 'sentry/views/insights/mobile/common/queries/useCrossPlatformProject'; import {PlatformSelector} from 'sentry/views/insights/mobile/screenload/components/platformSelector'; import {SETUP_CONTENT as TTFD_SETUP} from 'sentry/views/insights/mobile/screenload/data/setupContent'; @@ -287,6 +288,20 @@ export function ScreensLandingPage() { return undefined; }; + const {openVitalsDrawer} = useMobileVitalsDrawer({ + Component: , + vital: state.vital, + onClose: () => { + setState({vital: undefined, status: undefined}); + }, + }); + + useEffect(() => { + if (state.vital) { + openVitalsDrawer(); + } + }); + return ( @@ -347,13 +362,6 @@ export function ScreensLandingPage() { - { - setState({vital: undefined, status: undefined}); - }} - /> diff --git a/static/app/views/insights/mobile/ui/views/screenSummaryPage.tsx b/static/app/views/insights/mobile/ui/views/screenSummaryPage.tsx index 540d39cd82ed07..186e9b46428371 100644 --- a/static/app/views/insights/mobile/ui/views/screenSummaryPage.tsx +++ b/static/app/views/insights/mobile/ui/views/screenSummaryPage.tsx @@ -14,6 +14,7 @@ import {ModulePageFilterBar} from 'sentry/views/insights/common/components/modul import {ModulePageProviders} from 'sentry/views/insights/common/components/modulePageProviders'; import {ReleaseComparisonSelector} from 'sentry/views/insights/common/components/releaseSelector'; import {ToolRibbon} from 'sentry/views/insights/common/components/ribbon'; +import {useSamplesDrawer} from 'sentry/views/insights/common/utils/useSamplesDrawer'; import {SpanSamplesPanel} from 'sentry/views/insights/mobile/common/components/spanSamplesPanel'; import {SamplesTables} from 'sentry/views/insights/mobile/common/components/tables/samplesTables'; import {SpanOperationTable} from 'sentry/views/insights/mobile/ui/components/tables/spanOperationTable'; @@ -64,10 +65,33 @@ function ScreenSummary() { } export function ScreenSummaryContent() { - const location = useLocation(); const router = useRouter(); + const location = useLocation(); + + const {transaction: transactionName, spanGroup} = location.query; - const {transaction: transactionName, spanGroup, spanOp} = location.query; + useSamplesDrawer({ + Component: ( + { + router.replace({ + pathname: router.location.pathname, + query: omit( + router.location.query, + 'spanGroup', + 'transactionMethod', + 'spanDescription', + 'spanOp' + ), + }); + }} + /> + ), + moduleName: ModuleName.OTHER, + requiredParams: ['spanGroup', 'spanOp'], + }); return ( @@ -89,25 +113,6 @@ export function ScreenSummaryContent() { EventSamples={_props =>
} /> - - {spanGroup && spanOp && ( - { - router.replace({ - pathname: router.location.pathname, - query: omit( - router.location.query, - 'spanGroup', - 'transactionMethod', - 'spanDescription', - 'spanOp' - ), - }); - }} - /> - )} ); } diff --git a/static/app/views/insights/queues/components/messageSpanSamplesPanel.spec.tsx b/static/app/views/insights/queues/components/messageSpanSamplesPanel.spec.tsx index a58f04f2c5f47b..f14ec38cebfeac 100644 --- a/static/app/views/insights/queues/components/messageSpanSamplesPanel.spec.tsx +++ b/static/app/views/insights/queues/components/messageSpanSamplesPanel.spec.tsx @@ -223,7 +223,7 @@ describe('messageSpanSamplesPanel', () => { }) ); expect(screen.getByRole('table', {name: 'Span Samples'})).toBeInTheDocument(); - expect(screen.getByText('Consumer')).toBeInTheDocument(); + expect(screen.getByText(/Consumer/)).toBeInTheDocument(); // Metrics Ribbon expect(screen.getByText('Processed')).toBeInTheDocument(); expect(screen.getByText('Error Rate')).toBeInTheDocument(); @@ -318,7 +318,7 @@ describe('messageSpanSamplesPanel', () => { }) ); expect(screen.getByRole('table', {name: 'Span Samples'})).toBeInTheDocument(); - expect(screen.getByText('Producer')).toBeInTheDocument(); + expect(screen.getByText(/Producer/)).toBeInTheDocument(); // Metrics Ribbon expect(screen.getByText('Published')).toBeInTheDocument(); expect(screen.getByText('Error Rate')).toBeInTheDocument(); diff --git a/static/app/views/insights/queues/components/messageSpanSamplesPanel.tsx b/static/app/views/insights/queues/components/messageSpanSamplesPanel.tsx index 9fcd6566105986..54c474ca4e0e19 100644 --- a/static/app/views/insights/queues/components/messageSpanSamplesPanel.tsx +++ b/static/app/views/insights/queues/components/messageSpanSamplesPanel.tsx @@ -1,11 +1,8 @@ -import {useCallback} from 'react'; import styled from '@emotion/styled'; -import * as qs from 'query-string'; -import ProjectAvatar from 'sentry/components/avatar/projectAvatar'; import {Button} from 'sentry/components/button'; import {CompactSelect, type SelectOption} from 'sentry/components/compactSelect'; -import Link from 'sentry/components/links/link'; +import {DrawerHeader} from 'sentry/components/globalDrawer/components'; import {SpanSearchQueryBuilder} from 'sentry/components/performance/spanSearchQueryBuilder'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; @@ -21,7 +18,6 @@ import useOrganization from 'sentry/utils/useOrganization'; import usePageFilters from 'sentry/utils/usePageFilters'; import useProjects from 'sentry/utils/useProjects'; import {computeAxisMax} from 'sentry/views/insights/common/components/chart'; -import DetailPanel from 'sentry/views/insights/common/components/detailPanel'; import {MetricReadout} from 'sentry/views/insights/common/components/metricReadout'; import * as ModuleLayout from 'sentry/views/insights/common/components/moduleLayout'; import {ReadoutRibbon} from 'sentry/views/insights/common/components/ribbon'; @@ -31,7 +27,6 @@ import {useSampleScatterPlotSeries} from 'sentry/views/insights/common/views/spa import {DurationChartWithSamples} from 'sentry/views/insights/http/components/charts/durationChartWithSamples'; import {useSpanSamples} from 'sentry/views/insights/http/queries/useSpanSamples'; import {useDebouncedState} from 'sentry/views/insights/http/utils/useDebouncedState'; -import {useDomainViewFilters} from 'sentry/views/insights/pages/useFilters'; import {MessageSpanSamplesTable} from 'sentry/views/insights/queues/components/tables/messageSpanSamplesTable'; import {useQueuesMetricsQuery} from 'sentry/views/insights/queues/queries/useQueuesMetricsQuery'; import {Referrer} from 'sentry/views/insights/queues/referrers'; @@ -49,8 +44,9 @@ import { SpanIndexedField, type SpanMetricsResponse, } from 'sentry/views/insights/types'; -import {getTransactionSummaryBaseUrl} from 'sentry/views/performance/transactionSummary/utils'; -import {Subtitle} from 'sentry/views/profiling/landing/styles'; + +import {SampleDrawerBody} from '../../common/components/sampleDrawerBody'; +import {SampleDrawerHeaderTransaction} from '../../common/components/sampleDrawerHeaderTransaction'; export function MessageSpanSamplesPanel() { const navigate = useNavigate(); @@ -72,7 +68,6 @@ export function MessageSpanSamplesPanel() { const project = projects.find(p => query.project === p.id); const organization = useOrganization(); - const {view} = useDomainViewFilters(); const [highlightedSpanId, setHighlightedSpanId] = useDebouncedState( undefined, @@ -228,65 +223,20 @@ export function MessageSpanSamplesPanel() { }); }; - const handleClose = () => { - navigate({ - pathname: location.pathname, - query: { - ...location.query, - transaction: undefined, - transactionMethod: undefined, - }, - }); - }; - - const handleOpen = useCallback(() => { - if (query.transaction) { - trackAnalytics('performance_views.sample_spans.opened', { - organization, - source: ModuleName.QUEUE, - }); - } - }, [organization, query.transaction]); - return ( - + + + + + - - - {project ? ( - - ) : ( -
- )} - - - {messageActorType === MessageActorType.PRODUCER - ? t('Producer') - : t('Consumer')} - - - <Link - to={`${getTransactionSummaryBaseUrl(organization.slug, view)}?${qs.stringify( - { - project: query.project, - transaction: query.transaction, - } - )}`} - > - {query.transaction} - </Link> - - - - - {messageActorType === MessageActorType.PRODUCER ? ( @@ -400,7 +350,7 @@ export function MessageSpanSamplesPanel() { - + ); } @@ -497,32 +447,6 @@ const RETRY_COUNT_SELECT_OPTIONS = [ }), ]; -const SpanSummaryProjectAvatar = styled(ProjectAvatar)` - padding-right: ${space(1)}; -`; - -const HeaderContainer = styled('div')` - display: grid; - grid-template-rows: auto auto auto; - - @media (min-width: ${p => p.theme.breakpoints.small}) { - grid-template-rows: auto; - grid-template-columns: auto 1fr; - } -`; - -const TitleContainer = styled('div')` - width: 100%; - overflow: hidden; -`; - -const Title = styled('h4')` - overflow: inherit; - text-overflow: ellipsis; - white-space: nowrap; - margin: 0; -`; - const MetricsRibbonContainer = styled('div')` display: flex; flex-wrap: wrap; diff --git a/static/app/views/insights/queues/views/destinationSummaryPage.tsx b/static/app/views/insights/queues/views/destinationSummaryPage.tsx index 549fe1c419d7e4..c6acbcbb1669c5 100644 --- a/static/app/views/insights/queues/views/destinationSummaryPage.tsx +++ b/static/app/views/insights/queues/views/destinationSummaryPage.tsx @@ -28,6 +28,8 @@ import {DESTINATION_TITLE} from 'sentry/views/insights/queues/settings'; import {ModuleName} from 'sentry/views/insights/types'; import Onboarding from 'sentry/views/performance/onboarding'; +import {useSamplesDrawer} from '../../common/utils/useSamplesDrawer'; + function DestinationSummaryPage() { const organization = useOrganization(); const onboardingProject = useOnboardingProject(); @@ -41,6 +43,12 @@ function DestinationSummaryPage() { }); const errorRate = 1 - (data[0]?.['trace_status_rate(ok)'] ?? 0); + useSamplesDrawer({ + Component: , + moduleName: ModuleName.QUEUE, + requiredParams: ['transaction'], + }); + return ( - ); }