Compare commits

..

1 Commits

Author SHA1 Message Date
Gaurav Tewari
98a0d906ed fix(navigation): keep an explicit replace: false in safeNavigate 2026-11-13 01:06:21 +05:30
9 changed files with 48 additions and 930 deletions

View File

@@ -31,12 +31,3 @@ packages:
filename: "tokenizer.go"
structname: 'Mock{{.InterfaceName}}'
pkgname: '{{.SrcPackageName}}test'
github.com/ClickHouse/clickhouse-go/v2/lib/driver:
interfaces:
Rows:
ColumnType:
config:
dir: 'pkg/telemetrystore/drivertest'
filename: "driver.go"
structname: 'Mock{{.InterfaceName}}'
pkgname: '{{.SrcPackageName}}test'

View File

@@ -0,0 +1,32 @@
import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
import { back, getCurrentLocation } from 'lib/router/navigation';
import { act, AllTheProviders, renderHook, waitFor } from 'tests/test-utils';
// jest.config.ts maps `hooks/useSafeNavigate` to a mock; the `.ts` path loads the real hook.
const { useSafeNavigate } = jest.requireActual<
typeof import('hooks/useSafeNavigate')
>('hooks/useSafeNavigate.ts');
describe('useSafeNavigate', () => {
it('adds a history entry for replace: false even when the target adds params', async () => {
const initialSearch = `?${QueryParams.relativeTime}=15m`;
const { result } = renderHook(() => useSafeNavigate(), {
wrapper: ({ children }): JSX.Element => (
<AllTheProviders initialRoute={`${ROUTES.LOGS_EXPLORER}${initialSearch}`}>
{children}
</AllTheProviders>
),
});
act(() => {
result.current.safeNavigate(
`${ROUTES.LOGS_EXPLORER}?${QueryParams.startTime}=1000`,
{ replace: false },
);
});
act(() => back());
await waitFor(() => expect(getCurrentLocation().search).toBe(initialSearch));
});
});

View File

