mirror of
https://github.com/SigNoz/signoz.git
synced 2026-05-28 12:50:32 +01:00
Compare commits
12 Commits
chore/drop
...
refactor/i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4369e09b5a | ||
|
|
31deca945f | ||
|
|
939f0d7a05 | ||
|
|
9d1c27cb57 | ||
|
|
07ce2d341c | ||
|
|
9c3c21aec1 | ||
|
|
c6db1000d5 | ||
|
|
1ddb6f8647 | ||
|
|
9949a72799 | ||
|
|
02d60e78be | ||
|
|
dd72eaac73 | ||
|
|
e13014baba |
@@ -41,14 +41,22 @@ $item-spacing: 8px;
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
height: auto;
|
||||
color: var(--l1-foreground);
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.07px;
|
||||
padding: 0;
|
||||
&.ant-input:focus {
|
||||
|
||||
&:focus,
|
||||
&:focus-visible,
|
||||
&:hover {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
useState,
|
||||
} from 'react';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Input } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import cx from 'classnames';
|
||||
import { TimezonePickerShortcuts } from 'constants/shortcuts/TimezonePickerShortcuts';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Card, Form, Input } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Card, Form } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { Button, Input } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import cx from 'classnames';
|
||||
import { X } from '@signozhq/icons';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { Button, Input, InputNumber, Popover, Tooltip } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, InputNumber, Popover, Tooltip } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import type { DefaultOptionType } from 'antd/es/select';
|
||||
import cx from 'classnames';
|
||||
|
||||
@@ -259,6 +259,14 @@
|
||||
border-left: transparent;
|
||||
border-top-left-radius: 0px;
|
||||
border-bottom-left-radius: 0px;
|
||||
|
||||
&:focus:not(:focus-visible),
|
||||
&.ant-btn:focus:not(:focus-visible) {
|
||||
border-color: var(--l2-border);
|
||||
border-left-color: transparent;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -284,5 +292,21 @@
|
||||
.cm-placeholder {
|
||||
font-size: 12px !important;
|
||||
}
|
||||
|
||||
$add-on-row-height: 38px;
|
||||
|
||||
.periscope-input-with-label {
|
||||
.input {
|
||||
.ant-select {
|
||||
height: $add-on-row-height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.input-with-label {
|
||||
.input {
|
||||
height: $add-on-row-height;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,23 @@
|
||||
padding: 12px;
|
||||
gap: 12px;
|
||||
border-bottom: 1px solid var(--l1-border);
|
||||
|
||||
.search {
|
||||
input {
|
||||
--input-background: var(--l2-background);
|
||||
--input-hover-background: var(--l2-background);
|
||||
--input-focus-background: var(--l2-background);
|
||||
&::placeholder {
|
||||
color: var(--l3-foreground);
|
||||
}
|
||||
--input-font-size: 14px;
|
||||
--input-border-color: var(--l1-border);
|
||||
--input-focus-border-color: var(--primary-background);
|
||||
--input-focus-outline-width: 0;
|
||||
--input-focus-outline-offset: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-header-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* eslint-disable sonarjs/no-identical-functions */
|
||||
import { Fragment, useMemo, useState } from 'react';
|
||||
import { Button, Checkbox, Input, Skeleton } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Checkbox, Skeleton } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import cx from 'classnames';
|
||||
import { removeKeysFromExpression } from 'components/QueryBuilderV2/utils';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useMemo } from 'react';
|
||||
import { Button, Input } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button } from 'antd';
|
||||
import { Check, TableColumnsSplit, X } from '@signozhq/icons';
|
||||
import { Filter as FilterType } from 'types/api/quickFilters/getCustomFilters';
|
||||
|
||||
|
||||
@@ -21,14 +21,17 @@ import { useResizeObserver } from 'hooks/useDimensions';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
||||
import { LegendPosition } from 'lib/uPlotV2/components/types';
|
||||
import { getStartAndEndTimesInMilliseconds } from 'pages/MessagingQueues/MessagingQueuesUtils';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import ErrorState from './ErrorState';
|
||||
import { prepareStatusCodeBarChartsConfig } from './utils';
|
||||
import {
|
||||
getStepIntervalForQuery,
|
||||
getTracesTimeRangeFromStepInterval,
|
||||
prepareStatusCodeBarChartsConfig,
|
||||
} from './utils';
|
||||
|
||||
function StatusCodeBarCharts({
|
||||
endPointStatusCodeBarChartsDataQuery,
|
||||
@@ -135,6 +138,18 @@ function StatusCodeBarCharts({
|
||||
[domainName, filters],
|
||||
);
|
||||
|
||||
const activeApiResponse = useMemo(
|
||||
() =>
|
||||
currentWidgetInfoIndex === 0
|
||||
? formattedEndPointStatusCodeBarChartsDataPayload
|
||||
: formattedEndPointStatusCodeLatencyBarChartsDataPayload,
|
||||
[
|
||||
currentWidgetInfoIndex,
|
||||
formattedEndPointStatusCodeBarChartsDataPayload,
|
||||
formattedEndPointStatusCodeLatencyBarChartsDataPayload,
|
||||
],
|
||||
);
|
||||
|
||||
const graphClickHandler = useCallback(
|
||||
(
|
||||
xValue: number,
|
||||
@@ -144,11 +159,14 @@ function StatusCodeBarCharts({
|
||||
metric?: { [key: string]: string },
|
||||
queryData?: { queryName: string; inFocusOrNot: boolean },
|
||||
): void => {
|
||||
const TWO_AND_HALF_MINUTES_IN_MILLISECONDS = 2.5 * 60 * 1000; // 150,000 milliseconds
|
||||
const customFilters = getCustomFiltersForBarChart(metric);
|
||||
const { start, end } = getStartAndEndTimesInMilliseconds(
|
||||
const stepInterval = getStepIntervalForQuery(
|
||||
activeApiResponse,
|
||||
queryData?.queryName,
|
||||
);
|
||||
const { start, end } = getTracesTimeRangeFromStepInterval(
|
||||
xValue,
|
||||
TWO_AND_HALF_MINUTES_IN_MILLISECONDS,
|
||||
stepInterval,
|
||||
);
|
||||
|
||||
handleGraphClick({
|
||||
@@ -171,6 +189,7 @@ function StatusCodeBarCharts({
|
||||
});
|
||||
},
|
||||
[
|
||||
activeApiResponse,
|
||||
widget,
|
||||
navigateToExplorerPages,
|
||||
navigateToExplorer,
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import {
|
||||
getMinStepIntervalFromApiResponse,
|
||||
getStepIntervalForQuery,
|
||||
getTracesTimeRangeFromStepInterval,
|
||||
} from '../utils';
|
||||
|
||||
describe('StatusCodeBarCharts utils', () => {
|
||||
describe('getTracesTimeRangeFromStepInterval', () => {
|
||||
const xValue = 1609459200; // seconds
|
||||
|
||||
it('keeps start at click time with a minimum 5 minute end range', () => {
|
||||
const { start, end } = getTracesTimeRangeFromStepInterval(xValue, 60);
|
||||
|
||||
expect(start).toBe(xValue * 1000);
|
||||
expect(end - start).toBe(5 * 60 * 1000);
|
||||
expect(end).toBe(xValue * 1000 + 5 * 60 * 1000);
|
||||
});
|
||||
|
||||
it('extends end when step interval is larger than 5 minutes', () => {
|
||||
const stepInterval = 600; // 10 minutes
|
||||
const { start, end } = getTracesTimeRangeFromStepInterval(
|
||||
xValue,
|
||||
stepInterval,
|
||||
);
|
||||
|
||||
expect(start).toBe(xValue * 1000);
|
||||
expect(end - start).toBe(10 * 60 * 1000);
|
||||
expect(end).toBe(xValue * 1000 + 10 * 60 * 1000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMinStepIntervalFromApiResponse', () => {
|
||||
it('returns 60 when step intervals are missing', () => {
|
||||
expect(getMinStepIntervalFromApiResponse({} as any)).toBe(60);
|
||||
});
|
||||
|
||||
it('returns the minimum step interval from the response', () => {
|
||||
const apiResponse = {
|
||||
data: {
|
||||
newResult: {
|
||||
meta: {
|
||||
stepIntervals: { A: 120, B: 60 },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(getMinStepIntervalFromApiResponse(apiResponse as any)).toBe(60);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getStepIntervalForQuery', () => {
|
||||
it('returns query-specific step interval when available', () => {
|
||||
const apiResponse = {
|
||||
data: {
|
||||
newResult: {
|
||||
meta: {
|
||||
stepIntervals: { A: 120, B: 60 },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(getStepIntervalForQuery(apiResponse as any, 'A')).toBe(120);
|
||||
expect(getStepIntervalForQuery(apiResponse as any, 'B')).toBe(60);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -13,6 +13,65 @@ import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { QueryData } from 'types/api/widgets/getQuery';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
const DEFAULT_STEP_INTERVAL_SECONDS = 60;
|
||||
const MIN_TRACES_TIME_RANGE_MINUTES = 5;
|
||||
|
||||
export function getMinStepIntervalFromApiResponse(
|
||||
apiResponse: MetricRangePayloadProps,
|
||||
): number {
|
||||
const stepIntervals: ExecStats['stepIntervals'] = get(
|
||||
apiResponse,
|
||||
'data.newResult.meta.stepIntervals',
|
||||
{},
|
||||
);
|
||||
const values = Object.values(stepIntervals).filter(
|
||||
(value): value is number =>
|
||||
typeof value === 'number' && Number.isFinite(value),
|
||||
);
|
||||
|
||||
if (values.length === 0) {
|
||||
return DEFAULT_STEP_INTERVAL_SECONDS;
|
||||
}
|
||||
|
||||
return Math.min(...values);
|
||||
}
|
||||
|
||||
export function getStepIntervalForQuery(
|
||||
apiResponse: MetricRangePayloadProps,
|
||||
queryName?: string,
|
||||
): number {
|
||||
const minStepInterval = getMinStepIntervalFromApiResponse(apiResponse);
|
||||
|
||||
if (!queryName) {
|
||||
return minStepInterval;
|
||||
}
|
||||
|
||||
const stepIntervals: ExecStats['stepIntervals'] = get(
|
||||
apiResponse,
|
||||
'data.newResult.meta.stepIntervals',
|
||||
{},
|
||||
);
|
||||
|
||||
return get(stepIntervals, queryName, minStepInterval) ?? minStepInterval;
|
||||
}
|
||||
|
||||
export function getTracesTimeRangeFromStepInterval(
|
||||
xValue: number,
|
||||
stepIntervalSeconds: number,
|
||||
): { start: number; end: number } {
|
||||
const rangeMinutes = Math.max(
|
||||
stepIntervalSeconds / 60,
|
||||
MIN_TRACES_TIME_RANGE_MINUTES,
|
||||
);
|
||||
const rangeMs = rangeMinutes * 60 * 1000;
|
||||
const start = Math.floor(xValue * 1000);
|
||||
|
||||
return {
|
||||
start,
|
||||
end: Math.ceil(start + rangeMs),
|
||||
};
|
||||
}
|
||||
|
||||
export const prepareStatusCodeBarChartsConfig = ({
|
||||
timezone,
|
||||
isDarkMode,
|
||||
@@ -41,7 +100,7 @@ export const prepareStatusCodeBarChartsConfig = ({
|
||||
'data.newResult.meta.stepIntervals',
|
||||
{},
|
||||
);
|
||||
const minStepInterval = Math.min(...Object.values(stepIntervals));
|
||||
const minStepInterval = getMinStepIntervalFromApiResponse(apiResponse);
|
||||
|
||||
const config = buildBaseConfig({
|
||||
id: v4(),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { Button, Input, Select, Tooltip } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Select, Tooltip } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { CircleX, Trash } from '@signozhq/icons';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Collapse, Input } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Collapse } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
|
||||
import { useCreateAlertState } from '../context';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useMemo } from 'react';
|
||||
import { Input, Select } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Select } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
|
||||
import { ADVANCED_OPTIONS_TIME_UNIT_OPTIONS } from '../../context/constants';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Input } from 'antd';
|
||||
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import './TimeInput.scss';
|
||||
|
||||
export interface TimeInputProps {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Input, Select } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Select } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
|
||||
import { useCreateAlertState } from '../context';
|
||||
|
||||
@@ -16,7 +16,8 @@ import {
|
||||
Plus,
|
||||
X,
|
||||
} from '@signozhq/icons';
|
||||
import { Button, Card, Input, Modal, Popover, Tag, Tooltip } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Card, Modal, Popover, Tag, Tooltip } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import ConfigureIcon from 'assets/Integrations/ConfigureIcon';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Button, Input } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button } from 'antd';
|
||||
import { PrecisionOption, PrecisionOptionsEnum } from 'components/Graph/types';
|
||||
import { ResizeTable } from 'components/ResizeTable';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
|
||||
@@ -19,11 +19,11 @@ import {
|
||||
Info,
|
||||
} from '@signozhq/icons';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import {
|
||||
Button,
|
||||
ColorPicker,
|
||||
Divider,
|
||||
Input,
|
||||
Modal,
|
||||
RefSelectProps,
|
||||
Select,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Form, Input } from 'antd';
|
||||
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Form } from 'antd';
|
||||
import { EmailChannel } from '../../CreateAlertChannels/config';
|
||||
|
||||
function EmailForm({ setSelectedConfig }: EmailFormProps): JSX.Element {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Form, Input } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Form } from 'antd';
|
||||
import { MarkdownRenderer } from 'components/MarkdownRenderer/MarkdownRenderer';
|
||||
|
||||
import { WebhookChannel } from '../../CreateAlertChannels/config';
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Dispatch, ReactElement, SetStateAction } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Form, FormInstance, Input, Select } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Switch } from '@signozhq/ui/switch';
|
||||
import { Form, FormInstance, Select } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import type { Store } from 'antd/lib/form/interface';
|
||||
import ROUTES from 'constants/routes';
|
||||
|
||||
@@ -6,7 +6,8 @@ import { useIsFetching } from 'react-query';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button, Form, Input, Modal } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Form, Modal } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import cx from 'classnames';
|
||||
|
||||
@@ -5,12 +5,12 @@ import { useCopyToClipboard } from 'react-use';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Badge } from '@signozhq/ui/badge';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import {
|
||||
Col,
|
||||
Collapse,
|
||||
DatePicker,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
Modal,
|
||||
Row,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Form, Input } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Form } from 'antd';
|
||||
import { CloudintegrationtypesCredentialsDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
|
||||
function RenderConnectionFields({
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, Form, Input } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Form } from 'antd';
|
||||
import apply from 'api/v3/licenses/post';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||
import { ChangeEvent, useState } from 'react';
|
||||
import { Button, Input, Modal } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Modal } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import ApacheIcon from 'assets/CustomIcons/ApacheIcon';
|
||||
import DockerIcon from 'assets/CustomIcons/DockerIcon';
|
||||
|
||||
@@ -12,11 +12,11 @@ import { useTranslation } from 'react-i18next';
|
||||
import { generatePath } from 'react-router-dom';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import {
|
||||
Button,
|
||||
Dropdown,
|
||||
Flex,
|
||||
Input,
|
||||
MenuProps,
|
||||
Modal,
|
||||
Popover,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { LoaderCircle, Check } from '@signozhq/icons';
|
||||
import { Button, Input, Space } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Space } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
|
||||
@@ -2,8 +2,9 @@ import { ReactNode, useState } from 'react';
|
||||
import MEditor, { EditorProps, Monaco } from '@monaco-editor/react';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Switch } from '@signozhq/ui/switch';
|
||||
import { Collapse, Divider, Input, Tag } from 'antd';
|
||||
import { Collapse, Divider, Tag } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { AddToQueryHOCProps } from 'components/Logs/AddToQueryHOC';
|
||||
import { ChangeViewFunctionType } from 'container/ExplorerOptions/types';
|
||||
|
||||
@@ -2,7 +2,8 @@ import { ChangeEvent, useCallback, useState } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { CirclePlus, X } from '@signozhq/icons';
|
||||
import { Col, Input } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Col } from 'antd';
|
||||
import CategoryHeading from 'components/Logs/CategoryHeading';
|
||||
import { fieldSearchFilter } from 'lib/logs/fieldSearch';
|
||||
import { AppState } from 'store/reducers';
|
||||
|
||||
@@ -2,7 +2,8 @@ import { useCallback, useMemo, useState } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { SquareX, X } from '@signozhq/icons';
|
||||
import { Button, Input, Select } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Select } from 'antd';
|
||||
import CategoryHeading from 'components/Logs/CategoryHeading';
|
||||
import {
|
||||
ConditionalOperators,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Input } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { Select } from 'antd';
|
||||
// TODO(@signozhq/ui-input): migrate this <Input> once @signozhq/ui Input
|
||||
// supports the `onWheel` handler (used to blur on scroll for number inputs).
|
||||
import { Input, Select } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { TIME_AGGREGATION_OPTIONS } from './constants';
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import type { TableColumnsType as ColumnsType } from 'antd';
|
||||
import { Button, Collapse, Input, Select, Spin } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Collapse, Select, Spin } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import {
|
||||
|
||||
@@ -7,7 +7,8 @@ import {
|
||||
DropResult,
|
||||
} from 'react-beautiful-dnd';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button, Divider, Dropdown, Input, MenuProps, Tooltip } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Divider, Dropdown, MenuProps, Tooltip } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { FieldDataType } from 'api/v5/v5';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { Button, Col, Form, Input as AntInput, Input, Row } from 'antd';
|
||||
// TODO(@signozhq/ui-input): migrate <Input> once @signozhq/ui Input
|
||||
// supports the `spellCheck` prop on the URL input below.
|
||||
import { Button, Col, Form, Input, Input as AntInput, Row } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { CONTEXT_LINK_FIELDS } from 'container/NewWidget/RightContainer/ContextLinks/constants';
|
||||
import {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Blocks, Check, LoaderCircle } from '@signozhq/icons';
|
||||
import { Button, Card, Form, Input, Select, Space } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Card, Form, Select, Space } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import cx from 'classnames';
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Check, Server, LoaderCircle } from '@signozhq/icons';
|
||||
import { Button, Card, Form, Input, Space } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Card, Form, Space } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import cx from 'classnames';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Plus, Trash2 } from '@signozhq/icons';
|
||||
import { Button, Form, FormInstance, Input, Select, Space } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Form, FormInstance, Select, Space } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { requireErrorMessage } from 'utils/form/requireErrorMessage';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Form, Input } from 'antd';
|
||||
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Form } from 'antd';
|
||||
import { ProcessorFormField } from '../../AddNewProcessor/config';
|
||||
import { formValidationRules } from '../../config';
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { ChangeEventHandler, useState } from 'react';
|
||||
// TODO(@signozhq/ui-input): migrate to @signozhq/ui Input once the antd
|
||||
// `InputProps` spread (`size`, etc.) is no longer needed on this wrapper.
|
||||
import { Input, InputProps } from 'antd';
|
||||
|
||||
function CSVInput({ value, onChange, ...otherProps }: InputProps): JSX.Element {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Info } from '@signozhq/icons';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Switch } from '@signozhq/ui/switch';
|
||||
import { Flex, Form, Input, Space, Tooltip } from 'antd';
|
||||
import { Flex, Form, Space, Tooltip } from 'antd';
|
||||
import { ProcessorData } from 'types/api/pipeline/def';
|
||||
|
||||
import { PREDEFINED_MAPPING } from '../config';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Form, Input, Select, Space } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Switch } from '@signozhq/ui/switch';
|
||||
import { Form, Select, Space } from 'antd';
|
||||
import { ModalFooterTitle } from 'container/PipelinePage/styles';
|
||||
import { ProcessorData } from 'types/api/pipeline/def';
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@ import React, { ChangeEvent, useEffect, useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { Plus, Search } from '@signozhq/icons';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button, Flex, Form, Input, Tooltip } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Flex, Form, Tooltip } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import {
|
||||
useDeleteDowntimeScheduleByID,
|
||||
|
||||
@@ -8,4 +8,16 @@
|
||||
grid-template-columns: 60% 35%;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
|
||||
.input-with-label {
|
||||
.label {
|
||||
box-sizing: border-box;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.input {
|
||||
box-sizing: border-box;
|
||||
height: 36px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { Input, Skeleton } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Skeleton } from 'antd';
|
||||
import { getKeySuggestions } from 'api/querySuggestions/getKeySuggestions';
|
||||
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
|
||||
import { QUERY_BUILDER_KEY_TYPES } from 'constants/antlrQueryConstants';
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { ChangeEvent, useMemo } from 'react';
|
||||
import { Plus, Search } from '@signozhq/icons';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button, Flex, Input, Tooltip } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Flex, Tooltip } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { Input } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import cx from 'classnames';
|
||||
import CopyClipboardHOC from 'components/Logs/CopyClipboardHOC';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { Collapse, Input, Modal } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Collapse, Modal } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig';
|
||||
import { Diamond } from '@signozhq/icons';
|
||||
|
||||
@@ -9,10 +9,10 @@ import {
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Input,
|
||||
Modal,
|
||||
Select,
|
||||
Skeleton,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Input } from 'antd';
|
||||
// TODO(@signozhq/ui-input): migrate this styled(Input) once @signozhq/ui
|
||||
// Input supports `addonAfter` (the consumer renders `<InputComponent addonAfter="ms">`).
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { Input } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const DurationText = styled.div`
|
||||
|
||||
@@ -8,7 +8,8 @@ import {
|
||||
import { useQuery } from 'react-query';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AutoComplete, Input } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { AutoComplete } from 'antd';
|
||||
import getTagFilters from 'api/trace/getTagFilter';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
@@ -2,7 +2,8 @@ import { useMemo, useState } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AutoComplete, Input, Space } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { AutoComplete, Space } from 'antd';
|
||||
import getTagFilters from 'api/trace/getTagFilter';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ChangeEvent, useEffect, useMemo, useState } from 'react';
|
||||
import { ArrowLeft, Check, Loader, Plus, Search } from '@signozhq/icons';
|
||||
import { Button, Input, Spin } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Spin } from 'antd';
|
||||
import cx from 'classnames';
|
||||
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
|
||||
import SignozModal from 'components/SignozModal/SignozModal';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Input } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const InputComponent = styled(Input)`
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
|
||||
import { Input, Select } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Select } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
|
||||
import './DropRateView.styles.scss';
|
||||
|
||||
@@ -28,6 +28,10 @@
|
||||
.learn-more {
|
||||
font-size: 14px;
|
||||
}
|
||||
.search-input-container {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.ant-input-affix-wrapper {
|
||||
margin-top: 16px;
|
||||
|
||||
@@ -3,7 +3,8 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { ColorPicker, Input, Modal, Table, TableProps } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { ColorPicker, Modal, Table, TableProps } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import {
|
||||
@@ -311,12 +312,15 @@ function SaveView(): JSX.Element {
|
||||
Learn more
|
||||
</Typography.Link>
|
||||
</Typography.Text>
|
||||
<Input
|
||||
placeholder="Search for views..."
|
||||
prefix={<Search size={12} color={Color.BG_VANILLA_400} />}
|
||||
value={searchValue}
|
||||
onChange={handleSearch}
|
||||
/>
|
||||
<div className="search-input-container">
|
||||
<Input
|
||||
placeholder="Search for views..."
|
||||
prefix={<Search size={12} color={Color.BG_VANILLA_400} />}
|
||||
value={searchValue}
|
||||
onChange={handleSearch}
|
||||
className="search-input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Table
|
||||
columns={columns}
|
||||
|
||||
@@ -7,8 +7,8 @@ import {
|
||||
} from '@signozhq/ui/tabs';
|
||||
import cx from 'classnames';
|
||||
import { DetailsHeader } from 'components/DetailsPanel';
|
||||
import { themeColors } from 'constants/theme';
|
||||
import { generateColor } from 'lib/uPlotLib/utils/generateColor';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { generateColorPair } from 'pages/TraceDetailsV3/utils/generateColorPair';
|
||||
import { FloatingPanel } from 'periscope/components/FloatingPanel';
|
||||
|
||||
import { useTraceStore } from '../../stores/traceStore';
|
||||
@@ -35,6 +35,7 @@ function AnalyticsPanel({
|
||||
}: AnalyticsPanelProps): JSX.Element | null {
|
||||
const aggregations = useTraceStore((s) => s.aggregations);
|
||||
const colorByFieldName = useTraceStore((s) => s.colorByField.name);
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const execTimePct = useMemo(
|
||||
() =>
|
||||
@@ -57,13 +58,16 @@ function AnalyticsPanel({
|
||||
return [];
|
||||
}
|
||||
return Object.entries(execTimePct)
|
||||
.map(([group, percentage]) => ({
|
||||
group,
|
||||
percentage,
|
||||
color: generateColor(group, themeColors.traceDetailColorsV3),
|
||||
}))
|
||||
.map(([group, percentage]) => {
|
||||
const pair = generateColorPair(group);
|
||||
return {
|
||||
group,
|
||||
percentage,
|
||||
color: isDarkMode ? pair.color : pair.colorDark,
|
||||
};
|
||||
})
|
||||
.sort((a, b) => b.percentage - a.percentage);
|
||||
}, [execTimePct]);
|
||||
}, [execTimePct, isDarkMode]);
|
||||
|
||||
const spanCountRows = useMemo(() => {
|
||||
if (!spanCounts) {
|
||||
@@ -71,14 +75,17 @@ function AnalyticsPanel({
|
||||
}
|
||||
const max = Math.max(...Object.values(spanCounts), 1);
|
||||
return Object.entries(spanCounts)
|
||||
.map(([group, count]) => ({
|
||||
group,
|
||||
count,
|
||||
max,
|
||||
color: generateColor(group, themeColors.traceDetailColorsV3),
|
||||
}))
|
||||
.map(([group, count]) => {
|
||||
const pair = generateColorPair(group);
|
||||
return {
|
||||
group,
|
||||
count,
|
||||
max,
|
||||
color: isDarkMode ? pair.color : pair.colorDark,
|
||||
};
|
||||
})
|
||||
.sort((a, b) => b.count - a.count);
|
||||
}, [spanCounts]);
|
||||
}, [spanCounts, isDarkMode]);
|
||||
|
||||
if (!isOpen) {
|
||||
return null;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Checkbox, Input, Select, Skeleton } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Checkbox, Select, Skeleton } from 'antd';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import cx from 'classnames';
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
TooltipTrigger,
|
||||
} from '@signozhq/ui/tooltip';
|
||||
import { convertTimeToRelevantUnit } from 'container/TraceDetail/utils';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useTraceStore } from 'pages/TraceDetailsV3/stores/traceStore';
|
||||
import { getSpanAttribute, resolveSpanColor } from 'pages/TraceDetailsV3/utils';
|
||||
import { useMemo } from 'react';
|
||||
@@ -101,6 +102,7 @@ export function SpanHoverCard({
|
||||
}: SpanHoverCardProps): JSX.Element {
|
||||
const previewFields = useTraceStore((s) => s.previewFields);
|
||||
const colorByFieldName = useTraceStore((s) => s.colorByField.name);
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const hoverCardData = useMemo(() => {
|
||||
if (!hoveredSpanId) {
|
||||
@@ -121,11 +123,12 @@ export function SpanHoverCard({
|
||||
})
|
||||
.filter((r): r is SpanPreviewRow => r !== null);
|
||||
|
||||
const pair = resolveSpanColor(span, colorByFieldName);
|
||||
return {
|
||||
anchorTop: idx * rowHeight,
|
||||
tooltip: {
|
||||
spanName: span.name,
|
||||
color: resolveSpanColor(span, colorByFieldName),
|
||||
color: isDarkMode ? pair.color : pair.colorDark,
|
||||
hasError: span.has_error,
|
||||
relativeStartMs: span.timestamp - traceStartTime,
|
||||
durationMs: span.duration_nano / 1e6,
|
||||
@@ -139,6 +142,7 @@ export function SpanHoverCard({
|
||||
colorByFieldName,
|
||||
rowHeight,
|
||||
traceStartTime,
|
||||
isDarkMode,
|
||||
]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -87,6 +87,7 @@ describe('Canvas Draw Utils', () => {
|
||||
spanRectsArray,
|
||||
eventRectsArray: [],
|
||||
color: '#1890ff',
|
||||
colorDark: '#000',
|
||||
isDarkMode: false,
|
||||
metrics: METRICS,
|
||||
});
|
||||
@@ -94,7 +95,9 @@ describe('Canvas Draw Utils', () => {
|
||||
expect(ctx.beginPath).toHaveBeenCalled();
|
||||
expect(ctx.roundRect).toHaveBeenCalledWith(10, 1, 100, 22, 2);
|
||||
expect(ctx.fill).toHaveBeenCalled();
|
||||
expect(ctx.stroke).not.toHaveBeenCalled();
|
||||
// Rest state draws a subtle 1px rgba(0,0,0,0.3) outline to match spec
|
||||
expect(ctx.stroke).toHaveBeenCalled();
|
||||
expect(ctx.strokeStyle).toBe('rgba(0, 0, 0, 0.3)');
|
||||
expect(spanRectsArray).toHaveLength(1);
|
||||
expect(spanRectsArray[0]).toMatchObject({
|
||||
x: 10,
|
||||
@@ -126,15 +129,17 @@ describe('Canvas Draw Utils', () => {
|
||||
spanRectsArray,
|
||||
eventRectsArray: [],
|
||||
color: '#2F80ED',
|
||||
colorDark: '#000',
|
||||
isDarkMode: false,
|
||||
metrics: METRICS,
|
||||
selectedSpanId: 'sel',
|
||||
});
|
||||
|
||||
// Selected spans get solid l2-background fill + dashed border
|
||||
// Selected spans get solid l2-background fill + dashed border.
|
||||
// Light mode uses colorDark for the stroke for contrast against l2-background.
|
||||
expect(ctx.fill).toHaveBeenCalled();
|
||||
expect(ctx.setLineDash).toHaveBeenCalledWith(DASHED_BORDER_LINE_DASH);
|
||||
expect(ctx.strokeStyle).toBe('#2F80ED');
|
||||
expect(ctx.strokeStyle).toBe('#000');
|
||||
expect(ctx.lineWidth).toBe(2);
|
||||
expect(ctx.stroke).toHaveBeenCalled();
|
||||
expect(ctx.setLineDash).toHaveBeenLastCalledWith([]);
|
||||
@@ -161,6 +166,7 @@ describe('Canvas Draw Utils', () => {
|
||||
spanRectsArray,
|
||||
eventRectsArray: [],
|
||||
color: '#2F80ED',
|
||||
colorDark: '#000',
|
||||
isDarkMode: false,
|
||||
metrics: METRICS,
|
||||
hoveredSpanId: 'hov',
|
||||
@@ -193,6 +199,7 @@ describe('Canvas Draw Utils', () => {
|
||||
spanRectsArray,
|
||||
eventRectsArray: [],
|
||||
color: '#000',
|
||||
colorDark: '#000',
|
||||
isDarkMode: false,
|
||||
metrics: METRICS,
|
||||
});
|
||||
@@ -230,6 +237,7 @@ describe('Canvas Draw Utils', () => {
|
||||
spanRectsArray,
|
||||
eventRectsArray: [],
|
||||
color: '#000',
|
||||
colorDark: '#000',
|
||||
isDarkMode: false,
|
||||
metrics: METRICS,
|
||||
});
|
||||
@@ -254,6 +262,7 @@ describe('Canvas Draw Utils', () => {
|
||||
spanRectsArray: [],
|
||||
eventRectsArray: [],
|
||||
color: '#000',
|
||||
colorDark: '#000',
|
||||
isDarkMode: false,
|
||||
metrics: METRICS,
|
||||
});
|
||||
@@ -279,6 +288,7 @@ describe('Canvas Draw Utils', () => {
|
||||
spanRectsArray: [],
|
||||
eventRectsArray: [],
|
||||
color: '#000',
|
||||
colorDark: '#000',
|
||||
isDarkMode: false,
|
||||
metrics: METRICS,
|
||||
});
|
||||
@@ -314,6 +324,7 @@ describe('Canvas Draw Utils', () => {
|
||||
spanRectsArray: [],
|
||||
eventRectsArray: [],
|
||||
color: '#000',
|
||||
colorDark: '#000',
|
||||
isDarkMode: false,
|
||||
metrics: METRICS,
|
||||
});
|
||||
@@ -344,6 +355,7 @@ describe('Canvas Draw Utils', () => {
|
||||
spanRectsArray: [],
|
||||
eventRectsArray: [],
|
||||
color: '#000',
|
||||
colorDark: '#000',
|
||||
isDarkMode: false,
|
||||
metrics: METRICS,
|
||||
});
|
||||
@@ -371,8 +383,8 @@ describe('Canvas Draw Utils', () => {
|
||||
expect(ctx.save).toHaveBeenCalled();
|
||||
expect(ctx.translate).toHaveBeenCalledWith(50, 11);
|
||||
expect(ctx.rotate).toHaveBeenCalledWith(Math.PI / 4);
|
||||
expect(ctx.fillStyle).toBe('rgb(220, 38, 38)');
|
||||
expect(ctx.strokeStyle).toBe('rgb(153, 27, 27)');
|
||||
expect(ctx.fillStyle).toBe('#FC4E4E');
|
||||
expect(ctx.strokeStyle).toBe('#fd2c2c');
|
||||
expect(ctx.fillRect).toHaveBeenCalledWith(-3, -3, 6, 6);
|
||||
expect(ctx.strokeRect).toHaveBeenCalledWith(-3, -3, 6, 6);
|
||||
expect(ctx.restore).toHaveBeenCalled();
|
||||
@@ -408,8 +420,8 @@ describe('Canvas Draw Utils', () => {
|
||||
eventDotSize: 6,
|
||||
});
|
||||
|
||||
expect(ctx.fillStyle).toBe('rgb(239, 68, 68)');
|
||||
expect(ctx.strokeStyle).toBe('rgb(185, 28, 28)');
|
||||
expect(ctx.fillStyle).toBe('#FC4E4E');
|
||||
expect(ctx.strokeStyle).toBe('#fd2c2c');
|
||||
});
|
||||
|
||||
it('falls back to cyan/blue for unparseable span colors', () => {
|
||||
@@ -461,6 +473,7 @@ describe('Canvas Draw Utils', () => {
|
||||
spanRectsArray: [],
|
||||
eventRectsArray: [],
|
||||
color: '#000',
|
||||
colorDark: '#000',
|
||||
isDarkMode: false,
|
||||
metrics: METRICS,
|
||||
hoveredSpanId: 'p',
|
||||
@@ -483,6 +496,7 @@ describe('Canvas Draw Utils', () => {
|
||||
spanRectsArray: [],
|
||||
eventRectsArray: [],
|
||||
color: '#000',
|
||||
colorDark: '#000',
|
||||
isDarkMode: false,
|
||||
metrics: METRICS,
|
||||
selectedSpanId: 'p',
|
||||
@@ -524,6 +538,7 @@ describe('Canvas Draw Utils', () => {
|
||||
spanRectsArray: [],
|
||||
eventRectsArray: [],
|
||||
color: '#000',
|
||||
colorDark: '#000',
|
||||
isDarkMode: false,
|
||||
metrics: METRICS,
|
||||
});
|
||||
|
||||
@@ -3,11 +3,13 @@ import { TelemetryFieldKey } from 'types/api/v5/queryRange';
|
||||
import { getFlamegraphSpanGroupValue, getSpanColor } from '../utils';
|
||||
import { MOCK_SPAN } from './testUtils';
|
||||
|
||||
const mockGenerateColor = jest.fn();
|
||||
const mockGenerateColorPair = jest.fn();
|
||||
|
||||
jest.mock('lib/uPlotLib/utils/generateColor', () => ({
|
||||
generateColor: (key: string, colorMap: Record<string, string>): string =>
|
||||
mockGenerateColor(key, colorMap),
|
||||
jest.mock('pages/TraceDetailsV3/utils/generateColorPair', () => ({
|
||||
generateColorPair: (name: string): { color: string; colorDark: string } =>
|
||||
mockGenerateColorPair(name),
|
||||
RESERVED_ERROR: '#FC4E4E',
|
||||
darkenHex: (hex: string): string => hex,
|
||||
}));
|
||||
|
||||
const SERVICE_FIELD: TelemetryFieldKey = {
|
||||
@@ -24,48 +26,39 @@ const HOST_FIELD: TelemetryFieldKey = {
|
||||
describe('Presentation / Styling Utils', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockGenerateColor.mockReturnValue('#2F80ED');
|
||||
mockGenerateColorPair.mockReturnValue({
|
||||
color: '#2F80ED',
|
||||
colorDark: '#1a4d99',
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSpanColor', () => {
|
||||
it('uses generated colour from groupValue for normal span', () => {
|
||||
mockGenerateColor.mockReturnValue('#1890ff');
|
||||
mockGenerateColorPair.mockReturnValue({
|
||||
color: '#1890ff',
|
||||
colorDark: '#0d5599',
|
||||
});
|
||||
|
||||
const color = getSpanColor({
|
||||
const result = getSpanColor({
|
||||
span: { ...MOCK_SPAN, hasError: false },
|
||||
isDarkMode: false,
|
||||
groupValue: 'my-bucket',
|
||||
});
|
||||
|
||||
expect(mockGenerateColor).toHaveBeenCalledWith(
|
||||
'my-bucket',
|
||||
expect.any(Object),
|
||||
);
|
||||
expect(color).toBe('#1890ff');
|
||||
expect(mockGenerateColorPair).toHaveBeenCalledWith('my-bucket');
|
||||
expect(result.color).toBe('#1890ff');
|
||||
expect(result.colorDark).toBe('#0d5599');
|
||||
});
|
||||
|
||||
it('overrides with error color in light mode when span has error', () => {
|
||||
mockGenerateColor.mockReturnValue('#1890ff');
|
||||
|
||||
const color = getSpanColor({
|
||||
it('overrides with reserved error color when span has error', () => {
|
||||
const result = getSpanColor({
|
||||
span: { ...MOCK_SPAN, hasError: true },
|
||||
isDarkMode: false,
|
||||
groupValue: 'my-bucket',
|
||||
});
|
||||
|
||||
expect(color).toBe('rgb(220, 38, 38)');
|
||||
});
|
||||
|
||||
it('overrides with error color in dark mode when span has error', () => {
|
||||
mockGenerateColor.mockReturnValue('#1890ff');
|
||||
|
||||
const color = getSpanColor({
|
||||
span: { ...MOCK_SPAN, hasError: true },
|
||||
isDarkMode: true,
|
||||
groupValue: 'my-bucket',
|
||||
});
|
||||
|
||||
expect(color).toBe('rgb(239, 68, 68)');
|
||||
expect(result.color).toBe('#FC4E4E');
|
||||
expect(result.colorDark).toBe('#FC4E4E');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ export const EVENT_DOT_SIZE_RATIO = EVENT_DOT_SIZE / SPAN_BAR_HEIGHT;
|
||||
export const MIN_EVENT_DOT_SIZE = 4;
|
||||
export const MAX_EVENT_DOT_SIZE = EVENT_DOT_SIZE;
|
||||
|
||||
export const LABEL_FONT = '11px Inter, sans-serif';
|
||||
export const LABEL_FONT = '500 11px Inter, sans-serif';
|
||||
export const LABEL_PADDING_X = 8;
|
||||
export const MIN_WIDTH_FOR_NAME = 30;
|
||||
export const MIN_WIDTH_FOR_NAME_AND_DURATION = 80;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { RefObject, useCallback, useMemo, useRef } from 'react';
|
||||
import { themeColors } from 'constants/theme';
|
||||
import { generateColor } from 'lib/uPlotLib/utils/generateColor';
|
||||
import { generateColorPair } from 'pages/TraceDetailsV3/utils/generateColorPair';
|
||||
import { useTraceStore } from 'pages/TraceDetailsV3/stores/traceStore';
|
||||
import { FlamegraphSpan } from 'types/api/trace/getTraceFlamegraph';
|
||||
import { TelemetryFieldKey } from 'types/api/v5/queryRange';
|
||||
@@ -118,7 +117,11 @@ function drawLevel(args: DrawLevelArgs): void {
|
||||
width = clamp(width, 1, Infinity);
|
||||
|
||||
const groupValue = getFlamegraphSpanGroupValue(span, colorByField);
|
||||
const color = getSpanColor({ span, isDarkMode, groupValue });
|
||||
const { color, colorDark } = getSpanColor({
|
||||
span,
|
||||
isDarkMode,
|
||||
groupValue,
|
||||
});
|
||||
|
||||
const isDimmedByFilter =
|
||||
!!isFilterActiveInLevel &&
|
||||
@@ -135,6 +138,7 @@ function drawLevel(args: DrawLevelArgs): void {
|
||||
spanRectsArray,
|
||||
eventRectsArray,
|
||||
color,
|
||||
colorDark,
|
||||
isDarkMode,
|
||||
metrics,
|
||||
selectedSpanId,
|
||||
@@ -155,6 +159,7 @@ interface DrawConnectorLinesArgs {
|
||||
viewportHeight: number;
|
||||
metrics: FlamegraphRowMetrics;
|
||||
colorByField: TelemetryFieldKey;
|
||||
isDarkMode: boolean;
|
||||
}
|
||||
|
||||
function drawConnectorLines(args: DrawConnectorLinesArgs): void {
|
||||
@@ -168,6 +173,7 @@ function drawConnectorLines(args: DrawConnectorLinesArgs): void {
|
||||
viewportHeight,
|
||||
metrics,
|
||||
colorByField,
|
||||
isDarkMode,
|
||||
} = args;
|
||||
|
||||
ctx.save();
|
||||
@@ -197,8 +203,8 @@ function drawConnectorLines(args: DrawConnectorLinesArgs): void {
|
||||
{ serviceName: conn.serviceName, resource: conn.resource },
|
||||
colorByField,
|
||||
);
|
||||
const color = generateColor(groupValue, themeColors.traceDetailColorsV3);
|
||||
ctx.strokeStyle = color;
|
||||
const pair = generateColorPair(groupValue);
|
||||
ctx.strokeStyle = isDarkMode ? pair.color : pair.colorDark;
|
||||
|
||||
const x = clamp(xFrac * cssWidth, 0, cssWidth);
|
||||
ctx.beginPath();
|
||||
@@ -294,6 +300,7 @@ export function useFlamegraphDraw(
|
||||
viewportHeight,
|
||||
metrics,
|
||||
colorByField,
|
||||
isDarkMode,
|
||||
});
|
||||
|
||||
const spanRectsArray: SpanRect[] = [];
|
||||
|
||||
@@ -211,11 +211,14 @@ export function useFlamegraphHover(
|
||||
durationMs: span.durationNano / 1e6,
|
||||
clientX: e.clientX,
|
||||
clientY: e.clientY,
|
||||
spanColor: getSpanColor({
|
||||
span,
|
||||
isDarkMode,
|
||||
groupValue: getFlamegraphSpanGroupValue(span, colorByField),
|
||||
}),
|
||||
spanColor: ((): string => {
|
||||
const pair = getSpanColor({
|
||||
span,
|
||||
isDarkMode,
|
||||
groupValue: getFlamegraphSpanGroupValue(span, colorByField),
|
||||
});
|
||||
return isDarkMode ? pair.color : pair.colorDark;
|
||||
})(),
|
||||
event: {
|
||||
name: event.name,
|
||||
timeOffsetMs: eventTimeMs - span.timestamp,
|
||||
@@ -244,11 +247,14 @@ export function useFlamegraphHover(
|
||||
durationMs: span.durationNano / 1e6,
|
||||
clientX: e.clientX,
|
||||
clientY: e.clientY,
|
||||
spanColor: getSpanColor({
|
||||
span,
|
||||
isDarkMode,
|
||||
groupValue: getFlamegraphSpanGroupValue(span, colorByField),
|
||||
}),
|
||||
spanColor: ((): string => {
|
||||
const pair = getSpanColor({
|
||||
span,
|
||||
isDarkMode,
|
||||
groupValue: getFlamegraphSpanGroupValue(span, colorByField),
|
||||
});
|
||||
return isDarkMode ? pair.color : pair.colorDark;
|
||||
})(),
|
||||
previewRows: buildPreviewRows(span),
|
||||
});
|
||||
updateCursor(canvas, span);
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
/* eslint-disable sonarjs/cognitive-complexity */
|
||||
import { themeColors } from 'constants/theme';
|
||||
import { convertTimeToRelevantUnit } from 'container/TraceDetail/utils';
|
||||
import { generateColor } from 'lib/uPlotLib/utils/generateColor';
|
||||
import { getSpanAttribute } from 'pages/TraceDetailsV3/utils';
|
||||
import {
|
||||
ColorPair,
|
||||
darkenHex,
|
||||
generateColorPair,
|
||||
RESERVED_ERROR,
|
||||
} from 'pages/TraceDetailsV3/utils/generateColorPair';
|
||||
import { FlamegraphSpan } from 'types/api/trace/getTraceFlamegraph';
|
||||
import { TelemetryFieldKey } from 'types/api/v5/queryRange';
|
||||
|
||||
@@ -106,15 +110,12 @@ interface GetSpanColorArgs {
|
||||
groupValue: string;
|
||||
}
|
||||
|
||||
export function getSpanColor(args: GetSpanColorArgs): string {
|
||||
const { span, isDarkMode, groupValue } = args;
|
||||
let color = generateColor(groupValue, themeColors.traceDetailColorsV3);
|
||||
|
||||
export function getSpanColor(args: GetSpanColorArgs): ColorPair {
|
||||
const { span, groupValue } = args;
|
||||
if (span.hasError) {
|
||||
color = isDarkMode ? 'rgb(239, 68, 68)' : 'rgb(220, 38, 38)';
|
||||
return { color: RESERVED_ERROR, colorDark: RESERVED_ERROR };
|
||||
}
|
||||
|
||||
return color;
|
||||
return generateColorPair(groupValue);
|
||||
}
|
||||
|
||||
export interface EventDotColor {
|
||||
@@ -130,8 +131,8 @@ export function getEventDotColor(
|
||||
): EventDotColor {
|
||||
if (isError) {
|
||||
return {
|
||||
fill: isDarkMode ? 'rgb(239, 68, 68)' : 'rgb(220, 38, 38)',
|
||||
stroke: isDarkMode ? 'rgb(185, 28, 28)' : 'rgb(153, 27, 27)',
|
||||
fill: RESERVED_ERROR,
|
||||
stroke: darkenHex(RESERVED_ERROR, 0.22),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -209,6 +210,9 @@ interface DrawSpanBarArgs {
|
||||
spanRectsArray: SpanRect[];
|
||||
eventRectsArray: EventRect[];
|
||||
color: string;
|
||||
// Darkened variant used as foreground (stroke + label) on light mode
|
||||
// hover/selected, where the base color sits against a near-white panel.
|
||||
colorDark: string;
|
||||
isDarkMode: boolean;
|
||||
metrics: FlamegraphRowMetrics;
|
||||
selectedSpanId?: string | null;
|
||||
@@ -228,6 +232,7 @@ export function drawSpanBar(args: DrawSpanBarArgs): void {
|
||||
spanRectsArray,
|
||||
eventRectsArray,
|
||||
color,
|
||||
colorDark,
|
||||
isDarkMode,
|
||||
metrics,
|
||||
selectedSpanId,
|
||||
@@ -259,15 +264,21 @@ export function drawSpanBar(args: DrawSpanBarArgs): void {
|
||||
if (isSelected) {
|
||||
ctx.setLineDash(DASHED_BORDER_LINE_DASH);
|
||||
}
|
||||
ctx.strokeStyle = color;
|
||||
ctx.strokeStyle = isDarkMode ? color : colorDark;
|
||||
ctx.lineWidth = isSelected ? 2 : 1;
|
||||
ctx.stroke();
|
||||
if (isSelected) {
|
||||
ctx.setLineDash([]);
|
||||
}
|
||||
} else {
|
||||
ctx.fillStyle = color;
|
||||
// Light mode uses the darkened variant as fill so bars contrast against
|
||||
// the white panel background; dark mode keeps the bright base.
|
||||
ctx.fillStyle = isDarkMode ? color : colorDark;
|
||||
ctx.fill();
|
||||
// Subtle outline to match spec: 1px semi-transparent black border at rest
|
||||
ctx.strokeStyle = 'rgba(0, 0, 0, 0.3)';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
spanRectsArray.push({
|
||||
@@ -292,7 +303,10 @@ export function drawSpanBar(args: DrawSpanBarArgs): void {
|
||||
const eventX = x + (clampedOffset / 100) * width;
|
||||
const eventY = spanY + metrics.SPAN_BAR_HEIGHT / 2;
|
||||
|
||||
const dotColor = getEventDotColor(color, event.isError, isDarkMode);
|
||||
// Event dots derive from the effective bar color so they track the
|
||||
// light/dark variant the bar is rendered with.
|
||||
const parentBarColor = isDarkMode ? color : colorDark;
|
||||
const dotColor = getEventDotColor(parentBarColor, event.isError, isDarkMode);
|
||||
const eventKey = `${span.spanId}-${event.name}-${event.timeUnixNano}`;
|
||||
const isEventHovered = hoveredEventKey === eventKey;
|
||||
const dotSize = isEventHovered
|
||||
@@ -328,6 +342,7 @@ export function drawSpanBar(args: DrawSpanBarArgs): void {
|
||||
y: spanY,
|
||||
width,
|
||||
color,
|
||||
colorDark,
|
||||
isSelectedOrHovered,
|
||||
isDarkMode,
|
||||
spanBarHeight: metrics.SPAN_BAR_HEIGHT,
|
||||
@@ -347,6 +362,7 @@ interface DrawSpanLabelArgs {
|
||||
y: number;
|
||||
width: number;
|
||||
color: string;
|
||||
colorDark: string;
|
||||
isSelectedOrHovered: boolean;
|
||||
isDarkMode: boolean;
|
||||
spanBarHeight: number;
|
||||
@@ -360,6 +376,7 @@ function drawSpanLabel(args: DrawSpanLabelArgs): void {
|
||||
y,
|
||||
width,
|
||||
color,
|
||||
colorDark,
|
||||
isSelectedOrHovered,
|
||||
isDarkMode,
|
||||
spanBarHeight,
|
||||
@@ -379,11 +396,12 @@ function drawSpanLabel(args: DrawSpanLabelArgs): void {
|
||||
ctx.clip();
|
||||
|
||||
ctx.font = LABEL_FONT;
|
||||
const hoverLabelColor = isDarkMode ? color : colorDark;
|
||||
ctx.fillStyle = isSelectedOrHovered
|
||||
? color
|
||||
? hoverLabelColor
|
||||
: isDarkMode
|
||||
? 'rgba(0, 0, 0, 0.9)'
|
||||
: 'rgba(255, 255, 255, 0.9)';
|
||||
? 'rgba(0, 0, 0, 0.7)'
|
||||
: 'rgba(255, 255, 255, 0.95)';
|
||||
ctx.textBaseline = 'middle';
|
||||
|
||||
const textY = y + spanBarHeight / 2;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ChangeEvent, useEffect, useMemo, useState } from 'react';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Input, Spin } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Spin } from 'antd';
|
||||
import cx from 'classnames';
|
||||
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
|
||||
import SignozModal from 'components/SignozModal/SignozModal';
|
||||
|
||||
@@ -433,6 +433,7 @@
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
margin: 0 6px;
|
||||
background-color: var(--service-dot-color);
|
||||
|
||||
&.hasError {
|
||||
box-shadow: 0 0 0 2px rgba(255, 70, 70, 0.3);
|
||||
@@ -514,7 +515,7 @@
|
||||
.spanBar {
|
||||
position: absolute;
|
||||
height: 18px;
|
||||
top: 5px;
|
||||
top: 3px;
|
||||
border-radius: 2px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -522,7 +523,9 @@
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
color: rgba(0, 0, 0, 0.9);
|
||||
// Theme-resolved in JS: dark text on the bright dark-mode fill, white text on
|
||||
// the darkened light-mode fill. See SpanDuration in Success.tsx.
|
||||
color: var(--span-text-color);
|
||||
background-color: var(--span-color);
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
@@ -548,7 +551,6 @@
|
||||
|
||||
.spanDurationText {
|
||||
color: inherit;
|
||||
opacity: 0.8;
|
||||
font-size: 10px;
|
||||
margin-left: 8px;
|
||||
flex-shrink: 0;
|
||||
@@ -607,25 +609,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// `.spanBar` text color is the one place where semantic tokens don't fit
|
||||
// cleanly: in dark mode the bar's bright `--span-color` background needs dark
|
||||
// text; in light mode `generateColor` produces darker bar fills, so the text
|
||||
// must flip to white.
|
||||
:global(.lightMode) {
|
||||
.root {
|
||||
.spanDuration .spanBar {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.timelineRow:hover .spanBar,
|
||||
.timelineRow.hoveredSpan .spanBar,
|
||||
.isInterested .spanBar,
|
||||
.isSelectedNonMatching .spanBar {
|
||||
color: var(--span-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tooltips for the row's hover-revealed action buttons (Copy / Add to Funnel).
|
||||
// Bumped above FloatingPanel (z-index 999) so they stay visible when the
|
||||
// SpanDetailsPanel is docked as a floating panel.
|
||||
|
||||
@@ -29,6 +29,7 @@ import HttpStatusBadge from 'components/HttpStatusBadge/HttpStatusBadge';
|
||||
import TimelineV3 from 'components/TimelineV3/TimelineV3';
|
||||
import { convertTimeToRelevantUnit } from 'container/TraceDetail/utils';
|
||||
import { useCopySpanLink } from 'hooks/trace/useCopySpanLink';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { colorToRgb } from 'lib/uPlotLib/utils/generateColor';
|
||||
@@ -214,8 +215,12 @@ const SpanOverview = memo(function SpanOverview({
|
||||
const isRootSpan = span.level === 0;
|
||||
const { onSpanCopy } = useCopySpanLink(span);
|
||||
const colorByFieldName = useTraceStore((s) => s.colorByField.name);
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const color = resolveSpanColor(span, colorByFieldName);
|
||||
const { color, colorDark } = resolveSpanColor(span, colorByFieldName);
|
||||
// Single theme-resolved color: bright base in dark mode, darkened variant in
|
||||
// light mode so the dot stands out against the white panel.
|
||||
const effectiveColor = isDarkMode ? color : colorDark;
|
||||
|
||||
// Smart highlighting logic
|
||||
const {
|
||||
@@ -317,7 +322,11 @@ const SpanOverview = memo(function SpanOverview({
|
||||
{/* Colored service dot */}
|
||||
<span
|
||||
className={cx(styles.treeIcon, { [styles.hasError]: span.has_error })}
|
||||
style={{ backgroundColor: color }}
|
||||
style={
|
||||
{
|
||||
'--service-dot-color': effectiveColor,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Span name + service name */}
|
||||
@@ -391,9 +400,16 @@ export const SpanDuration = memo(function SpanDuration({
|
||||
const width = (span.duration_nano * 1e2) / (spread * 1e6);
|
||||
|
||||
const colorByFieldName = useTraceStore((s) => s.colorByField.name);
|
||||
const color = resolveSpanColor(span, colorByFieldName);
|
||||
// `resolveSpanColor` returns a CSS variable for errors; `colorToRgb` can't parse it.
|
||||
const rgbColor = span.has_error ? '239, 68, 68' : colorToRgb(color);
|
||||
const isDarkMode = useIsDarkMode();
|
||||
const { color, colorDark } = resolveSpanColor(span, colorByFieldName);
|
||||
// Single theme-resolved color: bright base in dark mode, darkened variant in
|
||||
// light mode (so the bar stands out against the white panel and hover/selected
|
||||
// foregrounds stay legible). The bar's text flips dark↔white to suit the fill.
|
||||
const effectiveColor = isDarkMode ? color : colorDark;
|
||||
const rgbColor = colorToRgb(effectiveColor);
|
||||
const spanTextColor = isDarkMode
|
||||
? 'rgba(0, 0, 0, 0.7)'
|
||||
: 'rgba(255, 255, 255, 0.95)';
|
||||
|
||||
const {
|
||||
isSelected,
|
||||
@@ -424,8 +440,9 @@ export const SpanDuration = memo(function SpanDuration({
|
||||
{
|
||||
left: `${leftOffset}%`,
|
||||
width: `${width}%`,
|
||||
'--span-color': color,
|
||||
'--span-color': effectiveColor,
|
||||
'--span-color-rgb': rgbColor,
|
||||
'--span-text-color': spanTextColor,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
|
||||
@@ -103,6 +103,7 @@ jest.mock('components/TimelineV3/TimelineV3', () => {
|
||||
jest.mock('lib/uPlotLib/utils/generateColor', () => ({
|
||||
generateColor: (): string => '#1890ff',
|
||||
colorToRgb: (): string => '24, 144, 255',
|
||||
hashFn: (): number => 0,
|
||||
}));
|
||||
|
||||
jest.mock('container/TraceDetail/utils', () => ({
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { themeColors } from 'constants/theme';
|
||||
import { generateColor } from 'lib/uPlotLib/utils/generateColor';
|
||||
import { SpanV3 } from 'types/api/trace/getTraceV3';
|
||||
|
||||
import {
|
||||
ColorPair,
|
||||
generateColorPair,
|
||||
RESERVED_ERROR,
|
||||
} from './utils/generateColorPair';
|
||||
|
||||
/**
|
||||
* Look up an attribute from both `resource` and `attributes` on a span.
|
||||
* Resources are checked first (service.name, k8s.* etc. live there).
|
||||
@@ -92,18 +96,16 @@ export function getSpanGroupValue(
|
||||
|
||||
/**
|
||||
* Resolves the rendering colour for a span. Error spans always get the
|
||||
* semantic destructive colour; everything else is derived deterministically
|
||||
* from its group value via `generateColor`.
|
||||
* reserved error colour; everything else is derived deterministically from its
|
||||
* group value via `generateColorPair`. Returns both the base color and a
|
||||
* darkened variant for light-mode hover/selected foregrounds.
|
||||
*/
|
||||
export function resolveSpanColor(
|
||||
span: SpanV3,
|
||||
colorByFieldName: string,
|
||||
): string {
|
||||
): ColorPair {
|
||||
if (span.has_error) {
|
||||
return 'var(--destructive)';
|
||||
return { color: RESERVED_ERROR, colorDark: RESERVED_ERROR };
|
||||
}
|
||||
return generateColor(
|
||||
getSpanGroupValue(span, colorByFieldName),
|
||||
themeColors.traceDetailColorsV3,
|
||||
);
|
||||
return generateColorPair(getSpanGroupValue(span, colorByFieldName));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
import {
|
||||
darkenHex,
|
||||
generateColorPair,
|
||||
PALETTE_V3,
|
||||
RESERVED_ERROR,
|
||||
RESERVED_OK,
|
||||
RESERVED_WARNING,
|
||||
} from '../generateColorPair';
|
||||
|
||||
describe('generateColorPair', () => {
|
||||
it('is deterministic: same name returns the same pair across calls', () => {
|
||||
const a = generateColorPair('payment-service');
|
||||
const b = generateColorPair('payment-service');
|
||||
expect(a).toBe(b); // cache hit returns the same reference
|
||||
expect(a.color).toBe(b.color);
|
||||
expect(a.colorDark).toBe(b.colorDark);
|
||||
});
|
||||
|
||||
it('returns a palette color for a normal name', () => {
|
||||
const { color } = generateColorPair('any-service');
|
||||
expect(PALETTE_V3).toContain(color);
|
||||
});
|
||||
|
||||
it('colorDark differs from color (darker variant computed via darkenHex)', () => {
|
||||
const { color, colorDark } = generateColorPair('checkout-svc');
|
||||
expect(colorDark).not.toBe(color);
|
||||
expect(colorDark).toMatch(/^#[0-9a-f]{6}$/i);
|
||||
});
|
||||
|
||||
it('produces different colors for different names (palette wraps modulo length)', () => {
|
||||
const a = generateColorPair('aaa');
|
||||
const b = generateColorPair('bbb');
|
||||
// Not strictly guaranteed (hash collisions exist with 28 buckets), but
|
||||
// for these two short strings djb2 produces different bucket indices.
|
||||
expect(a.color).not.toBe(b.color);
|
||||
});
|
||||
});
|
||||
|
||||
describe('darkenHex', () => {
|
||||
it('returns a darker hex than the input for amount > 0', () => {
|
||||
const input = '#4D6BD8';
|
||||
const out = darkenHex(input, 0.22);
|
||||
expect(out).toMatch(/^#[0-9a-f]{6}$/i);
|
||||
expect(out).not.toBe(input);
|
||||
});
|
||||
|
||||
it('handles amount = 0 as a near-identity', () => {
|
||||
const out = darkenHex('#4D6BD8', 0);
|
||||
// HSL round-trip may shift a digit; only assert format.
|
||||
expect(out).toMatch(/^#[0-9a-f]{6}$/i);
|
||||
});
|
||||
});
|
||||
|
||||
describe('reserved status colors', () => {
|
||||
it('matches spec section 8 hexes', () => {
|
||||
expect(RESERVED_ERROR).toBe('#FC4E4E');
|
||||
expect(RESERVED_WARNING).toBe('#fbbf24');
|
||||
expect(RESERVED_OK).toBe('#4ade80');
|
||||
});
|
||||
});
|
||||
|
||||
// Visual inspection table: each palette color paired with its darkenHex(0.22)
|
||||
// variant. Confirms the darkening produces a distinct, non-collapsed hex per
|
||||
// entry. Run with `yarn jest generateColorPair --verbose` to see the table.
|
||||
describe('PALETTE_V3 darken-pair table', () => {
|
||||
const PALETTE_NAMES = [
|
||||
'Slate blue',
|
||||
'Sage',
|
||||
'Amber',
|
||||
'Dusty pink',
|
||||
'Lavender',
|
||||
'Peach',
|
||||
'Sky teal',
|
||||
'Fuchsia',
|
||||
'Terracotta',
|
||||
'Forest',
|
||||
'Cornflower',
|
||||
'Iris',
|
||||
'Olive gold',
|
||||
'Mint',
|
||||
'Mauve',
|
||||
'Dusty teal',
|
||||
'Burnt orange',
|
||||
'Pistachio',
|
||||
'Periwinkle',
|
||||
'Coral blush',
|
||||
'Sienna',
|
||||
'Robin',
|
||||
'Sandy gold',
|
||||
'Powder blue',
|
||||
'Umber',
|
||||
'Aqua',
|
||||
'Warm tan',
|
||||
'Antique rose',
|
||||
];
|
||||
|
||||
it.each(
|
||||
PALETTE_V3.map((hex, i) => [PALETTE_NAMES[i] ?? `idx-${i}`, hex] as const),
|
||||
)('%s (%s) darkens to a distinct hex', (name, hex) => {
|
||||
const dark = darkenHex(hex, 0.22);
|
||||
expect(dark).toMatch(/^#[0-9a-f]{6}$/i);
|
||||
expect(dark.toLowerCase()).not.toBe(hex.toLowerCase());
|
||||
});
|
||||
|
||||
it('all 28 darkened variants are unique (no collisions)', () => {
|
||||
const darks = PALETTE_V3.map((hex) => darkenHex(hex, 0.22).toLowerCase());
|
||||
const unique = new Set(darks);
|
||||
expect(unique.size).toBe(PALETTE_V3.length);
|
||||
});
|
||||
|
||||
it('prints the base→dark table for visual inspection', () => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('\nPALETTE_V3 base → darkenHex(0.22) pairs:');
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('idx name base dark');
|
||||
PALETTE_V3.forEach((hex, i) => {
|
||||
const dark = darkenHex(hex, 0.22);
|
||||
const name = (PALETTE_NAMES[i] ?? '').padEnd(13);
|
||||
const idx = String(i).padStart(2, ' ');
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`${idx} ${name} ${hex} ${dark}`);
|
||||
});
|
||||
// Sentinel assertion so the test is not flagged as having none.
|
||||
expect(PALETTE_V3).toHaveLength(28);
|
||||
});
|
||||
});
|
||||
116
frontend/src/pages/TraceDetailsV3/utils/generateColorPair.ts
Normal file
116
frontend/src/pages/TraceDetailsV3/utils/generateColorPair.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
// Source-of-truth doc: ./COLOR_PALETTE.md
|
||||
//
|
||||
// Color system for TraceDetailsV3 (waterfall + flamegraph). Returns a base
|
||||
// color (deterministic per group name) plus a darkened variant used as the
|
||||
// light-mode foreground / fill. Reuses the shared djb2 `hashFn`.
|
||||
|
||||
import { hashFn } from 'lib/uPlotLib/utils/generateColor';
|
||||
|
||||
// 28 colors from the doc's "Updated Colour Palette" (Section 1), in doc order.
|
||||
// Hash output `% PALETTE.length` adjusts automatically if entries are added.
|
||||
export const PALETTE_V3: readonly string[] = [
|
||||
'#4D6BD8', // Slate blue
|
||||
'#84B270', // Sage
|
||||
'#EB9E40', // Amber
|
||||
'#D58998', // Dusty pink
|
||||
'#8278D5', // Lavender
|
||||
'#E69C6F', // Peach
|
||||
'#3CB4DA', // Sky teal
|
||||
'#E85DA8', // Fuchsia
|
||||
'#D4694A', // Terracotta
|
||||
'#4FCC8E', // Forest
|
||||
'#5BA2D6', // Cornflower
|
||||
'#9D57D0', // Iris
|
||||
'#D4B638', // Olive gold
|
||||
'#6CC4A4', // Mint
|
||||
'#D188CB', // Mauve
|
||||
'#2FB59B', // Dusty teal
|
||||
'#E68340', // Burnt orange
|
||||
'#B8C474', // Pistachio
|
||||
'#3C84E5', // Periwinkle
|
||||
'#E29F8E', // Coral blush
|
||||
'#C56330', // Sienna
|
||||
'#4E8CF8', // Robin
|
||||
'#E8B752', // Sandy gold
|
||||
'#8DBEDF', // Powder blue
|
||||
'#8B7544', // Umber
|
||||
'#23E0E8', // Aqua
|
||||
'#CB874A', // Warm tan
|
||||
'#C886A9', // Antique rose
|
||||
];
|
||||
|
||||
// Reserved status colors per spec section 8. Error is wired today;
|
||||
// warning + OK are exported for future use (no render path consumes them yet).
|
||||
export const RESERVED_ERROR = '#FC4E4E';
|
||||
export const RESERVED_WARNING = '#fbbf24';
|
||||
export const RESERVED_OK = '#4ade80';
|
||||
|
||||
function hexToHsl(hex: string): [number, number, number] {
|
||||
const n = parseInt(hex.slice(1), 16);
|
||||
const r = ((n >> 16) & 255) / 255;
|
||||
const g = ((n >> 8) & 255) / 255;
|
||||
const b = (n & 255) / 255;
|
||||
const mx = Math.max(r, g, b);
|
||||
const mn = Math.min(r, g, b);
|
||||
const d = mx - mn;
|
||||
const l = (mx + mn) / 2;
|
||||
const s = d === 0 ? 0 : d / (1 - Math.abs(2 * l - 1));
|
||||
let h: number;
|
||||
if (d === 0) {
|
||||
h = 0;
|
||||
} else if (mx === r) {
|
||||
h = 60 * (((g - b) / d) % 6);
|
||||
} else if (mx === g) {
|
||||
h = 60 * ((b - r) / d + 2);
|
||||
} else {
|
||||
h = 60 * ((r - g) / d + 4);
|
||||
}
|
||||
return [(h + 360) % 360, s * 100, l * 100];
|
||||
}
|
||||
|
||||
function hslToHex(h: number, s: number, l: number): string {
|
||||
const S = s / 100;
|
||||
const L = l / 100;
|
||||
const k = (n: number): number => (n + h / 30) % 12;
|
||||
const a = S * Math.min(L, 1 - L);
|
||||
const f = (n: number): number =>
|
||||
Math.round(
|
||||
255 * (L - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)))),
|
||||
);
|
||||
return `#${[f(0), f(8), f(4)]
|
||||
.map((v) => v.toString(16).padStart(2, '0'))
|
||||
.join('')}`;
|
||||
}
|
||||
|
||||
// Gentle darken: compress lightness relatively (l reduced by ~amount*0.45 of
|
||||
// itself) and barely bump saturation. hexToHsl here returns 0–100, so the
|
||||
// spec's 0–1 saturation step (`amount * 0.06`) is scaled by 100.
|
||||
export function darkenHex(hex: string, amount: number): string {
|
||||
const [h, s, l] = hexToHsl(hex);
|
||||
const newL = Math.max(0, l - l * amount * 0.45);
|
||||
const newS = Math.min(100, s + amount * 6);
|
||||
return hslToHex(h, newS, newL);
|
||||
}
|
||||
|
||||
export interface ColorPair {
|
||||
color: string;
|
||||
colorDark: string;
|
||||
}
|
||||
|
||||
// Distinct-name cardinality is bounded by deployment service count (~10s, not 1000s),
|
||||
// so unbounded growth is not a concern.
|
||||
const cache = new Map<string, ColorPair>();
|
||||
|
||||
export function generateColorPair(name: string): ColorPair {
|
||||
const hit = cache.get(name);
|
||||
if (hit) {
|
||||
return hit;
|
||||
}
|
||||
const base = PALETTE_V3[hashFn(name) % PALETTE_V3.length];
|
||||
const result: ColorPair = {
|
||||
color: base,
|
||||
colorDark: darkenHex(base, 0.22),
|
||||
};
|
||||
cache.set(name, result);
|
||||
return result;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { Input } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import SignozModal from 'components/SignozModal/SignozModal';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import { useRenameFunnel } from 'hooks/TracesFunnels/useFunnels';
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
.ant-input-prefix {
|
||||
margin-inline-end: 6px;
|
||||
}
|
||||
|
||||
&,
|
||||
input {
|
||||
font-family: Inter;
|
||||
background: var(--l2-background);
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
font-style: normal;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ChangeEvent } from 'react';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button, Input, Popover, Tooltip } from 'antd';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button, Popover, Tooltip } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { ArrowDownWideNarrow, Check, Plus, Search } from '@signozhq/icons';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
|
||||
@@ -204,5 +204,12 @@
|
||||
background: var(--l2-background);
|
||||
height: 38px;
|
||||
width: 38px;
|
||||
|
||||
&:focus:not(:focus-visible),
|
||||
&.ant-btn:focus:not(:focus-visible) {
|
||||
border-color: var(--l2-border);
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,11 @@ package logparsingpipeline
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"log/slog"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||
"github.com/SigNoz/signoz/pkg/types/pipelinetypes"
|
||||
@@ -26,6 +23,13 @@ var (
|
||||
CodeCollectorConfigLogsPipelineNotFound = errors.MustNewCode("collector_config_logs_pipeline_not_found")
|
||||
)
|
||||
|
||||
const (
|
||||
memoryLimiterProcessor = "memory_limiter"
|
||||
memoryLimiterProcessorPrefix = "memory_limiter/"
|
||||
batchProcessor = "batch"
|
||||
batchProcessorPrefix = "batch/"
|
||||
)
|
||||
|
||||
// check if the processors already exist
|
||||
// if yes then update the processor.
|
||||
// if something doesn't exists then remove it.
|
||||
@@ -79,6 +83,13 @@ func getOtelPipelineFromConfig(config map[string]interface{}) (*otelPipeline, er
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
// buildCollectorPipelineProcessorsList assembles the final processor list in the
|
||||
// required order:
|
||||
//
|
||||
// 1. memory_limiter processors (any processor named "memory_limiter" or "memory_limiter/<id>")
|
||||
// 2. signoz user-pipeline processors (in the order given by signozPipelineProcessorNames)
|
||||
// 3. custom processors (non-signoz, non-memory_limiter, non-batch processors from the current config)
|
||||
// 4. batch processors (any processor named "batch" or "batch/<id>") and anything after them
|
||||
func buildCollectorPipelineProcessorsList(
|
||||
currentCollectorProcessors []string,
|
||||
signozPipelineProcessorNames []string,
|
||||
@@ -86,90 +97,42 @@ func buildCollectorPipelineProcessorsList(
|
||||
lockLogsPipelineSpec.Lock()
|
||||
defer lockLogsPipelineSpec.Unlock()
|
||||
|
||||
exists := map[string]struct{}{}
|
||||
for _, v := range signozPipelineProcessorNames {
|
||||
exists[v] = struct{}{}
|
||||
// Build a set of user pipeline names so custom processors can skip duplicates.
|
||||
userPipelineSet := make(map[string]struct{}, len(signozPipelineProcessorNames))
|
||||
for _, p := range signozPipelineProcessorNames {
|
||||
userPipelineSet[p] = struct{}{}
|
||||
}
|
||||
|
||||
// removed the old processors which are not used
|
||||
var pipeline []string
|
||||
for _, procName := range currentCollectorProcessors {
|
||||
_, isInDesiredPipelineProcs := exists[procName]
|
||||
if isInDesiredPipelineProcs || !hasSignozPipelineProcessorPrefix(procName) {
|
||||
pipeline = append(pipeline, procName)
|
||||
}
|
||||
}
|
||||
var memoryLimiters []string
|
||||
var customProcessors []string
|
||||
batchIdx := -1
|
||||
|
||||
// create a reverse map of existing config processors and their position
|
||||
existing := map[string]int{}
|
||||
for i, p := range pipeline {
|
||||
name := p
|
||||
existing[name] = i
|
||||
}
|
||||
|
||||
// create mapping from our logsParserPipeline to position in existing processors (from current config)
|
||||
// this means, if "batch" holds position 3 in the current effective config, and 2 in our config, the map will be [2]: 3
|
||||
specVsExistingMap := map[int]int{}
|
||||
existingVsSpec := map[int]int{}
|
||||
|
||||
// go through plan and map its elements to current positions in effective config
|
||||
for i, m := range signozPipelineProcessorNames {
|
||||
if loc, ok := existing[m]; ok {
|
||||
specVsExistingMap[i] = loc
|
||||
existingVsSpec[loc] = i
|
||||
}
|
||||
}
|
||||
|
||||
lastMatched := 0
|
||||
newPipeline := []string{}
|
||||
|
||||
for i := 0; i < len(signozPipelineProcessorNames); i++ {
|
||||
m := signozPipelineProcessorNames[i]
|
||||
if loc, ok := specVsExistingMap[i]; ok {
|
||||
for j := lastMatched; j < loc; j++ {
|
||||
if hasSignozPipelineProcessorPrefix(pipeline[j]) {
|
||||
delete(specVsExistingMap, existingVsSpec[j])
|
||||
} else {
|
||||
newPipeline = append(newPipeline, pipeline[j])
|
||||
}
|
||||
for idx, p := range currentCollectorProcessors {
|
||||
switch {
|
||||
case p == batchProcessor || strings.HasPrefix(p, batchProcessorPrefix):
|
||||
batchIdx = idx
|
||||
case p == memoryLimiterProcessor || strings.HasPrefix(p, memoryLimiterProcessorPrefix):
|
||||
memoryLimiters = append(memoryLimiters, p)
|
||||
case hasSignozPipelineProcessorPrefix(p):
|
||||
// stale signoz pipeline processor — dropped; signozPipelineProcessorNames is authoritative
|
||||
default:
|
||||
if _, inUserPipelines := userPipelineSet[p]; !inUserPipelines {
|
||||
customProcessors = append(customProcessors, p)
|
||||
}
|
||||
newPipeline = append(newPipeline, pipeline[loc])
|
||||
lastMatched = loc + 1
|
||||
} else {
|
||||
newPipeline = append(newPipeline, m)
|
||||
}
|
||||
|
||||
}
|
||||
if lastMatched < len(pipeline) {
|
||||
newPipeline = append(newPipeline, pipeline[lastMatched:]...)
|
||||
}
|
||||
|
||||
if checkDuplicateString(newPipeline) {
|
||||
// duplicates are most likely because the processor sequence in effective config conflicts
|
||||
// with the planned sequence as per planned pipeline
|
||||
return pipeline, fmt.Errorf("the effective config has an unexpected processor sequence: %v", pipeline)
|
||||
}
|
||||
|
||||
return newPipeline, nil
|
||||
}
|
||||
|
||||
func checkDuplicateString(pipeline []string) bool {
|
||||
exists := make(map[string]bool, len(pipeline))
|
||||
slog.Debug("checking duplicate processors in the pipeline", "pipeline", pipeline)
|
||||
for _, processor := range pipeline {
|
||||
name := processor
|
||||
if _, ok := exists[name]; ok {
|
||||
slog.Error(
|
||||
"duplicate processor name detected in generated collector config for log pipelines",
|
||||
"processor", processor,
|
||||
"pipeline", pipeline,
|
||||
)
|
||||
return true
|
||||
if batchIdx >= 0 {
|
||||
break
|
||||
}
|
||||
|
||||
exists[name] = true
|
||||
}
|
||||
return false
|
||||
|
||||
result := make([]string, 0, len(currentCollectorProcessors)+len(signozPipelineProcessorNames))
|
||||
result = append(result, memoryLimiters...)
|
||||
result = append(result, signozPipelineProcessorNames...)
|
||||
result = append(result, customProcessors...)
|
||||
if batchIdx >= 0 {
|
||||
result = append(result, currentCollectorProcessors[batchIdx:]...)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func GenerateCollectorConfigWithPipelines(config []byte, pipelines []pipelinetypes.GettablePipeline) ([]byte, error) {
|
||||
|
||||
@@ -106,107 +106,109 @@ func TestBuildLogParsingProcessors(t *testing.T) {
|
||||
}
|
||||
|
||||
var BuildLogsPipelineTestData = []struct {
|
||||
Name string
|
||||
currentPipeline []string
|
||||
logsPipeline []string
|
||||
expectedPipeline []string
|
||||
Name string
|
||||
fromCollector []string
|
||||
userPipelines []string
|
||||
finalOutput []string
|
||||
}{
|
||||
{
|
||||
Name: "Add new pipelines",
|
||||
currentPipeline: []string{"processor1", "processor2"},
|
||||
logsPipeline: []string{constants.LogsPPLPfx + "a", constants.LogsPPLPfx + "b"},
|
||||
expectedPipeline: []string{constants.LogsPPLPfx + "a", constants.LogsPPLPfx + "b", "processor1", "processor2"},
|
||||
Name: "Add new pipelines",
|
||||
fromCollector: []string{"processor1", "processor2"},
|
||||
userPipelines: []string{constants.LogsPPLPfx + "a", constants.LogsPPLPfx + "b"},
|
||||
finalOutput: []string{constants.LogsPPLPfx + "a", constants.LogsPPLPfx + "b", "processor1", "processor2"},
|
||||
},
|
||||
{
|
||||
Name: "Add new pipeline and respect custom processors",
|
||||
currentPipeline: []string{constants.LogsPPLPfx + "a", "processor1", constants.LogsPPLPfx + "b", "processor2"},
|
||||
logsPipeline: []string{constants.LogsPPLPfx + "a", constants.LogsPPLPfx + "b", constants.LogsPPLPfx + "c"},
|
||||
expectedPipeline: []string{constants.LogsPPLPfx + "a", "processor1", constants.LogsPPLPfx + "b", constants.LogsPPLPfx + "c", "processor2"},
|
||||
Name: "Add new pipeline and respect custom processors",
|
||||
fromCollector: []string{constants.LogsPPLPfx + "a", "processor1", constants.LogsPPLPfx + "b", "processor2"},
|
||||
userPipelines: []string{constants.LogsPPLPfx + "a", constants.LogsPPLPfx + "b", constants.LogsPPLPfx + "c"},
|
||||
finalOutput: []string{constants.LogsPPLPfx + "a", constants.LogsPPLPfx + "b", constants.LogsPPLPfx + "c", "processor1", "processor2"},
|
||||
},
|
||||
{
|
||||
Name: "Add new pipeline and respect custom processors",
|
||||
currentPipeline: []string{constants.LogsPPLPfx + "a", "processor1", constants.LogsPPLPfx + "b", "processor2"},
|
||||
logsPipeline: []string{constants.LogsPPLPfx + "a", constants.LogsPPLPfx + "b", constants.LogsPPLPfx + "c", constants.LogsPPLPfx + "d"},
|
||||
expectedPipeline: []string{constants.LogsPPLPfx + "a", "processor1", constants.LogsPPLPfx + "b", constants.LogsPPLPfx + "c", constants.LogsPPLPfx + "d", "processor2"},
|
||||
Name: "Add new pipeline and respect custom processors with more",
|
||||
fromCollector: []string{constants.LogsPPLPfx + "a", "processor1", constants.LogsPPLPfx + "b", "processor2"},
|
||||
userPipelines: []string{constants.LogsPPLPfx + "a", constants.LogsPPLPfx + "b", constants.LogsPPLPfx + "c", constants.LogsPPLPfx + "d"},
|
||||
finalOutput: []string{constants.LogsPPLPfx + "a", constants.LogsPPLPfx + "b", constants.LogsPPLPfx + "c", constants.LogsPPLPfx + "d", "processor1", "processor2"},
|
||||
},
|
||||
{
|
||||
Name: "Add new pipeline and respect custom processors in the beginning and middle",
|
||||
currentPipeline: []string{"processor1", constants.LogsPPLPfx + "a", "processor2", constants.LogsPPLPfx + "b", "batch"},
|
||||
logsPipeline: []string{constants.LogsPPLPfx + "a", constants.LogsPPLPfx + "b", constants.LogsPPLPfx + "c"},
|
||||
expectedPipeline: []string{"processor1", constants.LogsPPLPfx + "a", "processor2", constants.LogsPPLPfx + "b", constants.LogsPPLPfx + "c", "batch"},
|
||||
Name: "Add new pipeline and respect custom processors in the beginning and middle",
|
||||
fromCollector: []string{"processor1", constants.LogsPPLPfx + "a", "processor2", constants.LogsPPLPfx + "b", "batch"},
|
||||
userPipelines: []string{constants.LogsPPLPfx + "a", constants.LogsPPLPfx + "b", constants.LogsPPLPfx + "c"},
|
||||
finalOutput: []string{constants.LogsPPLPfx + "a", constants.LogsPPLPfx + "b", constants.LogsPPLPfx + "c", "processor1", "processor2", "batch"},
|
||||
},
|
||||
{
|
||||
Name: "Remove old pipeline add add new",
|
||||
currentPipeline: []string{constants.LogsPPLPfx + "a", constants.LogsPPLPfx + "b", "processor1", "processor2"},
|
||||
logsPipeline: []string{constants.LogsPPLPfx + "a"},
|
||||
expectedPipeline: []string{constants.LogsPPLPfx + "a", "processor1", "processor2"},
|
||||
Name: "Remove old pipeline add add new",
|
||||
fromCollector: []string{constants.LogsPPLPfx + "a", constants.LogsPPLPfx + "b", "processor1", "processor2"},
|
||||
userPipelines: []string{constants.LogsPPLPfx + "a"},
|
||||
finalOutput: []string{constants.LogsPPLPfx + "a", "processor1", "processor2"},
|
||||
},
|
||||
{
|
||||
Name: "Remove old pipeline from middle",
|
||||
currentPipeline: []string{"processor1", "processor2", constants.LogsPPLPfx + "a", "processor3", constants.LogsPPLPfx + "b", "batch"},
|
||||
logsPipeline: []string{constants.LogsPPLPfx + "a"},
|
||||
expectedPipeline: []string{"processor1", "processor2", constants.LogsPPLPfx + "a", "processor3", "batch"},
|
||||
Name: "Remove old pipeline from middle",
|
||||
fromCollector: []string{"processor1", "processor2", constants.LogsPPLPfx + "a", "processor3", constants.LogsPPLPfx + "b", "batch"},
|
||||
userPipelines: []string{constants.LogsPPLPfx + "a"},
|
||||
finalOutput: []string{constants.LogsPPLPfx + "a", "processor1", "processor2", "processor3", "batch"},
|
||||
},
|
||||
{
|
||||
Name: "Remove old pipeline from middle and add new pipeline",
|
||||
currentPipeline: []string{"processor1", "processor2", constants.LogsPPLPfx + "a", "processor3", constants.LogsPPLPfx + "b", "batch"},
|
||||
logsPipeline: []string{constants.LogsPPLPfx + "a", constants.LogsPPLPfx + "c"},
|
||||
expectedPipeline: []string{"processor1", "processor2", constants.LogsPPLPfx + "a", constants.LogsPPLPfx + "c", "processor3", "batch"},
|
||||
Name: "Remove old pipeline from middle and add new pipeline",
|
||||
fromCollector: []string{"memory_limiter", "processor1", "processor2", constants.LogsPPLPfx + "a", "processor3", constants.LogsPPLPfx + "b", "batch"},
|
||||
userPipelines: []string{constants.LogsPPLPfx + "a", constants.LogsPPLPfx + "c"},
|
||||
finalOutput: []string{"memory_limiter", constants.LogsPPLPfx + "a", constants.LogsPPLPfx + "c", "processor1", "processor2", "processor3", "batch"},
|
||||
},
|
||||
{
|
||||
Name: "Remove multiple old pipelines from middle and add multiple new ones",
|
||||
currentPipeline: []string{"processor1", constants.LogsPPLPfx + "a", "processor2", constants.LogsPPLPfx + "b", "processor3", constants.LogsPPLPfx + "c", "processor4", constants.LogsPPLPfx + "d", "processor5", "batch"},
|
||||
logsPipeline: []string{constants.LogsPPLPfx + "a", constants.LogsPPLPfx + "a1", constants.LogsPPLPfx + "c", constants.LogsPPLPfx + "c1"},
|
||||
expectedPipeline: []string{"processor1", constants.LogsPPLPfx + "a", constants.LogsPPLPfx + "a1", "processor2", "processor3", constants.LogsPPLPfx + "c", constants.LogsPPLPfx + "c1", "processor4", "processor5", "batch"},
|
||||
},
|
||||
|
||||
// working
|
||||
{
|
||||
Name: "rearrange pipelines",
|
||||
currentPipeline: []string{"processor1", "processor2", constants.LogsPPLPfx + "_a", "processor3", constants.LogsPPLPfx + "_b", "batch"},
|
||||
logsPipeline: []string{constants.LogsPPLPfx + "_b", constants.LogsPPLPfx + "_a"},
|
||||
expectedPipeline: []string{"processor1", "processor2", "processor3", constants.LogsPPLPfx + "_b", constants.LogsPPLPfx + "_a", "batch"},
|
||||
Name: "Remove multiple old pipelines from middle and add multiple new ones",
|
||||
fromCollector: []string{"processor1", constants.LogsPPLPfx + "a", "processor2", constants.LogsPPLPfx + "b", "processor3", constants.LogsPPLPfx + "c", "processor4", constants.LogsPPLPfx + "d", "processor5", "batch"},
|
||||
userPipelines: []string{constants.LogsPPLPfx + "a", constants.LogsPPLPfx + "a1", constants.LogsPPLPfx + "c", constants.LogsPPLPfx + "c1"},
|
||||
finalOutput: []string{constants.LogsPPLPfx + "a", constants.LogsPPLPfx + "a1", constants.LogsPPLPfx + "c", constants.LogsPPLPfx + "c1", "processor1", "processor2", "processor3", "processor4", "processor5", "batch"},
|
||||
},
|
||||
{
|
||||
Name: "rearrange pipelines with new processor",
|
||||
currentPipeline: []string{"processor1", "processor2", constants.LogsPPLPfx + "_a", "processor3", constants.LogsPPLPfx + "_b", "batch"},
|
||||
logsPipeline: []string{constants.LogsPPLPfx + "_b", constants.LogsPPLPfx + "_a", constants.LogsPPLPfx + "_c"},
|
||||
expectedPipeline: []string{"processor1", "processor2", "processor3", constants.LogsPPLPfx + "_b", constants.LogsPPLPfx + "_a", constants.LogsPPLPfx + "_c", "batch"},
|
||||
// expectedPipeline: []string{"processor1", "processor2", constants.LogsPPLPfx + "_b", "processor3", constants.LogsPPLPfx + "_a", constants.LogsPPLPfx + "_c", "batch"},
|
||||
Name: "rearrange pipelines",
|
||||
fromCollector: []string{"processor1", "processor2", constants.LogsPPLPfx + "_a", "processor3", constants.LogsPPLPfx + "_b", "batch"},
|
||||
userPipelines: []string{constants.LogsPPLPfx + "_b", constants.LogsPPLPfx + "_a"},
|
||||
finalOutput: []string{constants.LogsPPLPfx + "_b", constants.LogsPPLPfx + "_a", "processor1", "processor2", "processor3", "batch"},
|
||||
},
|
||||
{
|
||||
Name: "delete processor",
|
||||
currentPipeline: []string{"processor1", "processor2", constants.LogsPPLPfx + "_a", "processor3", constants.LogsPPLPfx + "_b", "batch"},
|
||||
logsPipeline: []string{},
|
||||
expectedPipeline: []string{"processor1", "processor2", "processor3", "batch"},
|
||||
Name: "rearrange pipelines with new processor",
|
||||
fromCollector: []string{"processor1", "processor2", constants.LogsPPLPfx + "_a", "processor3", constants.LogsPPLPfx + "_b", "batch"},
|
||||
userPipelines: []string{constants.LogsPPLPfx + "_b", constants.LogsPPLPfx + "_a", constants.LogsPPLPfx + "_c"},
|
||||
finalOutput: []string{constants.LogsPPLPfx + "_b", constants.LogsPPLPfx + "_a", constants.LogsPPLPfx + "_c", "processor1", "processor2", "processor3", "batch"},
|
||||
},
|
||||
{
|
||||
Name: "last to first",
|
||||
currentPipeline: []string{"processor1", "processor2", constants.LogsPPLPfx + "_a", "processor3", "processor4", constants.LogsPPLPfx + "_b", "batch", constants.LogsPPLPfx + "_c"},
|
||||
logsPipeline: []string{constants.LogsPPLPfx + "_c", constants.LogsPPLPfx + "_a", constants.LogsPPLPfx + "_b"},
|
||||
expectedPipeline: []string{"processor1", "processor2", "processor3", "processor4", "batch", constants.LogsPPLPfx + "_c", constants.LogsPPLPfx + "_a", constants.LogsPPLPfx + "_b"},
|
||||
Name: "delete processor",
|
||||
fromCollector: []string{"processor1", "processor2", constants.LogsPPLPfx + "_a", "processor3", constants.LogsPPLPfx + "_b", "batch"},
|
||||
userPipelines: []string{},
|
||||
finalOutput: []string{"processor1", "processor2", "processor3", "batch"},
|
||||
},
|
||||
{
|
||||
Name: "multiple rearrange pipelines",
|
||||
currentPipeline: []string{"processor1", "processor2", constants.LogsPPLPfx + "_a", "processor3", constants.LogsPPLPfx + "_b", "batch", constants.LogsPPLPfx + "_c", "processor4", "processor5", constants.LogsPPLPfx + "_d", "processor6", "processor7"},
|
||||
logsPipeline: []string{constants.LogsPPLPfx + "_b", constants.LogsPPLPfx + "_a", constants.LogsPPLPfx + "_d", constants.LogsPPLPfx + "_c", constants.LogsPPLPfx + "_e"},
|
||||
expectedPipeline: []string{"processor1", "processor2", "processor3", constants.LogsPPLPfx + "_b", constants.LogsPPLPfx + "_a", "batch", "processor4", "processor5", constants.LogsPPLPfx + "_d", constants.LogsPPLPfx + "_c", constants.LogsPPLPfx + "_e", "processor6", "processor7"},
|
||||
Name: "last to first",
|
||||
fromCollector: []string{"processor1", "processor2", constants.LogsPPLPfx + "_a", "processor3", "processor4", constants.LogsPPLPfx + "_b", "batch", constants.LogsPPLPfx + "_c"},
|
||||
userPipelines: []string{constants.LogsPPLPfx + "_c", constants.LogsPPLPfx + "_a", constants.LogsPPLPfx + "_b"},
|
||||
finalOutput: []string{constants.LogsPPLPfx + "_c", constants.LogsPPLPfx + "_a", constants.LogsPPLPfx + "_b", "processor1", "processor2", "processor3", "processor4", "batch", constants.LogsPPLPfx + "_c"},
|
||||
},
|
||||
{
|
||||
Name: "multiple rearrange with new pipelines",
|
||||
currentPipeline: []string{"processor1", "processor2", constants.LogsPPLPfx + "_a", "processor3", constants.LogsPPLPfx + "_b", "batch", constants.LogsPPLPfx + "_c", "processor4", "processor5", constants.LogsPPLPfx + "_d", "processor6", "processor7"},
|
||||
logsPipeline: []string{constants.LogsPPLPfx + "_z", constants.LogsPPLPfx + "_b", constants.LogsPPLPfx + "_a", constants.LogsPPLPfx + "_d", constants.LogsPPLPfx + "_c", constants.LogsPPLPfx + "_e"},
|
||||
expectedPipeline: []string{constants.LogsPPLPfx + "_z", "processor1", "processor2", "processor3", constants.LogsPPLPfx + "_b", constants.LogsPPLPfx + "_a", "batch", "processor4", "processor5", constants.LogsPPLPfx + "_d", constants.LogsPPLPfx + "_c", constants.LogsPPLPfx + "_e", "processor6", "processor7"},
|
||||
Name: "multiple rearrange pipelines",
|
||||
fromCollector: []string{"processor1", "processor2", constants.LogsPPLPfx + "_a", "processor3", constants.LogsPPLPfx + "_b", "batch", constants.LogsPPLPfx + "_c", "processor4", "processor5", constants.LogsPPLPfx + "_d", "processor6", "processor7"},
|
||||
userPipelines: []string{constants.LogsPPLPfx + "_b", constants.LogsPPLPfx + "_a", constants.LogsPPLPfx + "_d", constants.LogsPPLPfx + "_c", constants.LogsPPLPfx + "_e"},
|
||||
finalOutput: []string{constants.LogsPPLPfx + "_b", constants.LogsPPLPfx + "_a", constants.LogsPPLPfx + "_d", constants.LogsPPLPfx + "_c", constants.LogsPPLPfx + "_e", "processor1", "processor2", "processor3", "batch", constants.LogsPPLPfx + "_c", "processor4", "processor5", constants.LogsPPLPfx + "_d", "processor6", "processor7"},
|
||||
},
|
||||
{
|
||||
Name: "multiple rearrange with new pipelines",
|
||||
fromCollector: []string{"memory_limiter", "processor1", "processor2", constants.LogsPPLPfx + "_a", "processor3", constants.LogsPPLPfx + "_b", "batch", constants.LogsPPLPfx + "_c", "processor4", "processor5", constants.LogsPPLPfx + "_d", "processor6", "processor7"},
|
||||
userPipelines: []string{constants.LogsPPLPfx + "_z", constants.LogsPPLPfx + "_b", constants.LogsPPLPfx + "_a", constants.LogsPPLPfx + "_d", constants.LogsPPLPfx + "_c", constants.LogsPPLPfx + "_e"},
|
||||
finalOutput: []string{"memory_limiter", constants.LogsPPLPfx + "_z", constants.LogsPPLPfx + "_b", constants.LogsPPLPfx + "_a", constants.LogsPPLPfx + "_d", constants.LogsPPLPfx + "_c", constants.LogsPPLPfx + "_e", "processor1", "processor2", "processor3", "batch", constants.LogsPPLPfx + "_c", "processor4", "processor5", constants.LogsPPLPfx + "_d", "processor6", "processor7"},
|
||||
},
|
||||
{
|
||||
Name: "Prefixed proc in desired set not duplicated from others",
|
||||
fromCollector: []string{"memory_limiter/logs", "custom_proc", "resourcedetection", "batch/logs"},
|
||||
userPipelines: []string{"custom_proc", constants.LogsPPLPfx + "a"},
|
||||
finalOutput: []string{"memory_limiter/logs", "custom_proc", constants.LogsPPLPfx + "a", "resourcedetection", "batch/logs"},
|
||||
},
|
||||
}
|
||||
|
||||
func TestBuildLogsPipeline(t *testing.T) {
|
||||
for _, test := range BuildLogsPipelineTestData {
|
||||
Convey(test.Name, t, func() {
|
||||
v, err := buildCollectorPipelineProcessorsList(test.currentPipeline, test.logsPipeline)
|
||||
v, err := buildCollectorPipelineProcessorsList(test.fromCollector, test.userPipelines)
|
||||
So(err, ShouldBeNil)
|
||||
fmt.Println(test.Name, "\n", test.currentPipeline, "\n", v, "\n", test.expectedPipeline)
|
||||
So(v, ShouldResemble, test.expectedPipeline)
|
||||
So(v, ShouldResemble, test.finalOutput)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user