diff --git a/static/app/components/events/metrics/metricsSection.tsx b/static/app/components/events/metrics/metricsSection.tsx index 85a5bb98ab2708..d4fd6d2bd720e1 100644 --- a/static/app/components/events/metrics/metricsSection.tsx +++ b/static/app/components/events/metrics/metricsSection.tsx @@ -1,4 +1,4 @@ -import {useCallback, useRef} from 'react'; +import {useCallback, useEffect, useRef} from 'react'; import {Flex} from '@sentry/scraps/layout/flex'; @@ -12,8 +12,11 @@ import type {Event} from 'sentry/types/event'; import type {Group} from 'sentry/types/group'; import type {Project} from 'sentry/types/project'; 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 {TraceItemAttributeProvider} from 'sentry/views/explore/contexts/traceItemAttributeContext'; +import {METRICS_DRAWER_QUERY_PARAM} from 'sentry/views/explore/metrics/constants'; import {MetricsSamplesTable} from 'sentry/views/explore/metrics/metricInfoTabs/metricsSamplesTable'; import {canUseMetricsUI} from 'sentry/views/explore/metrics/metricsFlags'; import {TraceItemDataset} from 'sentry/views/explore/types'; @@ -68,6 +71,8 @@ function MetricsSectionContent({ traceId: string; }) { const organization = useOrganization(); + const navigate = useNavigate(); + const location = useLocation(); const {openDrawer} = useDrawer(); const viewAllButtonRef = useRef(null); const {result, error} = useMetricsIssueSection({traceId}); @@ -81,6 +86,24 @@ function MetricsSectionContent({ trackAnalytics('metrics.issue_details.drawer_opened', { organization, }); + + navigate( + { + ...location, + query: { + ...location.query, + [METRICS_DRAWER_QUERY_PARAM]: 'true', + }, + }, + {replace: true} + ); + }, + [navigate, location, organization] + ); + + useEffect(() => { + const shouldOpenDrawer = location.query[METRICS_DRAWER_QUERY_PARAM] === 'true'; + if (shouldOpenDrawer) { openDrawer( () => ( @@ -99,14 +122,24 @@ function MetricsSectionContent({ const viewAllButton = viewAllButtonRef.current; return !viewAllButton?.contains(element); }, + onClose: () => { + navigate( + { + ...location, + query: { + ...location.query, + [METRICS_DRAWER_QUERY_PARAM]: undefined, + }, + }, + {replace: true} + ); + }, } ); - }, - [group, event, project, openDrawer, organization, traceId] - ); + } + }, [location.query, traceId, group, event, project, openDrawer, navigate, location]); if (!result.data || result.data.length === 0 || error) { - // Don't show the metrics section if there are no metrics return null; } diff --git a/static/app/components/events/ourlogs/ourlogsDrawer.tsx b/static/app/components/events/ourlogs/ourlogsDrawer.tsx index d4ebef5efc6be5..802264242e4b92 100644 --- a/static/app/components/events/ourlogs/ourlogsDrawer.tsx +++ b/static/app/components/events/ourlogs/ourlogsDrawer.tsx @@ -43,6 +43,10 @@ interface LogIssueDrawerProps { event: Event; group: Group; project: Project; + additionalData?: { + event?: Event; + scrollToDisabled?: boolean; + }; embeddedOptions?: { openWithExpandedIds?: string[]; }; @@ -53,6 +57,7 @@ export function OurlogsDrawer({ project, group, embeddedOptions, + additionalData: propAdditionalData, }: LogIssueDrawerProps) { const organization = useOrganization(); const setLogsQuery = useSetQueryParamsQuery(); @@ -81,8 +86,9 @@ export function OurlogsDrawer({ const additionalData = useMemo( () => ({ event, + scrollToDisabled: propAdditionalData?.scrollToDisabled, }), - [event] + [event, propAdditionalData?.scrollToDisabled] ); const exploreUrl = useMemo(() => { diff --git a/static/app/components/events/ourlogs/ourlogsSection.tsx b/static/app/components/events/ourlogs/ourlogsSection.tsx index 609c75a2216826..8147d2af09bb90 100644 --- a/static/app/components/events/ourlogs/ourlogsSection.tsx +++ b/static/app/components/events/ourlogs/ourlogsSection.tsx @@ -1,4 +1,4 @@ -import {useCallback, useRef} from 'react'; +import {useCallback, useEffect, useRef} from 'react'; import styled from '@emotion/styled'; import {Button} from 'sentry/components/core/button'; @@ -11,6 +11,8 @@ import type {Group} from 'sentry/types/group'; import type {Project} from 'sentry/types/project'; import {trackAnalytics} from 'sentry/utils/analytics'; import {LogsAnalyticsPageSource} from 'sentry/utils/analytics/logsAnalyticsEvent'; +import {useLocation} from 'sentry/utils/useLocation'; +import {useNavigate} from 'sentry/utils/useNavigate'; import useOrganization from 'sentry/utils/useOrganization'; import {TableBody} from 'sentry/views/explore/components/table'; import { @@ -18,6 +20,7 @@ import { useLogsPageDataQueryResult, } from 'sentry/views/explore/contexts/logs/logsPageData'; import {TraceItemAttributeProvider} from 'sentry/views/explore/contexts/traceItemAttributeContext'; +import {LOGS_DRAWER_QUERY_PARAM} from 'sentry/views/explore/logs/constants'; import {LogsQueryParamsProvider} from 'sentry/views/explore/logs/logsQueryParamsProvider'; import {LogRowContent} from 'sentry/views/explore/logs/tables/logsTableRow'; import {useQueryParamsSearch} from 'sentry/views/explore/queryParams/context'; @@ -58,6 +61,8 @@ function OurlogsSectionContent({ project: Project; }) { const organization = useOrganization(); + const navigate = useNavigate(); + const location = useLocation(); const feature = organization.features.includes('ourlogs-enabled'); const tableData = useLogsPageDataQueryResult(); const logsSearch = useQueryParamsSearch(); @@ -73,6 +78,34 @@ function OurlogsSectionContent({ trackAnalytics('logs.issue_details.drawer_opened', { organization, }); + + navigate( + { + ...location, + query: { + ...location.query, + [LOGS_DRAWER_QUERY_PARAM]: 'true', + ...(expandedLogId && {expandedLogId}), + }, + }, + {replace: true} + ); + }, + [navigate, location, organization] + ); + + const onEmbeddedRowClick = useCallback( + (logItemId: string, clickEvent: React.MouseEvent) => { + onOpenLogsDrawer(clickEvent, logItemId); + }, + [onOpenLogsDrawer] + ); + + useEffect(() => { + const shouldOpenDrawer = location.query[LOGS_DRAWER_QUERY_PARAM] === 'true'; + if (shouldOpenDrawer && traceId) { + const expandedLogId = location.query.expandedLogId as string | undefined; + openDrawer( () => ( @@ -97,23 +134,27 @@ function OurlogsSectionContent({ { ariaLabel: 'logs drawer', drawerKey: 'logs-issue-drawer', - shouldCloseOnInteractOutside: element => { const viewAllButton = viewAllButtonRef.current; return !viewAllButton?.contains(element); }, + onClose: () => { + navigate( + { + ...location, + query: { + ...location.query, + [LOGS_DRAWER_QUERY_PARAM]: undefined, + expandedLogId: undefined, + }, + }, + {replace: true} + ); + }, } ); - }, - [group, event, project, openDrawer, organization, traceId] - ); - - const onEmbeddedRowClick = useCallback( - (logItemId: string, clickEvent: React.MouseEvent) => { - onOpenLogsDrawer(clickEvent, logItemId); - }, - [onOpenLogsDrawer] - ); + } + }, [location.query, traceId, group, event, project, openDrawer, navigate, location]); if (!feature) { return null; } diff --git a/static/app/views/explore/logs/constants.tsx b/static/app/views/explore/logs/constants.tsx index 7430b63f61f3e3..045ca47a907878 100644 --- a/static/app/views/explore/logs/constants.tsx +++ b/static/app/views/explore/logs/constants.tsx @@ -79,6 +79,12 @@ export const LOGS_INSTRUCTIONS_URL = export const LOGS_FILTER_KEY_SECTIONS: FilterKeySection[] = [LOGS_FILTERS]; +/** + * Query parameter key for controlling the logs drawer state. + * When this parameter is set to 'true', the logs drawer should open automatically. + */ +export const LOGS_DRAWER_QUERY_PARAM = 'logsDrawer'; + export const VIRTUAL_STREAMED_INTERVAL_MS = 250; export const MINIMUM_INFINITE_SCROLL_FETCH_COOLDOWN_MS = 1000; diff --git a/static/app/views/explore/logs/tables/logsInfiniteTable.tsx b/static/app/views/explore/logs/tables/logsInfiniteTable.tsx index af947e45cd9253..eecc959bfc8e67 100644 --- a/static/app/views/explore/logs/tables/logsInfiniteTable.tsx +++ b/static/app/views/explore/logs/tables/logsInfiniteTable.tsx @@ -1,4 +1,4 @@ -import type {CSSProperties} from 'react'; +import type {CSSProperties, RefObject} from 'react'; import {Fragment, useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {useTheme} from '@emotion/react'; import styled from '@emotion/styled'; @@ -74,6 +74,7 @@ import {EmptyStateText} from 'sentry/views/explore/tables/tracesTable/styles'; type LogsTableProps = { additionalData?: { event?: Event; + scrollToDisabled?: boolean; }; allowPagination?: boolean; embedded?: boolean; @@ -132,6 +133,7 @@ export function LogsInfiniteTable({ } = useLogsPageDataQueryResult(); const baseData = localOnlyItemFilters?.filteredItems ?? originalData; + const baseDataLength = useBox(baseData.length); const pseudoRowIndex = useMemo(() => { if ( @@ -149,7 +151,7 @@ export function LogsInfiniteTable({ row => isRegularLogResponseItem(row) && getLogRowTimestampMillis(row) < eventTimestamp ); - return index === -1 ? baseData.length : index; // If the event is older than all the data, add it to the end. + return index === -1 ? -2 : index; // If the event is older than all the data, add it to the end with a sentinel value of -2. This causes the useEffect to not continously add it. }, [additionalData, baseData, isPending, isError]); const data: LogTableRowItem[] = useMemo(() => { @@ -165,8 +167,10 @@ export function LogsInfiniteTable({ } const newData: LogTableRowItem[] = [...baseData]; + const newSelectedIndex = + pseudoRowIndex === -2 ? baseDataLength.current : pseudoRowIndex; newData.splice( - pseudoRowIndex, + newSelectedIndex, 0, createPseudoLogResponseItem( additionalData.event, @@ -174,7 +178,7 @@ export function LogsInfiniteTable({ ) ); return newData; - }, [baseData, additionalData, isPending, isError, pseudoRowIndex]); + }, [baseData, additionalData, isPending, isError, pseudoRowIndex, baseDataLength]); // Calculate quantized start and end times for replay links const {logStart, logEnd} = useMemo(() => { @@ -280,15 +284,27 @@ export function LogsInfiniteTable({ ); useEffect(() => { - if (pseudoRowIndex !== -1 && scrollContainer?.current) { + if ( + pseudoRowIndex !== -1 && + scrollContainer?.current && + !additionalData?.scrollToDisabled + ) { setTimeout(() => { - containerVirtualizer.scrollToIndex(pseudoRowIndex, { + const scrollToIndex = + pseudoRowIndex === -2 ? baseDataLength.current : pseudoRowIndex; + containerVirtualizer.scrollToIndex(scrollToIndex, { behavior: 'smooth', align: 'center', }); }, 100); } - }, [pseudoRowIndex, containerVirtualizer, scrollContainer]); + }, [ + pseudoRowIndex, + containerVirtualizer, + scrollContainer, + baseDataLength, + additionalData?.scrollToDisabled, + ]); const hasReplay = !!embeddedOptions?.replay; @@ -773,3 +789,9 @@ function BackToTopButton({ ); } + +function useBox(value: T): RefObject { + const box = useRef(value); + box.current = value; + return box; +} diff --git a/static/app/views/explore/metrics/constants.tsx b/static/app/views/explore/metrics/constants.tsx index 1340cf8e634852..afc72fdab30d18 100644 --- a/static/app/views/explore/metrics/constants.tsx +++ b/static/app/views/explore/metrics/constants.tsx @@ -179,3 +179,9 @@ export const DEFAULT_YAXIS_BY_TYPE: Record = { distribution: 'p75', gauge: 'avg', }; + +/** + * Query parameter key for controlling the metrics drawer state. + * When this parameter is set to 'true', the metrics drawer should open automatically. + */ +export const METRICS_DRAWER_QUERY_PARAM = 'metricsDrawer';