@@ -140,7 +140,7 @@ export const useSafeNavigate = (
const navigationOptions = {
...options,
replace: isDefaultParamsNavigation || options?.replace,
replace: options?.replace ?? isDefaultParamsNavigation,
};
if (typeof to === 'string') {

View File

@@ -457,13 +457,6 @@ func emptyResultFor(kind qbtypes.RequestType, queryName string) *qbtypes.Result
// executeWithContext executes the query with query window and step context for partial value detection.
func (q *builderQuery[T]) executeWithContext(ctx context.Context, query string, args []any) (*qbtypes.Result, error) {
result, _, err := q.executeSkippingRaw(ctx, query, args, 0)
return result, err
}
// executeSkippingRaw is executeWithContext for a raw query that drops its first
// skip rows undecoded. It returns how many rows were skipped.
func (q *builderQuery[T]) executeSkippingRaw(ctx context.Context, query string, args []any, skip int) (*qbtypes.Result, int, error) {
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
instrumentationtypes.TelemetrySignal: q.spec.Signal.StringValue(),
instrumentationtypes.QueryDuration: instrumentationtypes.DurationBucket(q.fromMS, q.toMS),
@@ -482,19 +475,19 @@ func (q *builderQuery[T]) executeSkippingRaw(ctx context.Context, query string,
rows, err := q.telemetryStore.ClickhouseDB().Query(ctx, query, args...)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return nil, 0, errors.Newf(errors.TypeTimeout, errors.CodeTimeout, "Query timed out").
return nil, errors.Newf(errors.TypeTimeout, errors.CodeTimeout, "Query timed out").
WithAdditional("Try refining your search by adding relevant resource attributes filtering")
}
if !errors.Is(err, context.Canceled) {
return nil, 0, errors.Newf(
return nil, errors.Newf(
errors.TypeInternal,
errors.CodeInternal,
"Something went wrong on our end. It's not you, it's us. Our team is notified about it. Reach out to support if issue persists.",
)
}
return nil, 0, err
return nil, err
}
defer rows.Close()
@@ -508,15 +501,9 @@ func (q *builderQuery[T]) executeSkippingRaw(ctx context.Context, query string,
kind = qbtypes.RequestTypeTimeSeries
}
var payload any
skipped := 0
if skip > 0 {
payload, skipped, err = readAsRawSkipping(rows, q.spec.Name, skip)
} else {
payload, err = consume(rows, kind, queryWindow, q.spec.StepInterval, q.spec.Name)
}
payload, err := consume(rows, kind, queryWindow, q.spec.StepInterval, q.spec.Name)
if err != nil {
return nil, 0, err
return nil, err
}
// TODO: This should move to readAsRaw function in consume.go but for now we are keeping it here since it's only relevant for traces
@@ -536,7 +523,7 @@ func (q *builderQuery[T]) executeSkippingRaw(ctx context.Context, query string,
BytesScanned: totalBytes,
DurationMS: uint64(elapsed.Milliseconds()),
},
}, skipped, nil
}, nil
}
func (q *builderQuery[T]) executeWindowList(ctx context.Context) (*qbtypes.Result, error) {
@@ -636,7 +623,7 @@ func (q *builderQuery[T]) executeWindowList(ctx context.Context) (*qbtypes.Resul
}
}
// Execute with proper context for partial value detection
res, skipped, err := q.executeSkippingRaw(ctx, stmt.Query, stmt.Args, offsetLeft)
res, err := q.executeWithContext(ctx, stmt.Query, stmt.Args)
if err != nil {
return nil, err
}
@@ -644,10 +631,13 @@ func (q *builderQuery[T]) executeWindowList(ctx context.Context) (*qbtypes.Resul
totalBytes += res.Stats.BytesScanned
rawRows := res.Value.(*qbtypes.RawData).Rows
need -= skipped + len(rawRows)
offsetLeft -= skipped
need -= len(rawRows)
for _, rr := range rawRows {
if offsetLeft > 0 { // client-requested initial offset
offsetLeft--
continue
}
rows = append(rows, rr)
if len(rows) >= reqLimit { // page filled
break

View File

@@ -521,26 +521,13 @@ func derefValue(v any) any {
}
func readAsRaw(rows driver.Rows, queryName string) (*qbtypes.RawData, error) {
data, _, err := readAsRawSkipping(rows, queryName, 0)
return data, err
}
// readAsRawSkipping advances past the first skip rows without scanning them, so
// rows dropped for an offset are never decoded. It returns how many were skipped.
func readAsRawSkipping(rows driver.Rows, queryName string, skip int) (*qbtypes.RawData, int, error) {
colNames := rows.Columns()
colTypes := rows.ColumnTypes()
colCnt := len(colNames)
var outRows []*qbtypes.RawRow
skipped := 0
for rows.Next() {
if skipped < skip {
skipped++
continue
}
// fresh copy of the scan slice (otherwise the driver reuses pointers)
scan := make([]any, colCnt)
for i := range colTypes {
@@ -548,7 +535,7 @@ func readAsRawSkipping(rows driver.Rows, queryName string, skip int) (*qbtypes.R
}
if err := rows.Scan(scan...); err != nil {
return nil, 0, err
return nil, err
}
rr := qbtypes.RawRow{
@@ -580,13 +567,13 @@ func readAsRawSkipping(rows driver.Rows, queryName string, skip int) (*qbtypes.R
outRows = append(outRows, &rr)
}
if err := rows.Err(); err != nil {
return nil, 0, err
return nil, err
}
return &qbtypes.RawData{
QueryName: queryName,
Rows: outRows,
}, skipped, nil
}, nil
}
// mergeSpanAttributeColumns merges (attributes_string, attributes_number, attributes_bool, resources_string) into

View File

@@ -5,12 +5,9 @@ import (
"testing"
"github.com/ClickHouse/clickhouse-go/v2/lib/chcol"
"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/SigNoz/signoz/pkg/telemetrystore/drivertest"
"github.com/SigNoz/signoz/pkg/types/spantypes"
"github.com/SigNoz/signoz/pkg/types/telemetrystoretypes"
)
@@ -257,52 +254,3 @@ func TestMergeSpanAttributeColumns_JSONColumn(t *testing.T) {
})
}
}
func TestReadAsRawSkipping(t *testing.T) {
testCases := []struct {
name string
values []string
skip int
wantSkipped int
wantIDs []string
}{
{name: "NoSkip", values: []string{"a", "b", "c"}, skip: 0, wantSkipped: 0, wantIDs: []string{"a", "b", "c"}},
{name: "SkipInsideResult", values: []string{"d", "e", "f", "g"}, skip: 2, wantSkipped: 2, wantIDs: []string{"f", "g"}},
{name: "SkipWholeResult", values: []string{"h", "i"}, skip: 2, wantSkipped: 2, wantIDs: nil},
{name: "SkipPastResult", values: []string{"j"}, skip: 5, wantSkipped: 1, wantIDs: nil},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
columnType := drivertest.NewMockColumnType(t)
columnType.EXPECT().ScanType().Return(reflect.TypeOf("")).Maybe()
// Like clickhouse-go, Next advances on its own; Scan is expected only for returned rows.
rows := drivertest.NewMockRows(t)
pos := 0
rows.EXPECT().Columns().Return([]string{"id"})
rows.EXPECT().ColumnTypes().Return([]driver.ColumnType{columnType})
rows.EXPECT().Next().RunAndReturn(func() bool {
pos++
return pos <= len(testCase.values)
})
if len(testCase.wantIDs) > 0 {
rows.EXPECT().Scan(mock.Anything).RunAndReturn(func(dest ...any) error {
*dest[0].(*string) = testCase.values[pos-1]
return nil
}).Times(len(testCase.wantIDs))
}
rows.EXPECT().Err().Return(nil)
data, skipped, err := readAsRawSkipping(rows, "A", testCase.skip)
require.NoError(t, err)
var ids []string
for _, row := range data.Rows {
ids = append(ids, row.Data["id"].(string))
}
assert.Equal(t, testCase.wantSkipped, skipped)
assert.Equal(t, testCase.wantIDs, ids)
})
}
}

