Compare commits

...

25 Commits

Author SHA1 Message Date
ahmadshaheer
2ce2ef92fe feat: add constant to prevent consumers breaking 2025-10-22 10:06:37 +04:30
ahmadshaheer
0ca264237e fix: fix the background of border handle in light mode 2025-10-21 09:30:24 +04:30
ahmadshaheer
debf130a1d fix: update action button visibility based on open state 2025-10-21 09:24:21 +04:30
ahmadshaheer
645d0f2f6d fix: update filter button border color to match the theme 2025-10-21 08:56:09 +04:30
ahmadshaheer
1e041590d5 fix: enforce hover background color for attribute actions menu 2025-10-21 08:46:39 +04:30
ahmadshaheer
8781600a48 fix: enhance the UI of formula label to match the other add-ons 2025-10-21 08:45:47 +04:30
ahmadshaheer
879796cb52 chore: improve result table styles in trace funnels 2025-10-16 16:02:06 +04:30
ahmadshaheer
55249d68fc chore: remove unnecessary icon from QB in trace funnels step 2025-10-16 15:04:25 +04:30
ahmadshaheer
d809b351b9 chore: hide span selector in exceptions page 2025-10-16 15:01:55 +04:30
ahmadshaheer
5b0d90fcce fix: make the QB footer buttons styles consistent with other buttons 2025-10-16 14:57:14 +04:30
ahmadshaheer
be82703820 fix: don't display span attribute if it doesn't have value 2025-10-16 13:54:41 +04:30
ahmadshaheer
456b505b60 fix: fix similar colors for different queries in timeseries view 2025-10-16 13:16:53 +04:30
ahmadshaheer
9fe9c7a6ff fix: remove the temporary style change 2025-10-15 12:48:33 +04:30
ahmadshaheer
2bdefc1051 fix: prevent displaying double tooltips in span attributes 2025-10-15 12:47:42 +04:30
ahmadshaheer
c632fb5ef0 fix: add lightmode styles for attribute hover style 2025-10-15 12:05:40 +04:30
ahmadshaheer
bd42995de6 fix: fix the light mode colors for signoz radio group component hover and disabled states 2025-10-15 11:35:37 +04:30
ahmadshaheer
d0a8cc4de3 fix: make the % exec time colors consistent with colors in other components 2025-10-13 17:31:33 +04:30
ahmadshaheer
dd62215246 fix: sort service execution times in descending order for better visibility 2025-10-13 16:50:51 +04:30
ahmadshaheer
11a4f6ae96 fix: add hover bg for attributes on hover 2025-10-13 13:24:43 +04:30
ahmadshaheer
bfaa9df636 style: update action button background and remove unnecessary styles 2025-10-13 13:07:53 +04:30
ahmadshaheer
50e83be4af refactor: remove unnecessary order by functionality and related components from TracesView 2025-10-13 13:03:46 +04:30
ahmadshaheer
5609398f53 fix: fix the inconsistency in the styles of trace operator and other query addons consistent 2025-10-13 12:31:46 +04:30
ahmadshaheer
f70e71070a fix: make the trace operator label match the case and color of QB addons 2025-10-13 11:38:01 +04:30
ahmadshaheer
ff8e2ab6d0 chore: remove link to old trace details page and remove the component and files 2025-10-13 11:17:54 +04:30
ahmadshaheer
8121e61409 feat: add queriesCount prop to QueryV2 and conditionally render delete option 2025-10-13 09:29:13 +04:30
31 changed files with 204 additions and 537 deletions

View File

