Skip to content

feat(trace-tabs): Updating trace toolbar ui #91571

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
May 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion static/app/views/performance/newTraceDetails/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ const TraceExternalLayout = styled('div')`
const FlexBox = styled('div')`
display: flex;
flex-direction: column;
gap: ${space(2)};
gap: ${space(1)};
`;

const TraceInnerLayout = styled(FlexBox)`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {Button} from 'sentry/components/core/button';
import {DropdownMenu} from 'sentry/components/dropdownMenu';
import {IconEllipsis, IconOpen} from 'sentry/icons';
import {t} from 'sentry/locale';
import type {Project} from 'sentry/types/project';
import {trackAnalytics} from 'sentry/utils/analytics';
import type EventView from 'sentry/utils/discover/eventView';
import {SavedQueryDatasets} from 'sentry/utils/discover/types';
Expand All @@ -12,12 +11,11 @@ import {useNavigate} from 'sentry/utils/useNavigate';
import useOrganization from 'sentry/utils/useOrganization';
import useProjects from 'sentry/utils/useProjects';
import {hasDatasetSelector} from 'sentry/views/dashboards/utils';
import {OurLogKnownFieldKey} from 'sentry/views/explore/logs/types';
import {isTraceItemDetailsResponse} from 'sentry/views/performance/newTraceDetails/traceApi/utils';
import {getTraceProject} from 'sentry/views/performance/newTraceDetails/tracePreferencesDropdown';
import {useHasTraceTabsUI} from 'sentry/views/performance/newTraceDetails/useHasTraceTabsUI';