View File

@@ -1,701 +0,0 @@
// Code generated by mockery; DO NOT EDIT.
// github.com/vektra/mockery
// template: testify
package drivertest
import (
"reflect"
"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
mock "github.com/stretchr/testify/mock"
)
// NewMockRows creates a new instance of MockRows. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewMockRows(t interface {
mock.TestingT
Cleanup(func())
}) *MockRows {
if helper, ok := t.(interface{ Helper() }); ok {
helper.Helper()
}
mock := &MockRows{}
mock.Mock.Test(t)
t.Cleanup(func() {
if helper, ok := t.(interface{ Helper() }); ok {
helper.Helper()
}
mock.AssertExpectations(t)
})
return mock
}
// MockRows is an autogenerated mock type for the Rows type
type MockRows struct {
mock.Mock
}
type MockRows_Expecter struct {
mock *mock.Mock
}
func (_m *MockRows) EXPECT() *MockRows_Expecter {
return &MockRows_Expecter{mock: &_m.Mock}
}
// Close provides a mock function for the type MockRows
func (_mock *MockRows) Close() error {
ret := _mock.Called()
if len(ret) == 0 {
panic("no return value specified for Close")
}
var r0 error
if returnFunc, ok := ret.Get(0).(func() error); ok {
r0 = returnFunc()
} else {
r0 = ret.Error(0)
}
return r0
}
// MockRows_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close'
type MockRows_Close_Call struct {
*mock.Call
}
// Close is a helper method to define mock.On call
func (_e *MockRows_Expecter) Close() *MockRows_Close_Call {
return &MockRows_Close_Call{Call: _e.mock.On("Close")}
}
func (_c *MockRows_Close_Call) Run(run func()) *MockRows_Close_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockRows_Close_Call) Return(err error) *MockRows_Close_Call {
_c.Call.Return(err)
return _c
}
func (_c *MockRows_Close_Call) RunAndReturn(run func() error) *MockRows_Close_Call {
_c.Call.Return(run)
return _c
}
// ColumnTypes provides a mock function for the type MockRows
func (_mock *MockRows) ColumnTypes() []driver.ColumnType {
ret := _mock.Called()
if len(ret) == 0 {
panic("no return value specified for ColumnTypes")
}
var r0 []driver.ColumnType
if returnFunc, ok := ret.Get(0).(func() []driver.ColumnType); ok {
r0 = returnFunc()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]driver.ColumnType)
}
}
return r0
}
// MockRows_ColumnTypes_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ColumnTypes'
type MockRows_ColumnTypes_Call struct {
*mock.Call
}
// ColumnTypes is a helper method to define mock.On call
func (_e *MockRows_Expecter) ColumnTypes() *MockRows_ColumnTypes_Call {
return &MockRows_ColumnTypes_Call{Call: _e.mock.On("ColumnTypes")}
}
func (_c *MockRows_ColumnTypes_Call) Run(run func()) *MockRows_ColumnTypes_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockRows_ColumnTypes_Call) Return(columnTypes []driver.ColumnType) *MockRows_ColumnTypes_Call {
_c.Call.Return(columnTypes)
return _c
}
func (_c *MockRows_ColumnTypes_Call) RunAndReturn(run func() []driver.ColumnType) *MockRows_ColumnTypes_Call {
_c.Call.Return(run)
return _c
}
// Columns provides a mock function for the type MockRows
func (_mock *MockRows) Columns() []string {
ret := _mock.Called()
if len(ret) == 0 {
panic("no return value specified for Columns")
}
var r0 []string
if returnFunc, ok := ret.Get(0).(func() []string); ok {
r0 = returnFunc()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]string)
}
}
return r0
}
// MockRows_Columns_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Columns'
type MockRows_Columns_Call struct {
*mock.Call
}
// Columns is a helper method to define mock.On call
func (_e *MockRows_Expecter) Columns() *MockRows_Columns_Call {
return &MockRows_Columns_Call{Call: _e.mock.On("Columns")}
}
func (_c *MockRows_Columns_Call) Run(run func()) *MockRows_Columns_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockRows_Columns_Call) Return(strings []string) *MockRows_Columns_Call {
_c.Call.Return(strings)
return _c
}
func (_c *MockRows_Columns_Call) RunAndReturn(run func() []string) *MockRows_Columns_Call {
_c.Call.Return(run)
return _c
}
// Err provides a mock function for the type MockRows
func (_mock *MockRows) Err() error {
ret := _mock.Called()
if len(ret) == 0 {
panic("no return value specified for Err")
}
var r0 error
if returnFunc, ok := ret.Get(0).(func() error); ok {
r0 = returnFunc()
} else {
r0 = ret.Error(0)
}
return r0
}
// MockRows_Err_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Err'
type MockRows_Err_Call struct {
*mock.Call
}
// Err is a helper method to define mock.On call
func (_e *MockRows_Expecter) Err() *MockRows_Err_Call {
return &MockRows_Err_Call{Call: _e.mock.On("Err")}
}
func (_c *MockRows_Err_Call) Run(run func()) *MockRows_Err_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockRows_Err_Call) Return(err error) *MockRows_Err_Call {
_c.Call.Return(err)
return _c
}
func (_c *MockRows_Err_Call) RunAndReturn(run func() error) *MockRows_Err_Call {
_c.Call.Return(run)
return _c
}
// HasData provides a mock function for the type MockRows
func (_mock *MockRows) HasData() bool {
ret := _mock.Called()
if len(ret) == 0 {
panic("no return value specified for HasData")
}
var r0 bool
if returnFunc, ok := ret.Get(0).(func() bool); ok {
r0 = returnFunc()
} else {
r0 = ret.Get(0).(bool)
}
return r0
}
// MockRows_HasData_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HasData'
type MockRows_HasData_Call struct {
*mock.Call
}
// HasData is a helper method to define mock.On call
func (_e *MockRows_Expecter) HasData() *MockRows_HasData_Call {
return &MockRows_HasData_Call{Call: _e.mock.On("HasData")}
}
func (_c *MockRows_HasData_Call) Run(run func()) *MockRows_HasData_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockRows_HasData_Call) Return(b bool) *MockRows_HasData_Call {
_c.Call.Return(b)
return _c
}
func (_c *MockRows_HasData_Call) RunAndReturn(run func() bool) *MockRows_HasData_Call {
_c.Call.Return(run)
return _c
}
// Next provides a mock function for the type MockRows
func (_mock *MockRows) Next() bool {
ret := _mock.Called()
if len(ret) == 0 {
panic("no return value specified for Next")
}
var r0 bool
if returnFunc, ok := ret.Get(0).(func() bool); ok {
r0 = returnFunc()
} else {
r0 = ret.Get(0).(bool)
}
return r0
}
// MockRows_Next_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Next'
type MockRows_Next_Call struct {
*mock.Call
}
// Next is a helper method to define mock.On call
func (_e *MockRows_Expecter) Next() *MockRows_Next_Call {
return &MockRows_Next_Call{Call: _e.mock.On("Next")}
}
func (_c *MockRows_Next_Call) Run(run func()) *MockRows_Next_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockRows_Next_Call) Return(b bool) *MockRows_Next_Call {
_c.Call.Return(b)
return _c
}
func (_c *MockRows_Next_Call) RunAndReturn(run func() bool) *MockRows_Next_Call {
_c.Call.Return(run)
return _c
}
// Scan provides a mock function for the type MockRows
func (_mock *MockRows) Scan(dest ...any) error {
var tmpRet mock.Arguments
if len(dest) > 0 {
tmpRet = _mock.Called(dest)
} else {
tmpRet = _mock.Called()
}
ret := tmpRet
if len(ret) == 0 {
panic("no return value specified for Scan")
}
var r0 error
if returnFunc, ok := ret.Get(0).(func(...any) error); ok {
r0 = returnFunc(dest...)
} else {
r0 = ret.Error(0)
}
return r0
}
// MockRows_Scan_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Scan'
type MockRows_Scan_Call struct {
*mock.Call
}
// Scan is a helper method to define mock.On call
// - dest ...any
func (_e *MockRows_Expecter) Scan(dest ...any) *MockRows_Scan_Call {
return &MockRows_Scan_Call{Call: _e.mock.On("Scan",
append([]any{}, dest...)...)}
}
func (_c *MockRows_Scan_Call) Run(run func(dest ...any)) *MockRows_Scan_Call {
_c.Call.Run(func(args mock.Arguments) {
var arg0 []any
var variadicArgs []any
if len(args) > 0 {
variadicArgs = args[0].([]any)
}
arg0 = variadicArgs
run(
arg0...,
)
})
return _c
}
func (_c *MockRows_Scan_Call) Return(err error) *MockRows_Scan_Call {
_c.Call.Return(err)
return _c
}
func (_c *MockRows_Scan_Call) RunAndReturn(run func(dest ...any) error) *MockRows_Scan_Call {
_c.Call.Return(run)
return _c
}
// ScanStruct provides a mock function for the type MockRows
func (_mock *MockRows) ScanStruct(dest any) error {
ret := _mock.Called(dest)
if len(ret) == 0 {
panic("no return value specified for ScanStruct")
}
var r0 error
if returnFunc, ok := ret.Get(0).(func(any) error); ok {
r0 = returnFunc(dest)
} else {
r0 = ret.Error(0)
}
return r0
}
// MockRows_ScanStruct_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ScanStruct'
type MockRows_ScanStruct_Call struct {
*mock.Call
}
// ScanStruct is a helper method to define mock.On call
// - dest any
func (_e *MockRows_Expecter) ScanStruct(dest any) *MockRows_ScanStruct_Call {
return &MockRows_ScanStruct_Call{Call: _e.mock.On("ScanStruct", dest)}
}
func (_c *MockRows_ScanStruct_Call) Run(run func(dest any)) *MockRows_ScanStruct_Call {
_c.Call.Run(func(args mock.Arguments) {
var arg0 any
if args[0] != nil {
arg0 = args[0].(any)
}
run(
arg0,
)
})
return _c
}
func (_c *MockRows_ScanStruct_Call) Return(err error) *MockRows_ScanStruct_Call {
_c.Call.Return(err)
return _c
}
func (_c *MockRows_ScanStruct_Call) RunAndReturn(run func(dest any) error) *MockRows_ScanStruct_Call {
_c.Call.Return(run)
return _c
}
// Totals provides a mock function for the type MockRows
func (_mock *MockRows) Totals(dest ...any) error {
var tmpRet mock.Arguments
if len(dest) > 0 {
tmpRet = _mock.Called(dest)
} else {
tmpRet = _mock.Called()
}
ret := tmpRet
if len(ret) == 0 {
panic("no return value specified for Totals")
}
var r0 error
if returnFunc, ok := ret.Get(0).(func(...any) error); ok {
r0 = returnFunc(dest...)
} else {
r0 = ret.Error(0)
}
return r0
}
// MockRows_Totals_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Totals'
type MockRows_Totals_Call struct {
*mock.Call
}
// Totals is a helper method to define mock.On call
// - dest ...any
func (_e *MockRows_Expecter) Totals(dest ...any) *MockRows_Totals_Call {
return &MockRows_Totals_Call{Call: _e.mock.On("Totals",
append([]any{}, dest...)...)}
}
func (_c *MockRows_Totals_Call) Run(run func(dest ...any)) *MockRows_Totals_Call {
_c.Call.Run(func(args mock.Arguments) {
var arg0 []any
var variadicArgs []any
if len(args) > 0 {
variadicArgs = args[0].([]any)
}
arg0 = variadicArgs
run(
arg0...,
)
})
return _c
}
func (_c *MockRows_Totals_Call) Return(err error) *MockRows_Totals_Call {
_c.Call.Return(err)
return _c
}
func (_c *MockRows_Totals_Call) RunAndReturn(run func(dest ...any) error) *MockRows_Totals_Call {
_c.Call.Return(run)
return _c
}
// NewMockColumnType creates a new instance of MockColumnType. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewMockColumnType(t interface {
mock.TestingT
Cleanup(func())
}) *MockColumnType {
if helper, ok := t.(interface{ Helper() }); ok {
helper.Helper()
}
mock := &MockColumnType{}
mock.Mock.Test(t)
t.Cleanup(func() {
if helper, ok := t.(interface{ Helper() }); ok {
helper.Helper()
}
mock.AssertExpectations(t)
})
return mock
}
// MockColumnType is an autogenerated mock type for the ColumnType type
type MockColumnType struct {
mock.Mock
}
type MockColumnType_Expecter struct {
mock *mock.Mock
}
func (_m *MockColumnType) EXPECT() *MockColumnType_Expecter {
return &MockColumnType_Expecter{mock: &_m.Mock}
}
// DatabaseTypeName provides a mock function for the type MockColumnType
func (_mock *MockColumnType) DatabaseTypeName() string {
ret := _mock.Called()
if len(ret) == 0 {
panic("no return value specified for DatabaseTypeName")
}
var r0 string
if returnFunc, ok := ret.Get(0).(func() string); ok {
r0 = returnFunc()
} else {
r0 = ret.Get(0).(string)
}
return r0
}
// MockColumnType_DatabaseTypeName_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DatabaseTypeName'
type MockColumnType_DatabaseTypeName_Call struct {
*mock.Call
}
// DatabaseTypeName is a helper method to define mock.On call
func (_e *MockColumnType_Expecter) DatabaseTypeName() *MockColumnType_DatabaseTypeName_Call {
return &MockColumnType_DatabaseTypeName_Call{Call: _e.mock.On("DatabaseTypeName")}
}
func (_c *MockColumnType_DatabaseTypeName_Call) Run(run func()) *MockColumnType_DatabaseTypeName_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockColumnType_DatabaseTypeName_Call) Return(s string) *MockColumnType_DatabaseTypeName_Call {
_c.Call.Return(s)
return _c
}
func (_c *MockColumnType_DatabaseTypeName_Call) RunAndReturn(run func() string) *MockColumnType_DatabaseTypeName_Call {
_c.Call.Return(run)
return _c
}
// Name provides a mock function for the type MockColumnType
func (_mock *MockColumnType) Name() string {
ret := _mock.Called()
if len(ret) == 0 {
panic("no return value specified for Name")
}
var r0 string
if returnFunc, ok := ret.Get(0).(func() string); ok {
r0 = returnFunc()
} else {
r0 = ret.Get(0).(string)
}
return r0
}
// MockColumnType_Name_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Name'
type MockColumnType_Name_Call struct {
*mock.Call
}
// Name is a helper method to define mock.On call
func (_e *MockColumnType_Expecter) Name() *MockColumnType_Name_Call {
return &MockColumnType_Name_Call{Call: _e.mock.On("Name")}
}
func (_c *MockColumnType_Name_Call) Run(run func()) *MockColumnType_Name_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockColumnType_Name_Call) Return(s string) *MockColumnType_Name_Call {
_c.Call.Return(s)
return _c
}
func (_c *MockColumnType_Name_Call) RunAndReturn(run func() string) *MockColumnType_Name_Call {
_c.Call.Return(run)
return _c
}
// Nullable provides a mock function for the type MockColumnType
func (_mock *MockColumnType) Nullable() bool {
ret := _mock.Called()
if len(ret) == 0 {
panic("no return value specified for Nullable")
}
var r0 bool
if returnFunc, ok := ret.Get(0).(func() bool); ok {
r0 = returnFunc()
} else {
r0 = ret.Get(0).(bool)
}
return r0
}
// MockColumnType_Nullable_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Nullable'
type MockColumnType_Nullable_Call struct {
*mock.Call
}
// Nullable is a helper method to define mock.On call
func (_e *MockColumnType_Expecter) Nullable() *MockColumnType_Nullable_Call {
return &MockColumnType_Nullable_Call{Call: _e.mock.On("Nullable")}
}
func (_c *MockColumnType_Nullable_Call) Run(run func()) *MockColumnType_Nullable_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockColumnType_Nullable_Call) Return(b bool) *MockColumnType_Nullable_Call {
_c.Call.Return(b)
return _c
}
func (_c *MockColumnType_Nullable_Call) RunAndReturn(run func() bool) *MockColumnType_Nullable_Call {
_c.Call.Return(run)
return _c
}
// ScanType provides a mock function for the type MockColumnType
func (_mock *MockColumnType) ScanType() reflect.Type {
ret := _mock.Called()
if len(ret) == 0 {
panic("no return value specified for ScanType")
}
var r0 reflect.Type
if returnFunc, ok := ret.Get(0).(func() reflect.Type); ok {
r0 = returnFunc()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(reflect.Type)
}
}
return r0
}
// MockColumnType_ScanType_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ScanType'
type MockColumnType_ScanType_Call struct {
*mock.Call
}
// ScanType is a helper method to define mock.On call
func (_e *MockColumnType_Expecter) ScanType() *MockColumnType_ScanType_Call {
return &MockColumnType_ScanType_Call{Call: _e.mock.On("ScanType")}
}
func (_c *MockColumnType_ScanType_Call) Run(run func()) *MockColumnType_ScanType_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockColumnType_ScanType_Call) Return(typeParam reflect.Type) *MockColumnType_ScanType_Call {
_c.Call.Return(typeParam)
return _c
}
func (_c *MockColumnType_ScanType_Call) RunAndReturn(run func() reflect.Type) *MockColumnType_ScanType_Call {
_c.Call.Return(run)
return _c
}