@@ -37,7 +37,6 @@
border-radius: 2px 0px 0px 2px;
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-300);
border-right: none;
border-left: none;
@@ -45,6 +44,12 @@
border-bottom-right-radius: 0px;
border-top-left-radius: 0px;
border-bottom-left-radius: 0px;
font-size: 12px !important;
line-height: 27px;
&::placeholder {
color: #888 !important;
font-size: 12px !important;
}
}
.close-btn {

View File

@@ -6,6 +6,7 @@ import { useCopyToClipboard } from 'react-use';
function CopyClipboardHOC({
entityKey,
textToCopy,
tooltipText = 'Copy to clipboard',
children,
}: CopyClipboardHOCProps): JSX.Element {
const [value, setCopy] = useCopyToClipboard();
@@ -31,7 +32,7 @@ function CopyClipboardHOC({
<span onClick={onClick} role="presentation" tabIndex={-1}>
<Popover
placement="top"
content={<span style={{ fontSize: '0.9rem' }}>Copy to clipboard</span>}
content={<span style={{ fontSize: '0.9rem' }}>{tooltipText}</span>}
>
{children}
</Popover>
@@ -42,7 +43,11 @@ function CopyClipboardHOC({
interface CopyClipboardHOCProps {
entityKey: string | undefined;
textToCopy: string;
tooltipText?: string;
children: ReactNode;
}
export default CopyClipboardHOC;
CopyClipboardHOC.defaultProps = {
tooltipText: 'Copy to clipboard',
};

View File

@@ -251,6 +251,10 @@
.ant-input-group-addon {
border-top-left-radius: 0px !important;
border-top-right-radius: 0px !important;
background: var(--bg-ink-300);
color: var(--bg-vanilla-400);
font-size: 12px;
font-weight: 300;
}
.ant-input {

View File

@@ -194,6 +194,7 @@ export const QueryBuilderV2 = memo(function QueryBuilderV2({
showOnlyWhereClause={showOnlyWhereClause}
isListViewPanel={isListViewPanel}
signalSource={config?.signalSource || ''}
queriesCount={currentQuery.builder.queryData.length}
/>
))
)}

View File

@@ -236,6 +236,10 @@
background: var(--bg-ink-100) !important;
opacity: 0.5 !important;
}
.cm-activeLine > span {
font-size: 12px !important;
}
}
}
@@ -271,6 +275,9 @@
box-sizing: border-box;
position: relative;
.cm-placeholder {
font-size: 12px !important;
}
}
}

View File

@@ -20,6 +20,8 @@
border-radius: 2px;
flex: 1;
min-width: 0;
font-size: 12px;
color: #888 !important;
&.error {
.cm-editor {
@@ -231,6 +233,9 @@
.query-aggregation-interval-input {
input {
max-width: 120px;
&::placeholder {
color: #888;
}
}
}
}

View File

@@ -0,0 +1,5 @@
.add-trace-operator-button, .add-new-query-button, .add-formula-button {
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-300);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
}

View File

@@ -1,3 +1,5 @@
import './QueryFooter.styles.scss';
/* eslint-disable react/require-default-props */
import { Button, Tooltip, Typography } from 'antd';
import { DraftingCompass, Plus, Sigma } from 'lucide-react';
@@ -22,8 +24,7 @@ export default function QueryFooter({
<div className="qb-add-new-query">
<Tooltip title={<div style={{ textAlign: 'center' }}>Add New Query</div>}>
<Button
className="add-new-query-button periscope-btn secondary"
type="text"
className="add-new-query-button periscope-btn "
icon={<Plus size={16} />}
onClick={addNewBuilderQuery}
/>
@@ -49,7 +50,7 @@ export default function QueryFooter({
}
>
<Button
className="add-formula-button periscope-btn secondary"
className="add-formula-button periscope-btn "
icon={<Sigma size={16} />}
onClick={addNewFormula}
>
@@ -77,7 +78,7 @@ export default function QueryFooter({
}
>
<Button
className="add-trace-operator-button periscope-btn secondary"
className="add-trace-operator-button periscope-btn "
icon={<DraftingCompass size={16} />}
onClick={(): void => addTraceOperator?.()}
>

View File

@@ -33,7 +33,12 @@ export const QueryV2 = memo(function QueryV2({
showOnlyWhereClause = false,
signalSource = '',
isMultiQueryAllowed = false,
}: QueryProps & { ref: React.RefObject<HTMLDivElement> }): JSX.Element {
queriesCount = 1,
}: QueryProps & {
ref: React.RefObject<HTMLDivElement>;
// eslint-disable-next-line react/require-default-props
queriesCount?: number;
}): JSX.Element {
const { cloneQuery, panelType } = useQueryBuilder();
const showFunctions = query?.functions?.length > 0;
@@ -186,12 +191,16 @@ export const QueryV2 = memo(function QueryV2({
icon: <Copy size={14} />,
onClick: handleCloneEntity,
},
{
label: 'Delete',
key: 'delete-query',
icon: <Trash size={14} />,
onClick: handleDeleteQuery,
},
...(queriesCount && queriesCount > 1
? [
{
label: 'Delete',
key: 'delete-query',
icon: <Trash size={14} />,
onClick: handleDeleteQuery,
},
]
: []),
],
}}
placement="bottomRight"

View File

@@ -92,6 +92,9 @@
.qb-trace-operator-editor-container {
flex: 1;
.cm-activeLine > span {
font-size: 12px;
}
}
&.arrow-left {
@@ -113,6 +116,8 @@
text-overflow: ellipsis;
padding: 0px 8px;
border-right: 1px solid var(--bg-slate-400);
font-size: 12px;
font-weight: 300;
}
}
}

View File

@@ -68,7 +68,7 @@ export default function TraceOperator({
!isListViewPanel && 'qb-trace-operator-arrow',
)}
>
<Typography.Text className="label">TRACE OPERATOR</Typography.Text>
<Typography.Text className="label">Trace Operator</Typography.Text>
<div className="qb-trace-operator-editor-container">
<TraceOperatorEditor
value={traceOperator?.expression || ''}

View File

@@ -31,12 +31,14 @@
}
.tab {
border: 1px solid var(--bg-slate-400);
&:hover {
color: var(--text-vanilla-100);
}
&::before {
background: var(--bg-slate-400);
&:not(.ant-radio-button-wrapper-disabled) {
border: 1px solid var(--bg-slate-400);
&:hover {
color: var(--text-vanilla-100);
}
&::before {
background: var(--bg-slate-400);
}
}
}
@@ -56,16 +58,18 @@
// Light mode styles
.lightMode {
.signoz-radio-group {
&.ant-radio-group-disabled {
.tab,
.selected_view {
.tab,
.selected_view {
&.ant-radio-button-wrapper-disabled {
background: var(--bg-vanilla-200) !important;
border-color: var(--bg-vanilla-400) !important;
color: var(--text-ink-400) !important;
}
}
.tab:hover,
.selected_view:hover {
.tab:hover,
.selected_view:hover {
&:not(.ant-radio-button-wrapper-disabled) {
background: var(--bg-vanilla-200) !important;
border-color: var(--bg-vanilla-400) !important;
color: var(--text-ink-400) !important;
@@ -73,6 +77,7 @@
}
.tab {
border-color: var(--bg-vanilla-400) !important;
background: var(--bg-vanilla-100);
}

View File

@@ -34,7 +34,7 @@ const themeColors = {
cyan: '#00FFFF',
},
chartcolors: {
robin: '#3F5ECC',
radicalRed: '#FF1A66',
dodgerBlue: '#2F80ED',
mediumOrchid: '#BB6BD9',
seaBuckthorn: '#F2994A',
@@ -58,7 +58,7 @@ const themeColors = {
oliveDrab: '#66991A',
lavenderRose: '#FF99E6',
electricLime: '#CCFF1A',
radicalRed: '#FF1A66',
robin: '#3F5ECC',
harleyOrange: '#E6331A',
turquoise: '#33FFCC',
gladeGreen: '#66994D',
@@ -80,7 +80,7 @@ const themeColors = {
maroon: '#800000',
navy: '#000080',
aquamarine: '#7FFFD4',
gold: '#FFD700',
darkSeaGreen: '#8FBC8F',
gray: '#808080',
skyBlue: '#87CEEB',
indigo: '#4B0082',
@@ -105,7 +105,7 @@ const themeColors = {
lawnGreen: '#7CFC00',
mediumSeaGreen: '#3CB371',
lightCoral: '#F08080',
darkSeaGreen: '#8FBC8F',
gold: '#FFD700',
sandyBrown: '#F4A460',
darkKhaki: '#BDB76B',
cornflowerBlue: '#6495ED',
@@ -113,7 +113,7 @@ const themeColors = {
paleGreen: '#98FB98',
},
lightModeColor: {
robin: '#3F5ECC',
radicalRed: '#FF1A66',
dodgerBlueDark: '#0C6EED',
steelgrey: '#2f4b7c',
steelpurple: '#665191',
@@ -143,7 +143,7 @@ const themeColors = {
oliveDrab: '#66991A',
lavenderRoseDark: '#F024BD',
electricLimeDark: '#84A800',
radicalRed: '#FF1A66',
robin: '#3F5ECC',
harleyOrange: '#E6331A',
gladeGreen: '#66994D',
hemlock: '#66664D',
@@ -181,7 +181,7 @@ const themeColors = {
darkOrchid: '#9932CC',
mediumSeaGreenDark: '#109E50',
lightCoralDark: '#F85959',
darkSeaGreenDark: '#509F50',
gold: '#FFD700',
sandyBrownDark: '#D97117',
darkKhakiDark: '#99900A',
cornflowerBlueDark: '#3371E6',

View File

@@ -1,9 +1,11 @@
import { CaretDownFilled, CaretRightFilled } from '@ant-design/icons';
import { Col, Typography } from 'antd';
import { StyledCol, StyledRow } from 'components/Styled';
import { IIntervalUnit } from 'container/TraceDetail/utils';
import {
IIntervalUnit,
SPAN_DETAILS_LEFT_COL_WIDTH,
} from 'container/TraceDetail/utils';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { SPAN_DETAILS_LEFT_COL_WIDTH } from 'pages/TraceDetail/constants';
import {
Dispatch,
MouseEventHandler,

View File

@@ -5,7 +5,6 @@ import { AxiosError } from 'axios';
import Spinner from 'components/Spinner';
import { themeColors } from 'constants/theme';
import useGetTraceFlamegraph from 'hooks/trace/useGetTraceFlamegraph';
import { useIsDarkMode } from 'hooks/useDarkMode';
import useUrlQuery from 'hooks/useUrlQuery';
import { generateColor } from 'lib/uPlotLib/utils/generateColor';
import { useEffect, useMemo, useState } from 'react';
@@ -48,7 +47,6 @@ function TraceFlamegraph(props: ITraceFlamegraphProps): JSX.Element {
traceId,
selectedSpanId: firstSpanAtFetchLevel,
});
const isDarkMode = useIsDarkMode();
// get the current state of trace flamegraph based on the API lifecycle
const traceFlamegraphState = useMemo(() => {
@@ -132,36 +130,40 @@ function TraceFlamegraph(props: ITraceFlamegraphProps): JSX.Element {
>
<div className="exec-time-service">% exec time</div>
<div className="stats">
{Object.keys(serviceExecTime).map((service) => {
const spread = endTime - startTime;
const value = (serviceExecTime[service] * 100) / spread;
const color = generateColor(
service,
isDarkMode ? themeColors.chartcolors : themeColors.lightModeColor,
);
return (
<div key={service} className="value-row">
<section className="service-name">
<div className="square-box" style={{ backgroundColor: color }} />
<Tooltip title={service}>
<Typography.Text className="service-text" ellipsis>
{service}
{Object.keys(serviceExecTime)
.sort((a, b) => {
const spread = endTime - startTime;
const aValue = (serviceExecTime[a] * 100) / spread;
const bValue = (serviceExecTime[b] * 100) / spread;
return bValue - aValue;
})
.map((service) => {
const spread = endTime - startTime;
const value = (serviceExecTime[service] * 100) / spread;
const color = generateColor(service, themeColors.traceDetailColors);
return (
<div key={service} className="value-row">
<section className="service-name">
<div className="square-box" style={{ backgroundColor: color }} />
<Tooltip title={service}>
<Typography.Text className="service-text" ellipsis>
{service}
</Typography.Text>
</Tooltip>
</section>
<section className="progress-service">
<Progress
percent={parseFloat(value.toFixed(2))}
className="service-progress-indicator"
showInfo={false}
/>
<Typography.Text className="percent-value">
{parseFloat(value.toFixed(2))}%
</Typography.Text>
</Tooltip>
</section>
<section className="progress-service">
<Progress
percent={parseFloat(value.toFixed(2))}
className="service-progress-indicator"
showInfo={false}
/>
<Typography.Text className="percent-value">
{parseFloat(value.toFixed(2))}%
</Typography.Text>
</section>
</div>
);
})}
</section>
</div>
);
})}
</div>
</div>
<div

View File

@@ -57,7 +57,7 @@ function ResourceAttributesFilter(): JSX.Element | null {
query={query}
onChange={handleChangeTagFilters}
operatorConfigKey={OperatorConfigKeys.EXCEPTIONS}
hideSpanScopeSelector={false}
hideSpanScopeSelector
/>
</div>
);

View File

@@ -1,5 +1,6 @@
import { Button, Popover, Spin, Tooltip } from 'antd';
import GroupByIcon from 'assets/CustomIcons/GroupByIcon';
import cx from 'classnames';
import { OPERATORS } from 'constants/antlrQueryConstants';
import { useTraceActions } from 'hooks/trace/useTraceActions';
import {
@@ -124,7 +125,7 @@ export default function AttributeActions({
);
return (
<div className="action-btn">
<div className={cx('action-btn', { 'action-btn--is-open': isOpen })}>
<Tooltip title={isPinned ? 'Unpin attribute' : 'Pin attribute'}>
<Button
className={`filter-btn periscope-btn ${isPinned ? 'pinned' : ''}`}

View File

@@ -17,7 +17,7 @@
display: flex;
flex-direction: column;
gap: 12px;
padding: 12px;
padding-block: 12px;
.item {
display: flex;
@@ -25,8 +25,10 @@
gap: 8px;
justify-content: flex-start;
position: relative;
padding: 2px 12px;
&:hover {
background-color: var(--bg-slate-500);
.action-btn {
display: flex;
}
@@ -81,22 +83,23 @@
.action-btn {
display: none;
&--is-open {
display: flex;
}
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
gap: 4px;
background: rgba(0, 0, 0, 0.8);
border-radius: 4px;
padding: 2px;
.filter-btn {
display: flex;
align-items: center;
border: none;
border-color: var(--bg-slate-400);
box-shadow: none;
border-radius: 2px;
background: var(--bg-slate-400);
background: var(--bg-slate-500);
padding: 4px;
gap: 3px;
height: 24px;
@@ -129,7 +132,7 @@
gap: 8px;
&:hover {
background-color: var(--bg-slate-400);
background-color: var(--bg-slate-400) !important;
}
}
@@ -142,6 +145,7 @@
.ant-popover-inner {
padding: 8px;
min-width: 160px;
background: var(--bg-slate-500);
}
}
@@ -149,6 +153,9 @@
.attributes-corner {
.attributes-container {
.item {
&:hover {
background-color: var(--bg-vanilla-300);
}
.item-key {
color: var(--bg-ink-100);
}
@@ -163,8 +170,6 @@
}
.action-btn {
background: rgba(255, 255, 255, 0.9);
.filter-btn {
background: var(--bg-vanilla-200);

View File

@@ -1,6 +1,6 @@
import './Attributes.styles.scss';
import { Input, Tooltip, Typography } from 'antd';
import { Input, Typography } from 'antd';
import cx from 'classnames';
import CopyClipboardHOC from 'components/Logs/CopyClipboardHOC';
import { flattenObject } from 'container/LogDetailedView/utils';
@@ -82,37 +82,41 @@ function Attributes(props: IAttributesProps): JSX.Element {
<section
className={cx('attributes-container', isSearchVisible ? 'border-top' : '')}
>
{datasource.map((item) => (
<div
className={cx('item', { pinned: pinnedAttributes[item.field] })}
key={`${item.field} + ${item.value}`}
>
<div className="item-key-wrapper">
<Typography.Text className="item-key" ellipsis>
{item.field}
</Typography.Text>
{pinnedAttributes[item.field] && (
<Pin size={14} className="pin-icon" fill="currentColor" />
)}
</div>
<div className="value-wrapper">
<Tooltip title={item.value}>
{datasource
.filter((item) => !!item.value)
.map((item) => (
<div
className={cx('item', { pinned: pinnedAttributes[item.field] })}
key={`${item.field} + ${item.value}`}
>
<div className="item-key-wrapper">
<Typography.Text className="item-key" ellipsis>
{item.field}
</Typography.Text>
{pinnedAttributes[item.field] && (
<Pin size={14} className="pin-icon" fill="currentColor" />
)}
</div>
<div className="value-wrapper">
<div className="copy-wrapper">
<CopyClipboardHOC entityKey={item.value} textToCopy={item.value}>
<CopyClipboardHOC
entityKey={item.value}
textToCopy={item.value}
tooltipText={item.value}
>
<Typography.Text className="item-value" ellipsis>
{item.value}
</Typography.Text>
</CopyClipboardHOC>
</div>
</Tooltip>
<AttributeActions
record={item}
isPinned={pinnedAttributes[item.field]}
onTogglePin={togglePin}
/>
<AttributeActions
record={item}
isPinned={pinnedAttributes[item.field]}
onTogglePin={togglePin}
/>
</div>
</div>
</div>
))}
))}
</section>
</div>
);

View File

@@ -219,6 +219,12 @@
}
.lightMode {
.ant-tabs-content-holder {
.bg-border {
background: var(--bg-vanilla-300);
border-color: var(--bg-vanilla-300);
}
}
.span-details-drawer {
border-left: 1px solid var(--bg-vanilla-300);

View File

@@ -24,7 +24,6 @@ import { spanServiceNameToColorMapping } from 'lib/getRandomColor';
import history from 'lib/history';
import { map } from 'lodash-es';
import { PanelRight } from 'lucide-react';
import { SPAN_DETAILS_LEFT_COL_WIDTH } from 'pages/TraceDetail/constants';
import { useTimezone } from 'providers/Timezone';
import { useEffect, useMemo, useState } from 'react';
import { ITraceForest, PayloadProps } from 'types/api/trace/getTraceItem';
@@ -42,6 +41,7 @@ import {
getTreeLevelsCount,
IIntervalUnit,
INTERVAL_UNITS,
SPAN_DETAILS_LEFT_COL_WIDTH,
} from './utils';
const { Sider } = Layout;

View File

@@ -13,6 +13,8 @@ export const filterSpansByString = (
return JSON.stringify(spanWithoutChildren).includes(searchString);
});
export const SPAN_DETAILS_LEFT_COL_WIDTH = 350;
type TTimeUnitName = 'ms' | 's' | 'm' | 'hr' | 'day' | 'week';
export interface IIntervalUnit {

View File

@@ -1,7 +1,6 @@
import { Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import ErrorInPlace from 'components/ErrorInPlace/ErrorInPlace';
import ListViewOrderBy from 'components/OrderBy/ListViewOrderBy';
import { ResizeTable } from 'components/ResizeTable';
import { ENTITY_VERSION_V5 } from 'constants/app';
import { QueryParams } from 'constants/query';
@@ -13,16 +12,7 @@ import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { Pagination } from 'hooks/queryPagination';
import useUrlQueryData from 'hooks/useUrlQueryData';
import { ArrowUp10, Minus } from 'lucide-react';
import {
Dispatch,
memo,
SetStateAction,
useCallback,
useEffect,
useMemo,
useState,
} from 'react';
import { Dispatch, memo, SetStateAction, useEffect, useMemo } from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { Warning } from 'types/api';
@@ -30,7 +20,6 @@ import APIError from 'types/api/error';
import { DataSource } from 'types/common/queryBuilder';
import { GlobalReducer } from 'types/reducer/globalTime';
import DOCLINKS from 'utils/docLinks';
import { transformBuilderQueryFields } from 'utils/queryTransformers';
import TraceExplorerControls from '../Controls';
import { TracesLoading } from '../TraceLoading/TraceLoading';
@@ -43,13 +32,13 @@ interface TracesViewProps {
setIsLoadingQueries: Dispatch<SetStateAction<boolean>>;
}
// eslint-disable-next-line sonarjs/cognitive-complexity
function TracesView({
isFilterApplied,
setWarning,
setIsLoadingQueries,
}: TracesViewProps): JSX.Element {
const { stagedQuery, panelType } = useQueryBuilder();
const [orderBy, setOrderBy] = useState<string>('timestamp:desc');
const { selectedTime: globalSelectedTime, maxTime, minTime } = useSelector<
AppState,
@@ -60,26 +49,9 @@ function TracesView({
QueryParams.pagination,
);
const transformedQuery = useMemo(
() =>
transformBuilderQueryFields(stagedQuery || initialQueriesMap.traces, {
orderBy: [
{
columnName: orderBy.split(':')[0],
order: orderBy.split(':')[1] as 'asc' | 'desc',
},
],
}),
[stagedQuery, orderBy],
);
const handleOrderChange = useCallback((value: string) => {
setOrderBy(value);
}, []);
const { data, isLoading, isFetching, isError, error } = useGetQueryRange(
{
query: transformedQuery,
query: stagedQuery || initialQueriesMap.traces,
graphType: panelType || PANEL_TYPES.TRACE,
selectedTime: 'GLOBAL_TIME',
globalSelectedInterval: globalSelectedTime,
@@ -100,7 +72,6 @@ function TracesView({
stagedQuery,
panelType,
paginationQueryData,
orderBy,
],
enabled: !!stagedQuery && panelType === PANEL_TYPES.TRACE,
},
@@ -148,18 +119,6 @@ function TracesView({
</Typography>
<div className="trace-explorer-controls">
<div className="order-by-container">
<div className="order-by-label">
Order by <Minus size={14} /> <ArrowUp10 size={14} />
</div>
<ListViewOrderBy
value={orderBy}
onChange={handleOrderChange}
dataSource={DataSource.TRACES}
/>
</div>
<TraceExplorerControls
isLoading={isLoading}
totalCount={responseData?.length || 0}

View File

@@ -1,45 +0,0 @@
.old-trace-container {
display: flex;
flex-direction: column;
height: 100%;
.top-header {
display: flex;
flex-direction: row-reverse;
padding: 5px;
border-bottom: 1px solid var(--bg-slate-400);
.new-cta-btn {
display: flex;
padding: 4px 6px;
align-items: center;
gap: 8px;
color: var(--Vanilla-400, #c0c1c3);
/* Bifrost (Ancient)/Content/sm */
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
box-shadow: none;
.ant-btn-icon {
margin-inline-end: 0px;
}
}
}
}
.lightMode {
.old-trace-container {
.top-header {
border-bottom: 1px solid var(--bg-vanilla-300);
.new-cta-btn {
color: var(--bg-ink-400);
}
}
}
}

View File

@@ -1,231 +0,0 @@
/* eslint-disable sonarjs/no-duplicate-string */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import ROUTES from 'constants/routes';
import { MemoryRouter, Route } from 'react-router-dom';
import { fireEvent, render, screen } from 'tests/test-utils';
import TraceDetail from '..';
window.HTMLElement.prototype.scrollIntoView = jest.fn();
jest.mock('@signozhq/badge', () => ({
Badge: jest.fn(),
}));
jest.mock('@signozhq/resizable', () => ({
ResizableHandle: jest.fn(),
ResizablePanel: jest.fn(),
ResizablePanelGroup: jest.fn(),
}));
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useLocation: (): { pathname: string; search: string } => ({
pathname: `${process.env.FRONTEND_API_ENDPOINT}${ROUTES.TRACE_DETAIL}`,
search: '?spanId=28a8a67365d0bd8b&levelUp=0&levelDown=0',
}),
useParams: jest.fn().mockReturnValue({
id: '000000000000000071dc9b0a338729b4',
}),
}));
jest.mock('container/TraceFlameGraph/index.tsx', () => ({
__esModule: true,
default: (): JSX.Element => <div>TraceFlameGraph</div>,
}));
describe('TraceDetail', () => {
beforeEach(() => {
jest.useFakeTimers();
jest.setSystemTime(new Date('2023-10-20'));
});
afterEach(() => {
jest.useRealTimers();
});
it('should render tracedetail', async () => {
const { findByText, getByText, getAllByText, getByPlaceholderText } = render(
<MemoryRouter initialEntries={['/trace/000000000000000071dc9b0a338729b4']}>
<Route path={ROUTES.TRACE_DETAIL}>
<TraceDetail />
</Route>
,
</MemoryRouter>,
);
expect(await findByText('Trace Details')).toBeInTheDocument();
// as we have an active spanId, it should scroll to the selected span
expect(window.HTMLElement.prototype.scrollIntoView).toHaveBeenCalled();
// assertions
expect(getByText('TraceFlameGraph')).toBeInTheDocument();
expect(getByText('Focus on selected span')).toBeInTheDocument();
// span action buttons
expect(getByText('Reset Focus')).toBeInTheDocument();
expect(getByText('50 Spans')).toBeInTheDocument();
// trace span detail - parent -> child
expect(getAllByText('frontend')[0]).toBeInTheDocument();
expect(getByText('776.76 ms')).toBeInTheDocument();
[
{ trace: 'HTTP GET /dispatch', duration: '776.76 ms', count: '50' },
{ trace: 'HTTP GET: /customer', duration: '349.44 ms', count: '4' },
{
trace: '/driver.DriverService/FindNearest',
duration: '173.10 ms',
count: '15',
},
// and so on ...
].forEach((traceDetail) => {
expect(getByText(traceDetail.trace)).toBeInTheDocument();
expect(getByText(traceDetail.duration)).toBeInTheDocument();
expect(getByText(traceDetail.count)).toBeInTheDocument();
});
// Details for selected Span
expect(getByText('Details for selected Span')).toBeInTheDocument();
['Service', 'Operation', 'SpanKind', 'StatusCodeString'].forEach((detail) => {
expect(getByText(detail)).toBeInTheDocument();
});
// go to related logs button
const goToRelatedLogsButton = getByText('Go to Related logs');
expect(goToRelatedLogsButton).toBeInTheDocument();
// Tag and Event tabs
expect(getByText('Tags')).toBeInTheDocument();
expect(getByText('Events')).toBeInTheDocument();
expect(getByPlaceholderText('traceDetails:search_tags')).toBeInTheDocument();
// Tag details
[
{ title: 'client-uuid', value: '64a18ffd5f8adbfb' },
{ title: 'component', value: 'net/http' },
{ title: 'host.name', value: '4f6ec470feea' },
{ title: 'http.method', value: 'GET' },
{ title: 'http.url', value: '/route?dropoff=728%2C326&pickup=165%2C543' },
{ title: 'http.status_code', value: '200' },
{ title: 'ip', value: '172.25.0.2' },
{ title: 'opencensus.exporterversion', value: 'Jaeger-Go-2.30.0' },
].forEach((tag) => {
expect(getByText(tag.title)).toBeInTheDocument();
expect(getByText(tag.value)).toBeInTheDocument();
});
// see full value
expect(getAllByText('View full value')[0]).toBeInTheDocument();
});
it('should render tracedetail events tab', async () => {
const { findByText, getByText } = render(
<MemoryRouter initialEntries={['/trace/000000000000000071dc9b0a338729b4']}>
<Route path={ROUTES.TRACE_DETAIL}>
<TraceDetail />
</Route>
,
</MemoryRouter>,
);
expect(await findByText('Trace Details')).toBeInTheDocument();
fireEvent.click(getByText('Events'));
expect(await screen.findByText('HTTP request received')).toBeInTheDocument();
// event details
[
{ title: 'Event Start Time', value: '527.60 ms' },
{ title: 'level', value: 'info' },
].forEach((tag) => {
expect(getByText(tag.title)).toBeInTheDocument();
expect(getByText(tag.value)).toBeInTheDocument();
});
expect(getByText('View full log event message')).toBeInTheDocument();
});
it('should toggle slider - selected span details', async () => {
const { findByTestId, queryByText } = render(
<MemoryRouter initialEntries={['/trace/000000000000000071dc9b0a338729b4']}>
<Route path={ROUTES.TRACE_DETAIL}>
<TraceDetail />
</Route>
,
</MemoryRouter>,
);
const slider = await findByTestId('span-details-sider');
expect(slider).toBeInTheDocument();
fireEvent.click(slider.querySelector('.expand-collapse-btn') as HTMLElement);
expect(queryByText('Details for selected Span')).not.toBeInTheDocument();
});
it('should be able to selected another span and see its detail', async () => {
const { getByText } = render(
<MemoryRouter initialEntries={['/trace/000000000000000071dc9b0a338729b4']}>
<Route path={ROUTES.TRACE_DETAIL}>
<TraceDetail />
</Route>
,
</MemoryRouter>,
);
expect(await screen.findByText('Trace Details')).toBeInTheDocument();
const spanTitle = getByText('/driver.DriverService/FindNearest');
expect(spanTitle).toBeInTheDocument();
fireEvent.click(spanTitle);
// Tag details
[
{ title: 'client-uuid', value: '6fb81b8ca91b2b4d' },
{ title: 'component', value: 'gRPC' },
{ title: 'host.name', value: '4f6ec470feea' },
].forEach((tag) => {
expect(getByText(tag.title)).toBeInTheDocument();
expect(getByText(tag.value)).toBeInTheDocument();
});
});
it('focus on selected span and reset focus action', async () => {
const { getByText, getAllByText } = render(
<MemoryRouter initialEntries={['/trace/000000000000000071dc9b0a338729b4']}>
<Route path={ROUTES.TRACE_DETAIL}>
<TraceDetail />
</Route>
,
</MemoryRouter>,
);
expect(await screen.findByText('Trace Details')).toBeInTheDocument();
const spanTitle = getByText('/driver.DriverService/FindNearest');
expect(spanTitle).toBeInTheDocument();
fireEvent.click(spanTitle);
expect(await screen.findByText('6fb81b8ca91b2b4d')).toBeInTheDocument();
// focus on selected span
const focusButton = getByText('Focus on selected span');
expect(focusButton).toBeInTheDocument();
fireEvent.click(focusButton);
// assert selected span
expect(getByText('15 Spans')).toBeInTheDocument();
expect(getAllByText('/driver.DriverService/FindNearest')).toHaveLength(3);
expect(getByText('173.10 ms')).toBeInTheDocument();
// reset focus
expect(screen.queryByText('HTTP GET /dispatch')).not.toBeInTheDocument();
const resetFocusButton = getByText('Reset Focus');
expect(resetFocusButton).toBeInTheDocument();
fireEvent.click(resetFocusButton);
expect(window.HTMLElement.prototype.scrollIntoView).toHaveBeenCalled();
expect(screen.queryByText('HTTP GET /dispatch')).toBeInTheDocument();
});
});

View File

@@ -1,4 +0,0 @@
export const SPAN_DETAILS_LEFT_COL_WIDTH = 350;
export const noEventMessage =
'The requested trace id was not found. Sometimes this happens because of insertion delay in trace data. Please try again after some time';

View File

@@ -1,76 +0,0 @@
import './TraceDetail.styles.scss';
import { Button, Typography } from 'antd';
import getTraceItem from 'api/trace/getTraceItem';
import NotFound from 'components/NotFound';
import Spinner from 'components/Spinner';
import TraceDetailContainer from 'container/TraceDetail';
import useUrlQuery from 'hooks/useUrlQuery';
import { Undo } from 'lucide-react';
import TraceDetailsPage from 'pages/TraceDetailV2';
import { useMemo, useState } from 'react';
import { useQuery } from 'react-query';
import { useParams } from 'react-router-dom';
import { Props as TraceDetailProps } from 'types/api/trace/getTraceItem';
import { noEventMessage } from './constants';
function TraceDetail(): JSX.Element {
const { id } = useParams<TraceDetailProps>();
const [showNewTraceDetails, setShowNewTraceDetails] = useState<boolean>(false);
const urlQuery = useUrlQuery();
const { spanId, levelUp, levelDown } = useMemo(
() => ({
spanId: urlQuery.get('spanId'),
levelUp: urlQuery.get('levelUp'),
levelDown: urlQuery.get('levelDown'),
}),
[urlQuery],
);
const { data: traceDetailResponse, error, isLoading, isError } = useQuery(
`getTraceItem/${id}`,
() => getTraceItem({ id, spanId, levelUp, levelDown }),
{
cacheTime: 3000,
},
);
if (showNewTraceDetails) {
return <TraceDetailsPage />;
}
if (traceDetailResponse?.error || error || isError) {
return (
<Typography>
{traceDetailResponse?.error || 'Something went wrong'}
</Typography>
);
}
if (isLoading || !(traceDetailResponse && traceDetailResponse.payload)) {
return <Spinner tip="Loading.." />;
}
if (traceDetailResponse.payload[0].events.length === 0) {
return <NotFound text={noEventMessage} />;
}
return (
<div className="old-trace-container">
<div className="top-header">
<Button
onClick={(): void => setShowNewTraceDetails(true)}
icon={<Undo size={14} />}
type="text"
className="new-cta-btn"
>
New Trace Detail
</Button>
</div>
<TraceDetailContainer response={traceDetailResponse.payload} />;
</div>
);
}
export default TraceDetail;

View File

@@ -1,12 +1,10 @@
import './TraceDetailV2.styles.scss';
import { Button, Tabs } from 'antd';
import { Tabs } from 'antd';
import logEvent from 'api/common/logEvent';
import ROUTES from 'constants/routes';
import history from 'lib/history';
import { Compass, Cone, TowerControl, Undo } from 'lucide-react';
import TraceDetail from 'pages/TraceDetail';
import { useCallback, useState } from 'react';
import { Compass, Cone, TowerControl } from 'lucide-react';
import TraceDetailsV2 from './TraceDetailV2';
@@ -16,11 +14,10 @@ interface INewTraceDetailProps {
key: string;
children: JSX.Element;
}[];
handleOldTraceDetails: () => void;
}
function NewTraceDetail(props: INewTraceDetailProps): JSX.Element {
const { items, handleOldTraceDetails } = props;
const { items } = props;
return (
<div className="traces-module-container">
<Tabs
@@ -39,24 +36,12 @@ function NewTraceDetail(props: INewTraceDetailProps): JSX.Element {
history.push(ROUTES.TRACES_FUNNELS);
}
}}
tabBarExtraContent={
<Button
type="text"
onClick={handleOldTraceDetails}
className="old-switch"
icon={<Undo size={14} />}
>
Old Trace Details
</Button>
}
/>
</div>
);
}
export default function TraceDetailsPage(): JSX.Element {
const [showOldTraceDetails, setShowOldTraceDetails] = useState<boolean>(false);
const items = [
{
label: (
@@ -86,13 +71,6 @@ export default function TraceDetailsPage(): JSX.Element {
children: <div />,
},
];
const handleOldTraceDetails = useCallback(() => {
setShowOldTraceDetails(true);
}, []);
return showOldTraceDetails ? (
<TraceDetail />
) : (
<NewTraceDetail items={items} handleOldTraceDetails={handleOldTraceDetails} />
);
return <NewTraceDetail items={items} />;
}

View File

@@ -15,7 +15,7 @@ import { FilterSelect } from 'components/CeleryOverview/CeleryOverviewConfigOpti
import { QueryParams } from 'constants/query';
import { initialQueriesMap } from 'constants/queryBuilder';
import QueryBuilderSearchV2 from 'container/QueryBuilder/filters/QueryBuilderSearchV2/QueryBuilderSearchV2';
import { ChevronDown, HardHat, PencilLine } from 'lucide-react';
import { ChevronDown, PencilLine } from 'lucide-react';
import { LatencyPointers } from 'pages/TracesFunnelDetails/constants';
import { useFunnelContext } from 'pages/TracesFunnels/FunnelContext';
import { useAppContext } from 'providers/App/App';
@@ -194,7 +194,6 @@ function FunnelStep({
}
hasPopupContainer={false}
placeholder="Search for filters..."
suffixIcon={<HardHat size={12} color="var(--bg-vanilla-400)" />}
rootClassName="traces-funnel-where-filter"
/>
</Form.Item>

View File

@@ -1,12 +1,18 @@
:root {
--bg-vanilla-100-rgb: 255, 255, 255;
}
.funnel-table {
border-radius: 3px;
border: 1px solid var(--bg-slate-500);
background: linear-gradient(
0deg,
rgba(171, 189, 255, 0.01) 0%,
rgba(171, 189, 255, 0.01) 100%
),
#0b0c0e;
table {
background: linear-gradient(
0deg,
rgba(171, 189, 255, 0.01) 0%,
rgba(171, 189, 255, 0.01) 100%
),
#0b0c0e;
}
&__header {
padding: 12px 14px 12px;
@@ -97,9 +103,10 @@
}
.table-row-dark {
background: var(--bg-ink-300);
background: rgba(var(--bg-vanilla-100-rgb), 0.01);
}
.trace-id-cell {
color: var(--bg-robin-400);
cursor: pointer;

View File

@@ -160,6 +160,8 @@
min-width: 0;
.ant-select {
font-family: 'Space Mono', monospace !important;
border: none;
height: 36px;
}
@@ -167,6 +169,10 @@
.ant-select-selector {
border: none;
}
.ant-select-selection-placeholder {
color: #888;
}
}
.close-btn {