diff --git a/static/app/components/events/breadcrumbs/combinedBreadcrumbsAndLogsSection.tsx b/static/app/components/events/breadcrumbs/combinedBreadcrumbsAndLogsSection.tsx deleted file mode 100644 index acd428d8285aa1..00000000000000 --- a/static/app/components/events/breadcrumbs/combinedBreadcrumbsAndLogsSection.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import {Fragment} from 'react'; - -import type {BreadcrumbsDataSectionProps} from 'sentry/components/events/breadcrumbs/breadcrumbsDataSection'; -import BreadcrumbsDataSection from 'sentry/components/events/breadcrumbs/breadcrumbsDataSection'; -import {LogsAnalyticsPageSource} from 'sentry/utils/analytics/logsAnalyticsEvent'; -import useOrganization from 'sentry/utils/useOrganization'; -import {LogsPageParamsProvider} from 'sentry/views/explore/contexts/logs/logsPageParams'; -import {LogsIssuesSection} from 'sentry/views/explore/logs/logsIssuesSection'; -import {useExploreLogsTable} from 'sentry/views/explore/logs/useLogsQuery'; - -/** - * This component is a coordinator and provider wrapper to determine which section to display, collapse etc. since only one section should be displayed by default. - */ -export function CombinedBreadcrumbsAndLogsSection({ - event, - group, - project, -}: BreadcrumbsDataSectionProps) { - const organization = useOrganization(); - const feature = organization.features.includes('ourlogs-enabled'); - if (!feature) { - // If we don't have the feature, we should skip the providers as they make api calls. - return ; - } - - return ( - - - - ); -} - -function CombinedBreadcrumbsAndLogsSectionContent({ - event, - group, - project, -}: BreadcrumbsDataSectionProps) { - const tableData = useExploreLogsTable({}); - const shouldCollapseLogs = tableData.data.length === 0; - return ( - - - - - ); -} diff --git a/static/app/views/explore/logs/logsIssueDrawer.tsx b/static/app/components/events/ourlogs/ourlogsDrawer.tsx similarity index 58% rename from static/app/views/explore/logs/logsIssueDrawer.tsx rename to static/app/components/events/ourlogs/ourlogsDrawer.tsx index 94aa64fe4640b5..7f6e7f09e5cbb8 100644 --- a/static/app/views/explore/logs/logsIssueDrawer.tsx +++ b/static/app/components/events/ourlogs/ourlogsDrawer.tsx @@ -9,16 +9,21 @@ import { NavigationCrumbs, ShortId, } from 'sentry/components/events/eventDrawer'; +import {SearchQueryBuilderProvider} from 'sentry/components/searchQueryBuilder/context'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {Event} from 'sentry/types/event'; import type {Group} from 'sentry/types/group'; import type {Project} from 'sentry/types/project'; import {getShortEventId} from 'sentry/utils/events'; -import {TraceItemSearchQueryBuilder} from 'sentry/views/explore/components/traceItemSearchQueryBuilder'; +import {MutableSearch} from 'sentry/utils/tokenizeSearch'; +import { + TraceItemSearchQueryBuilder, + useSearchQueryBuilderProps, +} from 'sentry/views/explore/components/traceItemSearchQueryBuilder'; import { useLogsSearch, - useSetLogsQuery, + useSetLogsSearch, } from 'sentry/views/explore/contexts/logs/logsPageParams'; import {useTraceItemAttributes} from 'sentry/views/explore/contexts/traceItemAttributeContext'; import {LogsTable} from 'sentry/views/explore/logs/logsTable'; @@ -31,12 +36,24 @@ interface LogIssueDrawerProps { project: Project; } -export function LogsIssueDrawer({event, project, group}: LogIssueDrawerProps) { - const setLogsQuery = useSetLogsQuery(); +export function OurlogsDrawer({event, project, group}: LogIssueDrawerProps) { + const setLogsSearch = useSetLogsSearch(); const logsSearch = useLogsSearch(); const tableData = useExploreLogsTable({}); - const {attributes: stringTags} = useTraceItemAttributes('string'); - const {attributes: numberTags} = useTraceItemAttributes('number'); + const {attributes: stringAttributes} = useTraceItemAttributes('string'); + const {attributes: numberAttributes} = useTraceItemAttributes('number'); + + const tracesItemSearchQueryBuilderProps = { + initialQuery: logsSearch.formatString(), + searchSource: 'ourlogs', + onSearch: (query: string) => setLogsSearch(new MutableSearch(query)), + numberAttributes, + stringAttributes, + itemType: TraceItemDataset.LOGS, + }; + const searchQueryBuilderProps = useSearchQueryBuilderProps( + tracesItemSearchQueryBuilderProps + ); return ( @@ -57,17 +74,12 @@ export function LogsIssueDrawer({event, project, group}: LogIssueDrawerProps) { /> - - - - + + + + + + ); diff --git a/static/app/views/explore/logs/logsIssuesSection.tsx b/static/app/components/events/ourlogs/ourlogsSection.tsx similarity index 63% rename from static/app/views/explore/logs/logsIssuesSection.tsx rename to static/app/components/events/ourlogs/ourlogsSection.tsx index 054d7911b46d1d..2f7f4a7c122cbb 100644 --- a/static/app/views/explore/logs/logsIssuesSection.tsx +++ b/static/app/components/events/ourlogs/ourlogsSection.tsx @@ -2,6 +2,7 @@ import {useCallback} from 'react'; import styled from '@emotion/styled'; import {Button} from 'sentry/components/core/button'; +import {OurlogsDrawer} from 'sentry/components/events/ourlogs/ourlogsDrawer'; import useDrawer from 'sentry/components/globalDrawer'; import {IconChevron} from 'sentry/icons'; import {t} from 'sentry/locale'; @@ -14,37 +15,53 @@ import {LogsAnalyticsPageSource} from 'sentry/utils/analytics/logsAnalyticsEvent import useOrganization from 'sentry/utils/useOrganization'; import { LogsPageParamsProvider, - type LogsPageParamsProviderProps, + useLogsSearch, } from 'sentry/views/explore/contexts/logs/logsPageParams'; import {TraceItemAttributeProvider} from 'sentry/views/explore/contexts/traceItemAttributeContext'; -import {LogsIssueDrawer} from 'sentry/views/explore/logs/logsIssueDrawer'; import {LogsTable} from 'sentry/views/explore/logs/logsTable'; -import { - useExploreLogsTable, - type UseExploreLogsTableResult, -} from 'sentry/views/explore/logs/useLogsQuery'; +import {useExploreLogsTable} from 'sentry/views/explore/logs/useLogsQuery'; import {TraceItemDataset} from 'sentry/views/explore/types'; import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; import {InterimSection} from 'sentry/views/issueDetails/streamline/interimSection'; -export function LogsIssuesSection({ - initialCollapse, - isOnEmbeddedView, - limitToTraceId, +export function OurlogsSection({ event, project, group, }: { event: Event; group: Group; - initialCollapse: boolean; project: Project; -} & Omit) { +}) { + return ( + + + + ); +} + +function OurlogsSectionContent({ + event, + project, + group, +}: { + event: Event; + group: Group; + project: Project; +}) { const organization = useOrganization(); const feature = organization.features.includes('ourlogs-enabled'); const tableData = useExploreLogsTable({enabled: feature, limit: 10}); + const logsSearch = useLogsSearch(); + const abbreviatedTableData = {...tableData, data: (tableData.data ?? []).slice(0, 5)}; const {openDrawer} = useDrawer(); + const limitToTraceId = event.contexts?.trace?.trace_id; const onOpenLogsDrawer = useCallback(() => { trackAnalytics('logs.issue_details.drawer_opened', { organization, @@ -53,11 +70,11 @@ export function LogsIssuesSection({ () => ( - + ), @@ -75,7 +92,7 @@ export function LogsIssuesSection({ // We may change this in the future if we have a trace-group or we generate trace sids for these issue types. return null; } - if (tableData?.data?.length === 0) { + if (!tableData || (tableData.data?.length === 0 && logsSearch.isEmpty())) { // Like breadcrumbs, we don't show the logs section if there are no logs. return null; } @@ -85,52 +102,40 @@ export function LogsIssuesSection({ type={SectionKey.LOGS} title={t('Logs')} data-test-id="logs-data-section" - initialCollapse={initialCollapse} > - - - + onOpenLogsDrawer()}> + + {tableData.data?.length > 5 ? ( +
+ +
+ ) : null} +
); } -function LogsSectionContent({ - tableData, - openDrawer, -}: { - openDrawer: () => void; - tableData: UseExploreLogsTableResult; -}) { - const abbreviatedTableData = {...tableData, data: (tableData.data ?? []).slice(0, 5)}; - return ( - - - {tableData.data?.length > 5 ? ( -
- -
- ) : null} -
- ); -} - -const LogContentWrapper = styled('div')` +const LogContentWrapper = styled('button')` + all: unset; display: flex; flex-direction: column; gap: ${space(1)}; + pointer-events: auto; + cursor: pointer; + + * { + pointer-events: none !important; + cursor: inherit !important; + } `; diff --git a/static/app/views/explore/contexts/logs/logsPageParams.tsx b/static/app/views/explore/contexts/logs/logsPageParams.tsx index a1f96cf7f3f890..03cbe4f9362c1c 100644 --- a/static/app/views/explore/contexts/logs/logsPageParams.tsx +++ b/static/app/views/explore/contexts/logs/logsPageParams.tsx @@ -1,4 +1,4 @@ -import {useCallback, useLayoutEffect} from 'react'; +import {useCallback, useLayoutEffect, useState} from 'react'; import type {Location} from 'history'; import type {CursorHandler} from 'sentry/components/pagination'; @@ -31,13 +31,19 @@ export const LOGS_FIELDS_KEY = 'logsFields'; interface LogsPageParams { readonly analyticsPageSource: LogsAnalyticsPageSource; + readonly blockRowExpanding: boolean | undefined; readonly cursor: string; readonly fields: string[]; - readonly isTableEditingFrozen: boolean | undefined; + readonly isTableFrozen: boolean | undefined; readonly search: MutableSearch; + /** + * On frozen pages (like the issues page), we don't want to store the search in the URL + * Instead, use a useState in the context, so that it's dropped if you navigate away or refresh. + */ + readonly setSearchForFrozenPages: (val: MutableSearch) => void; readonly sortBys: Sort[]; /** - * The base search, which doesn't appear in the URL or the search bar, used for adding traceid etc.. + * The base search, which doesn't appear in the URL or the search bar, used for adding traceid etc. */ readonly baseSearch?: MutableSearch; /** @@ -57,7 +63,8 @@ const [_LogsPageParamsProvider, _useLogsPageParams, LogsPageParamsContext] = export interface LogsPageParamsProviderProps { analyticsPageSource: LogsAnalyticsPageSource; children: React.ReactNode; - isOnEmbeddedView?: boolean; + blockRowExpanding?: boolean; + isTableFrozen?: boolean; limitToProjectIds?: number[]; limitToSpanId?: string; limitToTraceId?: string; @@ -68,12 +75,17 @@ export function LogsPageParamsProvider({ limitToTraceId, limitToSpanId, limitToProjectIds, - isOnEmbeddedView, + blockRowExpanding, + isTableFrozen, analyticsPageSource, }: LogsPageParamsProviderProps) { const location = useLocation(); const logsQuery = decodeLogsQuery(location); - const search = new MutableSearch(logsQuery); + + // on embedded pages with search bars, use a useState instead of a URL parameter + const [searchForFrozenPages, setSearchForFrozenPages] = useState(new MutableSearch('')); + + const search = isTableFrozen ? searchForFrozenPages : new MutableSearch(logsQuery); let baseSearch: MutableSearch | undefined = undefined; if (limitToSpanId && limitToTraceId) { baseSearch = baseSearch ?? new MutableSearch(''); @@ -83,15 +95,12 @@ export function LogsPageParamsProvider({ baseSearch = baseSearch ?? new MutableSearch(''); baseSearch.addFilterValue(OurLogKnownFieldKey.TRACE_ID, limitToTraceId); } - const isTableEditingFrozen = isOnEmbeddedView; - const fields = isTableEditingFrozen - ? defaultLogFields() - : getLogFieldsFromLocation(location); - const sortBys = isTableEditingFrozen + const fields = isTableFrozen ? defaultLogFields() : getLogFieldsFromLocation(location); + const sortBys = isTableFrozen ? [logsTimestampDescendingSortBy] : getLogSortBysFromLocation(location, fields); const pageFilters = usePageFilters(); - const projectIds = isOnEmbeddedView + const projectIds = isTableFrozen ? (limitToProjectIds ?? [-1]) : pageFilters.selection.projects; // TODO we should handle environments in a similar way to projects - otherwise page filters might break embedded views @@ -103,9 +112,11 @@ export function LogsPageParamsProvider({ value={{ fields, search, + setSearchForFrozenPages, sortBys, cursor, - isTableEditingFrozen, + isTableFrozen, + blockRowExpanding, baseSearch, projectIds, analyticsPageSource, @@ -133,7 +144,7 @@ function setLogsPageParams(location: Location, pageParams: LogPageParamsUpdate) updateNullableLocation(target, LOGS_QUERY_KEY, pageParams.search?.formatString()); updateNullableLocation(target, LOGS_CURSOR_KEY, pageParams.cursor); updateNullableLocation(target, LOGS_FIELDS_KEY, pageParams.fields); - if (!pageParams.isTableEditingFrozen) { + if (!pageParams.isTableFrozen) { updateLocationWithLogSortBys(target, pageParams.sortBys); } return target; @@ -183,29 +194,29 @@ export function useLogsBaseSearch(): MutableSearch | undefined { return baseSearch; } -export function useSetLogsQuery() { - const setPageParams = useSetLogsPageParams(); - return useCallback( - (query: string) => { - setPageParams({search: new MutableSearch(query)}); - }, - [setPageParams] - ); -} - export function useSetLogsSearch() { const setPageParams = useSetLogsPageParams(); - return useCallback( + const {setSearchForFrozenPages, isTableFrozen} = useLogsPageParams(); + const setPageParamsCallback = useCallback( (search: MutableSearch) => { setPageParams({search}); }, [setPageParams] ); + if (isTableFrozen) { + return setSearchForFrozenPages; + } + return setPageParamsCallback; +} + +export function useLogsIsTableFrozen() { + const {isTableFrozen} = useLogsPageParams(); + return !!isTableFrozen; } -export function useLogsIsTableEditingFrozen() { - const {isTableEditingFrozen} = useLogsPageParams(); - return isTableEditingFrozen; +export function useLogsBlockRowExpanding() { + const {blockRowExpanding} = useLogsPageParams(); + return !!blockRowExpanding; } export function usePersistedLogsPageParams() { diff --git a/static/app/views/explore/logs/logsTable.spec.tsx b/static/app/views/explore/logs/logsTable.spec.tsx new file mode 100644 index 00000000000000..2392da4d5064cb --- /dev/null +++ b/static/app/views/explore/logs/logsTable.spec.tsx @@ -0,0 +1,200 @@ +import {LocationFixture} from 'sentry-fixture/locationFixture'; + +import {initializeOrg} from 'sentry-test/initializeOrg'; +import {render, screen, userEvent, within} from 'sentry-test/reactTestingLibrary'; + +import ProjectsStore from 'sentry/stores/projectsStore'; +import {LogsAnalyticsPageSource} from 'sentry/utils/analytics/logsAnalyticsEvent'; +import {useLocation} from 'sentry/utils/useLocation'; +import { + LOGS_FIELDS_KEY, + LOGS_QUERY_KEY, + LogsPageParamsProvider, +} from 'sentry/views/explore/contexts/logs/logsPageParams'; +import {LOGS_SORT_BYS_KEY} from 'sentry/views/explore/contexts/logs/sortBys'; +import {LogsTable} from 'sentry/views/explore/logs/logsTable'; +import {OurLogKnownFieldKey} from 'sentry/views/explore/logs/types'; +import type {UseExploreLogsTableResult} from 'sentry/views/explore/logs/useLogsQuery'; + +jest.mock('sentry/utils/useLocation'); +const mockUseLocation = jest.mocked(useLocation); + +jest.mock('sentry/utils/useRelease', () => ({ + useRelease: jest.fn().mockReturnValue({ + data: { + id: 10, + lastCommit: { + id: '1e5a9462e6ac23908299b218e18377837297bda1', + }, + }, + }), +})); + +jest.mock('sentry/views/explore/logs/useLogsQuery', () => ({ + useExploreLogsTableRow: jest.fn().mockReturnValue({}), + usePrefetchLogTableRowOnHover: jest.fn().mockReturnValue({}), +})); + +jest.mock('sentry/components/events/interfaces/frame/useStacktraceLink', () => ({ + __esModule: true, + default: jest.fn().mockReturnValue({ + data: { + sourceUrl: 'https://some-stacktrace-link', + integrations: [], + }, + error: null, + isPending: false, + }), +})); + +describe('LogsTable', function () { + const {organization, project} = initializeOrg({ + organization: { + features: ['ourlogs'], + }, + }); + ProjectsStore.loadInitialData([project]); + + const tableData = { + data: [ + { + 'sentry.item_id': '0196a1bc022d76d3bff2106ebbf65f49', + 'project.id': project.id, + trace: '32986bcdac1f43ed87445a2021b0099c', + severity_number: 9, + severity: 'info', + timestamp: '2025-05-05T18:36:15+00:00', + message: + '10.5.55.212 - - [05/May/2025:18:36:15 +0000] "POST /v1/automation/autofix/state HTTP/1.1" 200 293642 "-" "python-requests/2.32.3"', + 'sentry.release': '985bae16edc2f3f8132e346a4f6c5a559f7c968b', + 'code.file.path': '/usr/local/lib/python3.11/dist-packages/gunicorn/glogging.py', + 'tags[sentry.timestamp_precise,number]': 1.7464701752771756e18, + }, + { + 'sentry.item_id': '0196a1bc00e3720f8d47c84c53131891', + 'project.id': project.id, + trace: '6141dca24986471398232d340a4fd588', + severity_number: 9, + severity: 'info', + timestamp: '2025-05-05T18:36:14+00:00', + message: + '10.5.58.189 - - [05/May/2025:18:36:14 +0000] "POST /v0/issues/similar-issues HTTP/1.1" 200 131 "-" "python-urllib3/2.2.2"', + 'sentry.release': '985bae16edc2f3f8132e346a4f6c5a559f7c968b', + 'code.file.path': '/usr/local/lib/python3.11/dist-packages/gunicorn/glogging.py', + 'tags[sentry.timestamp_precise,number]': 1.746470174947077e18, + }, + { + 'sentry.item_id': '0196a1bc007c7dfbbe099b6328e41d12', + 'project.id': project.id, + trace: '6141dca24986471398232d340a4fd588', + severity_number: 9, + severity: 'info', + timestamp: '2025-05-05T18:36:14+00:00', + message: + '10.5.62.140 - - [05/May/2025:18:36:14 +0000] "POST /v0/issues/similar-issues HTTP/1.1" 200 586 "-" "python-urllib3/2.2.2"', + 'sentry.release': '985bae16edc2f3f8132e346a4f6c5a559f7c968b', + 'code.file.path': '/usr/local/lib/python3.11/dist-packages/gunicorn/glogging.py', + 'tags[sentry.timestamp_precise,number]': 1.7464701748443016e18, + }, + ], + meta: { + fields: { + 'sentry.item_id': 'string', + 'project.id': 'string', + trace: 'string', + severity_number: 'integer', + severity: 'string', + timestamp: 'string', + message: 'string', + 'sentry.release': 'string', + 'code.file.path': 'string', + 'tags[sentry.timestamp_precise,number]': 'number', + }, + }, + isLoading: false, + isPending: false, + isError: false, + error: null, + pageLinks: undefined, + } as unknown as UseExploreLogsTableResult; + + const visibleColumnFields = [ + 'message', + 'trace', + 'severity_number', + 'severity', + 'timestamp', + 'sentry.release', + 'code.file.path', + ]; + const frozenColumnFields = [OurLogKnownFieldKey.TIMESTAMP, OurLogKnownFieldKey.MESSAGE]; + + beforeEach(function () { + MockApiClient.clearMockResponses(); + mockUseLocation.mockReturnValue( + LocationFixture({ + pathname: `/organizations/${organization.slug}/explore/logs/?end=2025-04-10T20%3A04%3A51&project=${project.id}&start=2025-04-10T14%3A37%3A55`, + query: { + [LOGS_FIELDS_KEY]: visibleColumnFields, + [LOGS_SORT_BYS_KEY]: '-timestamp', + [LOGS_QUERY_KEY]: 'severity:error', + }, + }) + ); + + MockApiClient.addMockResponse({ + url: `/organizations/${organization.slug}/releases/stats/`, + method: 'GET', + body: {}, + }); + }); + + it('should be interactable', async () => { + render( + + + + ); + + const allTreeRows = await screen.findAllByTestId('log-table-row'); + expect(allTreeRows).toHaveLength(3); + for (const row of allTreeRows) { + for (const field of visibleColumnFields) { + const cell = await within(row).findByTestId(`log-table-cell-${field}`); + await userEvent.hover(cell); + const actionsButton = within(cell).queryByRole('button', { + name: 'Actions', + }); + if (field === 'timestamp') { + expect(actionsButton).toBeNull(); + } else { + expect(actionsButton).toBeInTheDocument(); + } + } + } + }); + + it('should not be interactable on embedded views', async () => { + render( + + + + ); + + const allTreeRows = await screen.findAllByTestId('log-table-row'); + expect(allTreeRows).toHaveLength(3); + for (const row of allTreeRows) { + for (const field of frozenColumnFields) { + const cell = await within(row).findByTestId(`log-table-cell-${field}`); + await userEvent.hover(cell); + const actionsButton = within(cell).queryByRole('button', { + name: 'Actions', + }); + expect(actionsButton).not.toBeInTheDocument(); + } + } + }); +}); diff --git a/static/app/views/explore/logs/logsTable.tsx b/static/app/views/explore/logs/logsTable.tsx index faa7aab152c619..067340177bfe59 100644 --- a/static/app/views/explore/logs/logsTable.tsx +++ b/static/app/views/explore/logs/logsTable.tsx @@ -20,7 +20,7 @@ import { } from 'sentry/views/explore/components/table'; import { useLogsFields, - useLogsIsTableEditingFrozen, + useLogsIsTableFrozen, useLogsSearch, useLogsSortBys, useSetLogsCursor, @@ -58,8 +58,7 @@ export function LogsTable({ const fields = useLogsFields(); const search = useLogsSearch(); const setCursor = useSetLogsCursor(); - const isTableEditingFrozen = useLogsIsTableEditingFrozen(); - const hideTableBorder = !!isTableEditingFrozen; + const isTableFrozen = useLogsIsTableFrozen(); const {data, isError, isPending, pageLinks, meta} = tableData; @@ -84,7 +83,7 @@ export function LogsTable({ ref={tableRef} styles={initialTableStyles} data-test-id="logs-table" - hideBorder={hideTableBorder} + hideBorder={isTableFrozen} > {showHeader ? ( @@ -113,10 +112,8 @@ export function LogsTable({ isFirst={index === 0} > setSortBys([{field}]) - } - isFrozen={isTableEditingFrozen} + onClick={isTableFrozen ? undefined : () => setSortBys([{field}])} + isFrozen={isTableFrozen} > {headerLabel} diff --git a/static/app/views/explore/logs/logsTableRow.tsx b/static/app/views/explore/logs/logsTableRow.tsx index b3a34a7def88ed..ada262d5f9fed7 100644 --- a/static/app/views/explore/logs/logsTableRow.tsx +++ b/static/app/views/explore/logs/logsTableRow.tsx @@ -1,4 +1,4 @@ -import type {SyntheticEvent} from 'react'; +import type {ComponentProps, SyntheticEvent} from 'react'; import {Fragment, useCallback, useState} from 'react'; import {useTheme} from '@emotion/react'; @@ -20,7 +20,9 @@ import type {TableColumn} from 'sentry/views/discover/table/types'; import {AttributesTree} from 'sentry/views/explore/components/traceItemAttributes/attributesTree'; import { useLogsAnalyticsPageSource, + useLogsBlockRowExpanding, useLogsFields, + useLogsIsTableFrozen, useLogsSearch, useSetLogsSearch, } from 'sentry/views/explore/contexts/logs/logsPageParams'; @@ -92,6 +94,8 @@ export function LogRowContent({ const fields = useLogsFields(); const search = useLogsSearch(); const setLogsSearch = useSetLogsSearch(); + const isTableFrozen = useLogsIsTableFrozen(); + const blockRowExpanding = useLogsBlockRowExpanding(); function toggleExpanded() { setExpanded(e => !e); @@ -163,24 +167,30 @@ export function LogRowContent({ projectSlug, }; + const rowInteractProps: ComponentProps = blockRowExpanding + ? {} + : { + ...hoverProps, + onPointerUp, + onTouchEnd: onPointerUp, + isClickable: true, + }; + return ( - + - } - aria-label={t('Toggle trace details')} - aria-expanded={expanded} - size="zero" - borderless - onClick={() => toggleExpanded()} - /> + {blockRowExpanding ? null : ( + } + aria-label={t('Toggle trace details')} + aria-expanded={expanded} + size="zero" + borderless + onClick={() => toggleExpanded()} + /> + )} @@ -203,7 +213,7 @@ export function LogRowContent({ }; return ( - + ` &:not(thead > &) { - cursor: pointer; + cursor: ${p => (p.isClickable ? 'pointer' : 'default')}; &:hover { background-color: ${p => p.theme.backgroundSecondary}; diff --git a/static/app/views/explore/logs/useLogAttributesTreeActions.tsx b/static/app/views/explore/logs/useLogAttributesTreeActions.tsx index ac5c3b6c95c3ba..06e8ffb1677221 100644 --- a/static/app/views/explore/logs/useLogAttributesTreeActions.tsx +++ b/static/app/views/explore/logs/useLogAttributesTreeActions.tsx @@ -5,7 +5,7 @@ import {t} from 'sentry/locale'; import type {AttributesTreeContent} from 'sentry/views/explore/components/traceItemAttributes/attributesTree'; import { useLogsFields, - useLogsIsTableEditingFrozen, + useLogsIsTableFrozen, useLogsSearch, useSetLogsFields, useSetLogsSearch, @@ -17,7 +17,7 @@ export function useLogAttributesTreeActions() { const search = useLogsSearch(); const fields = useLogsFields(); const setLogFields = useSetLogsFields(); - const isTableEditingFrozen = useLogsIsTableEditingFrozen(); + const isTableEditingFrozen = useLogsIsTableFrozen(); const addSearchFilter = useCallback( (content: AttributesTreeContent, negated?: boolean) => { diff --git a/static/app/views/issueDetails/groupEventDetails/groupEventDetailsContent.tsx b/static/app/views/issueDetails/groupEventDetails/groupEventDetailsContent.tsx index 69f07c42253643..f3af43891c5e02 100644 --- a/static/app/views/issueDetails/groupEventDetails/groupEventDetailsContent.tsx +++ b/static/app/views/issueDetails/groupEventDetails/groupEventDetailsContent.tsx @@ -3,11 +3,12 @@ import {ClassNames} from '@emotion/react'; import styled from '@emotion/styled'; import {usePrompt} from 'sentry/actionCreators/prompts'; +import Feature from 'sentry/components/acl/feature'; import GuideAnchor from 'sentry/components/assistant/guideAnchor'; import {CommitRow} from 'sentry/components/commitRow'; import {Button} from 'sentry/components/core/button'; import ErrorBoundary from 'sentry/components/errorBoundary'; -import {CombinedBreadcrumbsAndLogsSection} from 'sentry/components/events/breadcrumbs/combinedBreadcrumbsAndLogsSection'; +import BreadcrumbsDataSection from 'sentry/components/events/breadcrumbs/breadcrumbsDataSection'; import {EventContexts} from 'sentry/components/events/contexts'; import {EventDevice} from 'sentry/components/events/device'; import {EventAttachments} from 'sentry/components/events/eventAttachments'; @@ -48,6 +49,7 @@ import {StackTrace} from 'sentry/components/events/interfaces/stackTrace'; import {Template} from 'sentry/components/events/interfaces/template'; import {Threads} from 'sentry/components/events/interfaces/threads'; import {UptimeDataSection} from 'sentry/components/events/interfaces/uptime/uptimeDataSection'; +import {OurlogsSection} from 'sentry/components/events/ourlogs/ourlogsSection'; import {EventPackageData} from 'sentry/components/events/packageData'; import {EventRRWebIntegration} from 'sentry/components/events/rrwebIntegration'; import {DataSection} from 'sentry/components/events/styles'; @@ -407,7 +409,10 @@ export function EventDetailsContent({