import type {TraceRootEventQueryResults} from './traceApi/useTraceRootEvent';
import {
findSpanAttributeValue,
getSearchInExploreTarget,
TraceDrawerActionKind,
} from './traceDrawer/details/utils';
Expand All @@ -39,22 +37,14 @@ function TraceActionsMenu({
const {projects} = useProjects();
const navigate = useNavigate();
const hasExploreEnabled = organization.features.includes('visibility-explore-view');
const hasTraceTabsUi = useHasTraceTabsUI();

let traceProject: Project | undefined;
if (rootEventResults.data) {
if (isTraceItemDetailsResponse(rootEventResults.data)) {
const attributes = rootEventResults.data.attributes;
const projectId =
OurLogKnownFieldKey.PROJECT_ID in attributes
? attributes[OurLogKnownFieldKey.PROJECT_ID]
: findSpanAttributeValue(attributes, 'project_id');
traceProject = projects.find(p => p.id === projectId);
} else {
const projectSlug = rootEventResults.data.projectSlug;
traceProject = projects.find(p => p.slug === projectSlug);
}
if (hasTraceTabsUi) {
return null;
}

const traceProject = getTraceProject(projects, rootEventResults);

return (
<DropdownMenu
items={[
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {Button} from 'sentry/components/core/button';
import {t} from 'sentry/locale';
import type EventView from 'sentry/utils/discover/eventView';
import {useLocation} from 'sentry/utils/useLocation';
import {useNavigate} from 'sentry/utils/useNavigate';
import useOrganization from 'sentry/utils/useOrganization';
import {traceAnalytics} from 'sentry/views/performance/newTraceDetails/traceAnalytics';
import {
getSearchInExploreTarget,
TraceDrawerActionKind,
} from 'sentry/views/performance/newTraceDetails/traceDrawer/details/utils';
import {useHasTraceTabsUI} from 'sentry/views/performance/newTraceDetails/useHasTraceTabsUI';

type Props = {
traceEventView: EventView;
trace_id: string;
};

export function TraceOpenInExploreButton({trace_id, traceEventView}: Props) {
const organization = useOrganization();
const hasExploreEnabled = organization.features.includes('visibility-explore-view');
const hasTraceTabsUi = useHasTraceTabsUI();
const location = useLocation();
const navigate = useNavigate();

if (!hasExploreEnabled || !hasTraceTabsUi || !trace_id) {
return null;
}

return (
<Button
size="xs"
onClick={() => {
traceAnalytics.trackExploreSearch(
organization,
'trace',
trace_id ?? '',
TraceDrawerActionKind.INCLUDE,
'toolbar_menu'
);

const {start, end, statsPeriod} = traceEventView;
const target = getSearchInExploreTarget(
organization,
{
...location,
query: {
start,
end,
statsPeriod: start && end ? null : statsPeriod, // We don't want statsPeriod to have precedence over start and end
},
},
'-1',
'trace',
trace_id ?? '',
TraceDrawerActionKind.INCLUDE
);

navigate(target);
}}
>
{t('Open in Explore')}
</Button>
);
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
import {useCallback, useMemo} from 'react';

import {openModal} from 'sentry/actionCreators/modal';
import {CompactSelect, type SelectOption} from 'sentry/components/core/compactSelect';
import type {DropdownButtonProps} from 'sentry/components/dropdownButton';
import ExternalLink from 'sentry/components/links/externalLink';
import {IconSettings} from 'sentry/icons';
import {t} from 'sentry/locale';
import {t, tct} from 'sentry/locale';
import type {Project} from 'sentry/types/project';
import useOrganization from 'sentry/utils/useOrganization';
import useProjects from 'sentry/utils/useProjects';
import {OurLogKnownFieldKey} from 'sentry/views/explore/logs/types';
import {traceAnalytics} from 'sentry/views/performance/newTraceDetails/traceAnalytics';
import type {TraceRootEventQueryResults} from 'sentry/views/performance/newTraceDetails/traceApi/useTraceRootEvent';
import {isTraceItemDetailsResponse} from 'sentry/views/performance/newTraceDetails/traceApi/utils';
import {getCustomInstrumentationLink} from 'sentry/views/performance/newTraceDetails/traceConfigurations';
import {findSpanAttributeValue} from 'sentry/views/performance/newTraceDetails/traceDrawer/details/utils';
import {TraceShortcutsModal} from 'sentry/views/performance/newTraceDetails/traceShortcutsModal';
import {useHasTraceTabsUI} from 'sentry/views/performance/newTraceDetails/useHasTraceTabsUI';

const CompactSelectTriggerProps: DropdownButtonProps = {
icon: <IconSettings />,
Expand All @@ -17,27 +30,36 @@ interface TracePreferencesDropdownProps {
missingInstrumentation: boolean;
onAutogroupChange: () => void;
onMissingInstrumentationChange: () => void;
rootEventResults: TraceRootEventQueryResults;
}

const TRACE_PREFERENCES_DROPDOWN_OPTIONS: Array<SelectOption<string>> = [
{
label: t('Autogrouping'),
value: 'autogroup',
details: t(
'Collapses 5 or more sibling spans with the same description or any spans with 2 or more descendants with the same operation.'
),
},
{
label: t('No Instrumentation'),
value: 'no-instrumentation',
details: t(
'Shows when there is more than 100ms of unaccounted elapsed time between two spans.'
),
},
];

export function TracePreferencesDropdown(props: TracePreferencesDropdownProps) {
const values = useMemo(() => {
const hasTraceTabsUi = useHasTraceTabsUI();
const organization = useOrganization();
const {projects} = useProjects();

const traceProject = getTraceProject(projects, props.rootEventResults);
const selectOptions: Array<SelectOption<string>> = [
{
label: t('Autogrouping'),
value: 'autogroup',
details: t(
'Collapses 5 or more sibling spans with the same description or any spans with 2 or more descendants with the same operation.'
),
},
{
label: t('No Instrumentation'),
value: 'no-instrumentation',
details: tct(
'Shows when there is more than 100ms of unaccounted elapsed time between two spans.[link: Go to docs to instrument more.]',
{
link: <ExternalLink href={getCustomInstrumentationLink(traceProject)} />,
}
),
},
];

const values: string[] = useMemo(() => {
const value: string[] = [];
if (props.autogroup) {
value.push('autogroup');
Expand Down Expand Up @@ -78,16 +100,49 @@ export function TracePreferencesDropdown(props: TracePreferencesDropdownProps) {
[values, onAutogroupChange, onMissingInstrumentationChange]
);

const menuFooter = hasTraceTabsUi ? (
<a
onClick={() => {
traceAnalytics.trackViewShortcuts(organization);
openModal(p => <TraceShortcutsModal {...p} />);
}}
>
{t('See Shortcuts')}
</a>
) : null;

return (
<CompactSelect
multiple
value={values}
// Force the trigger to be so that we only render the icon
triggerLabel=""
triggerProps={CompactSelectTriggerProps}
options={TRACE_PREFERENCES_DROPDOWN_OPTIONS}
options={selectOptions}
onChange={onChange}
menuFooter={menuFooter}
menuWidth={300}
/>
);
}

export function getTraceProject(
projects: Project[],
rootEventResults: TraceRootEventQueryResults
): Project | undefined {
if (!rootEventResults.data) {
return undefined;
}

if (isTraceItemDetailsResponse(rootEventResults.data)) {
const attributes = rootEventResults.data.attributes;
const projectId =
OurLogKnownFieldKey.PROJECT_ID in attributes
? attributes[OurLogKnownFieldKey.PROJECT_ID]
: findSpanAttributeValue(attributes, 'project_id');
return projects.find(p => p.id === projectId);
}

const projectSlug = rootEventResults.data.projectSlug;
return projects.find(p => p.slug === projectSlug);
}
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ export class VirtualizedViewManager {

registerResetZoomRef(ref: HTMLButtonElement | null) {
this.reset_zoom_button = ref;
this.syncResetZoomButton();
}

registerGhostRowRef(column: string, ref: HTMLElement | null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,21 @@ import {t} from 'sentry/locale';
import type {Organization} from 'sentry/types/organization';
import {traceAnalytics} from 'sentry/views/performance/newTraceDetails/traceAnalytics';
import type {VirtualizedViewManager} from 'sentry/views/performance/newTraceDetails/traceRenderers/virtualizedViewManager';
import {useHasTraceTabsUI} from 'sentry/views/performance/newTraceDetails/useHasTraceTabsUI';

export function TraceResetZoomButton(props: {
organization: Organization;
viewManager: VirtualizedViewManager;
}) {
const hasTraceTabsUi = useHasTraceTabsUI();
const onResetZoom = useCallback(() => {
traceAnalytics.trackResetZoom(props.organization);
props.viewManager.resetZoom();
}, [props.viewManager, props.organization]);

return (
<ResetZoomButton
hide={hasTraceTabsUi && props.viewManager.reset_zoom_button?.disabled !== false}
size="xs"
onClick={onResetZoom}
ref={props.viewManager.registerResetZoomRef}
Expand All @@ -27,8 +30,10 @@ export function TraceResetZoomButton(props: {
);
}

const ResetZoomButton = styled(Button)`
transition: opacity 0.2s 0.5s ease-in-out;
const ResetZoomButton = styled(Button)<{
hide: boolean;
}>`
display: ${props => (props.hide ? 'none' : 'block')};

&[disabled] {
cursor: not-allowed;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import type {TraceRootEventQueryResults} from 'sentry/views/performance/newTrace
import {isTraceItemDetailsResponse} from 'sentry/views/performance/newTraceDetails/traceApi/utils';
import {TraceLinkNavigationButton} from 'sentry/views/performance/newTraceDetails/traceLinksNavigation/traceLinkNavigationButton';
import {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree';
import {TraceOpenInExploreButton} from 'sentry/views/performance/newTraceDetails/traceOpenInExploreButton';
import {useDividerResizeSync} from 'sentry/views/performance/newTraceDetails/useDividerResizeSync';
import {useHasTraceTabsUI} from 'sentry/views/performance/newTraceDetails/useHasTraceTabsUI';
import {useTraceSpaceListeners} from 'sentry/views/performance/newTraceDetails/useTraceSpaceListeners';
Expand Down Expand Up @@ -774,6 +775,10 @@ export function TraceWaterfall(props: TraceWaterfallProps) {
/>
<TraceToolbar>
<TraceSearchInput onTraceSearch={onTraceSearch} organization={organization} />
<TraceOpenInExploreButton
trace_id={props.traceSlug}
traceEventView={props.traceEventView}
/>
<TraceResetZoomButton
viewManager={viewManager}
organization={props.organization}
Expand All @@ -784,6 +789,7 @@ export function TraceWaterfall(props: TraceWaterfallProps) {
traceEventView={props.traceEventView}
/>
<TracePreferencesDropdown
rootEventResults={props.rootEventResults}
autogroup={
traceState.preferences.autogroup.parent &&
traceState.preferences.autogroup.sibling
Expand Down Expand Up @@ -896,9 +902,7 @@ const GrabberContainer = styled('div')`
`;

const TraceToolbar = styled('div')`
flex-grow: 0;
display: grid;
grid-template-columns: 1fr min-content min-content min-content;
display: flex;
gap: ${space(1)};
`;

Expand Down
Loading