View File

@@ -9,12 +9,7 @@ from fixtures import types
from fixtures.auth import USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD
from fixtures.logs import Logs
from fixtures.querier import (
BuilderQuery,
OrderBy,
RequestType,
TelemetryFieldKey,
assert_identical_query_response,
get_rows,
make_query_request,
)
@@ -503,64 +498,3 @@ def test_logs_list_with_order_by(
ids = [row["data"]["attributes_string"].get("id", "") for row in rows]
assert ids == expected_order
@pytest.mark.parametrize(
"limit,offset",
[
pytest.param(2, 0, id="first_chunk"),
pytest.param(2, 1, id="first_into_second_chunk"),
pytest.param(2, 2, id="second_chunk"),
pytest.param(2, 3, id="second_into_third_chunk"),
pytest.param(2, 4, id="third_chunk"),
pytest.param(4, 5, id="last_partial_page"),
pytest.param(2, 6, id="offset_past_end"),
],
)
def test_logs_list_offset_across_chunks(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_logs: Callable[[list[Logs]], None],
limit: int,
offset: int,
) -> None:
"""
A 4h list runs as 1h, 2h and 1h chunks, newest first. Two logs sit in each
chunk, so the offset lands in, or crosses into, every chunk.
"""
now = datetime.now(tz=UTC).replace(microsecond=0)
minutes_ago = [10, 20, 90, 150, 200, 220]
insert_logs(
[
Logs(
timestamp=now - timedelta(minutes=minutes),
resources={"service.name": "chunked-offset-service"},
body=f"chunked-log-{minutes}",
)
for minutes in minutes_ago
]
)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = make_query_request(
signoz,
token,
start_ms=int((now - timedelta(hours=4)).timestamp() * 1000),
end_ms=int((now + timedelta(seconds=1)).timestamp() * 1000),
request_type=RequestType.RAW,
queries=[
BuilderQuery(
signal="logs",
name="A",
limit=limit,
offset=offset,
filter_expression="service.name = 'chunked-offset-service'",
order=[OrderBy(TelemetryFieldKey("timestamp"), "desc"), OrderBy(TelemetryFieldKey("id"), "desc")],
).to_dict()
],
)
assert response.status_code == HTTPStatus.OK, response.text
expected_bodies = [f"chunked-log-{minutes}" for minutes in minutes_ago][offset : offset + limit]
assert [row["data"]["body"] for row in get_rows(response)] == expected_bodies

View File

@@ -917,69 +917,6 @@ def test_traces_list_pagination(
assert [row["data"]["span_id"] for row in rows] == expected_ids
@pytest.mark.parametrize(
"limit,offset",
[
pytest.param(2, 0, id="first_chunk"),
pytest.param(2, 1, id="first_into_second_chunk"),
pytest.param(2, 2, id="second_chunk"),
pytest.param(2, 3, id="second_into_third_chunk"),
pytest.param(2, 4, id="third_chunk"),
pytest.param(4, 5, id="last_partial_page"),
pytest.param(2, 6, id="offset_past_end"),
],
)
def test_traces_list_offset_across_chunks(
signoz: types.SigNoz,
create_user_admin: None, # pylint: disable=unused-argument
get_token: Callable[[str, str], str],
insert_traces: Callable[[list[Traces]], None],
limit: int,
offset: int,
) -> None:
"""
A 4h list runs as 1h, 2h and 1h chunks, newest first. Two spans sit in each
chunk, so the offset lands in, or crosses into, every chunk.
"""
now = datetime.now(tz=UTC).replace(microsecond=0)
minutes_ago = [10, 20, 90, 150, 200, 220]
insert_traces(
[
Traces(
timestamp=now - timedelta(minutes=minutes),
trace_id=TraceIdGenerator.trace_id(),
span_id=TraceIdGenerator.span_id(),
name=f"chunked-span-{minutes}",
resources={"service.name": "chunked-offset-service"},
)
for minutes in minutes_ago
]
)
token = get_token(USER_ADMIN_EMAIL, USER_ADMIN_PASSWORD)
response = make_query_request(
signoz,
token,
start_ms=int((now - timedelta(hours=4)).timestamp() * 1000),
end_ms=int((now + timedelta(seconds=1)).timestamp() * 1000),
request_type=RequestType.RAW,
queries=[
BuilderQuery(
signal="traces",
name="A",
limit=limit,
offset=offset,
filter_expression="service.name = 'chunked-offset-service'",
order=[OrderBy(TelemetryFieldKey("timestamp"), "desc")],
).to_dict()
],
)
assert response.status_code == HTTPStatus.OK, response.text
expected_names = [f"chunked-span-{minutes}" for minutes in minutes_ago][offset : offset + limit]
assert [row["data"]["name"] for row in get_rows(response)] == expected_names
# ============================================================================
# Time window
# ============================================================================