Compare commits

..

33 Commits

Author SHA1 Message Date
Abhi Kumar
74ab35d698 Merge branch 'main' of https://github.com/SigNoz/signoz into feat/histogram-panel 2026-02-16 18:53:42 +05:30
Abhi kumar
eb2c6b78c8 feat: added new barpanel (#10319)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
2026-02-16 12:52:06 +00:00
Abhi kumar
2d2d0c3d9f fix: fixed series visiblity logic, not relying on index now (#10318) 2026-02-16 12:40:37 +00:00
Ashwin Bhatkal
8a4544cbac chore: dynamic variables first load (#10316) 2026-02-16 17:13:32 +05:30
Abhi Kumar
7d29dd56e0 Merge branch 'feat/histogram-panel' of https://github.com/SigNoz/signoz into feat/histogram-panel 2026-02-13 16:27:03 +05:30
Abhi kumar
fb55eefc25 Merge branch 'feat/bar-panel' into feat/histogram-panel 2026-02-13 16:26:10 +05:30
Abhi kumar
a8fca4e8e2 Merge branch 'main' into feat/bar-panel 2026-02-13 16:25:29 +05:30
Abhi Kumar
1b2c2c20ea Merge branch 'feat/bar-panel' of https://github.com/SigNoz/signoz into feat/histogram-panel 2026-02-13 16:25:08 +05:30
Abhi Kumar
764546930b chore: updated structure for bar panel 2026-02-13 13:04:02 +05:30
Abhi kumar
c4dbb6e00a Merge branch 'feat/bar-panel' into feat/histogram-panel 2026-02-13 11:59:13 +05:30
Abhi Kumar
e4eddbe08e Merge branch 'main' of https://github.com/SigNoz/signoz into feat/bar-panel 2026-02-13 11:59:01 +05:30
Abhi kumar
e09415fabd Merge branch 'main' into feat/bar-panel 2026-02-12 21:21:57 +05:30
Abhi kumar
a379ff6286 Merge branch 'feat/bar-panel' into feat/histogram-panel 2026-02-12 21:03:41 +05:30
Abhi Kumar
2c02cace18 Merge branch 'main' of https://github.com/SigNoz/signoz into feat/bar-panel 2026-02-12 21:02:36 +05:30
Abhi Kumar
ccf337710a feat: added new histogram panel 2026-02-12 13:46:55 +05:30
Abhi kumar
0d9154ca72 Merge branch 'main' into feat/bar-panel 2026-02-12 13:44:28 +05:30
Abhi Kumar
55d095304d chore: added support for bar alignment configuration 2026-02-11 19:10:53 +05:30
Abhi Kumar
3f467bfe9e Merge branch 'main' of https://github.com/SigNoz/signoz into feat/bar-panel 2026-02-11 16:08:27 +05:30
Abhi kumar
eb8e1307e5 Merge branch 'chore/base-config-builder' into feat/bar-panel 2026-02-11 13:21:43 +05:30
Abhi kumar
304fcb1c10 Merge branch 'main' into chore/base-config-builder 2026-02-11 12:41:30 +05:30
Abhi Kumar
008d6b5f35 fix: added fix for pr review changes 2026-02-11 12:40:53 +05:30
Abhi kumar
eba011c2bb Merge branch 'chore/base-config-builder' into feat/bar-panel 2026-02-10 23:04:16 +05:30
Abhi Kumar
6c0843595a feat: added new barpanel component 2026-02-10 23:01:40 +05:30
Abhi kumar
703b221dfe Merge branch 'main' into chore/base-config-builder 2026-02-10 22:28:27 +05:30
Abhi Kumar
9f13086214 chore: removed dayjs extention 2026-02-10 22:23:33 +05:30
Abhi kumar
20e58db10d Merge branch 'main' into chore/base-config-builder 2026-02-10 16:54:27 +05:30
Abhi Kumar
974bfcd732 chore: added different tooltips 2026-02-10 16:53:49 +05:30
Abhi Kumar
e6d89465da fix: pr review changes 2026-02-10 16:05:04 +05:30
Abhi Kumar
a634ae9b66 fix: pr review changes 2026-02-10 15:48:29 +05:30
Abhi Kumar
bb1f5ba29f chore: tsc fix 2026-02-10 13:31:47 +05:30
Abhi Kumar
30b3a68154 Merge branch 'main' of https://github.com/SigNoz/signoz into chore/base-config-builder 2026-02-10 12:57:31 +05:30
Abhi Kumar
08aa8759ba chore: added a common chart wrapper 2026-02-10 12:57:02 +05:30
Abhi Kumar
c941233723 chore: refactored the config builder and added base config builder 2026-02-09 21:34:08 +05:30
78 changed files with 1533 additions and 1014 deletions

View File

@@ -1,4 +1,4 @@
FROM node:22-bookworm AS build
FROM node:18-bullseye AS build
WORKDIR /opt/
COPY ./frontend/ ./

View File

@@ -309,14 +309,3 @@ user:
allow_self: true
# The duration within which a user can reset their password.
max_token_lifetime: 6h
root:
# Whether to enable the root user. When enabled, a root user is provisioned
# on startup using the email and password below. The root user cannot be
# deleted, updated, or have their password changed through the UI.
enabled: false
# The email address of the root user.
email: ""
# The password of the root user. Must meet password requirements.
password: ""
# The name of the organization to create or look up for the root user.
org_name: default

View File

@@ -4678,8 +4678,6 @@ components:
type: string
id:
type: string
isRoot:
type: boolean
orgId:
type: string
role:

View File

@@ -45,7 +45,7 @@ type APIHandler struct {
}
// NewAPIHandler returns an APIHandler
func NewAPIHandler(opts APIHandlerOptions, signoz *signoz.SigNoz, config signoz.Config) (*APIHandler, error) {
func NewAPIHandler(opts APIHandlerOptions, signoz *signoz.SigNoz) (*APIHandler, error) {
baseHandler, err := baseapp.NewAPIHandler(baseapp.APIHandlerOpts{
Reader: opts.DataConnector,
RuleManager: opts.RulesManager,
@@ -58,7 +58,7 @@ func NewAPIHandler(opts APIHandlerOptions, signoz *signoz.SigNoz, config signoz.
Signoz: signoz,
QuerierAPI: querierAPI.NewAPI(signoz.Instrumentation.ToProviderSettings(), signoz.Querier, signoz.Analytics),
QueryParserAPI: queryparser.NewAPI(signoz.Instrumentation.ToProviderSettings(), signoz.QueryParser),
}, config)
})
if err != nil {
return nil, err

View File

@@ -175,7 +175,7 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
GlobalConfig: config.Global,
}
apiHandler, err := api.NewAPIHandler(apiOpts, signoz, config)
apiHandler, err := api.NewAPIHandler(apiOpts, signoz)
if err != nil {
return nil, err
}

View File

@@ -1542,10 +1542,6 @@ export interface TypesUserDTO {
* @type string
*/
id?: string;
/**
* @type boolean
*/
isRoot?: boolean;
/**
* @type string
*/

View File

@@ -23,6 +23,7 @@ export default function ChartWrapper({
width: containerWidth,
height: containerHeight,
showTooltip = true,
showLegend = true,
canPinTooltip = false,
syncMode,
syncKey,
@@ -36,6 +37,9 @@ export default function ChartWrapper({
const legendComponent = useCallback(
(averageLegendWidth: number): React.ReactNode => {
if (!showLegend) {
return null;
}
return (
<Legend
config={config}
@@ -44,7 +48,7 @@ export default function ChartWrapper({
/>
);
},
[config, legendConfig.position],
[config, legendConfig.position, showLegend],
);
const renderTooltipCallback = useCallback(
@@ -60,6 +64,7 @@ export default function ChartWrapper({
return (
<PlotContextProvider>
<ChartLayout
showLegend={showLegend}
config={config}
containerWidth={containerWidth}
containerHeight={containerHeight}

View File

@@ -0,0 +1,55 @@
import { useCallback } from 'react';
import ChartWrapper from 'container/DashboardContainer/visualization/charts/ChartWrapper/ChartWrapper';
import HistogramTooltip from 'lib/uPlotV2/components/Tooltip/HistogramTooltip';
import { buildTooltipContent } from 'lib/uPlotV2/components/Tooltip/utils';
import {
HistogramTooltipProps,
TooltipRenderArgs,
} from 'lib/uPlotV2/components/types';
import { HistogramChartProps } from '../types';
export default function Histogram(props: HistogramChartProps): JSX.Element {
const {
children,
renderTooltip: customRenderTooltip,
isQueriesMerged,
...rest
} = props;
const renderTooltip = useCallback(
(props: TooltipRenderArgs): React.ReactNode => {
if (customRenderTooltip) {
return customRenderTooltip(props);
}
const content = buildTooltipContent({
data: props.uPlotInstance.data,
series: props.uPlotInstance.series,
dataIndexes: props.dataIndexes,
activeSeriesIndex: props.seriesIndex,
uPlotInstance: props.uPlotInstance,
yAxisUnit: rest.yAxisUnit ?? '',
decimalPrecision: rest.decimalPrecision,
});
const tooltipProps: HistogramTooltipProps = {
...props,
timezone: rest.timezone,
yAxisUnit: rest.yAxisUnit,
decimalPrecision: rest.decimalPrecision,
content,
};
return <HistogramTooltip {...tooltipProps} />;
},
[customRenderTooltip, rest.timezone, rest.yAxisUnit, rest.decimalPrecision],
);
return (
<ChartWrapper
showLegend={!isQueriesMerged}
{...rest}
renderTooltip={renderTooltip}
>
{children}
</ChartWrapper>
);
}

View File

@@ -7,6 +7,7 @@ interface BaseChartProps {
width: number;
height: number;
showTooltip?: boolean;
showLegend?: boolean;
timezone: string;
canPinTooltip?: boolean;
yAxisUnit?: string;
@@ -17,6 +18,7 @@ interface BaseChartProps {
interface UPlotBasedChartProps {
config: UPlotConfigBuilder;
data: uPlot.AlignedData;
legendConfig: LegendConfig;
syncMode?: DashboardCursorSync;
syncKey?: string;
plotRef?: (plot: uPlot | null) => void;
@@ -26,14 +28,20 @@ interface UPlotBasedChartProps {
}
export interface TimeSeriesChartProps
extends BaseChartProps,
UPlotBasedChartProps {}
export interface HistogramChartProps
extends BaseChartProps,
UPlotBasedChartProps {
legendConfig: LegendConfig;
isQueriesMerged?: boolean;
}
export interface BarChartProps extends BaseChartProps, UPlotBasedChartProps {
legendConfig: LegendConfig;
isStackedBarChart?: boolean;
}
export type ChartProps = TimeSeriesChartProps | BarChartProps;
export type ChartProps =
| TimeSeriesChartProps
| BarChartProps
| HistogramChartProps;

View File

@@ -1,12 +1,14 @@
import { useMemo } from 'react';
import cx from 'classnames';
import { calculateChartDimensions } from 'container/DashboardContainer/visualization/charts/utils';
import { MAX_LEGEND_WIDTH } from 'lib/uPlotV2/components/Legend/Legend';
import { LegendConfig, LegendPosition } from 'lib/uPlotV2/components/types';
import { UPlotConfigBuilder } from 'lib/uPlotV2/config/UPlotConfigBuilder';
import './ChartLayout.styles.scss';
export interface ChartLayoutProps {
showLegend?: boolean;
legendComponent: (legendPerSet: number) => React.ReactNode;
children: (props: {
chartWidth: number;
@@ -20,6 +22,7 @@ export interface ChartLayoutProps {
config: UPlotConfigBuilder;
}
export default function ChartLayout({
showLegend = true,
legendComponent,
children,
layoutChildren,
@@ -30,6 +33,15 @@ export default function ChartLayout({
}: ChartLayoutProps): JSX.Element {
const chartDimensions = useMemo(
() => {
if (!showLegend) {
return {
width: containerWidth,
height: containerHeight,
legendWidth: 0,
legendHeight: 0,
averageLegendWidth: MAX_LEGEND_WIDTH,
};
}
const legendItemsMap = config.getLegendItems();
const seriesLabels = Object.values(legendItemsMap)
.map((item) => item.label)
@@ -42,7 +54,7 @@ export default function ChartLayout({
});
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[containerWidth, containerHeight, legendConfig],
[containerWidth, containerHeight, legendConfig, showLegend],
);
return (
@@ -60,15 +72,17 @@ export default function ChartLayout({
averageLegendWidth: chartDimensions.averageLegendWidth,
})}
</div>
<div
className="chart-layout__legend-wrapper"
style={{
height: chartDimensions.legendHeight,
width: chartDimensions.legendWidth,
}}
>
{legendComponent(chartDimensions.averageLegendWidth)}
</div>
{showLegend && (
<div
className="chart-layout__legend-wrapper"
style={{
height: chartDimensions.legendHeight,
width: chartDimensions.legendWidth,
}}
>
{legendComponent(chartDimensions.averageLegendWidth)}
</div>
)}
</div>
{layoutChildren}
</div>

View File

@@ -0,0 +1,121 @@
import { useEffect, useMemo, useRef } from 'react';
import { PanelWrapperProps } from 'container/PanelWrapper/panelWrapper.types';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useResizeObserver } from 'hooks/useDimensions';
import { LegendPosition } from 'lib/uPlotV2/components/types';
import { DashboardCursorSync } from 'lib/uPlotV2/plugins/TooltipPlugin/types';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useTimezone } from 'providers/Timezone';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import uPlot from 'uplot';
import Histogram from '../../charts/Histogram/Histogram';
import ChartManager from '../../components/ChartManager/ChartManager';
import {
prepareHistogramPanelConfig,
prepareHistogramPanelData,
} from './utils';
function HistogramPanel(props: PanelWrapperProps): JSX.Element {
const {
panelMode,
queryResponse,
widget,
isFullViewMode,
onToggleModelHandler,
} = props;
const uPlotRef = useRef<uPlot | null>(null);
const { toScrollWidgetId, setToScrollWidgetId } = useDashboard();
const graphRef = useRef<HTMLDivElement>(null);
const containerDimensions = useResizeObserver(graphRef);
const isDarkMode = useIsDarkMode();
const { timezone } = useTimezone();
useEffect(() => {
if (toScrollWidgetId === widget.id) {
graphRef.current?.scrollIntoView({
behavior: 'smooth',
block: 'center',
});
graphRef.current?.focus();
setToScrollWidgetId('');
}
}, [toScrollWidgetId, setToScrollWidgetId, widget.id]);
const config = useMemo(() => {
return prepareHistogramPanelConfig({
widget,
isDarkMode,
apiResponse: queryResponse?.data?.payload as MetricRangePayloadProps,
panelMode,
});
}, [widget, isDarkMode, queryResponse?.data?.payload, panelMode]);
const chartData = useMemo(() => {
if (!queryResponse?.data?.payload) {
return [];
}
return prepareHistogramPanelData({
apiResponse: queryResponse?.data?.payload as MetricRangePayloadProps,
bucketWidth: widget?.bucketWidth,
bucketCount: widget?.bucketCount,
mergeAllActiveQueries: widget?.mergeAllActiveQueries,
});
}, [
queryResponse?.data?.payload,
widget?.bucketWidth,
widget?.bucketCount,
widget?.mergeAllActiveQueries,
]);
const layoutChildren = useMemo(() => {
if (!isFullViewMode) {
return null;
}
return (
<ChartManager
config={config}
alignedData={chartData}
yAxisUnit={widget.yAxisUnit}
onCancel={onToggleModelHandler}
/>
);
}, [
isFullViewMode,
config,
chartData,
widget.yAxisUnit,
onToggleModelHandler,
]);
return (
<div style={{ height: '100%', width: '100%' }} ref={graphRef}>
{containerDimensions.width > 0 && containerDimensions.height > 0 && (
<Histogram
config={config}
legendConfig={{
position: widget?.legendPosition ?? LegendPosition.BOTTOM,
}}
plotRef={(plot: uPlot | null): void => {
uPlotRef.current = plot;
}}
onDestroy={(): void => {
uPlotRef.current = null;
}}
isQueriesMerged={widget.mergeAllActiveQueries}
yAxisUnit={widget.yAxisUnit}
decimalPrecision={widget.decimalPrecision}
syncMode={DashboardCursorSync.Crosshair}
timezone={timezone.value}
data={chartData as uPlot.AlignedData}
width={containerDimensions.width}
height={containerDimensions.height}
layoutChildren={layoutChildren}
/>
)}
</div>
);
}
export default HistogramPanel;

View File

@@ -0,0 +1,214 @@
import { histogramBucketSizes } from '@grafana/data';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { DEFAULT_BUCKET_COUNT } from 'container/PanelWrapper/constants';
import { getLegend } from 'lib/dashboard/getQueryResults';
import getLabelName from 'lib/getLabelName';
import { DrawStyle, VisibilityMode } from 'lib/uPlotV2/config/types';
import { UPlotConfigBuilder } from 'lib/uPlotV2/config/UPlotConfigBuilder';
import { incrRoundDn } from 'lib/uPlotV2/utils/scale';
import { Widgets } from 'types/api/dashboard/getAll';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { AlignedData } from 'uplot';
import { PanelMode } from '../types';
import { buildBaseConfig } from '../utils/baseConfigBuilder';
import {
addNullToFirstHistogram,
histogram,
join,
replaceUndefinedWithNull,
roundDecimals,
} from '../utils/histogram';
export interface PrepareHistogramPanelDataParams {
apiResponse: MetricRangePayloadProps;
bucketWidth?: number;
bucketCount?: number;
mergeAllActiveQueries?: boolean;
}
const BUCKET_OFFSET = 0;
const HIST_SORT = (a: number, b: number): number => a - b;
function extractNumericValues(
result: MetricRangePayloadProps['data']['result'],
): number[] {
const values: number[] = [];
for (const item of result) {
for (const [, valueStr] of item.values) {
values.push(Number.parseFloat(valueStr) || 0);
}
}
return values;
}
function computeSmallestDelta(sortedValues: number[]): number {
if (sortedValues.length <= 1) {
return 0;
}
let smallest = Infinity;
for (let i = 1; i < sortedValues.length; i++) {
const delta = sortedValues[i] - sortedValues[i - 1];
if (delta > 0) {
smallest = Math.min(smallest, delta);
}
}
return smallest === Infinity ? 0 : smallest;
}
function selectBucketSize({
range,
bucketCount,
smallestDelta,
bucketWidthOverride,
}: {
range: number;
bucketCount: number;
smallestDelta: number;
bucketWidthOverride?: number;
}): number {
if (bucketWidthOverride != null && bucketWidthOverride > 0) {
return bucketWidthOverride;
}
const targetSize = range / bucketCount;
for (const candidate of histogramBucketSizes) {
if (targetSize < candidate && candidate >= smallestDelta) {
return candidate;
}
}
return 0;
}
function buildFrames(
result: MetricRangePayloadProps['data']['result'],
mergeAllActiveQueries: boolean,
): number[][] {
const frames: number[][] = result.map((item) =>
item.values.map(([, valueStr]) => Number.parseFloat(valueStr) || 0),
);
if (mergeAllActiveQueries && frames.length > 1) {
const first = frames[0];
for (let i = 1; i < frames.length; i++) {
first.push(...frames[i]);
frames[i] = [];
}
}
return frames;
}
export function prepareHistogramPanelData({
apiResponse,
bucketWidth,
bucketCount: bucketCountProp = DEFAULT_BUCKET_COUNT,
mergeAllActiveQueries = false,
}: PrepareHistogramPanelDataParams): AlignedData {
const bucketCount = bucketCountProp ?? DEFAULT_BUCKET_COUNT;
const result = apiResponse.data.result;
const seriesValues = extractNumericValues(result);
if (seriesValues.length === 0) {
return [[]];
}
const sorted = [...seriesValues].sort((a, b) => a - b);
const min = sorted[0];
const max = sorted[sorted.length - 1];
const range = max - min;
const smallestDelta = computeSmallestDelta(sorted);
let bucketSize = selectBucketSize({
range,
bucketCount,
smallestDelta,
bucketWidthOverride: bucketWidth,
});
if (bucketSize <= 0) {
bucketSize = range > 0 ? range / bucketCount : 1;
}
const getBucket = (v: number): number =>
roundDecimals(incrRoundDn(v - BUCKET_OFFSET, bucketSize) + BUCKET_OFFSET, 9);
const frames = buildFrames(result, mergeAllActiveQueries);
const histograms: AlignedData[] = frames
.filter((frame) => frame.length > 0)
.map((frame) => histogram(frame, getBucket, HIST_SORT));
if (histograms.length === 0) {
return [[]];
}
const joined = join(histograms);
replaceUndefinedWithNull(joined);
addNullToFirstHistogram(joined, bucketSize);
return joined;
}
export function prepareHistogramPanelConfig({
widget,
apiResponse,
panelMode,
isDarkMode,
}: {
widget: Widgets;
apiResponse: MetricRangePayloadProps;
panelMode: PanelMode;
isDarkMode: boolean;
}): UPlotConfigBuilder {
const builder = buildBaseConfig({
widget,
isDarkMode,
apiResponse,
panelMode,
panelType: PANEL_TYPES.HISTOGRAM,
});
builder.setCursor({
drag: {
x: false,
y: false,
setScale: true,
},
focus: {
prox: 1e3,
},
});
builder.addScale({
scaleKey: 'x',
time: false,
auto: true,
});
builder.addScale({
scaleKey: 'y',
time: false,
auto: true,
});
const currentQuery = widget.query;
apiResponse.data.result.forEach((series) => {
const baseLabelName = getLabelName(
series.metric,
series.queryName || '', // query
series.legend || '',
);
const label = currentQuery
? getLegend(series, currentQuery, baseLabelName)
: baseLabelName;
builder.addSeries({
label: label,
scaleKey: 'y',
drawStyle: DrawStyle.Bar,
panelType: PANEL_TYPES.HISTOGRAM,
colorMapping: widget.customLegendColors ?? {},
spanGaps: false,
barWidthFactor: 1,
showPoints: VisibilityMode.Never,
pointSize: 5,
isDarkMode,
});
});
return builder;
}

View File

@@ -14,11 +14,6 @@ export interface GraphVisibilityState {
dataIndex: SeriesVisibilityItem[];
}
export interface SeriesVisibilityState {
labels: string[];
visibility: boolean[];
}
/**
* Context in which a panel is rendered. Used to vary behavior (e.g. persistence,
* interactions) per context.

View File

@@ -62,10 +62,10 @@ describe('legendVisibilityUtils', () => {
const result = getStoredSeriesVisibility('widget-1');
expect(result).not.toBeNull();
expect(result).toEqual({
labels: ['CPU', 'Memory'],
visibility: [true, false],
});
expect(result).toEqual([
{ label: 'CPU', show: true },
{ label: 'Memory', show: false },
]);
});
it('returns visibility by index including duplicate labels', () => {
@@ -85,10 +85,11 @@ describe('legendVisibilityUtils', () => {
const result = getStoredSeriesVisibility('widget-1');
expect(result).not.toBeNull();
expect(result).toEqual({
labels: ['CPU', 'CPU', 'Memory'],
visibility: [true, false, false],
});
expect(result).toEqual([
{ label: 'CPU', show: true },
{ label: 'CPU', show: false },
{ label: 'Memory', show: false },
]);
});
it('returns null on malformed JSON in localStorage', () => {
@@ -127,10 +128,10 @@ describe('legendVisibilityUtils', () => {
const stored = getStoredSeriesVisibility('widget-1');
expect(stored).not.toBeNull();
expect(stored).toEqual({
labels: ['CPU', 'Memory'],
visibility: [true, false],
});
expect(stored).toEqual([
{ label: 'CPU', show: true },
{ label: 'Memory', show: false },
]);
});
it('adds a new widget entry when other widgets already exist', () => {
@@ -149,7 +150,7 @@ describe('legendVisibilityUtils', () => {
const stored = getStoredSeriesVisibility('widget-new');
expect(stored).not.toBeNull();
expect(stored).toEqual({ labels: ['CPU'], visibility: [false] });
expect(stored).toEqual([{ label: 'CPU', show: false }]);
});
it('updates existing widget visibility when entry already exists', () => {
@@ -175,10 +176,10 @@ describe('legendVisibilityUtils', () => {
const stored = getStoredSeriesVisibility('widget-1');
expect(stored).not.toBeNull();
expect(stored).toEqual({
labels: ['CPU', 'Memory'],
visibility: [false, true],
});
expect(stored).toEqual([
{ label: 'CPU', show: false },
{ label: 'Memory', show: true },
]);
});
it('silently handles malformed existing JSON without throwing', () => {
@@ -201,10 +202,10 @@ describe('legendVisibilityUtils', () => {
const stored = getStoredSeriesVisibility('widget-1');
expect(stored).not.toBeNull();
expect(stored).toEqual({
labels: ['x-axis', 'CPU'],
visibility: [true, false],
});
expect(stored).toEqual([
{ label: 'x-axis', show: true },
{ label: 'CPU', show: false },
]);
const expected = [
{
name: 'widget-1',
@@ -231,14 +232,12 @@ describe('legendVisibilityUtils', () => {
{ label: 'B', show: true },
]);
expect(getStoredSeriesVisibility('widget-a')).toEqual({
labels: ['A'],
visibility: [true],
});
expect(getStoredSeriesVisibility('widget-b')).toEqual({
labels: ['B'],
visibility: [true],
});
expect(getStoredSeriesVisibility('widget-a')).toEqual([
{ label: 'A', show: true },
]);
expect(getStoredSeriesVisibility('widget-b')).toEqual([
{ label: 'B', show: true },
]);
});
it('calls setItem with storage key and stringified visibility states', () => {

View File

@@ -19,9 +19,9 @@ export interface BaseConfigBuilderProps {
widget: Widgets;
apiResponse: MetricRangePayloadProps;
isDarkMode: boolean;
onClick: OnClickPluginOpts['onClick'];
onDragSelect: (startTime: number, endTime: number) => void;
timezone: Timezone;
onClick?: OnClickPluginOpts['onClick'];
onDragSelect?: (startTime: number, endTime: number) => void;
timezone?: Timezone;
panelMode: PanelMode;
panelType: PANEL_TYPES;
minTimeScale?: number;
@@ -40,8 +40,10 @@ export function buildBaseConfig({
minTimeScale,
maxTimeScale,
}: BaseConfigBuilderProps): UPlotConfigBuilder {
const tzDate = (timestamp: number): Date =>
uPlot.tzDate(new Date(timestamp * 1e3), timezone.value);
const tzDate = timezone
? (timestamp: number): Date =>
uPlot.tzDate(new Date(timestamp * 1e3), timezone.value)
: undefined;
const builder = new UPlotConfigBuilder({
onDragSelect,

View File

@@ -0,0 +1,181 @@
/* eslint-disable sonarjs/cognitive-complexity */
/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable no-param-reassign */
import {
NULL_EXPAND,
NULL_REMOVE,
NULL_RETAIN,
} from 'container/PanelWrapper/constants';
import { AlignedData } from 'uplot';
export function incrRoundDn(num: number, incr: number): number {
return Math.floor(num / incr) * incr;
}
export function roundDecimals(val: number, dec = 0): number {
if (Number.isInteger(val)) {
return val;
}
const p = 10 ** dec;
const n = val * p * (1 + Number.EPSILON);
return Math.round(n) / p;
}
function nullExpand(
yVals: Array<number | null>,
nullIdxs: number[],
alignedLen: number,
): void {
for (let i = 0, xi, lastNullIdx = -1; i < nullIdxs.length; i++) {
const nullIdx = nullIdxs[i];
if (nullIdx > lastNullIdx) {
xi = nullIdx - 1;
while (xi >= 0 && yVals[xi] == null) {
yVals[xi--] = null;
}
xi = nullIdx + 1;
while (xi < alignedLen && yVals[xi] == null) {
yVals[(lastNullIdx = xi++)] = null;
}
}
}
}
export function join(
tables: AlignedData[],
nullModes?: number[][],
): AlignedData {
let xVals: Set<number>;
// eslint-disable-next-line prefer-const
xVals = new Set();
for (let ti = 0; ti < tables.length; ti++) {
const t = tables[ti];
const xs = t[0];
const len = xs.length;
for (let i = 0; i < len; i++) {
xVals.add(xs[i]);
}
}
const data = [Array.from(xVals).sort((a, b) => a - b)];
const alignedLen = data[0].length;
const xIdxs = new Map();
for (let i = 0; i < alignedLen; i++) {
xIdxs.set(data[0][i], i);
}
for (let ti = 0; ti < tables.length; ti++) {
const t = tables[ti];
const xs = t[0];
for (let si = 1; si < t.length; si++) {
const ys = t[si];
const yVals = Array(alignedLen).fill(undefined);
const nullMode = nullModes ? nullModes[ti][si] : NULL_RETAIN;
const nullIdxs = [];
for (let i = 0; i < ys.length; i++) {
const yVal = ys[i];
const alignedIdx = xIdxs.get(xs[i]);
if (yVal === null) {
if (nullMode !== NULL_REMOVE) {
yVals[alignedIdx] = yVal;
if (nullMode === NULL_EXPAND) {
nullIdxs.push(alignedIdx);
}
}
} else {
yVals[alignedIdx] = yVal;
}
}
nullExpand(yVals, nullIdxs, alignedLen);
data.push(yVals);
}
}
return data as AlignedData;
}
export function histogram(
vals: number[],
getBucket: (v: number) => number,
sort?: ((a: number, b: number) => number) | null,
): AlignedData {
const hist = new Map();
for (let i = 0; i < vals.length; i++) {
let v = vals[i];
if (v != null) {
v = getBucket(v);
}
const entry = hist.get(v);
if (entry) {
entry.count++;
} else {
hist.set(v, { value: v, count: 1 });
}
}
const bins = [...hist.values()];
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
sort && bins.sort((a, b) => sort(a.value, b.value));
const values = Array(bins.length);
const counts = Array(bins.length);
for (let i = 0; i < bins.length; i++) {
values[i] = bins[i].value;
counts[i] = bins[i].count;
}
return [values, counts];
}
export function replaceUndefinedWithNull(data: AlignedData): AlignedData {
const arrays = data as (number | null | undefined)[][];
for (let i = 0; i < arrays.length; i++) {
for (let j = 0; j < arrays[i].length; j++) {
if (arrays[i][j] === undefined) {
arrays[i][j] = null;
}
}
}
return data;
}
export function addNullToFirstHistogram(
data: AlignedData,
bucketSize: number,
): void {
const histograms = data as (number | null)[][];
if (
histograms.length > 0 &&
histograms[0].length > 0 &&
histograms[0][0] !== null
) {
histograms[0].unshift(histograms[0][0] - bucketSize);
for (let i = 1; i < histograms.length; i++) {
histograms[i].unshift(null);
}
}
}

View File

@@ -1,10 +1,6 @@
import { LOCALSTORAGE } from 'constants/localStorage';
import {
GraphVisibilityState,
SeriesVisibilityItem,
SeriesVisibilityState,
} from '../types';
import { GraphVisibilityState, SeriesVisibilityItem } from '../types';
/**
* Retrieves the stored series visibility for a specific widget from localStorage by index.
@@ -14,7 +10,7 @@ import {
*/
export function getStoredSeriesVisibility(
widgetId: string,
): SeriesVisibilityState | null {
): SeriesVisibilityItem[] | null {
try {
const storedData = localStorage.getItem(LOCALSTORAGE.GRAPH_VISIBILITY_STATES);
@@ -29,10 +25,7 @@ export function getStoredSeriesVisibility(
return null;
}
return {
labels: widgetState.dataIndex.map((item) => item.label),
visibility: widgetState.dataIndex.map((item) => item.show),
};
return widgetState.dataIndex;
} catch (error) {
if (error instanceof SyntaxError) {
// If the stored data is malformed, remove it

View File

@@ -1,11 +1,11 @@
import { PANEL_TYPES } from 'constants/queryBuilder';
import BarPanel from 'container/DashboardContainer/visualization/panels/BarPanel/BarPanel';
import TimeSeriesPanel from '../DashboardContainer/visualization/panels/TimeSeriesPanel/TimeSeriesPanel';
import HistogramPanelWrapper from './HistogramPanelWrapper';
import ListPanelWrapper from './ListPanelWrapper';
import PiePanelWrapper from './PiePanelWrapper';
import TablePanelWrapper from './TablePanelWrapper';
import UplotPanelWrapper from './UplotPanelWrapper';
import ValuePanelWrapper from './ValuePanelWrapper';
export const PanelTypeVsPanelWrapper = {
@@ -16,7 +16,7 @@ export const PanelTypeVsPanelWrapper = {
[PANEL_TYPES.TRACE]: null,
[PANEL_TYPES.EMPTY_WIDGET]: null,
[PANEL_TYPES.PIE]: PiePanelWrapper,
[PANEL_TYPES.BAR]: UplotPanelWrapper,
[PANEL_TYPES.BAR]: BarPanel,
[PANEL_TYPES.HISTOGRAM]: HistogramPanelWrapper,
};

View File

@@ -0,0 +1,8 @@
import { HistogramTooltipProps } from '../types';
import Tooltip from './Tooltip';
export default function HistogramTooltip(
props: HistogramTooltipProps,
): JSX.Element {
return <Tooltip {...props} showTooltipHeader={false} />;
}

View File

@@ -16,12 +16,16 @@ export default function Tooltip({
uPlotInstance,
timezone,
content,
showTooltipHeader = true,
}: TooltipProps): JSX.Element {
const isDarkMode = useIsDarkMode();
const tooltipContent = content ?? [];
const headerTitle = useMemo(() => {
if (!showTooltipHeader) {
return null;
}
const data = uPlotInstance.data;
const cursorIdx = uPlotInstance.cursor.idx;
if (cursorIdx == null) {
@@ -30,7 +34,12 @@ export default function Tooltip({
return dayjs(data[0][cursorIdx] * 1000)
.tz(timezone)
.format(DATE_TIME_FORMATS.MONTH_DATETIME_SECONDS);
}, [timezone, uPlotInstance.data, uPlotInstance.cursor.idx]);
}, [
timezone,
uPlotInstance.data,
uPlotInstance.cursor.idx,
showTooltipHeader,
]);
return (
<div
@@ -39,9 +48,11 @@ export default function Tooltip({
isDarkMode ? 'darkMode' : 'lightMode',
)}
>
<div className="uplot-tooltip-header">
<span>{headerTitle}</span>
</div>
{showTooltipHeader && (
<div className="uplot-tooltip-header">
<span>{headerTitle}</span>
</div>
)}
<div
style={{
height: Math.min(

View File

@@ -60,6 +60,7 @@ export interface TooltipRenderArgs {
}
export interface BaseTooltipProps {
showTooltipHeader?: boolean;
timezone: string;
yAxisUnit?: string;
decimalPrecision?: PrecisionOption;
@@ -74,7 +75,14 @@ export interface BarTooltipProps extends BaseTooltipProps, TooltipRenderArgs {
isStackedBarChart?: boolean;
}
export type TooltipProps = TimeSeriesTooltipProps | BarTooltipProps;
export interface HistogramTooltipProps
extends BaseTooltipProps,
TooltipRenderArgs {}
export type TooltipProps =
| TimeSeriesTooltipProps
| BarTooltipProps
| HistogramTooltipProps;
export enum LegendPosition {
BOTTOM = 'bottom',

View File

@@ -1,4 +1,4 @@
import { SeriesVisibilityState } from 'container/DashboardContainer/visualization/panels/types';
import { SeriesVisibilityItem } from 'container/DashboardContainer/visualization/panels/types';
import { getStoredSeriesVisibility } from 'container/DashboardContainer/visualization/panels/utils/legendVisibilityUtils';
import { ThresholdsDrawHookOptions } from 'lib/uPlotV2/hooks/types';
import { thresholdsDrawHook } from 'lib/uPlotV2/hooks/useThresholdsDrawHook';
@@ -238,7 +238,7 @@ export class UPlotConfigBuilder extends ConfigBuilder<
/**
* Returns stored series visibility by index from localStorage when preferences source is LOCAL_STORAGE, otherwise null.
*/
private getStoredVisibility(): SeriesVisibilityState | null {
private getStoredVisibility(): SeriesVisibilityItem[] | null {
if (
this.widgetId &&
this.selectionPreferencesSource === SelectionPreferencesSource.LOCAL_STORAGE
@@ -248,14 +248,98 @@ export class UPlotConfigBuilder extends ConfigBuilder<
return null;
}
/**
* Derive visibility resolution state from stored preferences and current series:
* - visibleStoredLabels: labels that should always be visible
* - hiddenStoredLabels: labels that should always be hidden
* - hasActivePreference: whether a "mix" preference applies to new labels
*/
// eslint-disable-next-line sonarjs/cognitive-complexity
private getVisibilityResolutionState(): {
visibleStoredLabels: Set<string>;
hiddenStoredLabels: Set<string>;
hasActivePreference: boolean;
} {
const seriesVisibilityState = this.getStoredVisibility();
if (!seriesVisibilityState || seriesVisibilityState.length === 0) {
return {
visibleStoredLabels: new Set<string>(),
hiddenStoredLabels: new Set<string>(),
hasActivePreference: false,
};
}
// Single pass over stored items to derive:
// - visibleStoredLabels: any label that is ever stored as visible
// - hiddenStoredLabels: labels that are only ever stored as hidden
// - hasMixPreference: there is at least one visible and one hidden entry
const visibleStoredLabels = new Set<string>();
const hiddenStoredLabels = new Set<string>();
let hasAnyVisible = false;
let hasAnyHidden = false;
for (const { label, show } of seriesVisibilityState) {
if (show) {
hasAnyVisible = true;
visibleStoredLabels.add(label);
// If a label is ever visible, it should not be treated as "only hidden"
if (hiddenStoredLabels.has(label)) {
hiddenStoredLabels.delete(label);
}
} else {
hasAnyHidden = true;
// Only track as hidden if we have not already seen it as visible
if (!visibleStoredLabels.has(label)) {
hiddenStoredLabels.add(label);
}
}
}
const hasMixPreference = hasAnyVisible && hasAnyHidden;
// Current series labels in this chart.
const currentSeriesLabels = this.series.map(
(s: UPlotSeriesBuilder) => s.getConfig().label ?? '',
);
// Check if any stored "visible" label exists in the current series list.
const hasVisibleIntersection =
visibleStoredLabels.size > 0 &&
currentSeriesLabels.some((label) => visibleStoredLabels.has(label));
// Active preference only when there is a mix AND at least one visible
// stored label is present in the current series list.
const hasActivePreference = hasMixPreference && hasVisibleIntersection;
// We apply stored visibility in two cases:
// - There is an active preference (mix + intersection), OR
// - There is no mix (all true or all false) preserve legacy behavior.
const shouldApplyStoredVisibility = !hasMixPreference || hasActivePreference;
if (!shouldApplyStoredVisibility) {
return {
visibleStoredLabels: new Set<string>(),
hiddenStoredLabels: new Set<string>(),
hasActivePreference,
};
}
return {
visibleStoredLabels,
hiddenStoredLabels,
hasActivePreference,
};
}
/**
* Get legend items with visibility state restored from localStorage if available
*/
getLegendItems(): Record<number, LegendItem> {
const seriesVisibilityState = this.getStoredVisibility();
const isAnySeriesHidden = !!seriesVisibilityState?.visibility?.some(
(show) => !show,
);
const {
visibleStoredLabels,
hiddenStoredLabels,
hasActivePreference,
} = this.getVisibilityResolutionState();
return this.series.reduce((acc, s: UPlotSeriesBuilder, index: number) => {
const seriesConfig = s.getConfig();
@@ -263,11 +347,11 @@ export class UPlotConfigBuilder extends ConfigBuilder<
// +1 because uPlot series 0 is x-axis/time; data series are at 1, 2, ... (also matches stored visibility[0]=time, visibility[1]=first data, ...)
const seriesIndex = index + 1;
const show = resolveSeriesVisibility({
seriesIndex,
seriesShow: seriesConfig.show,
seriesLabel: label,
seriesVisibilityState,
isAnySeriesHidden,
visibleStoredLabels,
hiddenStoredLabels,
hasActivePreference,
});
acc[seriesIndex] = {
@@ -296,22 +380,23 @@ export class UPlotConfigBuilder extends ConfigBuilder<
...DEFAULT_PLOT_CONFIG,
};
const seriesVisibilityState = this.getStoredVisibility();
const isAnySeriesHidden = !!seriesVisibilityState?.visibility?.some(
(show) => !show,
);
const {
visibleStoredLabels,
hiddenStoredLabels,
hasActivePreference,
} = this.getVisibilityResolutionState();
config.series = [
{ value: (): string => '' }, // Base series for timestamp
...this.series.map((s, index) => {
...this.series.map((s) => {
const series = s.getConfig();
// Stored visibility[0] is x-axis/time; data series start at visibility[1]
const visible = resolveSeriesVisibility({
seriesIndex: index + 1,
seriesShow: series.show,
seriesLabel: series.label ?? '',
seriesVisibilityState,
isAnySeriesHidden,
visibleStoredLabels,
hiddenStoredLabels,
hasActivePreference,
});
return {
...series,

View File

@@ -186,11 +186,10 @@ describe('UPlotConfigBuilder', () => {
});
it('restores visibility state from localStorage when selectionPreferencesSource is LOCAL_STORAGE', () => {
// Index 0 = x-axis/time; indices 1,2 = data series (Requests, Errors). resolveSeriesVisibility matches by seriesIndex + seriesLabel.
getStoredSeriesVisibilityMock.getStoredSeriesVisibility.mockReturnValue({
labels: ['x-axis', 'Requests', 'Errors'],
visibility: [true, true, false],
});
getStoredSeriesVisibilityMock.getStoredSeriesVisibility.mockReturnValue([
{ label: 'Requests', show: true },
{ label: 'Errors', show: false },
]);
const builder = new UPlotConfigBuilder({
widgetId: 'widget-1',
@@ -202,7 +201,7 @@ describe('UPlotConfigBuilder', () => {
const legendItems = builder.getLegendItems();
// When any series is hidden, legend visibility is driven by the stored map
// When any series is hidden, visibility is driven by stored label-based preferences
expect(legendItems[1].show).toBe(true);
expect(legendItems[2].show).toBe(false);
@@ -213,6 +212,109 @@ describe('UPlotConfigBuilder', () => {
expect(secondSeries?.show).toBe(false);
});
it('hides new series by default when there is a mixed preference and a visible label matches current series', () => {
getStoredSeriesVisibilityMock.getStoredSeriesVisibility.mockReturnValue([
{ label: 'Requests', show: true },
{ label: 'Errors', show: false },
]);
const builder = new UPlotConfigBuilder({
widgetId: 'widget-1',
selectionPreferencesSource: SelectionPreferencesSource.LOCAL_STORAGE,
});
builder.addSeries(createSeriesProps({ label: 'Requests' }));
builder.addSeries(createSeriesProps({ label: 'Errors' }));
builder.addSeries(createSeriesProps({ label: 'Latency' }));
const legendItems = builder.getLegendItems();
// Stored labels: Requests (visible), Errors (hidden).
// New label "Latency" should be hidden because there is a mixed preference
// and "Requests" (a visible stored label) is present in the current series.
expect(legendItems[1].label).toBe('Requests');
expect(legendItems[1].show).toBe(true);
expect(legendItems[2].label).toBe('Errors');
expect(legendItems[2].show).toBe(false);
expect(legendItems[3].label).toBe('Latency');
expect(legendItems[3].show).toBe(false);
const config = builder.getConfig();
const [, firstSeries, secondSeries, thirdSeries] = config.series ?? [];
expect(firstSeries?.label).toBe('Requests');
expect(firstSeries?.show).toBe(true);
expect(secondSeries?.label).toBe('Errors');
expect(secondSeries?.show).toBe(false);
expect(thirdSeries?.label).toBe('Latency');
expect(thirdSeries?.show).toBe(false);
});
it('shows all series when there is a mixed preference but no visible stored labels match current series', () => {
getStoredSeriesVisibilityMock.getStoredSeriesVisibility.mockReturnValue([
{ label: 'StoredVisible', show: true },
{ label: 'StoredHidden', show: false },
]);
const builder = new UPlotConfigBuilder({
widgetId: 'widget-1',
selectionPreferencesSource: SelectionPreferencesSource.LOCAL_STORAGE,
});
// None of these labels intersect with the stored visible label "StoredVisible"
builder.addSeries(createSeriesProps({ label: 'CPU' }));
builder.addSeries(createSeriesProps({ label: 'Memory' }));
const legendItems = builder.getLegendItems();
// Mixed preference exists in storage, but since no visible labels intersect
// with current series, stored preferences are ignored and all are visible.
expect(legendItems[1].label).toBe('CPU');
expect(legendItems[1].show).toBe(true);
expect(legendItems[2].label).toBe('Memory');
expect(legendItems[2].show).toBe(true);
const config = builder.getConfig();
const [, firstSeries, secondSeries] = config.series ?? [];
expect(firstSeries?.label).toBe('CPU');
expect(firstSeries?.show).toBe(true);
expect(secondSeries?.label).toBe('Memory');
expect(secondSeries?.show).toBe(true);
});
it('treats duplicate labels as visible when any stored entry for that label is visible', () => {
getStoredSeriesVisibilityMock.getStoredSeriesVisibility.mockReturnValue([
{ label: 'CPU', show: true },
{ label: 'CPU', show: false },
]);
const builder = new UPlotConfigBuilder({
widgetId: 'widget-dup',
selectionPreferencesSource: SelectionPreferencesSource.LOCAL_STORAGE,
});
// Two series with the same label; both should be visible because at least
// one stored entry for "CPU" is visible.
builder.addSeries(createSeriesProps({ label: 'CPU' }));
builder.addSeries(createSeriesProps({ label: 'CPU' }));
const legendItems = builder.getLegendItems();
expect(legendItems[1].label).toBe('CPU');
expect(legendItems[1].show).toBe(true);
expect(legendItems[2].label).toBe('CPU');
expect(legendItems[2].show).toBe(true);
const config = builder.getConfig();
const [, firstSeries, secondSeries] = config.series ?? [];
expect(firstSeries?.label).toBe('CPU');
expect(firstSeries?.show).toBe(true);
expect(secondSeries?.label).toBe('CPU');
expect(secondSeries?.show).toBe(true);
});
it('does not attempt to read stored visibility when using in-memory preferences', () => {
const builder = new UPlotConfigBuilder({
widgetId: 'widget-1',

View File

@@ -1,25 +1,44 @@
import { SeriesVisibilityState } from 'container/DashboardContainer/visualization/panels/types';
/**
* Resolve the visibility of a single series based on:
* - Stored per-series visibility (when applicable)
* - Whether there is an "active preference" (mix of visible/hidden that matches current series)
* - The series' own default show flag
*/
export function resolveSeriesVisibility({
seriesIndex,
seriesShow,
seriesLabel,
seriesVisibilityState,
isAnySeriesHidden,
visibleStoredLabels,
hiddenStoredLabels,
hasActivePreference,
}: {
seriesIndex: number;
seriesShow: boolean | undefined | null;
seriesLabel: string;
seriesVisibilityState: SeriesVisibilityState | null;
isAnySeriesHidden: boolean;
visibleStoredLabels: Set<string> | null;
hiddenStoredLabels: Set<string> | null;
hasActivePreference: boolean;
}): boolean {
if (
isAnySeriesHidden &&
seriesVisibilityState?.visibility &&
seriesVisibilityState.labels.length > seriesIndex &&
seriesVisibilityState.labels[seriesIndex] === seriesLabel
) {
return seriesVisibilityState.visibility[seriesIndex] ?? false;
const isStoredVisible = !!visibleStoredLabels?.has(seriesLabel);
const isStoredHidden = !!hiddenStoredLabels?.has(seriesLabel);
// If the label is explicitly stored as visible, always show it.
if (isStoredVisible) {
return true;
}
// If the label is explicitly stored as hidden (and never stored as visible),
// always hide it.
if (isStoredHidden) {
return false;
}
// "Active preference" means:
// - There is a mix of visible/hidden in storage, AND
// - At least one stored *visible* label exists in the current series list.
// For such a preference, any new/unknown series should be hidden by default.
if (hasActivePreference) {
return false;
}
// Otherwise fall back to the series' own config or show by default.
return seriesShow ?? true;
}

View File

@@ -204,6 +204,78 @@ describe('dashboardVariablesStore', () => {
expect(doAllVariablesHaveValuesSelected).toBe(true);
});
it('should treat DYNAMIC variable with allSelected=true and selectedValue=undefined as having a value', () => {
setDashboardVariablesStore({
dashboardId: 'dash-1',
variables: {
dyn1: createVariable({
name: 'dyn1',
type: 'DYNAMIC',
order: 0,
selectedValue: undefined,
allSelected: true,
}),
env: createVariable({
name: 'env',
type: 'QUERY',
order: 1,
selectedValue: 'prod',
}),
},
});
const { doAllVariablesHaveValuesSelected } = getVariableDependencyContext();
expect(doAllVariablesHaveValuesSelected).toBe(true);
});
it('should treat DYNAMIC variable with allSelected=true and empty string selectedValue as having a value', () => {
setDashboardVariablesStore({
dashboardId: 'dash-1',
variables: {
dyn1: createVariable({
name: 'dyn1',
type: 'DYNAMIC',
order: 0,
selectedValue: '',
allSelected: true,
}),
env: createVariable({
name: 'env',
type: 'QUERY',
order: 1,
selectedValue: 'prod',
}),
},
});
const { doAllVariablesHaveValuesSelected } = getVariableDependencyContext();
expect(doAllVariablesHaveValuesSelected).toBe(true);
});
it('should treat DYNAMIC variable with allSelected=true and empty array selectedValue as having a value', () => {
setDashboardVariablesStore({
dashboardId: 'dash-1',
variables: {
dyn1: createVariable({
name: 'dyn1',
type: 'DYNAMIC',
order: 0,
selectedValue: [] as any,
allSelected: true,
}),
env: createVariable({
name: 'env',
type: 'QUERY',
order: 1,
selectedValue: 'prod',
}),
},
});
const { doAllVariablesHaveValuesSelected } = getVariableDependencyContext();
expect(doAllVariablesHaveValuesSelected).toBe(true);
});
it('should report false when a DYNAMIC variable has empty selectedValue and allSelected is not true', () => {
setDashboardVariablesStore({
dashboardId: 'dash-1',

View File

@@ -76,7 +76,7 @@ export function getVariableDependencyContext(): VariableFetchContext {
(variable) => {
if (
variable.type === 'DYNAMIC' &&
variable.selectedValue === null &&
(variable.selectedValue === null || isEmpty(variable.selectedValue)) &&
variable.allSelected === true
) {
return true;

View File

@@ -30,7 +30,3 @@ func (module *getter) ListByOwnedKeyRange(ctx context.Context) ([]*types.Organiz
return module.store.ListByKeyRange(ctx, start, end)
}
func (module *getter) GetByName(ctx context.Context, name string) (*types.Organization, error) {
return module.store.GetByName(ctx, name)
}

View File

@@ -47,22 +47,6 @@ func (store *store) Get(ctx context.Context, id valuer.UUID) (*types.Organizatio
return organization, nil
}
func (store *store) GetByName(ctx context.Context, name string) (*types.Organization, error) {
organization := new(types.Organization)
err := store.
sqlstore.
BunDB().
NewSelect().
Model(organization).
Where("name = ?", name).
Scan(ctx)
if err != nil {
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrOrganizationNotFound, "organization with name %s does not exist", name)
}
return organization, nil
}
func (store *store) GetAll(ctx context.Context) ([]*types.Organization, error) {
organizations := make([]*types.Organization, 0)
err := store.

View File

@@ -14,9 +14,6 @@ type Getter interface {
// ListByOwnedKeyRange gets all the organizations owned by the instance
ListByOwnedKeyRange(context.Context) ([]*types.Organization, error)
// Gets the organization by name
GetByName(context.Context, string) (*types.Organization, error)
}
type Setter interface {

View File

@@ -151,10 +151,6 @@ func (module *module) CreateCallbackAuthNSession(ctx context.Context, authNProvi
return "", err
}
if err := user.ErrIfRoot(); err != nil {
return "", errors.WithAdditionalf(err, "root user can only authenticate via password")
}
token, err := module.tokenizer.CreateToken(ctx, authtypes.NewIdentity(user.ID, user.OrgID, user.Email, user.Role), map[string]string{})
if err != nil {
return "", err

View File

@@ -5,22 +5,11 @@ import (
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/valuer"
)
type Config struct {
Password PasswordConfig `mapstructure:"password"`
Root RootConfig `mapstructure:"root"`
}
type RootConfig struct {
Enabled bool `mapstructure:"enabled"`
Email valuer.Email `mapstructure:"email"`
Password string `mapstructure:"password"`
OrgName string `mapstructure:"org_name"`
}
type PasswordConfig struct {
Reset ResetConfig `mapstructure:"reset"`
}
@@ -42,10 +31,6 @@ func newConfig() factory.Config {
MaxTokenLifetime: 6 * time.Hour,
},
},
Root: RootConfig{
Enabled: false,
OrgName: "default",
},
}
}
@@ -54,17 +39,5 @@ func (c Config) Validate() error {
return errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "user::password::reset::max_token_lifetime must be positive")
}
if c.Root.Enabled {
if c.Root.Email.IsZero() {
return errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "user::root::email is required when root user is enabled")
}
if c.Root.Password == "" {
return errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "user::root::password is required when root user is enabled")
}
if !types.IsPasswordValid(c.Root.Password) {
return errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "user::root::password does not meet password requirements")
}
}
return nil
}

View File

@@ -16,10 +16,6 @@ func NewGetter(store types.UserStore) user.Getter {
return &getter{store: store}
}
func (module *getter) GetRootUserByOrgID(ctx context.Context, orgID valuer.UUID) (*types.User, error) {
return module.store.GetRootUserByOrgID(ctx, orgID)
}
func (module *getter) ListByOrgID(ctx context.Context, orgID valuer.UUID) ([]*types.User, error) {
users, err := module.store.ListUsersByOrgID(ctx, orgID)
if err != nil {

View File

@@ -103,12 +103,6 @@ func (m *Module) CreateBulkInvite(ctx context.Context, orgID valuer.UUID, userID
return nil, err
}
if existingUser != nil {
if err := existingUser.ErrIfRoot(); err != nil {
return nil, errors.WithAdditionalf(err, "cannot send invite to root user")
}
}
if existingUser != nil {
return nil, errors.New(errors.TypeAlreadyExists, errors.CodeAlreadyExists, "User already exists with the same email")
}
@@ -208,21 +202,27 @@ func (m *Module) UpdateUser(ctx context.Context, orgID valuer.UUID, id string, u
return nil, err
}
if err := existingUser.ErrIfRoot(); err != nil {
return nil, errors.WithAdditionalf(err, "cannot update root user")
}
requestor, err := m.store.GetUser(ctx, valuer.MustNewUUID(updatedBy))
if err != nil {
return nil, err
}
if user.Role != "" && user.Role != existingUser.Role && requestor.Role != types.RoleAdmin {
// only displayName, role can be updated
if user.DisplayName == "" {
user.DisplayName = existingUser.DisplayName
}
if user.Role == "" {
user.Role = existingUser.Role
}
if user.Role != existingUser.Role && requestor.Role != types.RoleAdmin {
return nil, errors.New(errors.TypeForbidden, errors.CodeForbidden, "only admins can change roles")
}
// Make sure that the request is not demoting the last admin user.
if user.Role != "" && user.Role != existingUser.Role && existingUser.Role == types.RoleAdmin {
// Make sure that th e request is not demoting the last admin user.
// also an admin user can only change role of their own or other user
if user.Role != existingUser.Role && existingUser.Role == types.RoleAdmin {
adminUsers, err := m.store.GetUsersByRoleAndOrgID(ctx, types.RoleAdmin, orgID)
if err != nil {
return nil, err
@@ -233,7 +233,7 @@ func (m *Module) UpdateUser(ctx context.Context, orgID valuer.UUID, id string, u
}
}
if user.Role != "" && user.Role != existingUser.Role {
if user.Role != existingUser.Role {
err = m.authz.ModifyGrant(ctx,
orgID,
roletypes.MustGetSigNozManagedRoleFromExistingRole(existingUser.Role),
@@ -245,28 +245,23 @@ func (m *Module) UpdateUser(ctx context.Context, orgID valuer.UUID, id string, u
}
}
existingUser.Update(user.DisplayName, user.Role)
if err := m.UpdateAnyUser(ctx, orgID, existingUser); err != nil {
user.UpdatedAt = time.Now()
updatedUser, err := m.store.UpdateUser(ctx, orgID, id, user)
if err != nil {
return nil, err
}
return existingUser, nil
}
traits := types.NewTraitsFromUser(updatedUser)
m.analytics.IdentifyUser(ctx, user.OrgID.String(), user.ID.String(), traits)
func (module *Module) UpdateAnyUser(ctx context.Context, orgID valuer.UUID, user *types.User) error {
if err := module.store.UpdateUser(ctx, orgID, user); err != nil {
return err
traits["updated_by"] = updatedBy
m.analytics.TrackUser(ctx, user.OrgID.String(), user.ID.String(), "User Updated", traits)
if err := m.tokenizer.DeleteIdentity(ctx, valuer.MustNewUUID(id)); err != nil {
return nil, err
}
traits := types.NewTraitsFromUser(user)
module.analytics.IdentifyUser(ctx, user.OrgID.String(), user.ID.String(), traits)
module.analytics.TrackUser(ctx, user.OrgID.String(), user.ID.String(), "User Updated", traits)
if err := module.tokenizer.DeleteIdentity(ctx, user.ID); err != nil {
return err
}
return nil
return updatedUser, nil
}
func (module *Module) DeleteUser(ctx context.Context, orgID valuer.UUID, id string, deletedBy string) error {
@@ -275,10 +270,6 @@ func (module *Module) DeleteUser(ctx context.Context, orgID valuer.UUID, id stri
return err
}
if err := user.ErrIfRoot(); err != nil {
return errors.WithAdditionalf(err, "cannot delete root user")
}
if slices.Contains(types.AllIntegrationUserEmails, types.IntegrationUserEmail(user.Email.String())) {
return errors.New(errors.TypeForbidden, errors.CodeForbidden, "integration user cannot be deleted")
}
@@ -373,10 +364,6 @@ func (module *Module) ForgotPassword(ctx context.Context, orgID valuer.UUID, ema
return err
}
if err := user.ErrIfRoot(); err != nil {
return errors.WithAdditionalf(err, "cannot reset password for root user")
}
token, err := module.GetOrCreateResetPasswordToken(ctx, user.ID)
if err != nil {
module.settings.Logger().ErrorContext(ctx, "failed to create reset password token", "error", err)
@@ -420,15 +407,6 @@ func (module *Module) UpdatePasswordByResetPasswordToken(ctx context.Context, to
return err
}
user, err := module.store.GetUser(ctx, valuer.MustNewUUID(password.UserID))
if err != nil {
return err
}
if err := user.ErrIfRoot(); err != nil {
return errors.WithAdditionalf(err, "cannot reset password for root user")
}
if err := password.Update(passwd); err != nil {
return err
}
@@ -437,15 +415,6 @@ func (module *Module) UpdatePasswordByResetPasswordToken(ctx context.Context, to
}
func (module *Module) UpdatePassword(ctx context.Context, userID valuer.UUID, oldpasswd string, passwd string) error {
user, err := module.store.GetUser(ctx, userID)
if err != nil {
return err
}
if err := user.ErrIfRoot(); err != nil {
return errors.WithAdditionalf(err, "cannot change password for root user")
}
password, err := module.store.GetPasswordByUserID(ctx, userID)
if err != nil {
return err
@@ -507,7 +476,7 @@ func (m *Module) RevokeAPIKey(ctx context.Context, id, removedByUserID valuer.UU
}
func (module *Module) CreateFirstUser(ctx context.Context, organization *types.Organization, name string, email valuer.Email, passwd string) (*types.User, error) {
user, err := types.NewRootUser(name, email, organization.ID)
user, err := types.NewUser(name, email, types.RoleAdmin, organization.ID)
if err != nil {
return nil, err
}

View File

@@ -1,187 +0,0 @@
package impluser
import (
"context"
"time"
"github.com/SigNoz/signoz/pkg/authz"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/user"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/roletypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type service struct {
settings factory.ScopedProviderSettings
store types.UserStore
module user.Module
orgGetter organization.Getter
authz authz.AuthZ
config user.RootConfig
stopC chan struct{}
}
func NewService(
providerSettings factory.ProviderSettings,
store types.UserStore,
module user.Module,
orgGetter organization.Getter,
authz authz.AuthZ,
config user.RootConfig,
) user.Service {
return &service{
settings: factory.NewScopedProviderSettings(providerSettings, "go.signoz.io/pkg/modules/user"),
store: store,
module: module,
orgGetter: orgGetter,
authz: authz,
config: config,
stopC: make(chan struct{}),
}
}
func (s *service) Start(ctx context.Context) error {
if !s.config.Enabled {
<-s.stopC
return nil
}
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
for {
err := s.reconcile(ctx)
if err == nil {
s.settings.Logger().InfoContext(ctx, "root user reconciliation completed successfully")
<-s.stopC
return nil
}
s.settings.Logger().WarnContext(ctx, "root user reconciliation failed, retrying", "error", err)
select {
case <-s.stopC:
return nil
case <-ticker.C:
continue
}
}
}
func (s *service) Stop(ctx context.Context) error {
close(s.stopC)
return nil
}
func (s *service) reconcile(ctx context.Context) error {
org, err := s.orgGetter.GetByName(ctx, s.config.OrgName)
if err != nil {
if errors.Ast(err, errors.TypeNotFound) {
newOrg := types.NewOrganizationWithName(s.config.OrgName)
_, err := s.module.CreateFirstUser(ctx, newOrg, s.config.Email.String(), s.config.Email, s.config.Password)
return err
}
return err
}
return s.reconcileRootUser(ctx, org.ID)
}
func (s *service) reconcileRootUser(ctx context.Context, orgID valuer.UUID) error {
existingRoot, err := s.store.GetRootUserByOrgID(ctx, orgID)
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
return err
}
if existingRoot == nil {
return s.createOrPromoteRootUser(ctx, orgID)
}
return s.updateExistingRootUser(ctx, orgID, existingRoot)
}
func (s *service) createOrPromoteRootUser(ctx context.Context, orgID valuer.UUID) error {
existingUser, err := s.store.GetUserByEmailAndOrgID(ctx, s.config.Email, orgID)
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
return err
}
if existingUser != nil {
oldRole := existingUser.Role
existingUser.PromoteToRoot()
if err := s.module.UpdateAnyUser(ctx, orgID, existingUser); err != nil {
return err
}
if oldRole != types.RoleAdmin {
if err := s.authz.ModifyGrant(ctx,
orgID,
roletypes.MustGetSigNozManagedRoleFromExistingRole(oldRole),
roletypes.MustGetSigNozManagedRoleFromExistingRole(types.RoleAdmin),
authtypes.MustNewSubject(authtypes.TypeableUser, existingUser.ID.StringValue(), orgID, nil),
); err != nil {
return err
}
}
return s.setPassword(ctx, existingUser.ID)
}
// Create new root user
newUser, err := types.NewRootUser(s.config.Email.String(), s.config.Email, orgID)
if err != nil {
return err
}
factorPassword, err := types.NewFactorPassword(s.config.Password, newUser.ID.StringValue())
if err != nil {
return err
}
return s.module.CreateUser(ctx, newUser, user.WithFactorPassword(factorPassword))
}
func (s *service) updateExistingRootUser(ctx context.Context, orgID valuer.UUID, existingRoot *types.User) error {
existingRoot.PromoteToRoot()
if existingRoot.Email != s.config.Email {
existingRoot.UpdateEmail(s.config.Email)
if err := s.module.UpdateAnyUser(ctx, orgID, existingRoot); err != nil {
return err
}
}
return s.setPassword(ctx, existingRoot.ID)
}
func (s *service) setPassword(ctx context.Context, userID valuer.UUID) error {
password, err := s.store.GetPasswordByUserID(ctx, userID)
if err != nil {
if !errors.Ast(err, errors.TypeNotFound) {
return err
}
factorPassword, err := types.NewFactorPassword(s.config.Password, userID.StringValue())
if err != nil {
return err
}
return s.store.CreatePassword(ctx, factorPassword)
}
if !password.Equals(s.config.Password) {
if err := password.Update(s.config.Password); err != nil {
return err
}
return s.store.UpdatePassword(ctx, password)
}
return nil
}

View File

@@ -210,24 +210,20 @@ func (store *store) GetUsersByRoleAndOrgID(ctx context.Context, role types.Role,
return users, nil
}
func (store *store) UpdateUser(ctx context.Context, orgID valuer.UUID, user *types.User) error {
_, err := store.
sqlstore.
BunDBCtx(ctx).
NewUpdate().
func (store *store) UpdateUser(ctx context.Context, orgID valuer.UUID, id string, user *types.User) (*types.User, error) {
user.UpdatedAt = time.Now()
_, err := store.sqlstore.BunDB().NewUpdate().
Model(user).
Column("display_name").
Column("email").
Column("role").
Column("is_root").
Column("updated_at").
Where("id = ?", id).
Where("org_id = ?", orgID).
Where("id = ?", user.ID).
Exec(ctx)
if err != nil {
return store.sqlstore.WrapNotFoundErrf(err, types.ErrCodeUserNotFound, "user does not exist in org: %s", orgID)
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrCodeUserNotFound, "user with id: %s does not exist in org: %s", id, orgID)
}
return nil
return user, nil
}
func (store *store) ListUsersByOrgID(ctx context.Context, orgID valuer.UUID) ([]*types.GettableUser, error) {
@@ -606,22 +602,6 @@ func (store *store) RunInTx(ctx context.Context, cb func(ctx context.Context) er
})
}
func (store *store) GetRootUserByOrgID(ctx context.Context, orgID valuer.UUID) (*types.User, error) {
user := new(types.User)
err := store.
sqlstore.
BunDBCtx(ctx).
NewSelect().
Model(user).
Where("org_id = ?", orgID).
Where("is_root = ?", true).
Scan(ctx)
if err != nil {
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrCodeUserNotFound, "root user for org %s not found", orgID)
}
return user, nil
}
func (store *store) ListUsersByEmailAndOrgIDs(ctx context.Context, email valuer.Email, orgIDs []valuer.UUID) ([]*types.User, error) {
users := []*types.User{}
err := store.

View File

@@ -1,7 +0,0 @@
package user
import "github.com/SigNoz/signoz/pkg/factory"
type Service interface {
factory.Service
}

View File

@@ -34,9 +34,6 @@ type Module interface {
ForgotPassword(ctx context.Context, orgID valuer.UUID, email valuer.Email, frontendBaseURL string) error
UpdateUser(ctx context.Context, orgID valuer.UUID, id string, user *types.User, updatedBy string) (*types.User, error)
// UpdateAnyUser updates a user and persists the changes to the database along with the analytics and identity deletion.
UpdateAnyUser(ctx context.Context, orgID valuer.UUID, user *types.User) error
DeleteUser(ctx context.Context, orgID valuer.UUID, id string, deletedBy string) error
// invite
@@ -57,9 +54,6 @@ type Module interface {
}
type Getter interface {
// Get root user by org id.
GetRootUserByOrgID(context.Context, valuer.UUID) (*types.User, error)
// Get gets the users based on the given id
ListByOrgID(context.Context, valuer.UUID) ([]*types.User, error)

View File

@@ -183,7 +183,7 @@ type APIHandlerOpts struct {
}
// NewAPIHandler returns an APIHandler
func NewAPIHandler(opts APIHandlerOpts, config signoz.Config) (*APIHandler, error) {
func NewAPIHandler(opts APIHandlerOpts) (*APIHandler, error) {
querierOpts := querier.QuerierOptions{
Reader: opts.Reader,
Cache: opts.Signoz.Cache,
@@ -270,11 +270,6 @@ func NewAPIHandler(opts APIHandlerOpts, config signoz.Config) (*APIHandler, erro
}
}
// If the root user is enabled, the setup is complete
if config.User.Root.Enabled {
aH.SetupCompleted = true
}
aH.Upgrader = &websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true

View File

@@ -135,7 +135,7 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
Signoz: signoz,
QuerierAPI: querierAPI.NewAPI(signoz.Instrumentation.ToProviderSettings(), signoz.Querier, signoz.Analytics),
QueryParserAPI: queryparser.NewAPI(signoz.Instrumentation.ToProviderSettings(), signoz.QueryParser),
}, config)
})
if err != nil {
return nil, err
}

View File

@@ -167,7 +167,6 @@ func NewSQLMigrationProviderFactories(
sqlmigration.NewMigrateRbacToAuthzFactory(sqlstore),
sqlmigration.NewMigratePublicDashboardsFactory(sqlstore),
sqlmigration.NewAddAnonymousPublicDashboardTransactionFactory(sqlstore),
sqlmigration.NewAddRootUserFactory(sqlstore, sqlschema),
)
}

View File

@@ -389,8 +389,6 @@ func New(
// Initialize all modules
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, analytics, querier, telemetrystore, telemetryMetadataStore, authNs, authz, cache, queryParser, config, dashboard)
userService := impluser.NewService(providerSettings, impluser.NewStore(sqlstore, providerSettings), modules.User, orgGetter, authz, config.User.Root)
// Initialize all handlers for the modules
handlers := NewHandlers(modules, providerSettings, querier, licensing, global, flagger, gateway, telemetryMetadataStore, authz)
@@ -440,7 +438,6 @@ func New(
factory.NewNamedService(factory.MustNewName("statsreporter"), statsReporter),
factory.NewNamedService(factory.MustNewName("tokenizer"), tokenizer),
factory.NewNamedService(factory.MustNewName("authz"), authz),
factory.NewNamedService(factory.MustNewName("user"), userService),
)
if err != nil {
return nil, err

View File

@@ -1,80 +0,0 @@
package sqlmigration
import (
"context"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/sqlschema"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/uptrace/bun"
"github.com/uptrace/bun/migrate"
)
type addRootUser struct {
sqlstore sqlstore.SQLStore
sqlschema sqlschema.SQLSchema
}
func NewAddRootUserFactory(sqlstore sqlstore.SQLStore, sqlschema sqlschema.SQLSchema) factory.ProviderFactory[SQLMigration, Config] {
return factory.NewProviderFactory(factory.MustNewName("add_root_user"), func(ctx context.Context, providerSettings factory.ProviderSettings, config Config) (SQLMigration, error) {
return &addRootUser{
sqlstore: sqlstore,
sqlschema: sqlschema,
}, nil
})
}
func (migration *addRootUser) Register(migrations *migrate.Migrations) error {
if err := migrations.Register(migration.Up, migration.Down); err != nil {
return err
}
return nil
}
func (migration *addRootUser) Up(ctx context.Context, db *bun.DB) error {
if err := migration.sqlschema.ToggleFKEnforcement(ctx, db, false); err != nil {
return err
}
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer func() {
_ = tx.Rollback()
}()
table, uniqueConstraints, err := migration.sqlschema.GetTable(ctx, sqlschema.TableName("users"))
if err != nil {
return err
}
column := &sqlschema.Column{
Name: sqlschema.ColumnName("is_root"),
DataType: sqlschema.DataTypeBoolean,
Nullable: false,
}
sqls := migration.sqlschema.Operator().AddColumn(table, uniqueConstraints, column, false)
for _, sql := range sqls {
if _, err := tx.ExecContext(ctx, string(sql)); err != nil {
return err
}
}
if err := tx.Commit(); err != nil {
return err
}
if err := migration.sqlschema.ToggleFKEnforcement(ctx, db, true); err != nil {
return err
}
return nil
}
func (migration *addRootUser) Down(ctx context.Context, db *bun.DB) error {
return nil
}

View File

@@ -41,22 +41,6 @@ func NewOrganization(displayName string) *Organization {
}
}
func NewOrganizationWithName(name string) *Organization {
id := valuer.GenerateUUID()
return &Organization{
Identifiable: Identifiable{
ID: id,
},
TimeAuditable: TimeAuditable{
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
},
Name: name,
DisplayName: name,
Key: NewOrganizationKey(id),
}
}
func NewOrganizationKey(orgID valuer.UUID) uint32 {
hasher := fnv.New32a()
@@ -90,7 +74,6 @@ type TTLSetting struct {
type OrganizationStore interface {
Create(context.Context, *Organization) error
Get(context.Context, valuer.UUID) (*Organization, error)
GetByName(context.Context, string) (*Organization, error)
GetAll(context.Context) ([]*Organization, error)
ListByKeyRange(context.Context, uint32, uint32) ([]*Organization, error)
Update(context.Context, *Organization) error

View File

@@ -11,16 +11,15 @@ import (
)
var (
ErrCodeUserNotFound = errors.MustNewCode("user_not_found")
ErrCodeAmbiguousUser = errors.MustNewCode("ambiguous_user")
ErrUserAlreadyExists = errors.MustNewCode("user_already_exists")
ErrPasswordAlreadyExists = errors.MustNewCode("password_already_exists")
ErrResetPasswordTokenAlreadyExists = errors.MustNewCode("reset_password_token_already_exists")
ErrPasswordNotFound = errors.MustNewCode("password_not_found")
ErrResetPasswordTokenNotFound = errors.MustNewCode("reset_password_token_not_found")
ErrAPIKeyAlreadyExists = errors.MustNewCode("api_key_already_exists")
ErrAPIKeyNotFound = errors.MustNewCode("api_key_not_found")
ErrCodeRootUserOperationUnsupported = errors.MustNewCode("root_user_operation_unsupported")
ErrCodeUserNotFound = errors.MustNewCode("user_not_found")
ErrCodeAmbiguousUser = errors.MustNewCode("ambiguous_user")
ErrUserAlreadyExists = errors.MustNewCode("user_already_exists")
ErrPasswordAlreadyExists = errors.MustNewCode("password_already_exists")
ErrResetPasswordTokenAlreadyExists = errors.MustNewCode("reset_password_token_already_exists")
ErrPasswordNotFound = errors.MustNewCode("password_not_found")
ErrResetPasswordTokenNotFound = errors.MustNewCode("reset_password_token_not_found")
ErrAPIKeyAlreadyExists = errors.MustNewCode("api_key_already_exists")
ErrAPIKeyNotFound = errors.MustNewCode("api_key_not_found")
)
type GettableUser = User
@@ -30,10 +29,9 @@ type User struct {
Identifiable
DisplayName string `bun:"display_name" json:"displayName"`
Email valuer.Email `bun:"email" json:"email"`
Role Role `bun:"role" json:"role"`
OrgID valuer.UUID `bun:"org_id" json:"orgId"`
IsRoot bool `bun:"is_root" json:"isRoot"`
Email valuer.Email `bun:"email,type:text" json:"email"`
Role Role `bun:"role,type:text" json:"role"`
OrgID valuer.UUID `bun:"org_id,type:text" json:"orgId"`
TimeAuditable
}
@@ -66,7 +64,6 @@ func NewUser(displayName string, email valuer.Email, role Role, orgID valuer.UUI
Email: email,
Role: role,
OrgID: orgID,
IsRoot: false,
TimeAuditable: TimeAuditable{
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
@@ -74,65 +71,6 @@ func NewUser(displayName string, email valuer.Email, role Role, orgID valuer.UUI
}, nil
}
func NewRootUser(displayName string, email valuer.Email, orgID valuer.UUID) (*User, error) {
if email.IsZero() {
return nil, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "email is required")
}
if orgID.IsZero() {
return nil, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "orgID is required")
}
return &User{
Identifiable: Identifiable{
ID: valuer.GenerateUUID(),
},
DisplayName: displayName,
Email: email,
Role: RoleAdmin,
OrgID: orgID,
IsRoot: true,
TimeAuditable: TimeAuditable{
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
},
}, nil
}
// Update applies mutable fields from the input to the user. Immutable fields
// (email, is_root, org_id, id) are preserved. Only non-zero input fields are applied.
func (u *User) Update(displayName string, role Role) {
if displayName != "" {
u.DisplayName = displayName
}
if role != "" {
u.Role = role
}
u.UpdatedAt = time.Now()
}
// PromoteToRoot promotes the user to a root user with admin role.
func (u *User) PromoteToRoot() {
u.IsRoot = true
u.Role = RoleAdmin
u.UpdatedAt = time.Now()
}
// UpdateEmail updates the email of the user.
func (u *User) UpdateEmail(email valuer.Email) {
u.Email = email
u.UpdatedAt = time.Now()
}
// ErrIfRoot returns an error if the user is a root user. The caller should
// enrich the error with the specific operation using errors.WithAdditionalf.
func (u *User) ErrIfRoot() error {
if u.IsRoot {
return errors.New(errors.TypeUnsupported, ErrCodeRootUserOperationUnsupported, "this operation is not supported for the root user")
}
return nil
}
func NewTraitsFromUser(user *User) map[string]any {
return map[string]any{
"name": user.DisplayName,
@@ -195,7 +133,7 @@ type UserStore interface {
// List users by email and org ids.
ListUsersByEmailAndOrgIDs(ctx context.Context, email valuer.Email, orgIDs []valuer.UUID) ([]*User, error)
UpdateUser(ctx context.Context, orgID valuer.UUID, user *User) error
UpdateUser(ctx context.Context, orgID valuer.UUID, id string, user *User) (*User, error)
DeleteUser(ctx context.Context, orgID string, id string) error
// Creates a password.
@@ -218,9 +156,6 @@ type UserStore interface {
CountByOrgID(ctx context.Context, orgID valuer.UUID) (int64, error)
// Get root user by org.
GetRootUserByOrgID(ctx context.Context, orgID valuer.UUID) (*User, error)
// Transaction
RunInTx(ctx context.Context, cb func(ctx context.Context) error) error
}

View File

@@ -6,7 +6,7 @@ import pytest
import requests
from fixtures import types
from fixtures.signoz import ROOT_USER_EMAIL, ROOT_USER_PASSWORD
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
from fixtures.logger import setup_logger
from fixtures.logs import Logs
from fixtures.metrics import Metrics
@@ -20,7 +20,7 @@ logger = setup_logger(__name__)
def create_alert_rule(
signoz: types.SigNoz, get_token: Callable[[str, str], str]
) -> Callable[[dict], str]:
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
rule_ids = []

View File

@@ -11,56 +11,55 @@ from wiremock.resources.mappings import (
WireMockMatchers,
)
from fixtures import types
from fixtures import dev, types
from fixtures.logger import setup_logger
from fixtures.signoz import ROOT_USER_EMAIL, ROOT_USER_PASSWORD
logger = setup_logger(__name__)
# USER_ADMIN_NAME = "admin"
# USER_ADMIN_EMAIL = "admin@integration.test"
# USER_ADMIN_PASSWORD = "password123Z$"
USER_ADMIN_NAME = "admin"
USER_ADMIN_EMAIL = "admin@integration.test"
USER_ADMIN_PASSWORD = "password123Z$"
USER_EDITOR_NAME = "editor"
USER_EDITOR_EMAIL = "editor@integration.test"
USER_EDITOR_PASSWORD = "password123Z$"
# @pytest.fixture(name="create_user_admin", scope="package")
# def create_user_admin(
# signoz: types.SigNoz, request: pytest.FixtureRequest, pytestconfig: pytest.Config
# ) -> types.Operation:
# def create() -> None:
# response = requests.post(
# signoz.self.host_configs["8080"].get("/api/v1/register"),
# json={
# "name": USER_ADMIN_NAME,
# "orgName": "",
# "email": USER_ADMIN_EMAIL,
# "password": USER_ADMIN_PASSWORD,
# },
# timeout=5,
# )
@pytest.fixture(name="create_user_admin", scope="package")
def create_user_admin(
signoz: types.SigNoz, request: pytest.FixtureRequest, pytestconfig: pytest.Config
) -> types.Operation:
def create() -> None:
response = requests.post(
signoz.self.host_configs["8080"].get("/api/v1/register"),
json={
"name": USER_ADMIN_NAME,
"orgName": "",
"email": USER_ADMIN_EMAIL,
"password": USER_ADMIN_PASSWORD,
},
timeout=5,
)
# assert response.status_code == HTTPStatus.OK
assert response.status_code == HTTPStatus.OK
# return types.Operation(name="create_user_admin")
return types.Operation(name="create_user_admin")
# def delete(_: types.Operation) -> None:
# pass
def delete(_: types.Operation) -> None:
pass
# def restore(cache: dict) -> types.Operation:
# return types.Operation(name=cache["name"])
def restore(cache: dict) -> types.Operation:
return types.Operation(name=cache["name"])
# return dev.wrap(
# request,
# pytestconfig,
# "create_user_admin",
# lambda: types.Operation(name=""),
# create,
# delete,
# restore,
# )
return dev.wrap(
request,
pytestconfig,
"create_user_admin",
lambda: types.Operation(name=""),
create,
delete,
restore,
)
@pytest.fixture(name="get_session_context", scope="function")
@@ -190,7 +189,7 @@ def add_license(
],
)
access_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
access_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = requests.post(
url=signoz.self.host_configs["8080"].get("/api/v3/licenses"),

View File

@@ -7,7 +7,7 @@ import pytest
import requests
from fixtures import types
from fixtures.signoz import ROOT_USER_EMAIL, ROOT_USER_PASSWORD
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
from fixtures.logger import setup_logger
logger = setup_logger(__name__)
@@ -73,7 +73,7 @@ def create_cloud_integration_account(
if created_account_id and cloud_provider_used:
get_token = request.getfixturevalue("get_token")
try:
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
r = _disconnect(admin_token, cloud_provider_used)
if r.status_code != HTTPStatus.OK:
logger.info(

View File

@@ -9,7 +9,7 @@ from testcontainers.core.container import Network
from wiremock.testing.testcontainer import WireMockContainer
from fixtures import dev, types
from fixtures.signoz import ROOT_USER_EMAIL, ROOT_USER_PASSWORD
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
from fixtures.logger import setup_logger
logger = setup_logger(__name__)
@@ -74,9 +74,10 @@ def notification_channel(
@pytest.fixture(name="create_webhook_notification_channel", scope="function")
def create_webhook_notification_channel(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> Callable[[str, str, dict, bool], str]:
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# function to create notification channel
def _create_webhook_notification_channel(

View File

@@ -15,9 +15,6 @@ from fixtures.logger import setup_logger
logger = setup_logger(__name__)
ROOT_USER_EMAIL = "root@integration.test"
ROOT_USER_PASSWORD = "Str0ngP@ssw0rd!"
@pytest.fixture(name="signoz", scope="package")
def signoz( # pylint: disable=too-many-arguments,too-many-positional-arguments
@@ -76,9 +73,6 @@ def signoz( # pylint: disable=too-many-arguments,too-many-positional-arguments
"SIGNOZ_ALERTMANAGER_SIGNOZ_POLL__INTERVAL": "5s",
"SIGNOZ_ALERTMANAGER_SIGNOZ_ROUTE_GROUP__WAIT": "1s",
"SIGNOZ_ALERTMANAGER_SIGNOZ_ROUTE_GROUP__INTERVAL": "5s",
"SIGNOZ_USER_ROOT_ENABLED": True,
"SIGNOZ_USER_ROOT_EMAIL": ROOT_USER_EMAIL,
"SIGNOZ_USER_ROOT_PASSWORD": ROOT_USER_PASSWORD,
}
| sqlstore.env
| clickhouse.env

View File

@@ -7,7 +7,7 @@ import requests
from wiremock.client import HttpMethods, Mapping, MappingRequest, MappingResponse
from fixtures import types
from fixtures.signoz import ROOT_USER_EMAIL, ROOT_USER_PASSWORD
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
from fixtures.logger import setup_logger
logger = setup_logger(__name__)
@@ -65,7 +65,7 @@ def test_webhook_notification_channel(
time.sleep(10)
# Call test API for the notification channel
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = requests.post(
url=signoz.self.host_configs["8080"].get("/api/v1/testChannel"),
json={

View File

@@ -35,6 +35,7 @@ def test_telemetry_databases_exist(signoz: types.SigNoz) -> None:
def test_teardown(
signoz: types.SigNoz, # pylint: disable=unused-argument
idp: types.TestContainerIDP, # pylint: disable=unused-argument
create_user_admin: types.Operation, # pylint: disable=unused-argument
migrator: types.Operation, # pylint: disable=unused-argument
) -> None:
pass

View File

@@ -3,15 +3,16 @@ from typing import Callable
import requests
from fixtures.signoz import ROOT_USER_EMAIL, ROOT_USER_PASSWORD
from fixtures.types import SigNoz
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
from fixtures.types import Operation, SigNoz
def test_create_and_get_domain(
signoz: SigNoz,
create_user_admin: Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
):
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Get domains which should be an empty list
response = requests.get(
@@ -90,9 +91,10 @@ def test_create_and_get_domain(
def test_create_invalid(
signoz: SigNoz,
create_user_admin: Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
):
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Create a domain with type saml and body for oidc, this should fail because oidcConfig is not allowed for saml
response = requests.post(
@@ -171,10 +173,11 @@ def test_create_invalid(
def test_create_invalid_role_mapping(
signoz: SigNoz,
create_user_admin: Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
):
"""Test that invalid role mappings are rejected."""
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Create domain with invalid defaultRole
response = requests.post(

View File

@@ -6,18 +6,22 @@ import requests
from selenium import webdriver
from wiremock.resources.mappings import Mapping
from fixtures.auth import add_license
from fixtures.auth import (
USER_ADMIN_EMAIL,
USER_ADMIN_PASSWORD,
add_license,
)
from fixtures.idputils import (
get_saml_domain,
get_user_by_email,
perform_saml_login,
)
from fixtures.types import SigNoz, TestContainerDocker, TestContainerIDP
from fixtures.signoz import ROOT_USER_EMAIL, ROOT_USER_PASSWORD
from fixtures.types import Operation, SigNoz, TestContainerDocker, TestContainerIDP
def test_apply_license(
signoz: SigNoz,
create_user_admin: Operation, # pylint: disable=unused-argument
make_http_mocks: Callable[[TestContainerDocker, List[Mapping]], None],
get_token: Callable[[str, str], str],
) -> None:
@@ -30,6 +34,7 @@ def test_create_auth_domain(
create_saml_client: Callable[[str, str], None],
update_saml_client_attributes: Callable[[str, Dict[str, Any]], None],
get_saml_settings: Callable[[], dict],
create_user_admin: Callable[[], None], # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
# Create a saml client in the idp.
@@ -39,7 +44,7 @@ def test_create_auth_domain(
settings = get_saml_settings()
# Create a auth domain in signoz.
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = requests.post(
signoz.self.host_configs["8080"].get("/api/v1/domains"),
@@ -122,7 +127,7 @@ def test_saml_authn(
driver.get(url)
idp_login("viewer@saml.integration.test", "password")
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Assert that the user was created in signoz.
response = requests.get(
@@ -173,7 +178,7 @@ def test_idp_initiated_saml_authn(
driver.get(idp_initiated_login_url)
idp_login("viewer.idp.initiated@saml.integration.test", "password")
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Assert that the user was created in signoz.
response = requests.get(
@@ -203,7 +208,7 @@ def test_saml_update_domain_with_group_mappings(
get_token: Callable[[str, str], str],
get_saml_settings: Callable[[], dict],
) -> None:
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
domain = get_saml_domain(signoz, admin_token)
settings = get_saml_settings()
@@ -261,7 +266,7 @@ def test_saml_role_mapping_single_group_admin(
signoz, driver, get_session_context, idp_login, email, "password"
)
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
@@ -287,7 +292,7 @@ def test_saml_role_mapping_single_group_editor(
signoz, driver, get_session_context, idp_login, email, "password"
)
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
@@ -317,7 +322,7 @@ def test_saml_role_mapping_multiple_groups_highest_wins(
signoz, driver, get_session_context, idp_login, email, "password"
)
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
@@ -344,7 +349,7 @@ def test_saml_role_mapping_explicit_viewer_group(
signoz, driver, get_session_context, idp_login, email, "password"
)
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
@@ -370,7 +375,7 @@ def test_saml_role_mapping_unmapped_group_uses_default(
signoz, driver, get_session_context, idp_login, email, "password"
)
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
@@ -385,7 +390,7 @@ def test_saml_update_domain_with_use_role_claim(
"""
Updates SAML domain to enable useRoleAttribute (direct role attribute).
"""
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
domain = get_saml_domain(signoz, admin_token)
settings = get_saml_settings()
@@ -447,7 +452,7 @@ def test_saml_role_mapping_role_claim_takes_precedence(
signoz, driver, get_session_context, idp_login, email, "password"
)
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
@@ -477,7 +482,7 @@ def test_saml_role_mapping_invalid_role_claim_fallback(
signoz, driver, get_session_context, idp_login, email, "password"
)
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
@@ -507,7 +512,7 @@ def test_saml_role_mapping_case_insensitive(
signoz, driver, get_session_context, idp_login, email, "password"
)
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
@@ -532,7 +537,7 @@ def test_saml_name_mapping(
signoz, driver, get_session_context, idp_login, email, "password"
)
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
@@ -560,7 +565,7 @@ def test_saml_empty_name_fallback(
signoz, driver, get_session_context, idp_login, email, "password"
)
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None

View File

@@ -6,18 +6,22 @@ import requests
from selenium import webdriver
from wiremock.resources.mappings import Mapping
from fixtures.auth import add_license
from fixtures.auth import (
USER_ADMIN_EMAIL,
USER_ADMIN_PASSWORD,
add_license,
)
from fixtures.idputils import (
get_oidc_domain,
get_user_by_email,
perform_oidc_login,
)
from fixtures.types import SigNoz, TestContainerDocker, TestContainerIDP
from fixtures.signoz import ROOT_USER_EMAIL, ROOT_USER_PASSWORD
from fixtures.types import Operation, SigNoz, TestContainerDocker, TestContainerIDP
def test_apply_license(
signoz: SigNoz,
create_user_admin: Operation, # pylint: disable=unused-argument
make_http_mocks: Callable[[TestContainerDocker, List[Mapping]], None],
get_token: Callable[[str, str], str],
) -> None:
@@ -32,6 +36,7 @@ def test_create_auth_domain(
idp: TestContainerIDP, # pylint: disable=unused-argument
create_oidc_client: Callable[[str, str], None],
get_oidc_settings: Callable[[], dict],
create_user_admin: Callable[[], None], # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""
@@ -45,7 +50,7 @@ def test_create_auth_domain(
settings = get_oidc_settings(client_id)
# Create a auth domain in signoz.
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = requests.post(
signoz.self.host_configs["8080"].get("/api/v1/domains"),
@@ -104,7 +109,7 @@ def test_oidc_authn(
driver.get(actual_url)
idp_login("viewer@oidc.integration.test", "password123")
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Assert that the user was created in signoz.
response = requests.get(
@@ -138,7 +143,7 @@ def test_oidc_update_domain_with_group_mappings(
"""
Updates OIDC domain to add role mapping with group mappings and claim mapping.
"""
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
domain = get_oidc_domain(signoz, admin_token)
client_id = f"oidc.integration.test.{signoz.self.host_configs['8080'].address}:{signoz.self.host_configs['8080'].port}"
settings = get_oidc_settings(client_id)
@@ -199,7 +204,7 @@ def test_oidc_role_mapping_single_group_admin(
signoz, idp, driver, get_session_context, idp_login, email, "password123"
)
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
@@ -225,7 +230,7 @@ def test_oidc_role_mapping_single_group_editor(
signoz, idp, driver, get_session_context, idp_login, email, "password123"
)
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
@@ -255,7 +260,7 @@ def test_oidc_role_mapping_multiple_groups_highest_wins(
signoz, idp, driver, get_session_context, idp_login, email, "password123"
)
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
@@ -282,7 +287,7 @@ def test_oidc_role_mapping_explicit_viewer_group(
signoz, idp, driver, get_session_context, idp_login, email, "password123"
)
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
@@ -308,7 +313,7 @@ def test_oidc_role_mapping_unmapped_group_uses_default(
signoz, idp, driver, get_session_context, idp_login, email, "password123"
)
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
@@ -324,7 +329,7 @@ def test_oidc_update_domain_with_use_role_claim(
"""
Updates OIDC domain to enable useRoleClaim.
"""
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
domain = get_oidc_domain(signoz, admin_token)
client_id = f"oidc.integration.test.{signoz.self.host_configs['8080'].address}:{signoz.self.host_configs['8080'].port}"
settings = get_oidc_settings(client_id)
@@ -388,7 +393,7 @@ def test_oidc_role_mapping_role_claim_takes_precedence(
signoz, idp, driver, get_session_context, idp_login, email, "password123"
)
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
@@ -420,7 +425,7 @@ def test_oidc_role_mapping_invalid_role_claim_fallback(
signoz, idp, driver, get_session_context, idp_login, email, "password123"
)
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
@@ -450,7 +455,7 @@ def test_oidc_role_mapping_case_insensitive(
signoz, idp, driver, get_session_context, idp_login, email, "password123"
)
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
found_user = get_user_by_email(signoz, admin_token, email)
assert found_user is not None
@@ -476,7 +481,7 @@ def test_oidc_name_mapping(
signoz, idp, driver, get_session_context, idp_login, email, "password123"
)
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/user"),
headers={"Authorization": f"Bearer {admin_token}"},
@@ -512,7 +517,7 @@ def test_oidc_empty_name_uses_fallback(
signoz, idp, driver, get_session_context, idp_login, email, "password123"
)
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/user"),
headers={"Authorization": f"Bearer {admin_token}"},

View File

@@ -11,21 +11,21 @@ from wiremock.client import (
)
from fixtures import types
from fixtures.auth import add_license
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD, add_license
from fixtures.logger import setup_logger
from fixtures.signoz import ROOT_USER_EMAIL, ROOT_USER_PASSWORD
logger = setup_logger(__name__)
def test_generate_connection_params(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
make_http_mocks: Callable[[types.TestContainerDocker, list], None],
get_token: Callable[[str, str], str],
) -> None:
"""Test to generate connection parameters for AWS SigNoz cloud integration."""
# Get authentication token for admin user
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
add_license(signoz, make_http_mocks, get_token)

View File

@@ -4,7 +4,7 @@ from typing import Callable
import requests
from fixtures import types
from fixtures.signoz import ROOT_USER_EMAIL, ROOT_USER_PASSWORD
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
from fixtures.logger import setup_logger
logger = setup_logger(__name__)
@@ -12,12 +12,13 @@ logger = setup_logger(__name__)
def test_generate_connection_url(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""Test to generate connection URL for AWS CloudFormation stack deployment."""
# Get authentication token for admin user
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
cloud_provider = "aws"
endpoint = (
f"/api/v1/cloud-integrations/{cloud_provider}/accounts/generate-connection-url"
@@ -100,11 +101,12 @@ def test_generate_connection_url(
def test_generate_connection_url_unsupported_provider(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""Test that unsupported cloud providers return an error."""
# Get authentication token for admin user
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Try with GCP (unsupported)
cloud_provider = "gcp"

View File

@@ -5,7 +5,7 @@ from typing import Callable
import requests
from fixtures import types
from fixtures.signoz import ROOT_USER_EMAIL, ROOT_USER_PASSWORD
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
from fixtures.cloudintegrationsutils import simulate_agent_checkin
from fixtures.logger import setup_logger
@@ -14,10 +14,11 @@ logger = setup_logger(__name__)
def test_list_connected_accounts_empty(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""Test listing connected accounts when there are none."""
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
cloud_provider = "aws"
endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/accounts"
@@ -42,11 +43,12 @@ def test_list_connected_accounts_empty(
def test_list_connected_accounts_with_account(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
create_cloud_integration_account: Callable,
) -> None:
"""Test listing connected accounts after creating one."""
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Create a test account
cloud_provider = "aws"
@@ -86,11 +88,12 @@ def test_list_connected_accounts_with_account(
def test_get_account_status(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
create_cloud_integration_account: Callable,
) -> None:
"""Test getting the status of a specific account."""
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Create a test account (no check-in needed for status check)
cloud_provider = "aws"
account_data = create_cloud_integration_account(admin_token, cloud_provider)
@@ -120,10 +123,11 @@ def test_get_account_status(
def test_get_account_status_not_found(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""Test getting status for a non-existent account."""
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
cloud_provider = "aws"
fake_account_id = "00000000-0000-0000-0000-000000000000"
@@ -143,11 +147,12 @@ def test_get_account_status_not_found(
def test_update_account_config(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
create_cloud_integration_account: Callable,
) -> None:
"""Test updating account configuration."""
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Create a test account
cloud_provider = "aws"
@@ -207,11 +212,12 @@ def test_update_account_config(
def test_disconnect_account(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
create_cloud_integration_account: Callable,
) -> None:
"""Test disconnecting an account."""
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Create a test account
cloud_provider = "aws"
@@ -261,10 +267,11 @@ def test_disconnect_account(
def test_disconnect_account_not_found(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""Test disconnecting a non-existent account."""
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
cloud_provider = "aws"
fake_account_id = "00000000-0000-0000-0000-000000000000"
@@ -284,11 +291,12 @@ def test_disconnect_account_not_found(
def test_list_accounts_unsupported_provider(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""Test listing accounts for an unsupported cloud provider."""
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
cloud_provider = "gcp" # Unsupported provider
endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/accounts"

View File

@@ -5,7 +5,7 @@ from typing import Callable
import requests
from fixtures import types
from fixtures.signoz import ROOT_USER_EMAIL, ROOT_USER_PASSWORD
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
from fixtures.cloudintegrationsutils import simulate_agent_checkin
from fixtures.logger import setup_logger
@@ -14,10 +14,11 @@ logger = setup_logger(__name__)
def test_list_services_without_account(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""Test listing available services without specifying an account."""
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
cloud_provider = "aws"
endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/services"
@@ -47,11 +48,12 @@ def test_list_services_without_account(
def test_list_services_with_account(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
create_cloud_integration_account: Callable,
) -> None:
"""Test listing services for a specific connected account."""
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Create a test account and do check-in
cloud_provider = "aws"
@@ -91,10 +93,11 @@ def test_list_services_with_account(
def test_get_service_details_without_account(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""Test getting service details without specifying an account."""
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
cloud_provider = "aws"
# First get the list of services to get a valid service ID
@@ -136,11 +139,12 @@ def test_get_service_details_without_account(
def test_get_service_details_with_account(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
create_cloud_integration_account: Callable,
) -> None:
"""Test getting service details for a specific connected account."""
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Create a test account and do check-in
cloud_provider = "aws"
@@ -191,10 +195,11 @@ def test_get_service_details_with_account(
def test_get_service_details_invalid_service(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""Test getting details for a non-existent service."""
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
cloud_provider = "aws"
fake_service_id = "non-existent-service"
@@ -213,10 +218,11 @@ def test_get_service_details_invalid_service(
def test_list_services_unsupported_provider(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""Test listing services for an unsupported cloud provider."""
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
cloud_provider = "gcp" # Unsupported provider
endpoint = f"/api/v1/cloud-integrations/{cloud_provider}/services"
@@ -234,11 +240,12 @@ def test_list_services_unsupported_provider(
def test_update_service_config(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
create_cloud_integration_account: Callable,
) -> None:
"""Test updating service configuration for a connected account."""
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Create a test account and do check-in
cloud_provider = "aws"
@@ -299,10 +306,11 @@ def test_update_service_config(
def test_update_service_config_without_account(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""Test updating service config without a connected account should fail."""
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
cloud_provider = "aws"
@@ -344,11 +352,12 @@ def test_update_service_config_without_account(
def test_update_service_config_invalid_service(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
create_cloud_integration_account: Callable,
) -> None:
"""Test updating config for a non-existent service should fail."""
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Create a test account and do check-in
cloud_provider = "aws"
@@ -387,11 +396,12 @@ def test_update_service_config_invalid_service(
def test_update_service_config_disable_service(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
create_cloud_integration_account: Callable,
) -> None:
"""Test disabling a service by updating config with enabled=false."""
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Create a test account and do check-in
cloud_provider = "aws"

View File

@@ -4,16 +4,16 @@ from typing import Callable, List
import requests
from wiremock.resources.mappings import Mapping
from fixtures.auth import add_license
from fixtures.signoz import ROOT_USER_EMAIL, ROOT_USER_PASSWORD
from fixtures.types import SigNoz, TestContainerDocker
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD, add_license
from fixtures.types import Operation, SigNoz, TestContainerDocker
def test_create_and_delete_dashboard_without_license(
signoz: SigNoz,
create_user_admin: Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
):
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = requests.post(
signoz.self.host_configs["8080"].get("/api/v1/dashboards"),
@@ -38,6 +38,7 @@ def test_create_and_delete_dashboard_without_license(
def test_apply_license(
signoz: SigNoz,
create_user_admin: Operation, # pylint: disable=unused-argument
make_http_mocks: Callable[[TestContainerDocker, List[Mapping]], None],
get_token: Callable[[str, str], str],
) -> None:
@@ -49,9 +50,10 @@ def test_apply_license(
def test_create_and_delete_dashboard_with_license(
signoz: SigNoz,
create_user_admin: Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
):
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = requests.post(
signoz.self.host_configs["8080"].get("/api/v1/dashboards"),

View File

@@ -6,13 +6,13 @@ import requests
from sqlalchemy import sql
from wiremock.resources.mappings import Mapping
from fixtures.auth import add_license
from fixtures.signoz import ROOT_USER_EMAIL, ROOT_USER_PASSWORD
from fixtures.types import SigNoz, TestContainerDocker
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD, add_license
from fixtures.types import Operation, SigNoz, TestContainerDocker
def test_apply_license(
signoz: SigNoz,
create_user_admin: Operation, # pylint: disable=unused-argument
make_http_mocks: Callable[[TestContainerDocker, List[Mapping]], None],
get_token: Callable[[str, str], str],
) -> None:
@@ -24,9 +24,10 @@ def test_apply_license(
def test_create_and_get_public_dashboard(
signoz: SigNoz,
create_user_admin: Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
):
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = requests.post(
signoz.self.host_configs["8080"].get("/api/v1/dashboards"),
@@ -71,9 +72,10 @@ def test_create_and_get_public_dashboard(
def test_public_dashboard_widget_query_range(
signoz: SigNoz,
create_user_admin: Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
):
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
dashboard_req = {
"title": "Test Widget Query Range Dashboard",
@@ -194,12 +196,13 @@ def test_public_dashboard_widget_query_range(
def test_anonymous_role_has_public_dashboard_permission(
request: pytest.FixtureRequest,
signoz: SigNoz,
create_user_admin: Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
):
"""
Verify that the signoz-anonymous role has the public-dashboard/* permission.
"""
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Get the roles to find the org_id
response = requests.get(

View File

@@ -11,11 +11,12 @@ from typing import Callable
import requests
from fixtures import types
from fixtures.signoz import ROOT_USER_EMAIL, ROOT_USER_PASSWORD
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
def test_create_logs_pipeline_success(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""
@@ -28,7 +29,7 @@ def test_create_logs_pipeline_success(
3. Verify the response contains version information
4. Verify the pipeline is returned in the response
"""
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
pipeline_payload = {
"pipelines": [
@@ -104,6 +105,7 @@ def test_create_logs_pipeline_success(
def test_list_logs_pipelines_success(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""
@@ -115,7 +117,7 @@ def test_list_logs_pipelines_success(
2. List all pipelines and verify the created pipeline is present
3. Verify the response structure
"""
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Create a pipeline first
create_payload = {
@@ -192,6 +194,7 @@ def test_list_logs_pipelines_success(
def test_list_logs_pipelines_by_version(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""
@@ -204,7 +207,7 @@ def test_list_logs_pipelines_by_version(
3. List pipelines by version 1 and verify it returns the original
4. List pipelines by version 2 and verify it returns the updated version
"""
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Create version 1
v1_payload = {
@@ -353,6 +356,7 @@ def test_list_logs_pipelines_by_version(
def test_preview_logs_pipelines_success(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""
@@ -364,7 +368,7 @@ def test_preview_logs_pipelines_success(
2. Verify the preview processes logs correctly
3. Verify the response contains processed logs
"""
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
preview_payload = {
"pipelines": [
@@ -444,6 +448,7 @@ def test_preview_logs_pipelines_success(
def test_create_multiple_pipelines_success(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""
@@ -455,7 +460,7 @@ def test_create_multiple_pipelines_success(
2. Verify all pipelines are created
3. Verify pipelines are ordered correctly
"""
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
multi_pipeline_payload = {
"pipelines": [
@@ -557,6 +562,7 @@ def test_create_multiple_pipelines_success(
def test_delete_all_pipelines_success(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
) -> None:
"""
@@ -569,7 +575,7 @@ def test_delete_all_pipelines_success(
3. Verify pipelines are deleted
4. Verify new version is created
"""
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Create a pipeline first
create_payload = {

View File

@@ -5,102 +5,100 @@ import requests
from fixtures import types
from fixtures.logger import setup_logger
from fixtures.signoz import ROOT_USER_EMAIL, ROOT_USER_PASSWORD
from fixtures.auth import USER_EDITOR_EMAIL, USER_EDITOR_PASSWORD, USER_EDITOR_NAME
logger = setup_logger(__name__)
# def test_register_with_invalid_input(signoz: types.SigNoz) -> None:
# """
# Test the register endpoint with invalid input.
# 1. Invalid Password
# 2. Invalid Email
# """
# response = requests.post(
# signoz.self.host_configs["8080"].get("/api/v1/register"),
# json={
# "name": "admin",
# "orgId": "",
# "orgName": "integration.test",
# "email": "admin@integration.test",
# "password": "password", # invalid password
# },
# timeout=2,
# )
def test_register_with_invalid_input(signoz: types.SigNoz) -> None:
"""
Test the register endpoint with invalid input.
1. Invalid Password
2. Invalid Email
"""
response = requests.post(
signoz.self.host_configs["8080"].get("/api/v1/register"),
json={
"name": "admin",
"orgId": "",
"orgName": "integration.test",
"email": "admin@integration.test",
"password": "password", # invalid password
},
timeout=2,
)
# assert response.status_code == HTTPStatus.BAD_REQUEST
assert response.status_code == HTTPStatus.BAD_REQUEST
# response = requests.post(
# signoz.self.host_configs["8080"].get("/api/v1/register"),
# json={
# "name": "admin",
# "orgId": "",
# "orgName": "integration.test",
# "email": "admin", # invalid email
# "password": "password123Z$",
# },
# timeout=2,
# )
response = requests.post(
signoz.self.host_configs["8080"].get("/api/v1/register"),
json={
"name": "admin",
"orgId": "",
"orgName": "integration.test",
"email": "admin", # invalid email
"password": "password123Z$",
},
timeout=2,
)
# assert response.status_code == HTTPStatus.BAD_REQUEST
assert response.status_code == HTTPStatus.BAD_REQUEST
# def test_register(signoz: types.SigNoz, get_token: Callable[[str, str], str]) -> None:
# response = requests.get(
# signoz.self.host_configs["8080"].get("/api/v1/version"), timeout=2
# )
def test_register(signoz: types.SigNoz, get_token: Callable[[str, str], str]) -> None:
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/version"), timeout=2
)
# assert response.status_code == HTTPStatus.OK
# assert response.json()["setupCompleted"] is False
assert response.status_code == HTTPStatus.OK
assert response.json()["setupCompleted"] is False
# response = requests.post(
# signoz.self.host_configs["8080"].get("/api/v1/register"),
# json={
# "name": "admin",
# "orgId": "",
# "orgName": "integration.test",
# "email": "admin@integration.test",
# "password": "password123Z$",
# },
# timeout=2,
# )
# assert response.status_code == HTTPStatus.OK
response = requests.post(
signoz.self.host_configs["8080"].get("/api/v1/register"),
json={
"name": "admin",
"orgId": "",
"orgName": "integration.test",
"email": "admin@integration.test",
"password": "password123Z$",
},
timeout=2,
)
assert response.status_code == HTTPStatus.OK
# response = requests.get(
# signoz.self.host_configs["8080"].get("/api/v1/version"), timeout=2
# )
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/version"), timeout=2
)
# assert response.status_code == HTTPStatus.OK
# assert response.json()["setupCompleted"] is True
assert response.status_code == HTTPStatus.OK
assert response.json()["setupCompleted"] is True
# admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token("admin@integration.test", "password123Z$")
# response = requests.get(
# signoz.self.host_configs["8080"].get("/api/v1/user"),
# timeout=2,
# headers={"Authorization": f"Bearer {admin_token}"},
# )
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/user"),
timeout=2,
headers={"Authorization": f"Bearer {admin_token}"},
)
# assert response.status_code == HTTPStatus.OK
assert response.status_code == HTTPStatus.OK
# user_response = response.json()["data"]
# found_user = next(
# (user for user in user_response if user["email"] == "admin@integration.test"),
# None,
# )
user_response = response.json()["data"]
found_user = next(
(user for user in user_response if user["email"] == "admin@integration.test"),
None,
)
# assert found_user is not None
# assert found_user["role"] == "ADMIN"
assert found_user is not None
assert found_user["role"] == "ADMIN"
# response = requests.get(
# signoz.self.host_configs["8080"].get(f"/api/v1/user/{found_user['id']}"),
# timeout=2,
# headers={"Authorization": f"Bearer {admin_token}"},
# )
response = requests.get(
signoz.self.host_configs["8080"].get(f"/api/v1/user/{found_user["id"]}"),
timeout=2,
headers={"Authorization": f"Bearer {admin_token}"},
)
# assert response.status_code == HTTPStatus.OK
# assert response.json()["data"]["role"] == "ADMIN"
assert response.status_code == HTTPStatus.OK
assert response.json()["data"]["role"] == "ADMIN"
def test_invite_and_register(
@@ -109,10 +107,10 @@ def test_invite_and_register(
# Generate an invite token for the editor user
response = requests.post(
signoz.self.host_configs["8080"].get("/api/v1/invite"),
json={"email": USER_EDITOR_EMAIL, "role": "EDITOR", "name": USER_EDITOR_NAME},
json={"email": "editor@integration.test", "role": "EDITOR", "name": "editor"},
timeout=2,
headers={
"Authorization": f"Bearer {get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)}"
"Authorization": f"Bearer {get_token("admin@integration.test", "password123Z$")}"
},
)
@@ -122,7 +120,7 @@ def test_invite_and_register(
signoz.self.host_configs["8080"].get("/api/v1/invite"),
timeout=2,
headers={
"Authorization": f"Bearer {get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)}"
"Authorization": f"Bearer {get_token("admin@integration.test", "password123Z$")}"
},
)
@@ -131,7 +129,7 @@ def test_invite_and_register(
(
invite
for invite in invite_response
if invite["email"] == USER_EDITOR_EMAIL
if invite["email"] == "editor@integration.test"
),
None,
)
@@ -140,8 +138,8 @@ def test_invite_and_register(
response = requests.post(
signoz.self.host_configs["8080"].get("/api/v1/invite/accept"),
json={
"password": USER_EDITOR_PASSWORD,
"displayName": USER_EDITOR_NAME,
"password": "password123Z$",
"displayName": "editor",
"token": f"{found_invite['token']}",
},
timeout=2,
@@ -161,7 +159,7 @@ def test_invite_and_register(
signoz.self.host_configs["8080"].get("/api/v1/user"),
timeout=2,
headers={
"Authorization": f"Bearer {get_token(USER_EDITOR_EMAIL, USER_EDITOR_PASSWORD)}"
"Authorization": f"Bearer {get_token("editor@integration.test", "password123Z$")}"
},
)
@@ -172,7 +170,7 @@ def test_invite_and_register(
signoz.self.host_configs["8080"].get("/api/v1/user"),
timeout=2,
headers={
"Authorization": f"Bearer {get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)}"
"Authorization": f"Bearer {get_token("admin@integration.test", "password123Z$")}"
},
)
@@ -180,20 +178,20 @@ def test_invite_and_register(
user_response = response.json()["data"]
found_user = next(
(user for user in user_response if user["email"] == USER_EDITOR_EMAIL),
(user for user in user_response if user["email"] == "editor@integration.test"),
None,
)
assert found_user is not None
assert found_user["role"] == "EDITOR"
assert found_user["displayName"] == USER_EDITOR_NAME
assert found_user["email"] == USER_EDITOR_EMAIL
assert found_user["displayName"] == "editor"
assert found_user["email"] == "editor@integration.test"
def test_revoke_invite_and_register(
signoz: types.SigNoz, get_token: Callable[[str, str], str]
) -> None:
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token("admin@integration.test", "password123Z$")
# Generate an invite token for the viewer user
response = requests.post(
signoz.self.host_configs["8080"].get("/api/v1/invite"),
@@ -208,7 +206,7 @@ def test_revoke_invite_and_register(
signoz.self.host_configs["8080"].get("/api/v1/invite"),
timeout=2,
headers={
"Authorization": f"Bearer {get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)}"
"Authorization": f"Bearer {get_token("admin@integration.test", "password123Z$")}"
},
)
@@ -236,7 +234,7 @@ def test_revoke_invite_and_register(
json={
"password": "password123Z$",
"displayName": "viewer",
"token": f"{found_invite['token']}",
"token": f"{found_invite["token"]}",
},
timeout=2,
)
@@ -247,7 +245,7 @@ def test_revoke_invite_and_register(
def test_self_access(
signoz: types.SigNoz, get_token: Callable[[str, str], str]
) -> None:
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token("admin@integration.test", "password123Z$")
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/user"),
@@ -259,7 +257,7 @@ def test_self_access(
user_response = response.json()["data"]
found_user = next(
(user for user in user_response if user["email"] == USER_EDITOR_EMAIL),
(user for user in user_response if user["email"] == "editor@integration.test"),
None,
)

View File

@@ -11,7 +11,6 @@ from wiremock.client import (
)
from fixtures import types
from fixtures.signoz import ROOT_USER_EMAIL, ROOT_USER_PASSWORD
def test_apply_license(
@@ -57,7 +56,7 @@ def test_apply_license(
],
)
access_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
access_token = get_token("admin@integration.test", "password123Z$")
response = requests.post(
url=signoz.self.host_configs["8080"].get("/api/v3/licenses"),
@@ -120,7 +119,7 @@ def test_refresh_license(
],
)
access_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
access_token = get_token("admin@integration.test", "password123Z$")
response = requests.put(
url=signoz.self.host_configs["8080"].get("/api/v3/licenses"),
@@ -177,7 +176,7 @@ def test_license_checkout(
],
)
access_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
access_token = get_token("admin@integration.test", "password123Z$")
response = requests.post(
url=signoz.self.host_configs["8080"].get("/api/v1/checkout"),
@@ -228,7 +227,7 @@ def test_license_portal(
],
)
access_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
access_token = get_token("admin@integration.test", "password123Z$")
response = requests.post(
url=signoz.self.host_configs["8080"].get("/api/v1/portal"),

View File

@@ -4,11 +4,10 @@ from typing import Callable
import requests
from fixtures import types
from fixtures.signoz import ROOT_USER_EMAIL, ROOT_USER_PASSWORD
def test_api_key(signoz: types.SigNoz, get_token: Callable[[str, str], str]) -> None:
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token("admin@integration.test", "password123Z$")
response = requests.post(
signoz.self.host_configs["8080"].get("/api/v1/pats"),
@@ -29,7 +28,7 @@ def test_api_key(signoz: types.SigNoz, get_token: Callable[[str, str], str]) ->
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/user"),
timeout=2,
headers={"SIGNOZ-API-KEY": f"{pat_response['data']['token']}"},
headers={"SIGNOZ-API-KEY": f"{pat_response["data"]["token"]}"},
)
assert response.status_code == HTTPStatus.OK
@@ -39,14 +38,14 @@ def test_api_key(signoz: types.SigNoz, get_token: Callable[[str, str], str]) ->
(
user
for user in user_response["data"]
if user["email"] == ROOT_USER_EMAIL
if user["email"] == "admin@integration.test"
),
None,
)
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/pats"),
headers={"SIGNOZ-API-KEY": f"{pat_response['data']['token']}"},
headers={"SIGNOZ-API-KEY": f"{pat_response["data"]["token"]}"},
timeout=2,
)

View File

@@ -6,7 +6,6 @@ from sqlalchemy import sql
from fixtures import types
from fixtures.logger import setup_logger
from fixtures.signoz import ROOT_USER_EMAIL, ROOT_USER_PASSWORD
logger = setup_logger(__name__)
@@ -14,7 +13,7 @@ logger = setup_logger(__name__)
def test_change_password(
signoz: types.SigNoz, get_token: Callable[[str, str], str]
) -> None:
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token("admin@integration.test", "password123Z$")
# Create another admin user
response = requests.post(
@@ -131,7 +130,7 @@ def test_change_password(
def test_reset_password(
signoz: types.SigNoz, get_token: Callable[[str, str], str]
) -> None:
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token("admin@integration.test", "password123Z$")
# Get the user id for admin+password@integration.test
response = requests.get(
@@ -189,7 +188,7 @@ def test_reset_password(
def test_reset_password_with_no_password(
signoz: types.SigNoz, get_token: Callable[[str, str], str]
) -> None:
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token("admin@integration.test", "password123Z$")
# Get the user id for admin+password@integration.test
response = requests.get(
@@ -254,7 +253,7 @@ def test_forgot_password_returns_204_for_nonexistent_email(
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v2/sessions/context"),
params={
"email": ROOT_USER_EMAIL,
"email": "admin@integration.test",
"ref": f"{signoz.self.host_configs['8080'].base()}",
},
timeout=5,
@@ -287,7 +286,7 @@ def test_forgot_password_creates_reset_token(
3. Use the token to reset password
4. Verify user can login with new password
"""
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token("admin@integration.test", "password123Z$")
# Create a user specifically for testing forgot password
response = requests.post(
@@ -419,7 +418,7 @@ def test_reset_password_with_expired_token(
"""
Test that resetting password with an expired token fails.
"""
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token("admin@integration.test", "password123Z$")
# Get user ID for the forgot@integration.test user
response = requests.get(

View File

@@ -4,7 +4,6 @@ from typing import Callable, Tuple
import requests
from fixtures import types
from fixtures.signoz import ROOT_USER_EMAIL, ROOT_USER_PASSWORD
def test_change_role(
@@ -12,7 +11,7 @@ def test_change_role(
get_token: Callable[[str, str], str],
get_tokens: Callable[[str, str], Tuple[str, str]],
):
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token("admin@integration.test", "password123Z$")
# Create a new user as VIEWER
response = requests.post(

View File

@@ -4,7 +4,7 @@ from typing import Callable
import requests
from fixtures import types
from fixtures.signoz import ROOT_USER_EMAIL, ROOT_USER_PASSWORD
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
from fixtures.logger import setup_logger
logger = setup_logger(__name__)
@@ -12,9 +12,10 @@ logger = setup_logger(__name__)
def test_get_user_preference(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
):
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/user/preferences"),
@@ -28,9 +29,10 @@ def test_get_user_preference(
def test_get_set_user_preference_by_name(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
):
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# preference does not exist
response = requests.get(

View File

@@ -4,7 +4,7 @@ from typing import Callable
import requests
from fixtures import types
from fixtures.signoz import ROOT_USER_EMAIL, ROOT_USER_PASSWORD
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
from fixtures.logger import setup_logger
logger = setup_logger(__name__)
@@ -12,9 +12,10 @@ logger = setup_logger(__name__)
def test_get_org_preference(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
):
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/org/preferences"),
@@ -28,9 +29,10 @@ def test_get_org_preference(
def test_get_set_org_preference_by_name(
signoz: types.SigNoz,
create_user_admin: types.Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
):
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# preference does not exist
response = requests.get(

View File

@@ -6,7 +6,7 @@ import pytest
import requests
from fixtures import types
from fixtures.signoz import ROOT_USER_EMAIL, ROOT_USER_PASSWORD
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
from fixtures.logs import Logs
from fixtures.querier import (
assert_minutely_bucket_values,
@@ -19,6 +19,7 @@ from src.querier.util import assert_identical_query_response
def test_logs_list(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_logs: Callable[[List[Logs]], None],
) -> None:
@@ -83,7 +84,7 @@ def test_logs_list(
]
)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Query Logs for the last 10 seconds and check if the logs are returned in the correct order
response = requests.post(
@@ -400,6 +401,7 @@ def test_logs_list(
def test_logs_list_with_corrupt_data(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_logs: Callable[[List[Logs]], None],
) -> None:
@@ -468,7 +470,7 @@ def test_logs_list_with_corrupt_data(
]
)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Query Logs for the last 10 seconds and check if the logs are returned in the correct order
response = requests.post(
@@ -581,6 +583,7 @@ def test_logs_list_with_corrupt_data(
)
def test_logs_list_with_order_by(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_logs: Callable[[List[Logs]], None],
order_by_context: str,
@@ -610,7 +613,7 @@ def test_logs_list_with_order_by(
]
)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
query = {
"type": "builder_query",
@@ -682,6 +685,7 @@ def test_logs_list_with_order_by(
def test_logs_time_series_count(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_logs: Callable[[List[Logs]], None],
) -> None:
@@ -784,7 +788,7 @@ def test_logs_time_series_count(
)
insert_logs(logs)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# count() of all logs for the last 5 minutes
response = requests.post(
@@ -1222,6 +1226,7 @@ def test_logs_time_series_count(
def test_datatype_collision(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_logs: Callable[[List[Logs]], None],
) -> None:
@@ -1327,7 +1332,7 @@ def test_datatype_collision(
insert_logs(logs)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# count() of all logs for the where severity_number > '7'
response = requests.post(
@@ -1656,6 +1661,7 @@ def test_datatype_collision(
def test_logs_fill_gaps(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_logs: Callable[[List[Logs]], None],
) -> None:
@@ -1681,7 +1687,7 @@ def test_logs_fill_gaps(
]
insert_logs(logs)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
start_ms = int((now - timedelta(minutes=5)).timestamp() * 1000)
end_ms = int(now.timestamp() * 1000)
@@ -1741,6 +1747,7 @@ def test_logs_fill_gaps(
def test_logs_fill_gaps_with_group_by(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_logs: Callable[[List[Logs]], None],
) -> None:
@@ -1766,7 +1773,7 @@ def test_logs_fill_gaps_with_group_by(
]
insert_logs(logs)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
start_ms = int((now - timedelta(minutes=5)).timestamp() * 1000)
end_ms = int(now.timestamp() * 1000)
@@ -1841,6 +1848,7 @@ def test_logs_fill_gaps_with_group_by(
def test_logs_fill_gaps_formula(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_logs: Callable[[List[Logs]], None],
) -> None:
@@ -1866,7 +1874,7 @@ def test_logs_fill_gaps_formula(
]
insert_logs(logs)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
start_ms = int((now - timedelta(minutes=5)).timestamp() * 1000)
end_ms = int(now.timestamp() * 1000)
@@ -1947,6 +1955,7 @@ def test_logs_fill_gaps_formula(
def test_logs_fill_gaps_formula_with_group_by(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_logs: Callable[[List[Logs]], None],
) -> None:
@@ -1972,7 +1981,7 @@ def test_logs_fill_gaps_formula_with_group_by(
]
insert_logs(logs)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
start_ms = int((now - timedelta(minutes=5)).timestamp() * 1000)
end_ms = int(now.timestamp() * 1000)
@@ -2074,6 +2083,7 @@ def test_logs_fill_gaps_formula_with_group_by(
def test_logs_fill_zero(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_logs: Callable[[List[Logs]], None],
) -> None:
@@ -2092,7 +2102,7 @@ def test_logs_fill_zero(
]
insert_logs(logs)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
start_ms = int((now - timedelta(minutes=5)).timestamp() * 1000)
end_ms = int(now.timestamp() * 1000)
@@ -2149,6 +2159,7 @@ def test_logs_fill_zero(
def test_logs_fill_zero_with_group_by(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_logs: Callable[[List[Logs]], None],
) -> None:
@@ -2174,7 +2185,7 @@ def test_logs_fill_zero_with_group_by(
]
insert_logs(logs)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
start_ms = int((now - timedelta(minutes=5)).timestamp() * 1000)
end_ms = int(now.timestamp() * 1000)
@@ -2248,6 +2259,7 @@ def test_logs_fill_zero_with_group_by(
def test_logs_fill_zero_formula(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_logs: Callable[[List[Logs]], None],
) -> None:
@@ -2273,7 +2285,7 @@ def test_logs_fill_zero_formula(
]
insert_logs(logs)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
start_ms = int((now - timedelta(minutes=5)).timestamp() * 1000)
end_ms = int(now.timestamp() * 1000)
@@ -2355,6 +2367,7 @@ def test_logs_fill_zero_formula(
def test_logs_fill_zero_formula_with_group_by(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_logs: Callable[[List[Logs]], None],
) -> None:
@@ -2380,7 +2393,7 @@ def test_logs_fill_zero_formula_with_group_by(
]
insert_logs(logs)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
start_ms = int((now - timedelta(minutes=5)).timestamp() * 1000)
end_ms = int(now.timestamp() * 1000)

View File

@@ -6,12 +6,13 @@ from typing import Callable, List
import requests
from fixtures import types
from fixtures.signoz import ROOT_USER_EMAIL, ROOT_USER_PASSWORD
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
from fixtures.logs import Logs
def test_logs_json_body_simple_searches(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_logs: Callable[[List[Logs]], None],
) -> None:
@@ -85,7 +86,7 @@ def test_logs_json_body_simple_searches(
]
)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(email=USER_ADMIN_EMAIL, password=USER_ADMIN_PASSWORD)
# Test 1: Search by body.message = "User logged in successfully"
response = requests.post(
@@ -300,6 +301,7 @@ def test_logs_json_body_simple_searches(
def test_logs_json_body_nested_keys(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_logs: Callable[[List[Logs]], None],
) -> None:
@@ -392,7 +394,7 @@ def test_logs_json_body_nested_keys(
]
)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(email=USER_ADMIN_EMAIL, password=USER_ADMIN_PASSWORD)
# Test 1: Search by body.user.name = "john_doe"
response = requests.post(
@@ -569,6 +571,7 @@ def test_logs_json_body_nested_keys(
def test_logs_json_body_array_membership(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_logs: Callable[[List[Logs]], None],
) -> None:
@@ -645,7 +648,7 @@ def test_logs_json_body_array_membership(
]
)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(email=USER_ADMIN_EMAIL, password=USER_ADMIN_PASSWORD)
# Test 1: Search by has(body.tags[*], "production")
response = requests.post(
@@ -776,6 +779,7 @@ def test_logs_json_body_array_membership(
def test_logs_json_body_listing(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_logs: Callable[[List[Logs]], None],
) -> None:
@@ -873,7 +877,7 @@ def test_logs_json_body_listing(
]
)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(email=USER_ADMIN_EMAIL, password=USER_ADMIN_PASSWORD)
# Test 1: List all logs with JSON bodies
response = requests.post(

View File

@@ -5,7 +5,7 @@ from typing import Callable, Dict, List
import requests
from fixtures import types
from fixtures.signoz import ROOT_USER_EMAIL, ROOT_USER_PASSWORD
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
from fixtures.metrics import Metrics
from fixtures.querier import (
assert_minutely_bucket_values,
@@ -16,6 +16,7 @@ from fixtures.querier import (
def test_metrics_fill_gaps(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_metrics: Callable[[List[Metrics]], None],
) -> None:
@@ -45,7 +46,7 @@ def test_metrics_fill_gaps(
]
insert_metrics(metrics)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
start_ms = int((now - timedelta(minutes=5)).timestamp() * 1000)
end_ms = int(now.timestamp() * 1000)
@@ -109,6 +110,7 @@ def test_metrics_fill_gaps(
def test_metrics_fill_gaps_with_group_by(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_metrics: Callable[[List[Metrics]], None],
) -> None:
@@ -137,7 +139,7 @@ def test_metrics_fill_gaps_with_group_by(
]
insert_metrics(metrics)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
start_ms = int((now - timedelta(minutes=5)).timestamp() * 1000)
end_ms = int(now.timestamp() * 1000)
@@ -217,6 +219,7 @@ def test_metrics_fill_gaps_with_group_by(
def test_metrics_fill_gaps_formula(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_metrics: Callable[[List[Metrics]], None],
) -> None:
@@ -246,7 +249,7 @@ def test_metrics_fill_gaps_formula(
]
insert_metrics(metrics)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
start_ms = int((now - timedelta(minutes=5)).timestamp() * 1000)
end_ms = int(now.timestamp() * 1000)
@@ -339,6 +342,7 @@ def test_metrics_fill_gaps_formula(
def test_metrics_fill_gaps_formula_with_group_by(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_metrics: Callable[[List[Metrics]], None],
) -> None:
@@ -381,7 +385,7 @@ def test_metrics_fill_gaps_formula_with_group_by(
]
insert_metrics(metrics)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
start_ms = int((now - timedelta(minutes=5)).timestamp() * 1000)
end_ms = int(now.timestamp() * 1000)
@@ -494,6 +498,7 @@ def test_metrics_fill_gaps_formula_with_group_by(
def test_metrics_fill_zero(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_metrics: Callable[[List[Metrics]], None],
) -> None:
@@ -515,7 +520,7 @@ def test_metrics_fill_zero(
]
insert_metrics(metrics)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
start_ms = int((now - timedelta(minutes=5)).timestamp() * 1000)
end_ms = int(now.timestamp() * 1000)
@@ -578,6 +583,7 @@ def test_metrics_fill_zero(
def test_metrics_fill_zero_with_group_by(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_metrics: Callable[[List[Metrics]], None],
) -> None:
@@ -605,7 +611,7 @@ def test_metrics_fill_zero_with_group_by(
]
insert_metrics(metrics)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
start_ms = int((now - timedelta(minutes=5)).timestamp() * 1000)
end_ms = int(now.timestamp() * 1000)
@@ -685,6 +691,7 @@ def test_metrics_fill_zero_with_group_by(
def test_metrics_fill_zero_formula(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_metrics: Callable[[List[Metrics]], None],
) -> None:
@@ -713,7 +720,7 @@ def test_metrics_fill_zero_formula(
]
insert_metrics(metrics)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
start_ms = int((now - timedelta(minutes=5)).timestamp() * 1000)
end_ms = int(now.timestamp() * 1000)
@@ -806,6 +813,7 @@ def test_metrics_fill_zero_formula(
def test_metrics_fill_zero_formula_with_group_by(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_metrics: Callable[[List[Metrics]], None],
) -> None:
@@ -848,7 +856,7 @@ def test_metrics_fill_zero_formula_with_group_by(
]
insert_metrics(metrics)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
start_ms = int((now - timedelta(minutes=5)).timestamp() * 1000)
end_ms = int(now.timestamp() * 1000)

View File

@@ -6,7 +6,7 @@ import pytest
import requests
from fixtures import types
from fixtures.signoz import ROOT_USER_EMAIL, ROOT_USER_PASSWORD
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
from fixtures.querier import (
assert_minutely_bucket_values,
find_named_result,
@@ -23,6 +23,7 @@ from src.querier.util import (
def test_traces_list(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_traces: Callable[[List[Traces]], None],
) -> None:
@@ -149,7 +150,7 @@ def test_traces_list(
]
)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Query all traces for the past 5 minutes
response = requests.post(
@@ -661,6 +662,7 @@ def test_traces_list(
)
def test_traces_list_with_corrupt_data(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_traces: Callable[[List[Traces]], None],
payload: Dict[str, Any],
@@ -678,7 +680,7 @@ def test_traces_list_with_corrupt_data(
# 4 Traces with corrupt metadata inserted
# traces[i] occured before traces[j] where i < j
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = make_query_request(
signoz,
@@ -764,6 +766,7 @@ def test_traces_list_with_corrupt_data(
)
def test_traces_aggergate_order_by_count(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_traces: Callable[[List[Traces]], None],
order_by: Dict[str, str],
@@ -890,7 +893,7 @@ def test_traces_aggergate_order_by_count(
]
)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
query = {
"type": "builder_query",
@@ -934,6 +937,7 @@ def test_traces_aggergate_order_by_count(
def test_traces_aggregate_with_mixed_field_selectors(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_traces: Callable[[List[Traces]], None],
) -> None:
@@ -1057,7 +1061,7 @@ def test_traces_aggregate_with_mixed_field_selectors(
]
)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
query = {
"type": "builder_query",
@@ -1110,6 +1114,7 @@ def test_traces_aggregate_with_mixed_field_selectors(
def test_traces_fill_gaps(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_traces: Callable[[List[Traces]], None],
) -> None:
@@ -1136,7 +1141,7 @@ def test_traces_fill_gaps(
]
insert_traces(traces)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
start_ms = int((now - timedelta(minutes=5)).timestamp() * 1000)
end_ms = int(now.timestamp() * 1000)
@@ -1193,6 +1198,7 @@ def test_traces_fill_gaps(
def test_traces_fill_gaps_with_group_by(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_traces: Callable[[List[Traces]], None],
) -> None:
@@ -1231,7 +1237,7 @@ def test_traces_fill_gaps_with_group_by(
]
insert_traces(traces)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
start_ms = int((now - timedelta(minutes=5)).timestamp() * 1000)
end_ms = int(now.timestamp() * 1000)
@@ -1305,6 +1311,7 @@ def test_traces_fill_gaps_with_group_by(
def test_traces_fill_gaps_formula(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_traces: Callable[[List[Traces]], None],
) -> None:
@@ -1343,7 +1350,7 @@ def test_traces_fill_gaps_formula(
]
insert_traces(traces)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
start_ms = int((now - timedelta(minutes=5)).timestamp() * 1000)
end_ms = int(now.timestamp() * 1000)
@@ -1424,6 +1431,7 @@ def test_traces_fill_gaps_formula(
def test_traces_fill_gaps_formula_with_group_by(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_traces: Callable[[List[Traces]], None],
) -> None:
@@ -1462,7 +1470,7 @@ def test_traces_fill_gaps_formula_with_group_by(
]
insert_traces(traces)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
start_ms = int((now - timedelta(minutes=5)).timestamp() * 1000)
end_ms = int(now.timestamp() * 1000)
@@ -1564,6 +1572,7 @@ def test_traces_fill_gaps_formula_with_group_by(
def test_traces_fill_zero(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_traces: Callable[[List[Traces]], None],
) -> None:
@@ -1589,7 +1598,7 @@ def test_traces_fill_zero(
]
insert_traces(traces)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
start_ms = int((now - timedelta(minutes=5)).timestamp() * 1000)
end_ms = int(now.timestamp() * 1000)
@@ -1646,6 +1655,7 @@ def test_traces_fill_zero(
def test_traces_fill_zero_with_group_by(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_traces: Callable[[List[Traces]], None],
) -> None:
@@ -1684,7 +1694,7 @@ def test_traces_fill_zero_with_group_by(
]
insert_traces(traces)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
start_ms = int((now - timedelta(minutes=5)).timestamp() * 1000)
end_ms = int(now.timestamp() * 1000)
@@ -1759,6 +1769,7 @@ def test_traces_fill_zero_with_group_by(
def test_traces_fill_zero_formula(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_traces: Callable[[List[Traces]], None],
) -> None:
@@ -1797,7 +1808,7 @@ def test_traces_fill_zero_formula(
]
insert_traces(traces)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
start_ms = int((now - timedelta(minutes=5)).timestamp() * 1000)
end_ms = int(now.timestamp() * 1000)
@@ -1878,6 +1889,7 @@ def test_traces_fill_zero_formula(
def test_traces_fill_zero_formula_with_group_by(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_traces: Callable[[List[Traces]], None],
) -> None:
@@ -1916,7 +1928,7 @@ def test_traces_fill_zero_formula_with_group_by(
]
insert_traces(traces)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
start_ms = int((now - timedelta(minutes=5)).timestamp() * 1000)
end_ms = int(now.timestamp() * 1000)

View File

@@ -8,7 +8,7 @@ from http import HTTPStatus
from typing import Any, Callable, List
from fixtures import types
from fixtures.signoz import ROOT_USER_EMAIL, ROOT_USER_PASSWORD
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
from fixtures.metrics import Metrics
from fixtures.querier import (
build_builder_query,
@@ -23,6 +23,7 @@ CUMULATIVE_COUNTERS_FILE = os.path.join(TESTDATA_DIR, "cumulative_counters_1h.js
def test_rate_with_steady_values_and_reset(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_metrics: Callable[[List[Metrics]], None],
) -> None:
@@ -38,7 +39,7 @@ def test_rate_with_steady_values_and_reset(
)
insert_metrics(metrics)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
query = build_builder_query(
"A",
metric_name,
@@ -72,6 +73,7 @@ def test_rate_with_steady_values_and_reset(
def test_rate_group_by_endpoint(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_metrics: Callable[[List[Metrics]], None],
) -> None:
@@ -87,7 +89,7 @@ def test_rate_group_by_endpoint(
)
insert_metrics(metrics)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
query = build_builder_query(
"A",
metric_name,

View File

@@ -5,7 +5,7 @@ from typing import Callable, Dict, List, Optional, Tuple
import requests
from fixtures import querier, types
from fixtures.signoz import ROOT_USER_EMAIL, ROOT_USER_PASSWORD
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
from fixtures.logs import Logs
from fixtures.metrics import Metrics
from fixtures.traces import TraceIdGenerator, Traces, TracesKind, TracesStatusCode
@@ -210,13 +210,14 @@ def build_metrics_query(
def test_logs_scalar_group_by_single_agg_no_order(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_logs: Callable[[List[Logs]], None],
) -> None:
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
insert_logs(generate_logs_with_counts(now, log_or_trace_service_counts))
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = make_scalar_query_request(
signoz,
token,
@@ -237,13 +238,14 @@ def test_logs_scalar_group_by_single_agg_no_order(
def test_logs_scalar_group_by_single_agg_order_by_agg_asc(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_logs: Callable[[List[Logs]], None],
) -> None:
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
insert_logs(generate_logs_with_counts(now, log_or_trace_service_counts))
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = make_scalar_query_request(
signoz,
token,
@@ -264,13 +266,14 @@ def test_logs_scalar_group_by_single_agg_order_by_agg_asc(
def test_logs_scalar_group_by_single_agg_order_by_agg_desc(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_logs: Callable[[List[Logs]], None],
) -> None:
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
insert_logs(generate_logs_with_counts(now, log_or_trace_service_counts))
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = make_scalar_query_request(
signoz,
token,
@@ -291,13 +294,14 @@ def test_logs_scalar_group_by_single_agg_order_by_agg_desc(
def test_logs_scalar_group_by_single_agg_order_by_grouping_key_asc(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_logs: Callable[[List[Logs]], None],
) -> None:
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
insert_logs(generate_logs_with_counts(now, log_or_trace_service_counts))
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = make_scalar_query_request(
signoz,
token,
@@ -322,13 +326,14 @@ def test_logs_scalar_group_by_single_agg_order_by_grouping_key_asc(
def test_logs_scalar_group_by_single_agg_order_by_grouping_key_desc(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_logs: Callable[[List[Logs]], None],
) -> None:
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
insert_logs(generate_logs_with_counts(now, log_or_trace_service_counts))
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = make_scalar_query_request(
signoz,
token,
@@ -353,13 +358,14 @@ def test_logs_scalar_group_by_single_agg_order_by_grouping_key_desc(
def test_logs_scalar_group_by_multiple_aggs_order_by_first_agg_asc(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_logs: Callable[[List[Logs]], None],
) -> None:
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
insert_logs(generate_logs_with_counts(now, log_or_trace_service_counts))
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = make_scalar_query_request(
signoz,
token,
@@ -384,13 +390,14 @@ def test_logs_scalar_group_by_multiple_aggs_order_by_first_agg_asc(
def test_logs_scalar_group_by_multiple_aggs_order_by_second_agg_desc(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_logs: Callable[[List[Logs]], None],
) -> None:
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
insert_logs(generate_logs_with_counts(now, log_or_trace_service_counts))
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = make_scalar_query_request(
signoz,
token,
@@ -416,13 +423,14 @@ def test_logs_scalar_group_by_multiple_aggs_order_by_second_agg_desc(
def test_logs_scalar_group_by_single_agg_order_by_agg_asc_limit_2(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_logs: Callable[[List[Logs]], None],
) -> None:
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
insert_logs(generate_logs_with_counts(now, log_or_trace_service_counts))
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = make_scalar_query_request(
signoz,
token,
@@ -447,13 +455,14 @@ def test_logs_scalar_group_by_single_agg_order_by_agg_asc_limit_2(
def test_logs_scalar_group_by_single_agg_order_by_agg_desc_limit_3(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_logs: Callable[[List[Logs]], None],
) -> None:
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
insert_logs(generate_logs_with_counts(now, log_or_trace_service_counts))
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = make_scalar_query_request(
signoz,
token,
@@ -478,13 +487,14 @@ def test_logs_scalar_group_by_single_agg_order_by_agg_desc_limit_3(
def test_logs_scalar_group_by_order_by_grouping_key_asc_limit_2(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_logs: Callable[[List[Logs]], None],
) -> None:
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
insert_logs(generate_logs_with_counts(now, log_or_trace_service_counts))
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = make_scalar_query_request(
signoz,
token,
@@ -509,13 +519,14 @@ def test_logs_scalar_group_by_order_by_grouping_key_asc_limit_2(
def test_traces_scalar_group_by_single_agg_no_order(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_traces: Callable[[List[Traces]], None],
) -> None:
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
insert_traces(generate_traces_with_counts(now, log_or_trace_service_counts))
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = make_scalar_query_request(
signoz,
token,
@@ -536,13 +547,14 @@ def test_traces_scalar_group_by_single_agg_no_order(
def test_traces_scalar_group_by_single_agg_order_by_agg_asc(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_traces: Callable[[List[Traces]], None],
) -> None:
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
insert_traces(generate_traces_with_counts(now, log_or_trace_service_counts))
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = make_scalar_query_request(
signoz,
token,
@@ -563,13 +575,14 @@ def test_traces_scalar_group_by_single_agg_order_by_agg_asc(
def test_traces_scalar_group_by_single_agg_order_by_agg_desc(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_traces: Callable[[List[Traces]], None],
) -> None:
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
insert_traces(generate_traces_with_counts(now, log_or_trace_service_counts))
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = make_scalar_query_request(
signoz,
token,
@@ -590,13 +603,14 @@ def test_traces_scalar_group_by_single_agg_order_by_agg_desc(
def test_traces_scalar_group_by_single_agg_order_by_grouping_key_asc(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_traces: Callable[[List[Traces]], None],
) -> None:
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
insert_traces(generate_traces_with_counts(now, log_or_trace_service_counts))
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = make_scalar_query_request(
signoz,
token,
@@ -621,13 +635,14 @@ def test_traces_scalar_group_by_single_agg_order_by_grouping_key_asc(
def test_traces_scalar_group_by_single_agg_order_by_grouping_key_desc(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_traces: Callable[[List[Traces]], None],
) -> None:
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
insert_traces(generate_traces_with_counts(now, log_or_trace_service_counts))
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = make_scalar_query_request(
signoz,
token,
@@ -652,13 +667,14 @@ def test_traces_scalar_group_by_single_agg_order_by_grouping_key_desc(
def test_traces_scalar_group_by_multiple_aggs_order_by_first_agg_asc(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_traces: Callable[[List[Traces]], None],
) -> None:
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
insert_traces(generate_traces_with_counts(now, log_or_trace_service_counts))
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = make_scalar_query_request(
signoz,
token,
@@ -683,13 +699,14 @@ def test_traces_scalar_group_by_multiple_aggs_order_by_first_agg_asc(
def test_traces_scalar_group_by_single_agg_order_by_agg_asc_limit_2(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_traces: Callable[[List[Traces]], None],
) -> None:
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
insert_traces(generate_traces_with_counts(now, log_or_trace_service_counts))
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = make_scalar_query_request(
signoz,
token,
@@ -714,13 +731,14 @@ def test_traces_scalar_group_by_single_agg_order_by_agg_asc_limit_2(
def test_traces_scalar_group_by_single_agg_order_by_agg_desc_limit_3(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_traces: Callable[[List[Traces]], None],
) -> None:
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
insert_traces(generate_traces_with_counts(now, log_or_trace_service_counts))
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = make_scalar_query_request(
signoz,
token,
@@ -745,13 +763,14 @@ def test_traces_scalar_group_by_single_agg_order_by_agg_desc_limit_3(
def test_traces_scalar_group_by_order_by_grouping_key_asc_limit_2(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_traces: Callable[[List[Traces]], None],
) -> None:
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
insert_traces(generate_traces_with_counts(now, log_or_trace_service_counts))
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = make_scalar_query_request(
signoz,
token,
@@ -776,13 +795,14 @@ def test_traces_scalar_group_by_order_by_grouping_key_asc_limit_2(
def test_metrics_scalar_group_by_single_agg_no_order(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_metrics: Callable[[List[Metrics]], None],
) -> None:
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
insert_metrics(generate_metrics_with_values(now, metric_values_for_test))
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = make_scalar_query_request(
signoz,
token,
@@ -808,13 +828,14 @@ def test_metrics_scalar_group_by_single_agg_no_order(
def test_metrics_scalar_group_by_single_agg_order_by_agg_asc(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_metrics: Callable[[List[Metrics]], None],
) -> None:
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
insert_metrics(generate_metrics_with_values(now, metric_values_for_test))
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = make_scalar_query_request(
signoz,
token,
@@ -845,13 +866,14 @@ def test_metrics_scalar_group_by_single_agg_order_by_agg_asc(
def test_metrics_scalar_group_by_single_agg_order_by_grouping_key_asc(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_metrics: Callable[[List[Metrics]], None],
) -> None:
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
insert_metrics(generate_metrics_with_values(now, metric_values_for_test))
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = make_scalar_query_request(
signoz,
token,
@@ -882,13 +904,14 @@ def test_metrics_scalar_group_by_single_agg_order_by_grouping_key_asc(
def test_metrics_scalar_group_by_single_agg_order_by_agg_asc_limit_2(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_metrics: Callable[[List[Metrics]], None],
) -> None:
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
insert_metrics(generate_metrics_with_values(now, metric_values_for_test))
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = make_scalar_query_request(
signoz,
token,
@@ -915,13 +938,14 @@ def test_metrics_scalar_group_by_single_agg_order_by_agg_asc_limit_2(
def test_metrics_scalar_group_by_single_agg_order_by_agg_desc_limit_3(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_metrics: Callable[[List[Metrics]], None],
) -> None:
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
insert_metrics(generate_metrics_with_values(now, metric_values_for_test))
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = make_scalar_query_request(
signoz,
token,
@@ -948,13 +972,14 @@ def test_metrics_scalar_group_by_single_agg_order_by_agg_desc_limit_3(
def test_metrics_scalar_group_by_order_by_grouping_key_asc_limit_2(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_metrics: Callable[[List[Metrics]], None],
) -> None:
now = datetime.now(tz=timezone.utc).replace(second=0, microsecond=0)
insert_metrics(generate_metrics_with_values(now, metric_values_for_test))
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = make_scalar_query_request(
signoz,
token,

View File

@@ -10,7 +10,7 @@ from typing import Callable, List
import pytest
from fixtures import types
from fixtures.signoz import ROOT_USER_EMAIL, ROOT_USER_PASSWORD
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
from fixtures.metrics import Metrics
from fixtures.querier import (
build_builder_query,
@@ -38,6 +38,7 @@ MULTI_TEMPORALITY_FILE_24h = get_testdata_file_path(
)
def test_with_steady_values_and_reset(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_metrics: Callable[[List[Metrics]], None],
time_aggregation: str,
@@ -57,7 +58,7 @@ def test_with_steady_values_and_reset(
)
insert_metrics(metrics)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
query = build_builder_query(
"A",
metric_name,
@@ -98,6 +99,7 @@ def test_with_steady_values_and_reset(
)
def test_group_by_endpoint(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_metrics: Callable[[List[Metrics]], None],
time_aggregation: str,
@@ -120,7 +122,7 @@ def test_group_by_endpoint(
)
insert_metrics(metrics)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
query = build_builder_query(
"A",
metric_name,
@@ -280,6 +282,7 @@ def test_group_by_endpoint(
)
def test_for_service_with_switch(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_metrics: Callable[[List[Metrics]], None],
time_aggregation: str,
@@ -299,7 +302,7 @@ def test_for_service_with_switch(
)
insert_metrics(metrics)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
query = build_builder_query(
"A",
metric_name,
@@ -336,6 +339,7 @@ def test_for_service_with_switch(
)
def test_for_week_long_time_range(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_metrics: Callable[[List[Metrics]], None],
time_aggregation: str,
@@ -353,7 +357,7 @@ def test_for_week_long_time_range(
)
insert_metrics(metrics)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
query = build_builder_query(
"A",
metric_name,
@@ -379,6 +383,7 @@ def test_for_week_long_time_range(
)
def test_for_month_long_time_range(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_metrics: Callable[[List[Metrics]], None],
time_aggregation: str,
@@ -396,7 +401,7 @@ def test_for_month_long_time_range(
)
insert_metrics(metrics)
token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
query = build_builder_query(
"A",
metric_name,

View File

@@ -5,17 +5,18 @@ import pytest
import requests
from sqlalchemy import sql
from fixtures.signoz import ROOT_USER_EMAIL, ROOT_USER_PASSWORD
from fixtures.types import SigNoz
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
from fixtures.types import Operation, SigNoz
ANONYMOUS_USER_ID = "00000000-0000-0000-0000-000000000000"
def test_managed_roles_create_on_register(
signoz: SigNoz,
create_user_admin: Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
):
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# get the list of all roles.
response = requests.get(
@@ -44,9 +45,10 @@ def test_managed_roles_create_on_register(
def test_root_user_signoz_admin_assignment(
request: pytest.FixtureRequest,
signoz: SigNoz,
create_user_admin: Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
):
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# Get the user from the /user/me endpoint and extract the id
user_response = requests.get(
@@ -104,9 +106,10 @@ def test_root_user_signoz_admin_assignment(
def test_anonymous_user_signoz_anonymous_assignment(
request: pytest.FixtureRequest,
signoz: SigNoz,
create_user_admin: Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
):
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/roles"),

View File

@@ -5,17 +5,22 @@ import pytest
import requests
from sqlalchemy import sql
from fixtures.auth import USER_EDITOR_EMAIL, USER_EDITOR_PASSWORD
from fixtures.signoz import ROOT_USER_EMAIL, ROOT_USER_PASSWORD
from fixtures.types import SigNoz
from fixtures.auth import (
USER_ADMIN_EMAIL,
USER_ADMIN_PASSWORD,
USER_EDITOR_EMAIL,
USER_EDITOR_PASSWORD,
)
from fixtures.types import Operation, SigNoz
def test_user_invite_accept_role_grant(
request: pytest.FixtureRequest,
signoz: SigNoz,
create_user_admin: Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
):
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
# invite a user as editor
invite_payload = {
@@ -95,6 +100,7 @@ def test_user_invite_accept_role_grant(
def test_user_update_role_grant(
request: pytest.FixtureRequest,
signoz: SigNoz,
create_user_admin: Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
):
# Get the editor user's id
@@ -108,7 +114,7 @@ def test_user_update_role_grant(
editor_id = user_me_response.json()["data"]["id"]
# Get the role id for viewer
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
roles_response = requests.get(
signoz.self.host_configs["8080"].get("/api/v1/roles"),
headers={"Authorization": f"Bearer {admin_token}"},
@@ -171,6 +177,7 @@ def test_user_update_role_grant(
def test_user_delete_role_revoke(
request: pytest.FixtureRequest,
signoz: SigNoz,
create_user_admin: Operation, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
):
# login with editor to get the user_id and check if user exists
@@ -184,7 +191,7 @@ def test_user_delete_role_revoke(
editor_id = user_me_response.json()["data"]["id"]
# delete the editor user
admin_token = get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)
admin_token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
delete_response = requests.delete(
signoz.self.host_configs["8080"].get(f"/api/v1/user/{editor_id}"),
headers={"Authorization": f"Bearer {admin_token}"},

View File

@@ -14,7 +14,7 @@ import pytest
import requests
from fixtures import types
from fixtures.signoz import ROOT_USER_EMAIL, ROOT_USER_PASSWORD
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
from fixtures.logger import setup_logger
from fixtures.logs import Logs
@@ -98,6 +98,14 @@ def verify_table_retention_expression(
)
@pytest.fixture(name="ttl_test_suite_setup", scope="package", autouse=True)
def ttl_test_suite_setup(create_user_admin): # pylint: disable=unused-argument
# This fixture creates a admin user for the entire ttl test suite
# The create_user_admin fixture is executed just by being a dependency
print("Setting up ttl test suite")
yield
def test_set_ttl_traces_success(
signoz: types.SigNoz,
get_token: Callable[[str, str], str],
@@ -113,7 +121,7 @@ def test_set_ttl_traces_success(
}
headers = {
"Authorization": f"Bearer {get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)}"
"Authorization": f"Bearer {get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)}"
}
response = requests.post(
@@ -163,7 +171,7 @@ def test_set_ttl_traces_with_cold_storage(
}
headers = {
"Authorization": f"Bearer {get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)}"
"Authorization": f"Bearer {get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)}"
}
response = requests.post(
@@ -225,7 +233,7 @@ def test_set_ttl_metrics_success(
}
headers = {
"Authorization": f"Bearer {get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)}"
"Authorization": f"Bearer {get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)}"
}
response = requests.post(
@@ -277,7 +285,7 @@ def test_set_ttl_metrics_with_cold_storage(
}
headers = {
"Authorization": f"Bearer {get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)}"
"Authorization": f"Bearer {get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)}"
}
response = requests.post(
@@ -342,7 +350,7 @@ def test_set_ttl_invalid_type(
}
headers = {
"Authorization": f"Bearer {get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)}"
"Authorization": f"Bearer {get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)}"
}
response = requests.post(
@@ -374,7 +382,7 @@ def test_set_custom_retention_ttl_basic(
}
headers = {
"Authorization": f"Bearer {get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)}"
"Authorization": f"Bearer {get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)}"
}
response = requests.post(
@@ -436,7 +444,7 @@ def test_set_custom_retention_ttl_basic_with_cold_storage(
}
headers = {
"Authorization": f"Bearer {get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)}"
"Authorization": f"Bearer {get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)}"
}
response = requests.post(
@@ -504,7 +512,7 @@ def test_set_custom_retention_ttl_basic_fallback(
}
headers = {
"Authorization": f"Bearer {get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)}"
"Authorization": f"Bearer {get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)}"
}
response = requests.post(
@@ -568,7 +576,7 @@ def test_set_custom_retention_ttl_basic_101_times(
}
headers = {
"Authorization": f"Bearer {get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)}"
"Authorization": f"Bearer {get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)}"
}
response = requests.post(
@@ -620,7 +628,7 @@ def test_set_custom_retention_ttl_with_conditions(
}
headers = {
"Authorization": f"Bearer {get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)}"
"Authorization": f"Bearer {get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)}"
}
response = requests.post(
@@ -731,7 +739,7 @@ def test_set_custom_retention_ttl_with_invalid_cold_storage(
insert_logs(logs)
headers = {
"Authorization": f"Bearer {get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)}"
"Authorization": f"Bearer {get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)}"
}
response = requests.post(
@@ -775,7 +783,7 @@ def test_set_custom_retention_ttl_duplicate_conditions(
}
headers = {
"Authorization": f"Bearer {get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)}"
"Authorization": f"Bearer {get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)}"
}
response = requests.post(
@@ -812,7 +820,7 @@ def test_set_custom_retention_ttl_invalid_condition(
}
headers = {
"Authorization": f"Bearer {get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)}"
"Authorization": f"Bearer {get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)}"
}
response = requests.post(
@@ -851,7 +859,7 @@ def test_get_custom_retention_ttl(
insert_logs(logs)
headers = {
"Authorization": f"Bearer {get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)}"
"Authorization": f"Bearer {get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)}"
}
set_response = requests.post(
signoz.self.host_configs["8080"].get("/api/v2/settings/ttl"),
@@ -866,7 +874,7 @@ def test_get_custom_retention_ttl(
# Now get the TTL configuration
headers = {
"Authorization": f"Bearer {get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)}"
"Authorization": f"Bearer {get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)}"
}
get_response = requests.get(
@@ -904,7 +912,7 @@ def test_set_ttl_logs_success(
}
headers = {
"Authorization": f"Bearer {get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)}"
"Authorization": f"Bearer {get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)}"
}
response = requests.post(
@@ -946,7 +954,7 @@ def test_get_ttl_traces_success(
}
headers = {
"Authorization": f"Bearer {get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)}"
"Authorization": f"Bearer {get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)}"
}
set_response = requests.post(
@@ -1016,7 +1024,7 @@ def test_large_ttl_conditions_list(
}
headers = {
"Authorization": f"Bearer {get_token(ROOT_USER_EMAIL, ROOT_USER_PASSWORD)}"
"Authorization": f"Bearer {get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)}"
}
response = requests.post(