diff --git a/static/app/components/pageFilters/actions.tsx b/static/app/components/pageFilters/actions.tsx index a9e1f369f6409c..efb3d77fdc482c 100644 --- a/static/app/components/pageFilters/actions.tsx +++ b/static/app/components/pageFilters/actions.tsx @@ -110,6 +110,10 @@ export type InitializeUrlStateParams = { organization: Organization; defaultSelection?: Partial; forceProject?: MinimalProject | null; + /** + * the maximum number of sequential days that can be selected on the date page filter + */ + maxDateRange?: number; /** * When set, the stats period will fallback to the `maxPickableDays` days if the stored selection exceeds the limit. */ @@ -157,6 +161,7 @@ export function initializeUrlState({ skipLoadLastUsed, skipLoadLastUsedEnvironment, maxPickableDays, + maxDateRange, shouldPersist = true, shouldForceProject, defaultSelection, @@ -308,6 +313,7 @@ export function initializeUrlState({ } let shouldUseMaxPickableDays = false; + let shouldUseMaxDateRange = false; if (maxPickableDays && pageFilters.datetime) { let {start, end} = pageFilters.datetime; @@ -320,16 +326,33 @@ export function initializeUrlState({ if (start && end) { const periodStart = new Date(start); + const periodEnd = new Date(end); const maxPeriod = parseStatsPeriod(`${maxPickableDays}d`); + const maxTimeRange = (maxDateRange ?? maxPickableDays) * 24 * 60 * 60 * 1000; const maxStart = new Date(maxPeriod.start); - if (periodStart.getTime() < maxStart.getTime()) { - shouldUseMaxPickableDays = true; - pageFilters.datetime = { - period: `${maxPickableDays}d`, - start: null, - end: null, - utc: datetime.utc, - }; + if (maxDateRange) { + if ( + periodEnd.getTime() - periodStart.getTime() > maxTimeRange || + periodStart.getTime() < maxStart.getTime() + ) { + shouldUseMaxDateRange = true; + pageFilters.datetime = { + period: `${maxDateRange}d`, + start: null, + end: null, + utc: datetime.utc, + }; + } + } else { + if (periodStart.getTime() < maxStart.getTime()) { + shouldUseMaxPickableDays = true; + pageFilters.datetime = { + period: `${maxPickableDays}d`, + start: null, + end: null, + utc: datetime.utc, + }; + } } } } @@ -343,21 +366,31 @@ export function initializeUrlState({ ); } - const newDatetime = shouldUseMaxPickableDays - ? { - period: `${maxPickableDays}d`, - start: null, - end: null, - utc: datetime.utc, - } - : { - ...datetime, - period: - parsed.start || parsed.end || parsed.period || shouldUsePinnedDatetime - ? datetime.period - : null, - utc: parsed.utc || shouldUsePinnedDatetime ? datetime.utc : null, - }; + let newDatetime: PageFiltersUpdate; + if (shouldUseMaxDateRange) { + newDatetime = { + period: `${maxDateRange}d`, + start: null, + end: null, + utc: datetime.utc, + }; + } else if (shouldUseMaxPickableDays) { + newDatetime = { + period: `${maxPickableDays}d`, + start: null, + end: null, + utc: datetime.utc, + }; + } else { + newDatetime = { + ...datetime, + period: + parsed.start || parsed.end || parsed.period || shouldUsePinnedDatetime + ? datetime.period + : null, + utc: parsed.utc || shouldUsePinnedDatetime ? datetime.utc : null, + }; + } if (!skipInitializeUrlParams) { updateParams({project, environment, ...newDatetime}, location, navigate, { diff --git a/static/app/components/pageFilters/container.spec.tsx b/static/app/components/pageFilters/container.spec.tsx index 2f0eaee741d6a1..9a7a316bdbc6d6 100644 --- a/static/app/components/pageFilters/container.spec.tsx +++ b/static/app/components/pageFilters/container.spec.tsx @@ -11,6 +11,7 @@ import {PageFiltersStore} from 'sentry/components/pageFilters/store'; import {OrganizationsStore} from 'sentry/stores/organizationsStore'; import {OrganizationStore} from 'sentry/stores/organizationStore'; import {ProjectsStore} from 'sentry/stores/projectsStore'; +import {getUtcToLocalDateObject} from 'sentry/utils/dates'; import {localStorageWrapper} from 'sentry/utils/localStorage'; describe('PageFiltersContainer', () => { @@ -561,6 +562,129 @@ describe('PageFiltersContainer', () => { }); }); + describe('maxDateRange param', () => { + it('resets period when maxDateRange appears and current selection exceeds it', async () => { + const {rerender} = render(, { + organization, + initialRouterConfig: { + location: { + pathname: '/organizations/org-slug/test/', + query: {statsPeriod: '14d'}, + }, + route: '/organizations/:orgId/test/', + }, + }); + + await waitFor(() => + expect(PageFiltersStore.getState().selection.datetime).toEqual({ + period: '14d', + utc: null, + start: null, + end: null, + }) + ); + + rerender(); + + await waitFor(() => + expect(PageFiltersStore.getState().selection.datetime).toEqual({ + period: '7d', + utc: null, + start: null, + end: null, + }) + ); + }); + + it('does not reset period when maxDateRange appears but selection is within it', async () => { + const {rerender} = render(, { + organization, + initialRouterConfig: { + location: { + pathname: '/organizations/org-slug/test/', + query: {statsPeriod: '7d'}, + }, + route: '/organizations/:orgId/test/', + }, + }); + + await waitFor(() => + expect(PageFiltersStore.getState().selection.datetime).toEqual({ + period: '7d', + utc: null, + start: null, + end: null, + }) + ); + + rerender(); + + await waitFor(() => + expect(PageFiltersStore.getState().selection.datetime).toEqual({ + period: '7d', + utc: null, + start: null, + end: null, + }) + ); + }); + + it('does not reset period when selection is within maxPickableDays and maxDateRange', async () => { + const start = moment().subtract(14, 'days').format('YYYY-MM-DDTHH:mm:ss'); + const end = moment().subtract(8, 'days').format('YYYY-MM-DDTHH:mm:ss'); + render(, { + organization, + initialRouterConfig: { + location: { + pathname: '/organizations/org-slug/test/', + query: {start, end}, + }, + route: '/organizations/:orgId/test/', + }, + }); + + await waitFor(() => + expect(PageFiltersStore.getState().selection.datetime).toEqual({ + period: null, + utc: null, + start: getUtcToLocalDateObject(start), + end: getUtcToLocalDateObject(end), + }) + ); + }); + + it('resets absolute range when maxDateRange appears and range exceeds it', async () => { + const start = moment().subtract(10, 'days').format('YYYY-MM-DDTHH:mm:ss'); + const end = moment().subtract(1, 'days').format('YYYY-MM-DDTHH:mm:ss'); + + const {rerender} = render(, { + organization, + initialRouterConfig: { + location: { + pathname: '/organizations/org-slug/test/', + query: {start, end}, + }, + route: '/organizations/:orgId/test/', + }, + }); + + await waitFor(() => + expect(PageFiltersStore.getState().selection.datetime.period).toBeNull() + ); + + rerender(); + + await waitFor(() => + expect(PageFiltersStore.getState().selection.datetime).toEqual({ + period: '7d', + utc: null, + start: null, + end: null, + }) + ); + }); + }); + describe('skipInitializeUrlParams', () => { const skipInitProjects = [ ProjectFixture({id: '1', slug: 'staging-project', environments: ['staging']}), diff --git a/static/app/components/pageFilters/container.tsx b/static/app/components/pageFilters/container.tsx index 2b1d73b87cc34d..5b43b009ec8a5f 100644 --- a/static/app/components/pageFilters/container.tsx +++ b/static/app/components/pageFilters/container.tsx @@ -16,6 +16,7 @@ import {usePageFilters} from 'sentry/components/pageFilters/usePageFilters'; import {parseStatsPeriod} from 'sentry/components/timeRangeSelector/utils'; import {DEFAULT_STATS_PERIOD} from 'sentry/constants'; import {statsPeriodToDays} from 'sentry/utils/duration/statsPeriodToDays'; +import {DAY as DAY_IN_MS} from 'sentry/utils/formatters'; import {isActiveSuperuser} from 'sentry/utils/isActiveSuperuser'; import {useLocation} from 'sentry/utils/useLocation'; import {useDefaultMaxPickableDays} from 'sentry/utils/useMaxPickableDays'; @@ -58,6 +59,7 @@ export function PageFiltersContainer({ skipLoadLastUsed, skipLoadLastUsedEnvironment, maxPickableDays, + maxDateRange, children, ...props }: Props) { @@ -103,6 +105,7 @@ export function PageFiltersContainer({ skipLoadLastUsed, skipLoadLastUsedEnvironment, maxPickableDays, + maxDateRange, memberProjects, nonMemberProjects, defaultSelection, @@ -132,9 +135,10 @@ export function PageFiltersContainer({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [projectsLoaded]); - // Handle dynamic maxPickableDays changes (e.g., switching between pages with different limits). + // Handle dynamic maxPickableDays/maxDateRange changes (e.g., switching between pages with different limits). // When the limit decreases and the current selection exceeds it, reset to the new max. const previousMaxPickableDays = usePrevious(maxPickableDays); + const previousMaxDateRange = usePrevious(maxDateRange); const shouldResetDateTime = useMemo(() => { // Don't act until page filters are initialized - selection.datetime contains // default values until isReady, not the actual URL state @@ -142,10 +146,13 @@ export function PageFiltersContainer({ return false; } - // Only act when maxPickableDays decreases (increasing the limit never invalidates selection) + const effectiveMaxDays = maxDateRange ?? maxPickableDays; + const previousEffectiveMaxDays = previousMaxDateRange ?? previousMaxPickableDays; + + // Only act when the effective limit decreases (increasing the limit never invalidates selection) if ( - previousMaxPickableDays === maxPickableDays || - previousMaxPickableDays < maxPickableDays + previousEffectiveMaxDays === effectiveMaxDays || + previousEffectiveMaxDays < effectiveMaxDays ) { return false; } @@ -154,19 +161,39 @@ export function PageFiltersContainer({ // For relative periods (e.g., "14d"), check if the period exceeds the new max if (period) { - return statsPeriodToDays(period) > maxPickableDays; + return statsPeriodToDays(period) > effectiveMaxDays; } // For absolute date ranges, check if the start date is before the allowed window. // Uses same calculation as initialization in pageFilters.tsx if (start && end) { + const periodStart = new Date(start); + const periodEnd = new Date(end); const maxPeriod = parseStatsPeriod(`${maxPickableDays}d`); const maxStart = new Date(maxPeriod.start); - return new Date(start).getTime() < maxStart.getTime(); + + if (maxDateRange) { + const maxTimeRange = maxDateRange * DAY_IN_MS; + return ( + periodEnd.getTime() - periodStart.getTime() > maxTimeRange || + periodStart.getTime() < maxStart.getTime() + ); + } + + return periodStart.getTime() < maxStart.getTime(); } return false; - }, [isReady, maxPickableDays, previousMaxPickableDays, selection.datetime]); + }, [ + isReady, + maxDateRange, + maxPickableDays, + previousMaxDateRange, + previousMaxPickableDays, + selection.datetime, + ]); + + const resetPeriodDays = maxDateRange ?? maxPickableDays; useLayoutEffect(() => { if (!shouldResetDateTime) { @@ -175,7 +202,7 @@ export function PageFiltersContainer({ // Reset to a relative period matching the new max (clears any absolute dates) const newDateState = getDatetimeFromState({ - period: `${maxPickableDays}d`, + period: `${resetPeriodDays}d`, start: null, end: null, utc: selection.datetime.utc, @@ -183,7 +210,7 @@ export function PageFiltersContainer({ project: [], }); updateDateTime(newDateState, location, navigate); - }, [maxPickableDays, location, navigate, selection.datetime.utc, shouldResetDateTime]); + }, [location, navigate, resetPeriodDays, selection.datetime.utc, shouldResetDateTime]); // Update store persistence when `disablePersistence` changes useEffect(() => updatePersistence(!disablePersistence), [disablePersistence]); diff --git a/static/app/components/timeRangeSelector/utils.tsx b/static/app/components/timeRangeSelector/utils.tsx index eeaf386bfa604a..a6e1d5250806da 100644 --- a/static/app/components/timeRangeSelector/utils.tsx +++ b/static/app/components/timeRangeSelector/utils.tsx @@ -277,7 +277,7 @@ export const _timeRangeAutoCompleteFilter = function { @@ -26,9 +27,12 @@ export function useDatePageFilterProps({ [90, '90d', t('Last 90 days')], ]; + // if maxDateRange is set, we need to make sure the options shown don't exceed this max range. // find the relative options that should be enabled based on the maxPickableDays const pickableIndex = - availableRelativeOptions.findLastIndex(([days]) => days <= maxPickableDays) + 1; + availableRelativeOptions.findLastIndex(([days]) => + maxDateRange ? days <= maxDateRange : days <= maxPickableDays + ) + 1; const enabledOptions = Object.fromEntries( availableRelativeOptions .slice(0, pickableIndex) @@ -54,6 +58,7 @@ export function useDatePageFilterProps({ defaultPeriod, isOptionDisabled, maxPickableDays, + maxDateRange, menuFooter, relativeOptions: ({arbitraryOptions}) => ({ ...arbitraryOptions, @@ -61,5 +66,5 @@ export function useDatePageFilterProps({ ...disabledOptions, }), }; - }, [defaultPeriod, maxPickableDays, maxUpgradableDays, upsellFooter]); + }, [defaultPeriod, maxDateRange, maxPickableDays, maxUpgradableDays, upsellFooter]); } diff --git a/static/app/utils/useMaxPickableDays.tsx b/static/app/utils/useMaxPickableDays.tsx index c82bee8eb02fac..6fbc051ae31823 100644 --- a/static/app/utils/useMaxPickableDays.tsx +++ b/static/app/utils/useMaxPickableDays.tsx @@ -35,6 +35,10 @@ export interface MaxPickableDaysOptions { */ maxUpgradableDays: NonNullable; defaultPeriod?: DatePageFilterProps['defaultPeriod']; + /** + * The maximum number of sequential days that can be selected on the date page filter + */ + maxDateRange?: number; upsellFooter?: ReactNode; } diff --git a/static/app/views/dashboards/editAccessSelector.tsx b/static/app/views/dashboards/editAccessSelector.tsx index 214e40fcc50e4f..44695afbb6f4be 100644 --- a/static/app/views/dashboards/editAccessSelector.tsx +++ b/static/app/views/dashboards/editAccessSelector.tsx @@ -361,6 +361,7 @@ export function EditAccessSelector({ /> } + position="bottom-end" strategy="fixed" preventOverflowOptions={{mainAxis: false}} disabled={disabled} diff --git a/static/app/views/dashboards/utils.spec.tsx b/static/app/views/dashboards/utils.spec.tsx index ee76c15f8f821a..8ed3833a318d06 100644 --- a/static/app/views/dashboards/utils.spec.tsx +++ b/static/app/views/dashboards/utils.spec.tsx @@ -194,6 +194,30 @@ describe('Dashboards util', () => { const urlParams = new URLSearchParams(queryString); expect(urlParams.get('query')).toBe('(is:unresolved) release:["1.0.0","2.0.0"] '); }); + it('applies global filters scoped to the issue dataset', () => { + const url = getWidgetIssueUrl( + widget, + { + globalFilter: [ + { + dataset: WidgetType.ISSUE, + tag: {key: 'transaction', name: 'transaction'}, + value: 'transaction:/api/foo', + }, + { + dataset: WidgetType.DISCOVER, + tag: {key: 'transaction', name: 'transaction'}, + value: 'transaction:/api/bar', + }, + ], + }, + selection, + OrganizationFixture() + ); + const queryString = url.split('?')[1]; + const urlParams = new URLSearchParams(queryString); + expect(urlParams.get('query')).toBe('(is:unresolved) transaction:/api/foo'); + }); }); describe('flattenErrors', () => { diff --git a/static/app/views/dashboards/utils.tsx b/static/app/views/dashboards/utils.tsx index 32fb5a597ee8dc..49f3d37079cdc5 100644 --- a/static/app/views/dashboards/utils.tsx +++ b/static/app/views/dashboards/utils.tsx @@ -278,7 +278,11 @@ export function getWidgetIssueUrl( ? {start: getUtcDateString(start), end: getUtcDateString(end), utc} : {statsPeriod: period}; const issuesLocation = `/organizations/${organization.slug}/issues/?${qs.stringify({ - query: applyDashboardFilters(widget.queries?.[0]?.conditions, dashboardFilters), + query: applyDashboardFilters( + widget.queries?.[0]?.conditions, + dashboardFilters, + widget.widgetType + ), sort: widget.queries?.[0]?.orderby, ...datetime, // Pass empty string when projects is empty to preserve "My Projects" selection in URL diff --git a/static/app/views/dashboards/widgetCard/widgetFrame.tsx b/static/app/views/dashboards/widgetCard/widgetFrame.tsx index 6a0afac14447a8..11e571562f37d3 100644 --- a/static/app/views/dashboards/widgetCard/widgetFrame.tsx +++ b/static/app/views/dashboards/widgetCard/widgetFrame.tsx @@ -108,7 +108,10 @@ export function WidgetFrame(props: WidgetFrameProps) { { + e.stopPropagation(); + actions[0]!.onAction?.(); + }} to={actions[0]!.to} > {actions[0]!.label} @@ -117,7 +120,10 @@ export function WidgetFrame(props: WidgetFrameProps) { @@ -148,7 +154,8 @@ export function WidgetFrame(props: WidgetFrameProps) { aria-label={t('Copy Widget URL')} variant="transparent" icon={} - onClick={() => { + onClick={e => { + e.stopPropagation(); props.onCopyUrlClick?.(); }} /> @@ -161,7 +168,8 @@ export function WidgetFrame(props: WidgetFrameProps) { aria-label={t('Open Full-Screen View')} variant="transparent" icon={} - onClick={() => { + onClick={e => { + e.stopPropagation(); props.onFullScreenViewClick?.(); }} /> diff --git a/static/app/views/explore/spans/content.spec.tsx b/static/app/views/explore/spans/content.spec.tsx index 4c56614d70fccf..f294d998e14f6f 100644 --- a/static/app/views/explore/spans/content.spec.tsx +++ b/static/app/views/explore/spans/content.spec.tsx @@ -135,7 +135,7 @@ describe('ExploreContent', () => { ).toBeInTheDocument(); }); - it('resets period when max pickable days decreases', async () => { + it('resets period when maxDateRange is applied after cross events are added', async () => { PageFiltersStore.onInitializeUrlState({ projects: [project].map(p => parseInt(p.id, 10)), environments: [], diff --git a/static/app/views/explore/spans/content.tsx b/static/app/views/explore/spans/content.tsx index 5ecde70d0ac12f..721c173ca54df7 100644 --- a/static/app/views/explore/spans/content.tsx +++ b/static/app/views/explore/spans/content.tsx @@ -48,12 +48,6 @@ import {TraceItemDataset} from 'sentry/views/explore/types'; import {useOnboardingProject} from 'sentry/views/insights/common/queries/useOnboardingProject'; import {TopBar} from 'sentry/views/navigation/topBar'; -const CROSS_EVENTS_DATE_OVERRIDE: MaxPickableDaysOptions = { - defaultPeriod: MAX_PERIOD_FOR_CROSS_EVENTS, - maxPickableDays: MAX_DAYS_FOR_CROSS_EVENTS, - maxUpgradableDays: MAX_DAYS_FOR_CROSS_EVENTS, -}; - function useHasCrossEvents() { const crossEvents = useQueryParamsCrossEvents(); return defined(crossEvents) && crossEvents.length > 0; @@ -77,6 +71,13 @@ function ExploreContentInner() { dataCategories: [DataCategory.SPANS], }); + const CROSS_EVENTS_DATE_OVERRIDE: MaxPickableDaysOptions = { + defaultPeriod: MAX_PERIOD_FOR_CROSS_EVENTS, + maxPickableDays: dataCategoryMaxPickableDays.maxPickableDays, + maxUpgradableDays: MAX_DAYS_FOR_CROSS_EVENTS, + maxDateRange: MAX_DAYS_FOR_CROSS_EVENTS, + }; + const maxPickableDays = hasCrossEvents ? CROSS_EVENTS_DATE_OVERRIDE : dataCategoryMaxPickableDays; @@ -86,7 +87,10 @@ function ExploreContentInner() { return ( - + diff --git a/static/app/views/explore/spans/crossEvents/crossEventMetricsSearchBar.tsx b/static/app/views/explore/spans/crossEvents/crossEventMetricsSearchBar.tsx index 7f37496af3b8a7..e05dc73cac1c17 100644 --- a/static/app/views/explore/spans/crossEvents/crossEventMetricsSearchBar.tsx +++ b/static/app/views/explore/spans/crossEvents/crossEventMetricsSearchBar.tsx @@ -1,6 +1,6 @@ import {memo, useCallback, useMemo} from 'react'; -import {Grid} from '@sentry/scraps/layout'; +import {Container, Grid} from '@sentry/scraps/layout'; import {SearchQueryBuilderProvider} from 'sentry/components/searchQueryBuilder/context'; import { @@ -143,19 +143,23 @@ export const SpansTabCrossEventMetricsSearchBar = memo( }); return ( - - - - - + + + + + + + + + ); } diff --git a/static/app/views/explore/spans/crossEvents/crossEventSearchBars.tsx b/static/app/views/explore/spans/crossEvents/crossEventSearchBars.tsx index e852318d7caec2..e7194c61586c48 100644 --- a/static/app/views/explore/spans/crossEvents/crossEventSearchBars.tsx +++ b/static/app/views/explore/spans/crossEvents/crossEventSearchBars.tsx @@ -2,7 +2,7 @@ import {Fragment, useEffect, useEffectEvent} from 'react'; import {Button} from '@sentry/scraps/button'; import {CompactSelect} from '@sentry/scraps/compactSelect'; -import {Container} from '@sentry/scraps/layout'; +import {Container, Grid} from '@sentry/scraps/layout'; import {OverlayTrigger} from '@sentry/scraps/overlayTrigger'; import {addErrorMessage} from 'sentry/actionCreators/indicator'; @@ -30,7 +30,13 @@ import {TraceItemDataset} from 'sentry/views/explore/types'; const EMPTY_CROSS_EVENTS: CrossEvent[] = []; -export function SpansTabCrossEventSearchBars() { +interface SpansTabCrossEventSearchBarsProps { + hasIndependentDateColumn?: boolean; +} + +export function SpansTabCrossEventSearchBars({ + hasIndependentDateColumn = false, +}: SpansTabCrossEventSearchBarsProps) { const organization = useOrganization(); const crossEvents = useQueryParamsCrossEvents() ?? EMPTY_CROSS_EVENTS; const setCrossEvents = useSetQueryParamsCrossEvents(); @@ -71,7 +77,7 @@ export function SpansTabCrossEventSearchBars() { return null; } - return visibleCrossEvents.map(({crossEvent, index}, visibleIndex) => { + const crossEventRows = visibleCrossEvents.map(({crossEvent, index}, visibleIndex) => { let traceItemType = TraceItemDataset.SPANS; if (crossEvent.type === 'logs') { traceItemType = TraceItemDataset.LOGS; @@ -176,4 +182,17 @@ export function SpansTabCrossEventSearchBars() { ); }); + + if (!hasIndependentDateColumn) { + return {crossEventRows}; + } + + return ( + + {crossEventRows} + + ); } diff --git a/static/app/views/explore/spans/spansTabSearchSection.tsx b/static/app/views/explore/spans/spansTabSearchSection.tsx index ac7e2979817e8b..480c47c2bc684f 100644 --- a/static/app/views/explore/spans/spansTabSearchSection.tsx +++ b/static/app/views/explore/spans/spansTabSearchSection.tsx @@ -9,6 +9,7 @@ import {DatePageFilter} from 'sentry/components/pageFilters/date/datePageFilter' import {EnvironmentPageFilter} from 'sentry/components/pageFilters/environment/environmentPageFilter'; import {PageFilterBar} from 'sentry/components/pageFilters/pageFilterBar'; import {ProjectPageFilter} from 'sentry/components/pageFilters/project/projectPageFilter'; +import {usePageFilters} from 'sentry/components/pageFilters/usePageFilters'; import {useSpanSearchQueryBuilderProps} from 'sentry/components/performance/spanSearchQueryBuilder'; import { SearchQueryBuilderProvider, @@ -73,6 +74,7 @@ export function SpanTabSearchSection({datePageFilterProps}: SpanTabSearchSection const crossEvents = useQueryParamsCrossEvents(); const setQueryParams = useSetQueryParams(); const [caseInsensitive, setCaseInsensitive] = useCaseInsensitivity(); + const {selection} = usePageFilters(); const organization = useOrganization(); const hasRawSearchReplacement = organization.features.includes( @@ -80,6 +82,9 @@ export function SpanTabSearchSection({datePageFilterProps}: SpanTabSearchSection ); const hasCrossEvents = defined(crossEvents) && crossEvents.length > 0; + const hasAbsoluteDateSelection = Boolean( + selection.datetime.start && selection.datetime.end && !selection.datetime.period + ); const {attributes: numberAttributes, isLoading: numberAttributesLoading} = useSpanItemAttributes({}, 'number'); @@ -167,20 +172,30 @@ export function SpanTabSearchSection({datePageFilterProps}: SpanTabSearchSection > {tourProps => (
- - - - - - - - - {hasCrossEvents ? : null} + + + + + + + + + + {hasCrossEvents && !hasAbsoluteDateSelection ? ( + + ) : null} + + {hasCrossEvents && hasAbsoluteDateSelection ? ( + + ) : null} {hasCrossEvents ? null : (