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 && (
-
- )}
-
-
- {query.transaction}
-
-
-
-
-
-
+
);
}
@@ -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 && (
-
- )}
-
- {label}
-
-
+
+
+
+
+
-
+
);
}
-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 && (
-
- )}
-
-
- {query.transaction &&
- query.transactionMethod &&
- !query.transaction.startsWith(query.transactionMethod)
- ? `${query.transactionMethod} ${query.transaction}`
- : query.transaction}
-
-
-
-
-
)}
-
+
);
}
@@ -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')}
-
-
-
- {query.transaction}
-
-
-
-
-
-
{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 (
-
);
}