mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 15:05:55 +08:00
feat: added copy span link support alongside span click expand in waterfall graph
This commit is contained in:
parent
9338efcefc
commit
0944af3d31
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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 (
|
||||||
|
<div className={`span-line-action-buttons ${customClassName}`}>
|
||||||
|
<Tooltip title="Copy Span Link">
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
icon={<LinkOutlined size={14} />}
|
||||||
|
onClick={onSpanCopy}
|
||||||
|
className="copy-span-btn"
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
SpanLineActionButtons.defaultProps = {
|
||||||
|
customClassName: '',
|
||||||
|
};
|
@ -9,6 +9,7 @@ import cx from 'classnames';
|
|||||||
import { TableV3 } from 'components/TableV3/TableV3';
|
import { TableV3 } from 'components/TableV3/TableV3';
|
||||||
import { themeColors } from 'constants/theme';
|
import { themeColors } from 'constants/theme';
|
||||||
import { convertTimeToRelevantUnit } from 'container/TraceDetail/utils';
|
import { convertTimeToRelevantUnit } from 'container/TraceDetail/utils';
|
||||||
|
import SpanLineActionButtons from 'container/TraceWaterfall/SpanLineActionButtons';
|
||||||
import { IInterestedSpan } from 'container/TraceWaterfall/TraceWaterfall';
|
import { IInterestedSpan } from 'container/TraceWaterfall/TraceWaterfall';
|
||||||
import { generateColor } from 'lib/uPlotLib/utils/generateColor';
|
import { generateColor } from 'lib/uPlotLib/utils/generateColor';
|
||||||
import {
|
import {
|
||||||
@ -25,7 +26,9 @@ import {
|
|||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
import { useHistory, useLocation } from 'react-router-dom';
|
||||||
import { Span } from 'types/api/trace/getTraceV2';
|
import { Span } from 'types/api/trace/getTraceV2';
|
||||||
import { toFixed } from 'utils/toFixed';
|
import { toFixed } from 'utils/toFixed';
|
||||||
|
|
||||||
@ -166,20 +169,38 @@ function SpanDuration({
|
|||||||
const leftOffset = ((span.timestamp - traceMetadata.startTime) * 1e2) / spread;
|
const leftOffset = ((span.timestamp - traceMetadata.startTime) * 1e2) / spread;
|
||||||
const width = (span.durationNano * 1e2) / (spread * 1e6);
|
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);
|
let color = generateColor(span.serviceName, themeColors.traceDetailColors);
|
||||||
|
|
||||||
if (span.hasError) {
|
if (span.hasError) {
|
||||||
color = `var(--bg-cherry-500)`;
|
color = `var(--bg-cherry-500)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [hasActionButtons, setHasActionButtons] = useState(false);
|
||||||
|
|
||||||
|
const handleMouseEnter = (): void => {
|
||||||
|
setHasActionButtons(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseLeave = (): void => {
|
||||||
|
setHasActionButtons(false);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
'span-duration',
|
'span-duration',
|
||||||
selectedSpan?.spanId === span.spanId ? 'interested-span' : '',
|
selectedSpan?.spanId === span.spanId ? 'interested-span' : '',
|
||||||
)}
|
)}
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
onClick={(): void => {
|
onClick={(): void => {
|
||||||
setSelectedSpan(span);
|
setSelectedSpan(span);
|
||||||
|
searchParams.set('spanId', span.spanId);
|
||||||
|
history.replace({ search: searchParams.toString() });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@ -190,6 +211,7 @@ function SpanDuration({
|
|||||||
backgroundColor: color,
|
backgroundColor: color,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{hasActionButtons && <SpanLineActionButtons span={span} />}
|
||||||
<Tooltip title={`${toFixed(time, 2)} ${timeUnitName}`}>
|
<Tooltip title={`${toFixed(time, 2)} ${timeUnitName}`}>
|
||||||
<Typography.Text
|
<Typography.Text
|
||||||
className="span-line-text"
|
className="span-line-text"
|
||||||
|
42
frontend/src/hooks/trace/useCopySpanLink.ts
Normal file
42
frontend/src/hooks/trace/useCopySpanLink.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
|
import useUrlQuery from 'hooks/useUrlQuery';
|
||||||
|
import { MouseEventHandler, useCallback } from 'react';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
import { useCopyToClipboard } from 'react-use';
|
||||||
|
import { Span } from 'types/api/trace/getTraceV2';
|
||||||
|
|
||||||
|
export const useCopySpanLink = (
|
||||||
|
span?: Span,
|
||||||
|
): { onSpanCopy: MouseEventHandler<HTMLElement> } => {
|
||||||
|
const urlQuery = useUrlQuery();
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
const [, setCopy] = useCopyToClipboard();
|
||||||
|
const { notifications } = useNotifications();
|
||||||
|
|
||||||
|
const onSpanCopy: MouseEventHandler<HTMLElement> = 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,
|
||||||
|
};
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user