mirror of
https://github.com/SigNoz/signoz.git
synced 2026-05-20 17:00:29 +01:00
Compare commits
3 Commits
issue_5015
...
chore/migr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94621e41d3 | ||
|
|
847bc71f4e | ||
|
|
8d7d3e5c64 |
6
.github/workflows/e2eci.yaml
vendored
6
.github/workflows/e2eci.yaml
vendored
@@ -45,15 +45,9 @@ jobs:
|
||||
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))) && contains(github.event.pull_request.labels.*.name, 'safe-to-e2e')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
SIGNOZ_BUILDX_GHA_SCOPE: signoz-e2e
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: setup-buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: expose-gha-runtime
|
||||
uses: crazy-max/ghaction-github-runtime@v3
|
||||
- name: python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
|
||||
6
.github/workflows/integrationci.yaml
vendored
6
.github/workflows/integrationci.yaml
vendored
@@ -69,15 +69,9 @@ jobs:
|
||||
((github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
|
||||
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))) && contains(github.event.pull_request.labels.*.name, 'safe-to-integrate')
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
SIGNOZ_BUILDX_GHA_SCOPE: signoz-integration
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: setup-buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: expose-gha-runtime
|
||||
uses: crazy-max/ghaction-github-runtime@v3
|
||||
- name: python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
|
||||
16
Makefile
16
Makefile
@@ -34,7 +34,6 @@ DOCKER_BUILD_ARCHS_ENTERPRISE = $(addprefix docker-build-enterprise-,$(ARCHS))
|
||||
DOCKERFILE_ENTERPRISE = $(SRC)/cmd/enterprise/Dockerfile
|
||||
DOCKER_REGISTRY_ENTERPRISE ?= docker.io/signoz/signoz
|
||||
JS_BUILD_CONTEXT = $(SRC)/frontend
|
||||
DOCKER_BUILDX_PRUNE_FLAGS ?= --force
|
||||
|
||||
##############################################################
|
||||
# directories
|
||||
@@ -230,21 +229,6 @@ py-clean: ## Clear all pycache and pytest cache from tests directory recursively
|
||||
@find tests -type f -name "*.pyo" -delete 2>/dev/null || true
|
||||
@echo ">> python cache cleaned"
|
||||
|
||||
.PHONY: py-docker-clean
|
||||
py-docker-clean: ## Remove Docker image and build caches used by python integration tests
|
||||
@echo ">> removing SigNoz integration test image"
|
||||
@docker image rm -f signoz:integration 2>/dev/null || true
|
||||
@echo ">> removing local integration buildx cache directories"
|
||||
@rm -rf /tmp/signoz-integration-buildx-cache /tmp/signoz-integration-buildx-cache-next /tmp/signoz-e2e-buildx-cache /tmp/signoz-e2e-buildx-cache-next
|
||||
@echo ">> pruning docker buildx cache with flags: $(DOCKER_BUILDX_PRUNE_FLAGS)"
|
||||
@docker buildx prune $(DOCKER_BUILDX_PRUNE_FLAGS)
|
||||
|
||||
.PHONY: py-test-clean
|
||||
py-test-clean: ## Tear down python test stack and remove python/Docker test caches
|
||||
@$(MAKE) py-test-teardown || true
|
||||
@$(MAKE) py-clean
|
||||
@$(MAKE) py-docker-clean
|
||||
|
||||
|
||||
##############################################################
|
||||
# generate commands
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# syntax=docker/dockerfile:1.13
|
||||
|
||||
FROM golang:1.25-bookworm
|
||||
|
||||
ARG OS="linux"
|
||||
@@ -23,8 +21,7 @@ RUN set -eux; \
|
||||
|
||||
COPY go.mod go.sum ./
|
||||
|
||||
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||
go mod download
|
||||
RUN go mod download
|
||||
|
||||
COPY ./cmd/ ./cmd/
|
||||
COPY ./ee/ ./ee/
|
||||
@@ -32,9 +29,7 @@ COPY ./pkg/ ./pkg/
|
||||
COPY ./templates/email /root/templates
|
||||
|
||||
COPY Makefile Makefile
|
||||
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=cache,target=/root/.cache/go-build \
|
||||
TARGET_DIR=/root ARCHS=${TARGETARCH} ZEUS_URL=${ZEUSURL} LICENSE_URL=${ZEUSURL}/api/v1 make go-build-enterprise-race
|
||||
RUN TARGET_DIR=/root ARCHS=${TARGETARCH} ZEUS_URL=${ZEUSURL} LICENSE_URL=${ZEUSURL}/api/v1 make go-build-enterprise-race
|
||||
RUN mv /root/linux-${TARGETARCH}/signoz /root/signoz
|
||||
|
||||
RUN chmod 755 /root /root/signoz
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# syntax=docker/dockerfile:1.13
|
||||
|
||||
FROM node:22-bookworm AS build
|
||||
|
||||
WORKDIR /opt/
|
||||
@@ -32,8 +30,7 @@ RUN set -eux; \
|
||||
|
||||
COPY go.mod go.sum ./
|
||||
|
||||
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||
go mod download
|
||||
RUN go mod download
|
||||
|
||||
COPY ./cmd/ ./cmd/
|
||||
COPY ./ee/ ./ee/
|
||||
@@ -41,9 +38,7 @@ COPY ./pkg/ ./pkg/
|
||||
COPY ./templates/email /root/templates
|
||||
|
||||
COPY Makefile Makefile
|
||||
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=cache,target=/root/.cache/go-build \
|
||||
TARGET_DIR=/root ARCHS=${TARGETARCH} ZEUS_URL=${ZEUSURL} LICENSE_URL=${ZEUSURL}/api/v1 make go-build-enterprise-race
|
||||
RUN TARGET_DIR=/root ARCHS=${TARGETARCH} ZEUS_URL=${ZEUSURL} LICENSE_URL=${ZEUSURL}/api/v1 make go-build-enterprise-race
|
||||
RUN mv /root/linux-${TARGETARCH}/signoz /root/signoz
|
||||
|
||||
COPY --from=build /opt/build ./web/
|
||||
|
||||
12
frontend/src/components/InputNumber/InputNumber.styles.scss
Normal file
12
frontend/src/components/InputNumber/InputNumber.styles.scss
Normal file
@@ -0,0 +1,12 @@
|
||||
/* Hide native browser spinners for our number input, matching antd defaults. */
|
||||
.signoz-input-number {
|
||||
&::-webkit-outer-spin-button,
|
||||
&::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&[type='number'] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
}
|
||||
130
frontend/src/components/InputNumber/InputNumber.tsx
Normal file
130
frontend/src/components/InputNumber/InputNumber.tsx
Normal file
@@ -0,0 +1,130 @@
|
||||
import './InputNumber.styles.scss';
|
||||
|
||||
import {
|
||||
ChangeEvent,
|
||||
CSSProperties,
|
||||
FocusEventHandler,
|
||||
forwardRef,
|
||||
KeyboardEventHandler,
|
||||
ReactNode,
|
||||
} from 'react';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import cx from 'classnames';
|
||||
|
||||
export type InputNumberProps = {
|
||||
value?: number | null;
|
||||
defaultValue?: number | null;
|
||||
onChange?: (value: number | null) => void;
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
/** When set, values emitted via onChange are rounded to this many decimals. */
|
||||
precision?: number;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
prefix?: ReactNode;
|
||||
suffix?: ReactNode;
|
||||
className?: string;
|
||||
rootClassName?: string;
|
||||
style?: CSSProperties;
|
||||
id?: string;
|
||||
name?: string;
|
||||
testId?: string;
|
||||
autoFocus?: boolean;
|
||||
onKeyDown?: KeyboardEventHandler<HTMLInputElement>;
|
||||
onBlur?: FocusEventHandler<HTMLInputElement>;
|
||||
onFocus?: FocusEventHandler<HTMLInputElement>;
|
||||
'aria-label'?: string;
|
||||
'data-testid'?: string;
|
||||
};
|
||||
|
||||
const toInputValue = (value: number | null | undefined): string | undefined => {
|
||||
if (value === null || value === undefined || Number.isNaN(value)) {
|
||||
return '';
|
||||
}
|
||||
return String(value);
|
||||
};
|
||||
|
||||
const parseValue = (raw: string, precision?: number): number | null => {
|
||||
if (raw === '' || raw === '-') {
|
||||
return null;
|
||||
}
|
||||
const parsed = Number(raw);
|
||||
if (Number.isNaN(parsed)) {
|
||||
return null;
|
||||
}
|
||||
if (precision === undefined) {
|
||||
return parsed;
|
||||
}
|
||||
const factor = 10 ** precision;
|
||||
return Math.round(parsed * factor) / factor;
|
||||
};
|
||||
|
||||
const InputNumber = forwardRef<HTMLInputElement, InputNumberProps>(
|
||||
(
|
||||
{
|
||||
value,
|
||||
defaultValue,
|
||||
onChange,
|
||||
min,
|
||||
max,
|
||||
step,
|
||||
precision,
|
||||
placeholder,
|
||||
disabled,
|
||||
prefix,
|
||||
suffix,
|
||||
className,
|
||||
rootClassName,
|
||||
style,
|
||||
id,
|
||||
name,
|
||||
testId,
|
||||
autoFocus,
|
||||
onKeyDown,
|
||||
onBlur,
|
||||
onFocus,
|
||||
'aria-label': ariaLabel,
|
||||
'data-testid': dataTestId,
|
||||
},
|
||||
ref,
|
||||
): JSX.Element => {
|
||||
const handleChange = (event: ChangeEvent<HTMLInputElement>): void => {
|
||||
onChange?.(parseValue(event.target.value, precision));
|
||||
};
|
||||
|
||||
return (
|
||||
<Input
|
||||
ref={ref}
|
||||
type="number"
|
||||
value={value === undefined ? undefined : toInputValue(value)}
|
||||
defaultValue={
|
||||
defaultValue === undefined ? undefined : toInputValue(defaultValue)
|
||||
}
|
||||
onChange={handleChange}
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
placeholder={placeholder}
|
||||
disabled={disabled}
|
||||
prefix={prefix}
|
||||
suffix={suffix}
|
||||
className={cx('signoz-input-number', className)}
|
||||
containerClassName={cx('signoz-input-number-container', rootClassName)}
|
||||
style={style}
|
||||
id={id}
|
||||
name={name}
|
||||
testId={testId ?? dataTestId}
|
||||
autoFocus={autoFocus}
|
||||
onKeyDown={onKeyDown}
|
||||
onBlur={onBlur}
|
||||
onFocus={onFocus}
|
||||
aria-label={ariaLabel}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
InputNumber.displayName = 'InputNumber';
|
||||
|
||||
export default InputNumber;
|
||||
2
frontend/src/components/InputNumber/index.ts
Normal file
2
frontend/src/components/InputNumber/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default } from './InputNumber';
|
||||
export type { InputNumberProps } from './InputNumber';
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { Button, Input, InputNumber, Popover, Tooltip } from 'antd';
|
||||
import { Button, Input, Popover, Tooltip } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import type { DefaultOptionType } from 'antd/es/select';
|
||||
import cx from 'classnames';
|
||||
import InputNumber from 'components/InputNumber';
|
||||
import { LogViewMode } from 'container/LogsTable';
|
||||
import { FontSize, OptionsMenuConfig } from 'container/OptionsMenu/types';
|
||||
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
||||
|
||||
@@ -3,14 +3,13 @@ import {
|
||||
Checkbox,
|
||||
Collapse,
|
||||
Form,
|
||||
InputNumber,
|
||||
InputNumberProps,
|
||||
Select,
|
||||
SelectProps,
|
||||
Space,
|
||||
} from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import type { DefaultOptionType } from 'antd/es/select';
|
||||
import InputNumber from 'components/InputNumber';
|
||||
import {
|
||||
getCategoryByOptionId,
|
||||
getCategorySelectOptionByName,
|
||||
@@ -289,7 +288,7 @@ function RuleOptions({
|
||||
</Form.Item>
|
||||
);
|
||||
|
||||
const onChange: InputNumberProps['onChange'] = (value): void => {
|
||||
const onChange = (value: number | null): void => {
|
||||
setAlertDef({
|
||||
...alertDef,
|
||||
condition: {
|
||||
@@ -391,11 +390,9 @@ function RuleOptions({
|
||||
<Space direction="horizontal" align="center">
|
||||
<Form.Item noStyle>
|
||||
<InputNumber
|
||||
addonBefore={t('field_threshold')}
|
||||
prefix={t('field_threshold')}
|
||||
value={alertDef?.condition?.target}
|
||||
onChange={onChange}
|
||||
type="number"
|
||||
onWheel={(e): void => e.currentTarget.blur()}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
@@ -455,8 +452,6 @@ function RuleOptions({
|
||||
},
|
||||
});
|
||||
}}
|
||||
type="number"
|
||||
onWheel={(e): void => e.currentTarget.blur()}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Typography.Text>{t('text_for')}</Typography.Text>
|
||||
@@ -494,8 +489,6 @@ function RuleOptions({
|
||||
},
|
||||
});
|
||||
}}
|
||||
type="number"
|
||||
onWheel={(e): void => e.currentTarget.blur()}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Typography.Text>{t('text_num_points')}</Typography.Text>
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
DatePicker,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
Modal,
|
||||
Row,
|
||||
Select,
|
||||
@@ -23,6 +22,7 @@ import {
|
||||
Tooltip,
|
||||
} from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import InputNumber from 'components/InputNumber';
|
||||
import type { NotificationInstance } from 'antd/es/notification/interface';
|
||||
import type { CollapseProps } from 'antd/lib';
|
||||
import {
|
||||
@@ -1212,7 +1212,7 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
<Form.Item name="dailyLimit" key="dailyLimit">
|
||||
<InputNumber
|
||||
disabled={!activeSignal?.config?.day?.enabled}
|
||||
addonAfter={
|
||||
suffix={
|
||||
<Select defaultValue="GiB" disabled>
|
||||
<Option value="TiB">TiB</Option>
|
||||
<Option value="GiB">GiB</Option>
|
||||
@@ -1235,7 +1235,7 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
<Form.Item name="dailyCount" key="dailyCount">
|
||||
<InputNumber
|
||||
placeholder="Enter max # of samples/day"
|
||||
addonAfter={
|
||||
suffix={
|
||||
<Form.Item
|
||||
name="dailyCountUnit"
|
||||
noStyle
|
||||
@@ -1302,7 +1302,7 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
<Form.Item name="secondsLimit" key="secondsLimit">
|
||||
<InputNumber
|
||||
disabled={!activeSignal?.config?.second?.enabled}
|
||||
addonAfter={
|
||||
suffix={
|
||||
<Select defaultValue="GiB" disabled>
|
||||
<Option value="TiB">TiB</Option>
|
||||
<Option value="GiB">GiB</Option>
|
||||
@@ -1325,7 +1325,7 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
<Form.Item name="secondsCount" key="secondsCount">
|
||||
<InputNumber
|
||||
placeholder="Enter max # of samples/s"
|
||||
addonAfter={
|
||||
suffix={
|
||||
<Form.Item
|
||||
name="secondsCountUnit"
|
||||
noStyle
|
||||
|
||||
@@ -22,3 +22,9 @@
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
// FieldRenderer is used inside log/trace/metric detail drawers (z-index 1000).
|
||||
// The design-system tooltip defaults to z-index 50 and would render behind them.
|
||||
.field-renderer-tooltip-content {
|
||||
--tooltip-z-index: 1000;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Divider, Tooltip } from 'antd';
|
||||
import { Divider } from 'antd';
|
||||
import { TooltipSimple } from '@signozhq/ui/tooltip';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
|
||||
import { TagContainer, TagLabel, TagValue } from './FieldRenderer.styles';
|
||||
@@ -7,6 +8,10 @@ import { getFieldAttributes } from './utils';
|
||||
|
||||
import './FieldRenderer.styles.scss';
|
||||
|
||||
const TOOLTIP_CONTENT_PROPS = {
|
||||
className: 'field-renderer-tooltip-content',
|
||||
};
|
||||
|
||||
function FieldRenderer({ field }: FieldRendererProps): JSX.Element {
|
||||
const { dataType, newField, logType } = getFieldAttributes(field);
|
||||
|
||||
@@ -14,11 +19,16 @@ function FieldRenderer({ field }: FieldRendererProps): JSX.Element {
|
||||
<span className="field-renderer-container">
|
||||
{dataType && newField && logType ? (
|
||||
<>
|
||||
<Tooltip placement="left" title={newField} mouseLeaveDelay={0}>
|
||||
<TooltipSimple
|
||||
title={newField}
|
||||
side="left"
|
||||
tooltipContentProps={TOOLTIP_CONTENT_PROPS}
|
||||
arrow
|
||||
>
|
||||
<Typography.Text truncate={1} className="label">
|
||||
{newField}{' '}
|
||||
</Typography.Text>
|
||||
</Tooltip>
|
||||
</TooltipSimple>
|
||||
|
||||
<div className="tags">
|
||||
<TagContainer>
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button, Spin, Tooltip } from 'antd';
|
||||
import { Button, Spin } from 'antd';
|
||||
import { TooltipSimple } from '@signozhq/ui/tooltip';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { useGetMetricHighlights } from 'api/generated/services/metrics';
|
||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
import { Info } from '@signozhq/icons';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
|
||||
import { formatNumberIntoHumanReadableFormat } from '../Summary/utils';
|
||||
import { HighlightsProps } from './types';
|
||||
@@ -11,6 +14,10 @@ import {
|
||||
formatTimestampToReadableDate,
|
||||
} from './utils';
|
||||
|
||||
const TOOLTIP_CONTENT_PROPS = {
|
||||
className: 'metric-highlights-tooltip-content',
|
||||
};
|
||||
|
||||
function Highlights({ metricName }: HighlightsProps): JSX.Element {
|
||||
const {
|
||||
data: metricHighlightsData,
|
||||
@@ -39,6 +46,13 @@ function Highlights({ metricName }: HighlightsProps): JSX.Element {
|
||||
const lastReceivedText = formatTimestampToReadableDate(
|
||||
metricHighlights?.lastReceived,
|
||||
);
|
||||
const { formatTimezoneAdjustedTimestamp } = useTimezone();
|
||||
const lastReceivedTooltipText = metricHighlights?.lastReceived
|
||||
? `Last received on ${formatTimezoneAdjustedTimestamp(
|
||||
metricHighlights.lastReceived,
|
||||
DATE_TIME_FORMATS.DASH_DATETIME_UTC,
|
||||
)}`
|
||||
: 'No data received yet';
|
||||
|
||||
if (isErrorMetricHighlights) {
|
||||
return (
|
||||
@@ -90,27 +104,42 @@ function Highlights({ metricName }: HighlightsProps): JSX.Element {
|
||||
className="metric-details-grid-value"
|
||||
data-testid="metric-highlights-data-points"
|
||||
>
|
||||
<Tooltip title={metricHighlights?.dataPoints?.toLocaleString()}>
|
||||
{formatNumberIntoHumanReadableFormat(metricHighlights?.dataPoints ?? 0)}
|
||||
</Tooltip>
|
||||
<TooltipSimple
|
||||
title={metricHighlights?.dataPoints?.toLocaleString()}
|
||||
tooltipContentProps={TOOLTIP_CONTENT_PROPS}
|
||||
arrow
|
||||
>
|
||||
<span>
|
||||
{formatNumberIntoHumanReadableFormat(
|
||||
metricHighlights?.dataPoints ?? 0,
|
||||
)}
|
||||
</span>
|
||||
</TooltipSimple>
|
||||
</Typography.Text>
|
||||
<Typography.Text
|
||||
className="metric-details-grid-value"
|
||||
data-testid="metric-highlights-time-series-total"
|
||||
>
|
||||
<Tooltip
|
||||
title="Active time series are those that have received data points in the last 1
|
||||
hour."
|
||||
placement="top"
|
||||
<TooltipSimple
|
||||
title="Active time series are those that have received data points in the last 1 hour."
|
||||
side="top"
|
||||
tooltipContentProps={TOOLTIP_CONTENT_PROPS}
|
||||
arrow
|
||||
>
|
||||
<span>{`${timeSeriesTotal} total ⎯ ${timeSeriesActive} active`}</span>
|
||||
</Tooltip>
|
||||
</TooltipSimple>
|
||||
</Typography.Text>
|
||||
<Typography.Text
|
||||
className="metric-details-grid-value"
|
||||
data-testid="metric-highlights-last-received"
|
||||
>
|
||||
<Tooltip title={lastReceivedText}>{lastReceivedText}</Tooltip>
|
||||
<TooltipSimple
|
||||
title={lastReceivedTooltipText}
|
||||
tooltipContentProps={TOOLTIP_CONTENT_PROPS}
|
||||
arrow
|
||||
>
|
||||
<span>{lastReceivedText}</span>
|
||||
</TooltipSimple>
|
||||
</Typography.Text>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -510,6 +510,12 @@
|
||||
color: var(--bg-robin-400) !important;
|
||||
}
|
||||
|
||||
// The MetricDetails Drawer sits at z-index 1000; the design-system tooltip
|
||||
// defaults to z-index 50 and would otherwise render behind the drawer.
|
||||
.metric-highlights-tooltip-content {
|
||||
--tooltip-z-index: 1000;
|
||||
}
|
||||
|
||||
@keyframes fade-in-out {
|
||||
0% {
|
||||
opacity: 0;
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { TooltipProvider } from '@signozhq/ui/tooltip';
|
||||
import * as metricsExplorerHooks from 'api/generated/services/metrics';
|
||||
import TimezoneProvider from 'providers/Timezone';
|
||||
|
||||
import Highlights from '../Highlights';
|
||||
import { formatTimestampToReadableDate } from '../utils';
|
||||
import { getMockMetricHighlightsData, MOCK_METRIC_NAME } from './testUtlls';
|
||||
|
||||
function renderHighlights(metricName: string): ReturnType<typeof render> {
|
||||
return render(
|
||||
<TimezoneProvider>
|
||||
<TooltipProvider>
|
||||
<Highlights metricName={metricName} />
|
||||
</TooltipProvider>
|
||||
</TimezoneProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
const useGetMetricHighlightsMock = jest.spyOn(
|
||||
metricsExplorerHooks,
|
||||
'useGetMetricHighlights',
|
||||
@@ -16,7 +28,7 @@ describe('Highlights', () => {
|
||||
});
|
||||
|
||||
it('should render all highlights data correctly', () => {
|
||||
render(<Highlights metricName={MOCK_METRIC_NAME} />);
|
||||
renderHighlights(MOCK_METRIC_NAME);
|
||||
|
||||
const dataPoints = screen.getByTestId('metric-highlights-data-points');
|
||||
const timeSeriesTotal = screen.getByTestId(
|
||||
@@ -41,7 +53,7 @@ describe('Highlights', () => {
|
||||
),
|
||||
);
|
||||
|
||||
render(<Highlights metricName={MOCK_METRIC_NAME} />);
|
||||
renderHighlights(MOCK_METRIC_NAME);
|
||||
|
||||
expect(
|
||||
screen.getByTestId('metric-highlights-error-state'),
|
||||
@@ -58,7 +70,7 @@ describe('Highlights', () => {
|
||||
),
|
||||
);
|
||||
|
||||
render(<Highlights metricName={MOCK_METRIC_NAME} />);
|
||||
renderHighlights(MOCK_METRIC_NAME);
|
||||
|
||||
expect(screen.getByText('SAMPLES')).toBeInTheDocument();
|
||||
expect(screen.getByText('TIME SERIES')).toBeInTheDocument();
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { InputNumber, Select } from 'antd';
|
||||
import { Select } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { Axis3D, ChartLine, Spline } from '@signozhq/icons';
|
||||
import InputNumber from 'components/InputNumber';
|
||||
|
||||
import SettingsSection from '../../components/SettingsSection/SettingsSection';
|
||||
|
||||
@@ -48,7 +49,6 @@ export default function AxesSection({
|
||||
<section className="container">
|
||||
<Typography.Text className="text">Soft Min</Typography.Text>
|
||||
<InputNumber
|
||||
type="number"
|
||||
value={softMin}
|
||||
onChange={softMinHandler}
|
||||
rootClassName="input"
|
||||
@@ -58,7 +58,6 @@ export default function AxesSection({
|
||||
<Typography.Text className="text">Soft Max</Typography.Text>
|
||||
<InputNumber
|
||||
value={softMax}
|
||||
type="number"
|
||||
rootClassName="input"
|
||||
onChange={softMaxHandler}
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { InputNumber, Switch } from 'antd';
|
||||
import { Switch } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import InputNumber from 'components/InputNumber';
|
||||
|
||||
import SettingsSection from '../../components/SettingsSection/SettingsSection';
|
||||
|
||||
@@ -31,7 +32,6 @@ export default function HistogramBucketsSection({
|
||||
</Typography.Text>
|
||||
<InputNumber
|
||||
value={bucketCount || null}
|
||||
type="number"
|
||||
min={0}
|
||||
rootClassName="bucket-input"
|
||||
placeholder="Default: 30"
|
||||
@@ -44,7 +44,6 @@ export default function HistogramBucketsSection({
|
||||
</Typography.Text>
|
||||
<InputNumber
|
||||
value={bucketWidth || null}
|
||||
type="number"
|
||||
precision={2}
|
||||
placeholder="Default: Auto"
|
||||
step={0.1}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/* eslint-disable sonarjs/cognitive-complexity */
|
||||
import { useMemo, useRef, useState } from 'react';
|
||||
import { useDrag, useDrop, XYCoord } from 'react-dnd';
|
||||
import { Button, Input, InputNumber, Select, Space } from 'antd';
|
||||
import { Button, Input, Select, Space } from 'antd';
|
||||
import InputNumber from 'components/InputNumber';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import YAxisUnitSelector from 'components/YAxisUnitSelector';
|
||||
import { Y_AXIS_UNIT_NAMES } from 'components/YAxisUnitSelector/constants';
|
||||
|
||||
@@ -14,12 +14,7 @@ function MaxLinesField({ config }: MaxLinesFieldProps): JSX.Element | null {
|
||||
return (
|
||||
<MaxLinesFieldWrapper>
|
||||
<FieldTitle>{t('options_menu.maxLines')}</FieldTitle>
|
||||
<MaxLinesInput
|
||||
controls
|
||||
size="small"
|
||||
value={config.value}
|
||||
onChange={config.onChange}
|
||||
/>
|
||||
<MaxLinesInput value={config.value} onChange={config.onChange} />
|
||||
</MaxLinesFieldWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { InputNumber } from 'antd';
|
||||
import InputNumber from 'components/InputNumber';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const MaxLinesFieldWrapper = styled.div`
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { InputNumberProps, RadioProps, SelectProps } from 'antd';
|
||||
import { RadioProps, SelectProps } from 'antd';
|
||||
import { TelemetryFieldKey } from 'api/v5/v5';
|
||||
import type { InputNumberProps } from 'components/InputNumber';
|
||||
import { LogViewMode } from 'container/LogsTable';
|
||||
|
||||
export enum FontSize {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useMemo } from 'react';
|
||||
import { InputNumber, InputNumberProps } from 'antd';
|
||||
import InputNumber from 'components/InputNumber';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
@@ -15,9 +15,9 @@ function AggregateEveryFilter({
|
||||
[query.dataSource],
|
||||
);
|
||||
|
||||
const onChangeHandler: InputNumberProps<number>['onChange'] = (event) => {
|
||||
if (event && event >= 0) {
|
||||
onChange(event);
|
||||
const onChangeHandler = (value: number | null): void => {
|
||||
if (value !== null && value >= 0) {
|
||||
onChange(value);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { InputNumber } from 'antd';
|
||||
import InputNumber from 'components/InputNumber';
|
||||
|
||||
import { selectStyle } from '../../QueryBuilderSearch/config';
|
||||
import { handleKeyDownLimitFilter } from '../../utils';
|
||||
@@ -8,7 +8,6 @@ function LimitFilter({ onChange, formula }: LimitFilterProps): JSX.Element {
|
||||
return (
|
||||
<InputNumber
|
||||
min={1}
|
||||
type="number"
|
||||
value={formula.limit}
|
||||
style={selectStyle}
|
||||
onChange={onChange}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { InputNumber } from 'antd';
|
||||
import InputNumber from 'components/InputNumber';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
@@ -13,7 +13,6 @@ function LimitFilter({ onChange, query }: LimitFilterProps): JSX.Element {
|
||||
return (
|
||||
<InputNumber
|
||||
min={1}
|
||||
type="number"
|
||||
value={query.limit}
|
||||
style={selectStyle}
|
||||
disabled={isDisabled}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { InputNumber, Row, Space } from 'antd';
|
||||
import { Row, Space } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import InputNumber from 'components/InputNumber';
|
||||
|
||||
interface PopoverContentProps {
|
||||
linesPerRow: number;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState } from 'react';
|
||||
import { X } from '@signozhq/icons';
|
||||
import { Card, InputNumber } from 'antd';
|
||||
import { Card } from 'antd';
|
||||
import InputNumber from 'components/InputNumber';
|
||||
import Spinner from 'components/Spinner';
|
||||
import TextToolTip from 'components/TextToolTip';
|
||||
import {
|
||||
|
||||
2
go.mod
2
go.mod
@@ -11,6 +11,7 @@ require (
|
||||
github.com/SigNoz/signoz-otel-collector v0.144.3
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1
|
||||
github.com/antonmedv/expr v1.15.3
|
||||
github.com/bytedance/sonic v1.14.1
|
||||
github.com/cespare/xxhash/v2 v2.3.0
|
||||
github.com/coreos/go-oidc/v3 v3.17.0
|
||||
github.com/dgraph-io/ristretto/v2 v2.3.0
|
||||
@@ -112,7 +113,6 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 // indirect
|
||||
github.com/aws/smithy-go v1.24.2 // indirect
|
||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||
github.com/bytedance/sonic v1.14.1 // indirect
|
||||
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect
|
||||
|
||||
@@ -12,8 +12,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/bytedance/sonic"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -22,6 +24,8 @@ var (
|
||||
// written clickhouse query. The column alias indcate which value is
|
||||
// to be considered as final result (or target).
|
||||
legacyReservedColumnTargetAliases = []string{"__result", "__value", "result", "res", "value"}
|
||||
|
||||
CodeFailUnmarshalJSONColumn = errors.MustNewCode("fail_unmarshal_json_column")
|
||||
)
|
||||
|
||||
// consume reads every row and shapes it into the payload expected for the
|
||||
@@ -393,11 +397,16 @@ func readAsRaw(rows driver.Rows, queryName string) (*qbtypes.RawData, error) {
|
||||
|
||||
// de-reference the typed pointer to any
|
||||
val := reflect.ValueOf(cellPtr).Elem().Interface()
|
||||
// Post-process JSON columns: normalize into String value
|
||||
// Post-process JSON columns: unmarshal bytes into map[string]any
|
||||
if strings.HasPrefix(strings.ToUpper(colTypes[i].DatabaseTypeName()), "JSON") {
|
||||
switch x := val.(type) {
|
||||
case []byte:
|
||||
val = string(x)
|
||||
var m map[string]any
|
||||
err := sonic.Unmarshal(x, &m)
|
||||
if err != nil {
|
||||
return nil, errors.WrapInternalf(err, CodeFailUnmarshalJSONColumn, "failed to unmarshal JSON column %s", name)
|
||||
}
|
||||
val = m
|
||||
default:
|
||||
// already a structured type (map[string]any, []any, etc.)
|
||||
}
|
||||
|
||||
@@ -12,9 +12,12 @@ import (
|
||||
"github.com/SigNoz/govaluate"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/flagger"
|
||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
"github.com/SigNoz/signoz/pkg/querybuilder"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
// queryInfo holds common query properties.
|
||||
@@ -50,7 +53,7 @@ func getQueryName(spec any) string {
|
||||
return getqueryInfo(spec).Name
|
||||
}
|
||||
|
||||
func (q *querier) postProcessResults(ctx context.Context, results map[string]any, req *qbtypes.QueryRangeRequest) (map[string]any, error) {
|
||||
func (q *querier) postProcessResults(ctx context.Context, orgID valuer.UUID, results map[string]any, req *qbtypes.QueryRangeRequest) (map[string]any, error) {
|
||||
// Convert results to typed format for processing
|
||||
typedResults := make(map[string]*qbtypes.Result)
|
||||
for name, result := range results {
|
||||
@@ -69,6 +72,7 @@ func (q *querier) postProcessResults(ctx context.Context, results map[string]any
|
||||
case qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]:
|
||||
if result, ok := typedResults[spec.Name]; ok {
|
||||
result = postProcessBuilderQuery(q, result, spec, req)
|
||||
result = q.postProcessLogBody(ctx, orgID, result, req)
|
||||
typedResults[spec.Name] = result
|
||||
}
|
||||
case qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation]:
|
||||
@@ -1046,3 +1050,33 @@ func (q *querier) calculateFormulaStep(expression string, req *qbtypes.QueryRang
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// postProcessLogBody removes the "message" key from the body map when it is empty.
|
||||
// Only runs for raw list queries with the use_json_body feature enabled.
|
||||
func (q *querier) postProcessLogBody(ctx context.Context, orgID valuer.UUID, result *qbtypes.Result, req *qbtypes.QueryRangeRequest) *qbtypes.Result {
|
||||
if req.RequestType != qbtypes.RequestTypeRaw {
|
||||
return result
|
||||
}
|
||||
if !q.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(orgID)) {
|
||||
return result
|
||||
}
|
||||
rawData, ok := result.Value.(*qbtypes.RawData)
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
for _, row := range rawData.Rows {
|
||||
bodyMap, ok := row.Data["body"].(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if msg, exists := bodyMap["message"]; exists {
|
||||
switch v := msg.(type) {
|
||||
case string:
|
||||
if v == "" {
|
||||
delete(bodyMap, "message")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/flagger"
|
||||
"github.com/SigNoz/signoz/pkg/prometheus"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/utils"
|
||||
"github.com/SigNoz/signoz/pkg/querybuilder"
|
||||
@@ -35,6 +36,7 @@ var (
|
||||
|
||||
type querier struct {
|
||||
logger *slog.Logger
|
||||
fl flagger.Flagger
|
||||
telemetryStore telemetrystore.TelemetryStore
|
||||
metadataStore telemetrytypes.MetadataStore
|
||||
promEngine prometheus.Prometheus
|
||||
@@ -62,10 +64,12 @@ func New(
|
||||
meterStmtBuilder qbtypes.StatementBuilder[qbtypes.MetricAggregation],
|
||||
traceOperatorStmtBuilder qbtypes.TraceOperatorStatementBuilder,
|
||||
bucketCache BucketCache,
|
||||
flagger flagger.Flagger,
|
||||
) *querier {
|
||||
querierSettings := factory.NewScopedProviderSettings(settings, "github.com/SigNoz/signoz/pkg/querier")
|
||||
return &querier{
|
||||
logger: querierSettings.Logger(),
|
||||
fl: flagger,
|
||||
telemetryStore: telemetryStore,
|
||||
metadataStore: metadataStore,
|
||||
promEngine: promEngine,
|
||||
@@ -684,7 +688,7 @@ func (q *querier) run(
|
||||
}
|
||||
|
||||
gomaps.Copy(results, preseededResults)
|
||||
processedResults, err := q.postProcessResults(ctx, results, req)
|
||||
processedResults, err := q.postProcessResults(ctx, orgID, results, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
cmock "github.com/srikanthccv/ClickHouse-go-mock"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/flagger/flaggertest"
|
||||
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrystore"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrystore/telemetrystoretest"
|
||||
@@ -44,14 +45,15 @@ func TestQueryRange_MetricTypeMissing(t *testing.T) {
|
||||
providerSettings,
|
||||
nil, // telemetryStore
|
||||
metadataStore,
|
||||
nil, // prometheus
|
||||
nil, // traceStmtBuilder
|
||||
nil, // logStmtBuilder
|
||||
nil, // auditStmtBuilder
|
||||
nil, // metricStmtBuilder
|
||||
nil, // meterStmtBuilder
|
||||
nil, // traceOperatorStmtBuilder
|
||||
nil, // bucketCache
|
||||
nil, // prometheus
|
||||
nil, // traceStmtBuilder
|
||||
nil, // logStmtBuilder
|
||||
nil, // auditStmtBuilder
|
||||
nil, // metricStmtBuilder
|
||||
nil, // meterStmtBuilder
|
||||
nil, // traceOperatorStmtBuilder
|
||||
nil, // bucketCache
|
||||
flaggertest.New(t), // flagger
|
||||
)
|
||||
|
||||
req := &qbtypes.QueryRangeRequest{
|
||||
@@ -116,6 +118,7 @@ func TestQueryRange_MetricTypeFromStore(t *testing.T) {
|
||||
nil, // meterStmtBuilder
|
||||
nil, // traceOperatorStmtBuilder
|
||||
nil, // bucketCache
|
||||
flaggertest.New(t), // flagger
|
||||
)
|
||||
|
||||
req := &qbtypes.QueryRangeRequest{
|
||||
|
||||
@@ -186,5 +186,6 @@ func newProvider(
|
||||
meterStmtBuilder,
|
||||
traceOperatorStmtBuilder,
|
||||
bucketCache,
|
||||
flagger,
|
||||
), nil
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ func prepareQuerierForMetrics(t *testing.T, telemetryStore telemetrystore.Teleme
|
||||
nil, // meterStmtBuilder
|
||||
nil, // traceOperatorStmtBuilder
|
||||
nil, // bucketCache
|
||||
flagger,
|
||||
), metadataStore
|
||||
}
|
||||
|
||||
@@ -102,6 +103,7 @@ func prepareQuerierForLogs(t *testing.T, telemetryStore telemetrystore.Telemetry
|
||||
nil, // meterStmtBuilder
|
||||
nil, // traceOperatorStmtBuilder
|
||||
nil, // bucketCache
|
||||
fl,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -146,5 +148,6 @@ func prepareQuerierForTraces(t *testing.T, telemetryStore telemetrystore.Telemet
|
||||
nil, // meterStmtBuilder
|
||||
nil, // traceOperatorStmtBuilder
|
||||
nil, // bucketCache
|
||||
fl,
|
||||
)
|
||||
}
|
||||
|
||||
5
tests/fixtures/http.py
vendored
5
tests/fixtures/http.py
vendored
@@ -17,8 +17,6 @@ from fixtures.logger import setup_logger
|
||||
|
||||
logger = setup_logger(__name__)
|
||||
|
||||
ZEUS_NETWORK_ALIAS = "signoz-zeus"
|
||||
|
||||
|
||||
@pytest.fixture(name="zeus", scope="package")
|
||||
def zeus(
|
||||
@@ -33,7 +31,6 @@ def zeus(
|
||||
def create() -> types.TestContainerDocker:
|
||||
container = WireMockContainer(image="wiremock/wiremock:2.35.1-1", secure=False)
|
||||
container.with_network(network)
|
||||
container.with_network_aliases(ZEUS_NETWORK_ALIAS)
|
||||
container.start()
|
||||
|
||||
return types.TestContainerDocker(
|
||||
@@ -45,7 +42,7 @@ def zeus(
|
||||
container.get_exposed_port(8080),
|
||||
)
|
||||
},
|
||||
container_configs={"8080": types.TestContainerUrlConfig("http", ZEUS_NETWORK_ALIAS, 8080)},
|
||||
container_configs={"8080": types.TestContainerUrlConfig("http", container.get_wrapped_container().name, 8080)},
|
||||
)
|
||||
|
||||
def delete(container: types.TestContainerDocker):
|
||||
|
||||
136
tests/fixtures/signoz.py
vendored
136
tests/fixtures/signoz.py
vendored
@@ -1,19 +1,14 @@
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import subprocess
|
||||
import threading
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from http import HTTPStatus
|
||||
from os import path
|
||||
from pathlib import Path
|
||||
|
||||
import docker
|
||||
import docker.errors
|
||||
import pytest
|
||||
import requests
|
||||
from testcontainers.core.container import DockerContainer, Network
|
||||
from testcontainers.core.image import DockerImage
|
||||
|
||||
from fixtures import reuse, types
|
||||
from fixtures.logger import setup_logger
|
||||
@@ -21,110 +16,6 @@ from fixtures.logger import setup_logger
|
||||
logger = setup_logger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SigNozImageBuild:
|
||||
process: subprocess.Popen
|
||||
command: list[str]
|
||||
cache_path: Path | None = None
|
||||
next_cache_path: Path | None = None
|
||||
reader: threading.Thread | None = None
|
||||
|
||||
|
||||
def _stream_build_output(pipe, log) -> None:
|
||||
try:
|
||||
for line in iter(pipe.readline, ""):
|
||||
log.info("buildx: %s", line.rstrip())
|
||||
finally:
|
||||
pipe.close()
|
||||
|
||||
|
||||
def start_signoz_image_build(pytestconfig: pytest.Config, dockerfile_path: str, arch: str, zeus_url: str) -> SigNozImageBuild:
|
||||
root = pytestconfig.rootpath.parent
|
||||
command = [
|
||||
"docker",
|
||||
"buildx",
|
||||
"build",
|
||||
"--load",
|
||||
"--progress",
|
||||
"plain",
|
||||
"--tag",
|
||||
"signoz:integration",
|
||||
"--file",
|
||||
dockerfile_path,
|
||||
"--build-arg",
|
||||
f"TARGETARCH={arch}",
|
||||
"--build-arg",
|
||||
f"ZEUSURL={zeus_url}",
|
||||
str(root),
|
||||
]
|
||||
|
||||
cache_path = None
|
||||
next_cache_path = None
|
||||
if os.environ.get("ACTIONS_RUNTIME_TOKEN"):
|
||||
# Running in GitHub Actions — use BuildKit's native GHA cache backend.
|
||||
# Avoids the local-write races and partial exports seen with type=local.
|
||||
scope = os.environ.get("SIGNOZ_BUILDX_GHA_SCOPE", "signoz-integration")
|
||||
command.extend(["--cache-from", f"type=gha,scope={scope}"])
|
||||
command.extend(["--cache-to", f"type=gha,scope={scope},mode=max"])
|
||||
elif build_cache_dir := os.environ.get("SIGNOZ_INTEGRATION_BUILD_CACHE_DIR"):
|
||||
# Local cache for developer machines / non-GHA CI.
|
||||
cache_path = Path(build_cache_dir)
|
||||
next_cache_path = Path(f"{build_cache_dir}-next")
|
||||
cache_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.rmtree(next_cache_path, ignore_errors=True)
|
||||
|
||||
if cache_path.exists():
|
||||
command.extend(["--cache-from", f"type=local,src={cache_path}"])
|
||||
command.extend(["--cache-to", f"type=local,dest={next_cache_path},mode=max"])
|
||||
|
||||
logger.info("Building SigNoz integration image with %s", " ".join(command))
|
||||
process = subprocess.Popen(
|
||||
command,
|
||||
cwd=root,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
bufsize=1,
|
||||
)
|
||||
reader = threading.Thread(target=_stream_build_output, args=(process.stdout, logger), daemon=True)
|
||||
reader.start()
|
||||
|
||||
return SigNozImageBuild(
|
||||
process=process,
|
||||
command=command,
|
||||
cache_path=cache_path,
|
||||
next_cache_path=next_cache_path,
|
||||
reader=reader,
|
||||
)
|
||||
|
||||
|
||||
def wait_for_signoz_image_build(build: SigNozImageBuild) -> None:
|
||||
returncode = build.process.wait()
|
||||
if build.reader is not None:
|
||||
build.reader.join(timeout=5)
|
||||
if returncode != 0:
|
||||
raise subprocess.CalledProcessError(returncode, build.command)
|
||||
|
||||
if build.cache_path and build.next_cache_path and build.next_cache_path.exists():
|
||||
shutil.rmtree(build.cache_path, ignore_errors=True)
|
||||
shutil.move(build.next_cache_path, build.cache_path)
|
||||
|
||||
|
||||
def stop_signoz_image_build(build: SigNozImageBuild) -> None:
|
||||
if build.process.poll() is not None:
|
||||
return
|
||||
|
||||
build.process.terminate()
|
||||
try:
|
||||
build.process.wait(timeout=10)
|
||||
except subprocess.TimeoutExpired:
|
||||
build.process.kill()
|
||||
build.process.wait()
|
||||
|
||||
if build.reader is not None:
|
||||
build.reader.join(timeout=5)
|
||||
|
||||
|
||||
def create_signoz(
|
||||
network: Network,
|
||||
zeus: types.TestContainerDocker,
|
||||
@@ -142,6 +33,9 @@ def create_signoz(
|
||||
"""
|
||||
|
||||
def create() -> types.SigNoz:
|
||||
# Run the migrations for clickhouse
|
||||
request.getfixturevalue("migrator")
|
||||
|
||||
# Get the no-web flag
|
||||
with_web = pytestconfig.getoption("--with-web")
|
||||
|
||||
@@ -154,15 +48,19 @@ def create_signoz(
|
||||
if with_web:
|
||||
dockerfile_path = "cmd/enterprise/Dockerfile.with-web.integration"
|
||||
|
||||
# The SigNoz image build does not depend on ClickHouse migrations, so
|
||||
# build it while the migrator container runs.
|
||||
image_build = start_signoz_image_build(pytestconfig, dockerfile_path, arch, zeus.container_configs["8080"].base())
|
||||
try:
|
||||
request.getfixturevalue("migrator")
|
||||
wait_for_signoz_image_build(image_build)
|
||||
except Exception: # pylint: disable=broad-exception-caught
|
||||
stop_signoz_image_build(image_build)
|
||||
raise
|
||||
# Docker build context is the repo root — one up from pytest's
|
||||
# rootdir (tests/).
|
||||
self = DockerImage(
|
||||
path=str(pytestconfig.rootpath.parent),
|
||||
dockerfile_path=dockerfile_path,
|
||||
tag="signoz:integration",
|
||||
buildargs={
|
||||
"TARGETARCH": arch,
|
||||
"ZEUSURL": zeus.container_configs["8080"].base(),
|
||||
},
|
||||
)
|
||||
|
||||
self.build()
|
||||
|
||||
env = (
|
||||
{
|
||||
|
||||
@@ -21,7 +21,7 @@ from fixtures.querier import (
|
||||
|
||||
|
||||
def _get_bodies(response: requests.Response) -> list[dict[str, Any]]:
|
||||
return [json.loads(row["data"]["body"]) for row in get_rows(response)]
|
||||
return [row["data"]["body"] for row in get_rows(response)]
|
||||
|
||||
|
||||
def _run_query_case(signoz: types.SigNoz, token: str, now: datetime, case: dict[str, Any]) -> None:
|
||||
@@ -1188,7 +1188,7 @@ def test_message_searches(
|
||||
token = get_token(email=USER_ADMIN_EMAIL, password=USER_ADMIN_PASSWORD)
|
||||
|
||||
def _body_messages(response: requests.Response) -> list[str]:
|
||||
return [json.loads(row["data"]["body"]).get("message", "") for row in get_rows(response)]
|
||||
return [row["data"]["body"].get("message", "") for row in get_rows(response)]
|
||||
|
||||
payment_messages = {
|
||||
"Payment processed successfully",
|
||||
|
||||
@@ -69,11 +69,11 @@ def _scalar(
|
||||
|
||||
|
||||
def _body_users(response: requests.Response) -> set[str | None]:
|
||||
return {json.loads(row["data"]["body"]).get("user") for row in get_rows(response)}
|
||||
return {row["data"]["body"].get("user") for row in get_rows(response)}
|
||||
|
||||
|
||||
def _body_scores(response: requests.Response) -> list[int | None]:
|
||||
return [json.loads(row["data"]["body"]).get("score") for row in get_rows(response)]
|
||||
return [row["data"]["body"].get("score") for row in get_rows(response)]
|
||||
|
||||
|
||||
def _services(response: requests.Response) -> list[str]:
|
||||
|
||||
Reference in New Issue
Block a user