mirror of
https://github.com/SigNoz/signoz.git
synced 2026-07-03 13:20:34 +01:00
Compare commits
5 Commits
main
...
feat/trace
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd223dab8f | ||
|
|
61493ccea5 | ||
|
|
ef3cc006bb | ||
|
|
036e41d0f3 | ||
|
|
93a7a4232b |
@@ -60,6 +60,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
// The Badge is already pill-rounded, so an 18x18 box renders a circle for a
|
||||
// single digit and a capsule for more.
|
||||
.eventsBadge {
|
||||
// l3 background (!important beats the Badge's [data-color] rule).
|
||||
--badge-background: var(--l3-background) !important;
|
||||
--badge-padding: 0 5px;
|
||||
// Static count, not interactive — cancel the Badge's hover background change.
|
||||
--badge-hover-background: var(--badge-background) !important;
|
||||
|
||||
margin-left: 6px;
|
||||
min-width: 18px;
|
||||
height: 18px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.tabsScroll {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { Badge } from '@signozhq/ui/badge';
|
||||
import {
|
||||
TabsContent,
|
||||
TabsList,
|
||||
@@ -281,6 +282,8 @@ function SpanDetailsContent({
|
||||
// .map((key) => ({ key, value: allAttrs[key] }));
|
||||
// }, [selectedSpan]);
|
||||
|
||||
const eventsCount = selectedSpan.events?.length || 0;
|
||||
|
||||
return (
|
||||
<div className={styles.body}>
|
||||
<div className={styles.detailsSection}>
|
||||
@@ -397,7 +400,10 @@ function SpanDetailsContent({
|
||||
<Bookmark size={14} /> Overview
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="events" variant="secondary">
|
||||
<ScrollText size={14} /> Events ({selectedSpan.events?.length || 0})
|
||||
<ScrollText size={14} /> Events
|
||||
<Badge color="secondary" className={styles.eventsBadge}>
|
||||
{eventsCount}
|
||||
</Badge>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="logs" variant="secondary">
|
||||
<List size={14} /> Logs
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
.backBtn {
|
||||
flex-shrink: 0;
|
||||
border: 1px solid var(--l1-border);
|
||||
}
|
||||
|
||||
.traceIdSection {
|
||||
@@ -26,6 +27,11 @@
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
|
||||
// Tabular figures so the trace ID's digits line up at a fixed width.
|
||||
:global(.key-value-label__value) {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
}
|
||||
|
||||
.filterSection {
|
||||
|
||||
@@ -133,7 +133,7 @@ function TraceDetailsHeader({
|
||||
<Button
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
size="md"
|
||||
size="icon"
|
||||
className={styles.backBtn}
|
||||
onClick={handlePreviousBtnClick}
|
||||
aria-label="Back"
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { ChevronsRight, Copy, Search, X } from '@signozhq/icons';
|
||||
import { ArrowRightFromLine, Search, X } from '@signozhq/icons';
|
||||
import { Switch } from '@signozhq/ui/switch';
|
||||
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import {
|
||||
TooltipRoot,
|
||||
@@ -21,6 +19,7 @@ import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
|
||||
import { uniqBy } from 'lodash-es';
|
||||
import NozButton from 'pages/TraceDetailsV3/TraceDetailsHeader/NozButton';
|
||||
import CopyButton from 'periscope/components/CopyButton/CopyButton';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import {
|
||||
@@ -89,7 +88,6 @@ function Filters({
|
||||
onExpand: () => void;
|
||||
onCollapse: () => void;
|
||||
}): JSX.Element {
|
||||
const [, setCopy] = useCopyToClipboard();
|
||||
const [filters, setFilters] = useState<TagFilter>(
|
||||
BASE_FILTER_QUERY.filters || { items: [], op: 'AND' },
|
||||
);
|
||||
@@ -301,20 +299,7 @@ function Filters({
|
||||
<div className={styles.pillPopover}>
|
||||
<div className={styles.pillPopoverHeader}>
|
||||
<Typography.Text>Search query</Typography.Text>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
color="secondary"
|
||||
onClick={(): void => {
|
||||
setCopy(expression);
|
||||
toast.success('Copied to clipboard', {
|
||||
richColors: false,
|
||||
position: 'top-right',
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Copy size={12} />
|
||||
</Button>
|
||||
<CopyButton value={expression} size={12} />
|
||||
</div>
|
||||
<div className={styles.pillPopoverExpression}>{expression}</div>
|
||||
</div>
|
||||
@@ -421,7 +406,7 @@ function Filters({
|
||||
color="secondary"
|
||||
onClick={onCollapse}
|
||||
>
|
||||
<ChevronsRight size={14} />
|
||||
<ArrowRightFromLine size={14} />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Collapse filters</TooltipContent>
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
// Square copy button whose icon cross-fades between copy and check.
|
||||
.copyButton {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
// Both icons occupy the same box; only one is visible at a time.
|
||||
.iconStack {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.icon {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
transition:
|
||||
opacity 300ms ease,
|
||||
filter 300ms ease,
|
||||
transform 300ms ease;
|
||||
}
|
||||
|
||||
// Idle state: copy visible, check blurred/rotated/faded out.
|
||||
.copyIcon {
|
||||
opacity: 1;
|
||||
filter: blur(0);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
.checkIcon {
|
||||
opacity: 0;
|
||||
filter: blur(4px);
|
||||
transform: rotate(-90deg);
|
||||
// Green checkmark to signal a successful copy; eases in a touch slower.
|
||||
color: var(--bg-forest-500);
|
||||
transition-duration: 500ms;
|
||||
}
|
||||
|
||||
// Copied state: copy fades/blurs/rotates out, check animates in.
|
||||
.iconStack[data-copied='true'] {
|
||||
.copyIcon {
|
||||
opacity: 0;
|
||||
filter: blur(4px);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.checkIcon {
|
||||
opacity: 1;
|
||||
filter: blur(0);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
}
|
||||
71
frontend/src/periscope/components/CopyButton/CopyButton.tsx
Normal file
71
frontend/src/periscope/components/CopyButton/CopyButton.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import { CSSProperties, useCallback } from 'react';
|
||||
import { Check, Copy } from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import cx from 'classnames';
|
||||
import { useCopyToClipboard } from 'hooks/useCopyToClipboard';
|
||||
|
||||
import styles from './CopyButton.module.scss';
|
||||
|
||||
export interface CopyButtonProps {
|
||||
/** Text written to the clipboard on click. */
|
||||
value: string;
|
||||
/** Icon size in px. Default 14. */
|
||||
size?: number;
|
||||
/** Accessible label for the idle (not-yet-copied) state. Default "Copy". */
|
||||
ariaLabel?: string;
|
||||
/** Extra class merged onto the button. */
|
||||
className?: string;
|
||||
/** Fired after a successful copy (e.g. to show a toast). */
|
||||
onCopy?: () => void;
|
||||
testId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Square, icon-only copy button. Shows a copy icon that cross-fades to a
|
||||
* checkmark (blur + rotate + fade) on copy, reverting after 2s. The checkmark
|
||||
* uses the hover-state icon colour.
|
||||
*/
|
||||
function CopyButton({
|
||||
value,
|
||||
size = 14,
|
||||
ariaLabel = 'Copy',
|
||||
className,
|
||||
onCopy,
|
||||
testId,
|
||||
}: CopyButtonProps): JSX.Element {
|
||||
const { copyToClipboard, isCopied } = useCopyToClipboard();
|
||||
|
||||
const handleClick = useCallback((): void => {
|
||||
copyToClipboard(value);
|
||||
onCopy?.();
|
||||
}, [copyToClipboard, value, onCopy]);
|
||||
|
||||
const stackStyle: CSSProperties = { width: size, height: size };
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
size="icon"
|
||||
className={cx(styles.copyButton, className)}
|
||||
onClick={handleClick}
|
||||
aria-label={isCopied ? 'Copied' : ariaLabel}
|
||||
testId={testId}
|
||||
>
|
||||
<span className={styles.iconStack} style={stackStyle} data-copied={isCopied}>
|
||||
<Copy size={size} className={cx(styles.icon, styles.copyIcon)} />
|
||||
<Check size={size} className={cx(styles.icon, styles.checkIcon)} />
|
||||
</span>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
CopyButton.defaultProps = {
|
||||
size: 14,
|
||||
ariaLabel: 'Copy',
|
||||
className: undefined,
|
||||
onCopy: undefined,
|
||||
testId: undefined,
|
||||
};
|
||||
|
||||
export default CopyButton;
|
||||
@@ -22,23 +22,6 @@
|
||||
--dropdown-menu-content-z-index: 1000;
|
||||
}
|
||||
|
||||
&__copy-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4px 8px;
|
||||
border: 1px solid var(--l2-border);
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
color: var(--l2-foreground);
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: var(--l1-foreground);
|
||||
background: var(--l3-background);
|
||||
}
|
||||
}
|
||||
|
||||
// Shared content container — no scroll, each view handles its own
|
||||
&__content {
|
||||
flex: 1;
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { ChevronDown, Copy } from '@signozhq/icons';
|
||||
import { ChevronDown } from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { DropdownMenuSimple as Dropdown } from '@signozhq/ui/dropdown-menu';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import CopyButton from 'periscope/components/CopyButton/CopyButton';
|
||||
import { JsonView } from 'periscope/components/JsonView';
|
||||
import { PrettyView } from 'periscope/components/PrettyView';
|
||||
import { PrettyViewProps } from 'periscope/components/PrettyView';
|
||||
import { PrettyView, PrettyViewProps } from 'periscope/components/PrettyView';
|
||||
|
||||
import './DataViewer.styles.scss';
|
||||
|
||||
@@ -33,7 +31,6 @@ function DataViewer({
|
||||
prettyViewProps,
|
||||
}: DataViewerProps): JSX.Element {
|
||||
const [viewMode, setViewMode] = useState<ViewMode>('pretty');
|
||||
const [, setCopy] = useCopyToClipboard();
|
||||
|
||||
const jsonString = useMemo(() => JSON.stringify(data, null, 2), [data]);
|
||||
|
||||
@@ -51,14 +48,6 @@ function DataViewer({
|
||||
}
|
||||
};
|
||||
|
||||
const handleCopy = (): void => {
|
||||
const text = JSON.stringify(data, null, 2);
|
||||
setCopy(text);
|
||||
toast.success('Copied to clipboard', {
|
||||
position: 'top-right',
|
||||
});
|
||||
};
|
||||
|
||||
const currentLabel =
|
||||
VIEW_MODE_OPTIONS.find((opt) => opt.value === viewMode)?.label ?? 'Pretty';
|
||||
|
||||
@@ -94,14 +83,7 @@ function DataViewer({
|
||||
{currentLabel}
|
||||
</Button>
|
||||
</Dropdown>
|
||||
<button
|
||||
type="button"
|
||||
className="data-viewer__copy-btn"
|
||||
onClick={handleCopy}
|
||||
aria-label="Copy JSON"
|
||||
>
|
||||
<Copy size={14} />
|
||||
</button>
|
||||
<CopyButton value={jsonString} ariaLabel="Copy JSON" />
|
||||
</div>
|
||||
|
||||
<div className="data-viewer__content">
|
||||
|
||||
Reference in New Issue
Block a user