diff --git a/frontend/src/container/TraceWaterfall/SpanLineActionButtons/SpanLineActionButtons.styles.scss b/frontend/src/container/TraceWaterfall/SpanLineActionButtons/SpanLineActionButtons.styles.scss new file mode 100644 index 0000000000..6cfa385f6f --- /dev/null +++ b/frontend/src/container/TraceWaterfall/SpanLineActionButtons/SpanLineActionButtons.styles.scss @@ -0,0 +1,42 @@ +.span-line-action-buttons { + display: flex; + position: absolute; + transform: translate(-50%, -50%); + top: 50%; + right: 0; + cursor: pointer; + border-radius: 2px; + border: 1px solid var(--bg-slate-400); + background: var(--bg-ink-400); + + .ant-btn-default { + border: none; + box-shadow: none; + padding: 9px; + justify-content: center; + align-items: center; + display: flex; + + &.active-tab { + background-color: var(--bg-slate-400); + } + } + + .copy-span-btn { + border-color: var(--bg-slate-400) !important; + } +} + +.lightMode { + .span-line-action-buttons { + border: 1px solid var(--bg-vanilla-400); + background: var(--bg-vanilla-400); + + .ant-btn-default { + } + + .copy-span-btn { + border-color: var(--bg-vanilla-400) !important; + } + } +} diff --git a/frontend/src/container/TraceWaterfall/SpanLineActionButtons/index.tsx b/frontend/src/container/TraceWaterfall/SpanLineActionButtons/index.tsx new file mode 100644 index 0000000000..f8646cd186 --- /dev/null +++ b/frontend/src/container/TraceWaterfall/SpanLineActionButtons/index.tsx @@ -0,0 +1,34 @@ +import './SpanLineActionButtons.styles.scss'; + +import { LinkOutlined } from '@ant-design/icons'; +import { Button, Tooltip } from 'antd'; +import { useCopySpanLink } from 'hooks/trace/useCopySpanLink'; +import { Span } from 'types/api/trace/getTraceV2'; + +export interface SpanLineActionButtonsProps { + span: Span; + customClassName?: string; +} +export default function SpanLineActionButtons({ + span, + customClassName = '', +}: SpanLineActionButtonsProps): JSX.Element { + const { onSpanCopy } = useCopySpanLink(span); + + return ( +
+ +
+ ); +} + +SpanLineActionButtons.defaultProps = { + customClassName: '', +}; diff --git a/frontend/src/container/TraceWaterfall/TraceWaterfallStates/Success/Success.tsx b/frontend/src/container/TraceWaterfall/TraceWaterfallStates/Success/Success.tsx index b3a113665f..9e432d8b25 100644 --- a/frontend/src/container/TraceWaterfall/TraceWaterfallStates/Success/Success.tsx +++ b/frontend/src/container/TraceWaterfall/TraceWaterfallStates/Success/Success.tsx @@ -9,6 +9,7 @@ import cx from 'classnames'; import { TableV3 } from 'components/TableV3/TableV3'; import { themeColors } from 'constants/theme'; import { convertTimeToRelevantUnit } from 'container/TraceDetail/utils'; +import SpanLineActionButtons from 'container/TraceWaterfall/SpanLineActionButtons'; import { IInterestedSpan } from 'container/TraceWaterfall/TraceWaterfall'; import { generateColor } from 'lib/uPlotLib/utils/generateColor'; import { @@ -25,7 +26,9 @@ import { useEffect, useMemo, useRef, + useState, } from 'react'; +import { useHistory, useLocation } from 'react-router-dom'; import { Span } from 'types/api/trace/getTraceV2'; import { toFixed } from 'utils/toFixed'; @@ -166,20 +169,38 @@ function SpanDuration({ const leftOffset = ((span.timestamp - traceMetadata.startTime) * 1e2) / spread; const width = (span.durationNano * 1e2) / (spread * 1e6); + const { search } = useLocation(); + const history = useHistory(); + const searchParams = new URLSearchParams(search); + let color = generateColor(span.serviceName, themeColors.traceDetailColors); if (span.hasError) { color = `var(--bg-cherry-500)`; } + const [hasActionButtons, setHasActionButtons] = useState(false); + + const handleMouseEnter = (): void => { + setHasActionButtons(true); + }; + + const handleMouseLeave = (): void => { + setHasActionButtons(false); + }; + return (
{ setSelectedSpan(span); + searchParams.set('spanId', span.spanId); + history.replace({ search: searchParams.toString() }); }} >
+ {hasActionButtons && } } => { + const urlQuery = useUrlQuery(); + const { pathname } = useLocation(); + const [, setCopy] = useCopyToClipboard(); + const { notifications } = useNotifications(); + + const onSpanCopy: MouseEventHandler = useCallback( + (event) => { + if (!span) return; + + event.preventDefault(); + event.stopPropagation(); + + urlQuery.delete('spanId'); + + if (span.spanId) { + urlQuery.set('spanId', span?.spanId); + } + + const link = `${window.location.origin}${pathname}?${urlQuery.toString()}`; + + setCopy(link); + notifications.success({ + message: 'Copied to clipboard', + }); + }, + [span, urlQuery, pathname, setCopy, notifications], + ); + + return { + onSpanCopy, + }; +};