From 6ac812b5af0707d744f821a2bf6ceb7a73d8c692 Mon Sep 17 00:00:00 2001 From: Vishal Sharma Date: Thu, 25 Sep 2025 16:38:38 +0530 Subject: [PATCH 1/8] chore: change update workspace URL to upgrade guide (#9178) * chore: change update workspace URL to upgrade guide * chore: change upgrade workspace url --- frontend/src/components/ChangelogModal/ChangelogModal.tsx | 2 +- .../components/ChangelogModal/__test__/ChangelogModal.test.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/ChangelogModal/ChangelogModal.tsx b/frontend/src/components/ChangelogModal/ChangelogModal.tsx index 129ca9897188..586563ccfb08 100644 --- a/frontend/src/components/ChangelogModal/ChangelogModal.tsx +++ b/frontend/src/components/ChangelogModal/ChangelogModal.tsx @@ -87,7 +87,7 @@ function ChangelogModal({ changelog, onClose }: Props): JSX.Element { const onClickUpdateWorkspace = (): void => { window.open( - 'https://github.com/SigNoz/signoz/releases', + 'https://signoz.io/upgrade-path', '_blank', 'noopener,noreferrer', ); diff --git a/frontend/src/components/ChangelogModal/__test__/ChangelogModal.test.tsx b/frontend/src/components/ChangelogModal/__test__/ChangelogModal.test.tsx index da92fd6affd6..36d43da61199 100644 --- a/frontend/src/components/ChangelogModal/__test__/ChangelogModal.test.tsx +++ b/frontend/src/components/ChangelogModal/__test__/ChangelogModal.test.tsx @@ -91,7 +91,7 @@ describe('ChangelogModal', () => { renderChangelog(); fireEvent.click(screen.getByText('Update my workspace')); expect(window.open).toHaveBeenCalledWith( - 'https://github.com/SigNoz/signoz/releases', + 'https://signoz.io/upgrade-path', '_blank', 'noopener,noreferrer', ); From 1aa5f5d0e1f1f51befaf10a18b39091755b77343 Mon Sep 17 00:00:00 2001 From: Yunus M Date: Thu, 25 Sep 2025 19:00:40 +0530 Subject: [PATCH 2/8] fix: extra content passed by consuming component (#9191) --- frontend/src/components/RouteTab/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/RouteTab/index.tsx b/frontend/src/components/RouteTab/index.tsx index 43d652b2e4ab..9a8aec7ae5c3 100644 --- a/frontend/src/components/RouteTab/index.tsx +++ b/frontend/src/components/RouteTab/index.tsx @@ -61,8 +61,6 @@ function RouteTab({ defaultActiveKey={currentRoute?.key || activeKey} animated items={items} - // eslint-disable-next-line react/jsx-props-no-spreading - {...rest} tabBarExtraContent={ showRightSection && ( ) } + // eslint-disable-next-line react/jsx-props-no-spreading ---- TODO: remove this once follow the linting rules + {...rest} /> ); } From 96cdf21a928d251bcaf87da630706086201bf2b1 Mon Sep 17 00:00:00 2001 From: Aditya Singh Date: Thu, 25 Sep 2025 19:14:05 +0530 Subject: [PATCH 3/8] Fix: Opening logs link broken (Pref framework) (#9182) * fix: logs popover content logic extracted out * fix: logs popover content in live view * fix: destory popover on close * feat: add logs format tests * feat: minor refactor * feat: test case refactor * feat: remove menu refs in logs live view --- .../LogsFormatOptionsMenu.tsx | 24 ++- .../__tests__/LogsFormatOptionsMenu.test.tsx | 157 ++++++++++++++++++ .../LiveLogs/LiveLogsContainer/index.tsx | 39 +---- 3 files changed, 183 insertions(+), 37 deletions(-) create mode 100644 frontend/src/components/LogsFormatOptionsMenu/__tests__/LogsFormatOptionsMenu.test.tsx diff --git a/frontend/src/components/LogsFormatOptionsMenu/LogsFormatOptionsMenu.tsx b/frontend/src/components/LogsFormatOptionsMenu/LogsFormatOptionsMenu.tsx index f37c2be1eb9e..7d2be2066d50 100644 --- a/frontend/src/components/LogsFormatOptionsMenu/LogsFormatOptionsMenu.tsx +++ b/frontend/src/components/LogsFormatOptionsMenu/LogsFormatOptionsMenu.tsx @@ -26,7 +26,7 @@ interface LogsFormatOptionsMenuProps { config: OptionsMenuConfig; } -export default function LogsFormatOptionsMenu({ +function OptionsMenu({ items, selectedOptionFormat, config, @@ -49,7 +49,6 @@ export default function LogsFormatOptionsMenu({ const [selectedValue, setSelectedValue] = useState(null); const listRef = useRef(null); const initialMouseEnterRef = useRef(false); - const [isPopoverOpen, setIsPopoverOpen] = useState(false); const onChange = useCallback( (key: LogViewMode) => { @@ -209,7 +208,7 @@ export default function LogsFormatOptionsMenu({ }; }, [selectedValue]); - const popoverContent = ( + return (
); +} + +function LogsFormatOptionsMenu({ + items, + selectedOptionFormat, + config, +}: LogsFormatOptionsMenuProps): JSX.Element { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); return ( + } trigger="click" placement="bottomRight" arrow={false} open={isPopoverOpen} onOpenChange={setIsPopoverOpen} rootClassName="format-options-popover" + destroyTooltipOnHide >
-
-
+ {showLiveLogsFrequencyChart && ( From 9a5bcb6b64516866f09ae3d462df114d44090252 Mon Sep 17 00:00:00 2001 From: Abhi kumar Date: Thu, 25 Sep 2025 19:27:05 +0530 Subject: [PATCH 4/8] revert: removed changes done for cursor position jump fix (#9193) --- .../QueryV2/QuerySearch/QuerySearch.tsx | 69 +++---------------- .../QueryV2/__tests__/QuerySearch.test.tsx | 22 ------ 2 files changed, 11 insertions(+), 80 deletions(-) diff --git a/frontend/src/components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch.tsx b/frontend/src/components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch.tsx index f93a6ba6ee36..4b0debe8e6a1 100644 --- a/frontend/src/components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch.tsx +++ b/frontend/src/components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch.tsx @@ -97,7 +97,7 @@ function QuerySearch({ onRun?: (query: string) => void; }): JSX.Element { const isDarkMode = useIsDarkMode(); - const [query, setQuery] = useState(''); + const [query, setQuery] = useState(queryData.filter?.expression || ''); const [valueSuggestions, setValueSuggestions] = useState([]); const [activeKey, setActiveKey] = useState(''); const [isLoadingSuggestions, setIsLoadingSuggestions] = useState(false); @@ -108,10 +108,6 @@ function QuerySearch({ errors: [], }); - const [cursorPos, setCursorPos] = useState({ line: 0, ch: 0 }); - const [isFocused, setIsFocused] = useState(false); - const [hasInteractedWithQB, setHasInteractedWithQB] = useState(false); - const handleQueryValidation = (newQuery: string): void => { try { const validationResponse = validateQuery(newQuery); @@ -131,28 +127,13 @@ function QuerySearch({ useEffect(() => { const newQuery = queryData.filter?.expression || ''; - // Only update query from external source when editor is not focused - // When focused, just update the lastExternalQuery to track changes + // Only mark as external change if the query actually changed from external source if (newQuery !== lastExternalQuery) { setQuery(newQuery); setIsExternalQueryChange(true); setLastExternalQuery(newQuery); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [queryData.filter?.expression]); - - useEffect(() => { - // Update the query when the editor is blurred and the query has changed - // Only call onChange if the editor has been focused before (not on initial mount) - if ( - !isFocused && - hasInteractedWithQB && - query !== queryData.filter?.expression - ) { - onChange(query); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isFocused]); + }, [queryData.filter?.expression, lastExternalQuery]); // Validate query when it changes externally (from queryData) useEffect(() => { @@ -168,6 +149,9 @@ function QuerySearch({ const [showExamples] = useState(false); + const [cursorPos, setCursorPos] = useState({ line: 0, ch: 0 }); + const [isFocused, setIsFocused] = useState(false); + const [ isFetchingCompleteValuesList, setIsFetchingCompleteValuesList, @@ -181,9 +165,6 @@ function QuerySearch({ const lastFetchedKeyRef = useRef(''); const lastValueRef = useRef(''); const isMountedRef = useRef(true); - const [shouldRunQueryPostUpdate, setShouldRunQueryPostUpdate] = useState( - false, - ); const { handleRunQuery } = useQueryBuilder(); @@ -229,7 +210,6 @@ function QuerySearch({ return (): void => clearTimeout(timeoutId); }, - // eslint-disable-next-line react-hooks/exhaustive-deps [isFocused], ); @@ -575,6 +555,7 @@ function QuerySearch({ const handleChange = (value: string): void => { setQuery(value); + onChange(value); // Mark as internal change to avoid triggering external validation setIsExternalQueryChange(false); // Update lastExternalQuery to prevent external validation trigger @@ -1238,25 +1219,6 @@ function QuerySearch({ ); - // Effect to handle query run after update - useEffect( - () => { - // Only run the query post updating the filter expression. - // This runs the query in the next update cycle of react, when it's guaranteed that the query is updated. - // Because both the things are sequential and react batches the updates so it was still taking the old query. - if (shouldRunQueryPostUpdate) { - if (onRun && typeof onRun === 'function') { - onRun(query); - } else { - handleRunQuery(); - } - setShouldRunQueryPostUpdate(false); - } - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [shouldRunQueryPostUpdate, handleRunQuery, onRun], - ); - return (
{editingMode && ( @@ -1331,7 +1293,6 @@ function QuerySearch({ theme={isDarkMode ? copilot : githubLight} onChange={handleChange} onUpdate={handleUpdate} - data-testid="query-where-clause-editor" className={cx('query-where-clause-editor', { isValid: validation.isValid === true, hasErrors: validation.errors.length > 0, @@ -1368,14 +1329,11 @@ function QuerySearch({ // and instead run a custom action // Mod-Enter is usually Ctrl-Enter or Cmd-Enter based on OS run: (): boolean => { - if ( - onChange && - typeof onChange === 'function' && - query !== queryData.filter?.expression - ) { - onChange(query); + if (onRun && typeof onRun === 'function') { + onRun(query); + } else { + handleRunQuery(); } - setShouldRunQueryPostUpdate(true); return true; }, }, @@ -1394,13 +1352,8 @@ function QuerySearch({ }} onFocus={(): void => { setIsFocused(true); - setHasInteractedWithQB(true); }} onBlur={handleBlur} - onCreateEditor={(view: EditorView): EditorView => { - editorRef.current = view; - return view; - }} /> {query && validation.isValid === false && !isFocused && ( diff --git a/frontend/src/components/QueryBuilderV2/QueryV2/__tests__/QuerySearch.test.tsx b/frontend/src/components/QueryBuilderV2/QueryV2/__tests__/QuerySearch.test.tsx index b3862283f6b3..290ef518b5ec 100644 --- a/frontend/src/components/QueryBuilderV2/QueryV2/__tests__/QuerySearch.test.tsx +++ b/frontend/src/components/QueryBuilderV2/QueryV2/__tests__/QuerySearch.test.tsx @@ -222,28 +222,6 @@ describe('QuerySearch', () => { expect(screen.getByPlaceholderText(PLACEHOLDER_TEXT)).toBeInTheDocument(); }); - it('calls onChange on blur after user edits', async () => { - const handleChange = jest.fn() as jest.MockedFunction<(v: string) => void>; - const user = userEvent.setup({ pointerEventsCheck: 0 }); - - render( - , - ); - - const editor = screen.getByTestId(TESTID_EDITOR); - await user.click(editor); - await user.type(editor, SAMPLE_VALUE_TYPING_COMPLETE); - // Blur triggers validation + onChange (only if focused at least once and value changed) - editor.blur(); - - await waitFor(() => expect(handleChange).toHaveBeenCalledTimes(1)); - expect(handleChange.mock.calls[0][0]).toContain("service.name = 'frontend'"); - }); - it('fetches key suggestions when typing a key (debounced)', async () => { jest.useFakeTimers(); const advance = (ms: number): void => { From 2c19f0171f7781a05a902f342dd257381de40ed3 Mon Sep 17 00:00:00 2001 From: "primus-bot[bot]" <171087277+primus-bot[bot]@users.noreply.github.com> Date: Thu, 25 Sep 2025 20:47:24 +0530 Subject: [PATCH 5/8] chore(release): bump to v0.96.1 (#9194) Co-authored-by: primus-bot[bot] <171087277+primus-bot[bot]@users.noreply.github.com> --- deploy/docker-swarm/docker-compose.ha.yaml | 2 +- deploy/docker-swarm/docker-compose.yaml | 2 +- deploy/docker/docker-compose.ha.yaml | 2 +- deploy/docker/docker-compose.yaml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/deploy/docker-swarm/docker-compose.ha.yaml b/deploy/docker-swarm/docker-compose.ha.yaml index 3d7fb6a7a7bc..56537d57db39 100644 --- a/deploy/docker-swarm/docker-compose.ha.yaml +++ b/deploy/docker-swarm/docker-compose.ha.yaml @@ -176,7 +176,7 @@ services: # - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml signoz: !!merge <<: *db-depend - image: signoz/signoz:v0.96.0 + image: signoz/signoz:v0.96.1 command: - --config=/root/config/prometheus.yml ports: diff --git a/deploy/docker-swarm/docker-compose.yaml b/deploy/docker-swarm/docker-compose.yaml index c29b0a93c572..46f136b7aa8f 100644 --- a/deploy/docker-swarm/docker-compose.yaml +++ b/deploy/docker-swarm/docker-compose.yaml @@ -117,7 +117,7 @@ services: # - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml signoz: !!merge <<: *db-depend - image: signoz/signoz:v0.96.0 + image: signoz/signoz:v0.96.1 command: - --config=/root/config/prometheus.yml ports: diff --git a/deploy/docker/docker-compose.ha.yaml b/deploy/docker/docker-compose.ha.yaml index d24ec442dc5d..2faeed24feff 100644 --- a/deploy/docker/docker-compose.ha.yaml +++ b/deploy/docker/docker-compose.ha.yaml @@ -179,7 +179,7 @@ services: # - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml signoz: !!merge <<: *db-depend - image: signoz/signoz:${VERSION:-v0.96.0} + image: signoz/signoz:${VERSION:-v0.96.1} container_name: signoz command: - --config=/root/config/prometheus.yml diff --git a/deploy/docker/docker-compose.yaml b/deploy/docker/docker-compose.yaml index 3639e312dc8a..66e7433b7688 100644 --- a/deploy/docker/docker-compose.yaml +++ b/deploy/docker/docker-compose.yaml @@ -111,7 +111,7 @@ services: # - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml signoz: !!merge <<: *db-depend - image: signoz/signoz:${VERSION:-v0.96.0} + image: signoz/signoz:${VERSION:-v0.96.1} container_name: signoz command: - --config=/root/config/prometheus.yml From 6d5f0adab953aa0b565f244ca9077a8197931b9e Mon Sep 17 00:00:00 2001 From: Niladri Adhikary <91966855+niladrix719@users.noreply.github.com> Date: Fri, 26 Sep 2025 06:38:35 +0530 Subject: [PATCH 6/8] fix: prevent panels with all queries disabled (#9093) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: “niladrix719” --- .../querybuildertypesv5/validation.go | 63 ++++ .../querybuildertypesv5/validation_test.go | 334 ++++++++++++++++++ 2 files changed, 397 insertions(+) create mode 100644 pkg/types/querybuildertypes/querybuildertypesv5/validation_test.go diff --git a/pkg/types/querybuildertypes/querybuildertypesv5/validation.go b/pkg/types/querybuildertypes/querybuildertypesv5/validation.go index 3d1a84de61c9..e44cef4064e6 100644 --- a/pkg/types/querybuildertypes/querybuildertypesv5/validation.go +++ b/pkg/types/querybuildertypes/querybuildertypesv5/validation.go @@ -481,6 +481,69 @@ func (r *QueryRangeRequest) Validate() error { return err } + // Check if all queries are disabled + if err := r.validateAllQueriesNotDisabled(); err != nil { + return err + } + + return nil +} + +// validateAllQueriesNotDisabled validates that at least one query in the composite query is enabled +func (r *QueryRangeRequest) validateAllQueriesNotDisabled() error { + allDisabled := true + for _, envelope := range r.CompositeQuery.Queries { + switch envelope.Type { + case QueryTypeBuilder, QueryTypeSubQuery: + switch spec := envelope.Spec.(type) { + case QueryBuilderQuery[TraceAggregation]: + if !spec.Disabled { + allDisabled = false + } + case QueryBuilderQuery[LogAggregation]: + if !spec.Disabled { + allDisabled = false + } + case QueryBuilderQuery[MetricAggregation]: + if !spec.Disabled { + allDisabled = false + } + } + case QueryTypeFormula: + if spec, ok := envelope.Spec.(QueryBuilderFormula); ok && !spec.Disabled { + allDisabled = false + } + case QueryTypeTraceOperator: + if spec, ok := envelope.Spec.(QueryBuilderTraceOperator); ok && !spec.Disabled { + allDisabled = false + } + case QueryTypeJoin: + if spec, ok := envelope.Spec.(QueryBuilderJoin); ok && !spec.Disabled { + allDisabled = false + } + case QueryTypePromQL: + if spec, ok := envelope.Spec.(PromQuery); ok && !spec.Disabled { + allDisabled = false + } + case QueryTypeClickHouseSQL: + if spec, ok := envelope.Spec.(ClickHouseQuery); ok && !spec.Disabled { + allDisabled = false + } + } + + // Early exit if we find at least one enabled query + if !allDisabled { + break + } + } + + if allDisabled { + return errors.NewInvalidInputf( + errors.CodeInvalidInput, + "all queries are disabled - at least one query must be enabled", + ) + } + return nil } diff --git a/pkg/types/querybuildertypes/querybuildertypesv5/validation_test.go b/pkg/types/querybuildertypes/querybuildertypesv5/validation_test.go new file mode 100644 index 000000000000..37e6f1c00640 --- /dev/null +++ b/pkg/types/querybuildertypes/querybuildertypesv5/validation_test.go @@ -0,0 +1,334 @@ +package querybuildertypesv5 + +import ( + "strings" + "testing" + + "github.com/SigNoz/signoz/pkg/types/telemetrytypes" +) + +func contains(s, substr string) bool { + return strings.Contains(s, substr) +} + +func TestQueryRangeRequest_ValidateAllQueriesNotDisabled(t *testing.T) { + tests := []struct { + name string + request QueryRangeRequest + wantErr bool + errMsg string + }{ + { + name: "all queries disabled should return error", + request: QueryRangeRequest{ + Start: 1640995200000, + End: 1640998800000, + RequestType: RequestTypeTimeSeries, + CompositeQuery: CompositeQuery{ + Queries: []QueryEnvelope{ + { + Type: QueryTypeBuilder, + Spec: QueryBuilderQuery[MetricAggregation]{ + Name: "A", + Disabled: true, + Signal: telemetrytypes.SignalMetrics, + }, + }, + { + Type: QueryTypeBuilder, + Spec: QueryBuilderQuery[LogAggregation]{ + Name: "B", + Disabled: true, + Signal: telemetrytypes.SignalLogs, + }, + }, + }, + }, + }, + wantErr: true, + errMsg: "all queries are disabled - at least one query must be enabled", + }, + { + name: "mixed disabled and enabled queries should pass", + request: QueryRangeRequest{ + Start: 1640995200000, + End: 1640998800000, + RequestType: RequestTypeTimeSeries, + CompositeQuery: CompositeQuery{ + Queries: []QueryEnvelope{ + { + Type: QueryTypeBuilder, + Spec: QueryBuilderQuery[MetricAggregation]{ + Name: "A", + Disabled: true, + Signal: telemetrytypes.SignalMetrics, + }, + }, + { + Type: QueryTypeBuilder, + Spec: QueryBuilderQuery[LogAggregation]{ + Name: "B", + Disabled: false, + Signal: telemetrytypes.SignalLogs, + Aggregations: []LogAggregation{ + { + Expression: "count()", + }, + }, + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "all queries enabled should pass", + request: QueryRangeRequest{ + Start: 1640995200000, + End: 1640998800000, + RequestType: RequestTypeTimeSeries, + CompositeQuery: CompositeQuery{ + Queries: []QueryEnvelope{ + { + Type: QueryTypeBuilder, + Spec: QueryBuilderQuery[LogAggregation]{ + Name: "A", + Disabled: false, + Signal: telemetrytypes.SignalLogs, + Aggregations: []LogAggregation{ + { + Expression: "count()", + }, + }, + }, + }, + { + Type: QueryTypeBuilder, + Spec: QueryBuilderQuery[LogAggregation]{ + Name: "B", + Disabled: false, + Signal: telemetrytypes.SignalLogs, + Aggregations: []LogAggregation{ + { + Expression: "sum(duration)", + }, + }, + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "all formula queries disabled should return error", + request: QueryRangeRequest{ + Start: 1640995200000, + End: 1640998800000, + RequestType: RequestTypeTimeSeries, + CompositeQuery: CompositeQuery{ + Queries: []QueryEnvelope{ + { + Type: QueryTypeFormula, + Spec: QueryBuilderFormula{ + Name: "F1", + Expression: "A + B", + Disabled: true, + }, + }, + { + Type: QueryTypeFormula, + Spec: QueryBuilderFormula{ + Name: "F2", + Expression: "A * 2", + Disabled: true, + }, + }, + }, + }, + }, + wantErr: true, + errMsg: "all queries are disabled - at least one query must be enabled", + }, + { + name: "all PromQL queries disabled should return error", + request: QueryRangeRequest{ + Start: 1640995200000, + End: 1640998800000, + RequestType: RequestTypeTimeSeries, + CompositeQuery: CompositeQuery{ + Queries: []QueryEnvelope{ + { + Type: QueryTypePromQL, + Spec: PromQuery{ + Name: "P1", + Query: "up", + Disabled: true, + }, + }, + { + Type: QueryTypePromQL, + Spec: PromQuery{ + Name: "P2", + Query: "rate(http_requests_total[5m])", + Disabled: true, + }, + }, + }, + }, + }, + wantErr: true, + errMsg: "all queries are disabled - at least one query must be enabled", + }, + { + name: "mixed query types with all disabled should return error", + request: QueryRangeRequest{ + Start: 1640995200000, + End: 1640998800000, + RequestType: RequestTypeTimeSeries, + CompositeQuery: CompositeQuery{ + Queries: []QueryEnvelope{ + { + Type: QueryTypeBuilder, + Spec: QueryBuilderQuery[MetricAggregation]{ + Name: "A", + Disabled: true, + Signal: telemetrytypes.SignalMetrics, + }, + }, + { + Type: QueryTypeFormula, + Spec: QueryBuilderFormula{ + Name: "F1", + Expression: "A + 1", + Disabled: true, + }, + }, + { + Type: QueryTypePromQL, + Spec: PromQuery{ + Name: "P1", + Query: "up", + Disabled: true, + }, + }, + }, + }, + }, + wantErr: true, + errMsg: "all queries are disabled - at least one query must be enabled", + }, + { + name: "single disabled query should return error", + request: QueryRangeRequest{ + Start: 1640995200000, + End: 1640998800000, + RequestType: RequestTypeTimeSeries, + CompositeQuery: CompositeQuery{ + Queries: []QueryEnvelope{ + { + Type: QueryTypeBuilder, + Spec: QueryBuilderQuery[LogAggregation]{ + Name: "A", + Disabled: true, + Signal: telemetrytypes.SignalLogs, + }, + }, + }, + }, + }, + wantErr: true, + errMsg: "all queries are disabled - at least one query must be enabled", + }, + { + name: "single enabled query should pass", + request: QueryRangeRequest{ + Start: 1640995200000, + End: 1640998800000, + RequestType: RequestTypeTimeSeries, + CompositeQuery: CompositeQuery{ + Queries: []QueryEnvelope{ + { + Type: QueryTypeBuilder, + Spec: QueryBuilderQuery[LogAggregation]{ + Name: "A", + Disabled: false, + Signal: telemetrytypes.SignalLogs, + Aggregations: []LogAggregation{ + { + Expression: "count()", + }, + }, + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "all ClickHouse queries disabled should return error", + request: QueryRangeRequest{ + Start: 1640995200000, + End: 1640998800000, + RequestType: RequestTypeTimeSeries, + CompositeQuery: CompositeQuery{ + Queries: []QueryEnvelope{ + { + Type: QueryTypeClickHouseSQL, + Spec: ClickHouseQuery{ + Name: "CH1", + Query: "SELECT count() FROM logs", + Disabled: true, + }, + }, + }, + }, + }, + wantErr: true, + errMsg: "all queries are disabled - at least one query must be enabled", + }, + { + name: "all trace operator queries disabled should return error", + request: QueryRangeRequest{ + Start: 1640995200000, + End: 1640998800000, + RequestType: RequestTypeTimeSeries, + CompositeQuery: CompositeQuery{ + Queries: []QueryEnvelope{ + { + Type: QueryTypeTraceOperator, + Spec: QueryBuilderTraceOperator{ + Name: "TO1", + Expression: "count()", + Disabled: true, + }, + }, + }, + }, + }, + wantErr: true, + errMsg: "all queries are disabled - at least one query must be enabled", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.request.Validate() + if tt.wantErr { + if err == nil { + t.Errorf("QueryRangeRequest.Validate() expected error but got none") + return + } + if tt.errMsg != "" && !contains(err.Error(), tt.errMsg) { + t.Errorf("QueryRangeRequest.Validate() error = %v, want to contain %v", err.Error(), tt.errMsg) + } + } else { + if err != nil { + t.Errorf("QueryRangeRequest.Validate() unexpected error = %v", err) + } + } + }) + } +} From 7ddaa84387748a7ee36a1e19199078f86518eee3 Mon Sep 17 00:00:00 2001 From: Ekansh Gupta Date: Fri, 26 Sep 2025 11:14:13 +0530 Subject: [PATCH 7/8] feat: add materialise ttl = 0 in set ttl v2 (#9189) --- pkg/query-service/app/clickhouseReader/reader.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/query-service/app/clickhouseReader/reader.go b/pkg/query-service/app/clickhouseReader/reader.go index c4183455a080..21ecad5656f8 100644 --- a/pkg/query-service/app/clickhouseReader/reader.go +++ b/pkg/query-service/app/clickhouseReader/reader.go @@ -1675,7 +1675,7 @@ func (r *ClickHouseReader) SetTTLV2(ctx context.Context, orgID string, params *m queries = append(queries, fmt.Sprintf(`ALTER TABLE %s ON CLUSTER %s MODIFY COLUMN _retention_days_cold UInt16 DEFAULT %d`, tableNames[0], r.cluster, coldStorageDuration)) - queries = append(queries, fmt.Sprintf(`ALTER TABLE %s ON CLUSTER %s MODIFY TTL toDateTime(timestamp / 1000000000) + toIntervalDay(_retention_days) DELETE, toDateTime(timestamp / 1000000000) + toIntervalDay(_retention_days_cold) TO VOLUME '%s'`, + queries = append(queries, fmt.Sprintf(`ALTER TABLE %s ON CLUSTER %s MODIFY TTL toDateTime(timestamp / 1000000000) + toIntervalDay(_retention_days) DELETE, toDateTime(timestamp / 1000000000) + toIntervalDay(_retention_days_cold) TO VOLUME '%s' SETTINGS materialize_ttl_after_modify=0`, tableNames[0], r.cluster, params.ColdStorageVolume)) } @@ -1690,7 +1690,7 @@ func (r *ClickHouseReader) SetTTLV2(ctx context.Context, orgID string, params *m resourceQueries = append(resourceQueries, fmt.Sprintf(`ALTER TABLE %s ON CLUSTER %s MODIFY COLUMN _retention_days_cold UInt16 DEFAULT %d`, tableNames[1], r.cluster, coldStorageDuration)) - resourceQueries = append(resourceQueries, fmt.Sprintf(`ALTER TABLE %s ON CLUSTER %s MODIFY TTL toDateTime(seen_at_ts_bucket_start) + toIntervalSecond(1800) + toIntervalDay(_retention_days) DELETE, toDateTime(seen_at_ts_bucket_start) + toIntervalSecond(1800) + toIntervalDay(_retention_days_cold) TO VOLUME '%s'`, + resourceQueries = append(resourceQueries, fmt.Sprintf(`ALTER TABLE %s ON CLUSTER %s MODIFY TTL toDateTime(seen_at_ts_bucket_start) + toIntervalSecond(1800) + toIntervalDay(_retention_days) DELETE, toDateTime(seen_at_ts_bucket_start) + toIntervalSecond(1800) + toIntervalDay(_retention_days_cold) TO VOLUME '%s' SETTINGS materialize_ttl_after_modify=0`, tableNames[1], r.cluster, params.ColdStorageVolume)) } From d595dcc22260cddfb658ea8ba5576dfb1af7ea88 Mon Sep 17 00:00:00 2001 From: Abhi kumar Date: Fri, 26 Sep 2025 13:17:38 +0530 Subject: [PATCH 8/8] fix: added fix for passing activeLogId in query range in log context view (#9180) * fix: added fix for passing activitylogId in query range in log context view * chore: added tests --- .../src/container/LogsExplorerViews/index.tsx | 20 +++++- .../tests/LogsExplorerViews.test.tsx | 63 +++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) diff --git a/frontend/src/container/LogsExplorerViews/index.tsx b/frontend/src/container/LogsExplorerViews/index.tsx index aaff83ab3d42..8063bc62b630 100644 --- a/frontend/src/container/LogsExplorerViews/index.tsx +++ b/frontend/src/container/LogsExplorerViews/index.tsx @@ -59,6 +59,7 @@ import { Query, TagFilter, } from 'types/api/queryBuilder/queryBuilderData'; +import { Filter } from 'types/api/v5/queryRange'; import { QueryDataV3 } from 'types/api/widgets/getQuery'; import { DataSource, LogsAggregatorOperator } from 'types/common/queryBuilder'; import { GlobalReducer } from 'types/reducer/globalTime'; @@ -171,6 +172,11 @@ function LogsExplorerViewsContainer({ return; } + let updatedFilterExpression = listQuery.filter?.expression || ''; + if (activeLogId) { + updatedFilterExpression = `${updatedFilterExpression} id <= '${activeLogId}'`.trim(); + } + const modifiedQueryData: IBuilderQuery = { ...listQuery, aggregateOperator: LogsAggregatorOperator.COUNT, @@ -183,6 +189,10 @@ function LogsExplorerViewsContainer({ }, ], legend: '{{severity_text}}', + filter: { + ...listQuery?.filter, + expression: updatedFilterExpression || '', + }, ...(activeLogId && { filters: { ...listQuery?.filters, @@ -286,6 +296,7 @@ function LogsExplorerViewsContainer({ page: number; pageSize: number; filters: TagFilter; + filter: Filter; }, ): Query | null => { if (!query) return null; @@ -297,6 +308,7 @@ function LogsExplorerViewsContainer({ // Add filter for activeLogId if present let updatedFilters = params.filters; + let updatedFilterExpression = params.filter?.expression || ''; if (activeLogId) { updatedFilters = { ...params.filters, @@ -315,6 +327,7 @@ function LogsExplorerViewsContainer({ ], op: 'AND', }; + updatedFilterExpression = `${updatedFilterExpression} id <= '${activeLogId}'`.trim(); } // Create orderBy array based on orderDirection @@ -336,6 +349,9 @@ function LogsExplorerViewsContainer({ ...(listQuery || initialQueryBuilderFormValues), ...paginateData, ...(updatedFilters ? { filters: updatedFilters } : {}), + filter: { + expression: updatedFilterExpression || '', + }, ...(selectedView === ExplorerViews.LIST ? { order: newOrderBy, orderBy: newOrderBy } : { order: [] }), @@ -368,7 +384,7 @@ function LogsExplorerViewsContainer({ if (isLimit) return; if (logs.length < pageSize) return; - const { limit, filters } = listQuery; + const { limit, filters, filter } = listQuery; const nextLogsLength = logs.length + pageSize; @@ -379,6 +395,7 @@ function LogsExplorerViewsContainer({ const newRequestData = getRequestData(stagedQuery, { filters: filters || { items: [], op: 'AND' }, + filter: filter || { expression: '' }, page: page + 1, pageSize: nextPageSize, }); @@ -526,6 +543,7 @@ function LogsExplorerViewsContainer({ const newRequestData = getRequestData(stagedQuery, { filters: listQuery?.filters || initialFilters, + filter: listQuery?.filter || { expression: '' }, page: 1, pageSize, }); diff --git a/frontend/src/container/LogsExplorerViews/tests/LogsExplorerViews.test.tsx b/frontend/src/container/LogsExplorerViews/tests/LogsExplorerViews.test.tsx index bf9690fb7651..f5a72e2ce9c8 100644 --- a/frontend/src/container/LogsExplorerViews/tests/LogsExplorerViews.test.tsx +++ b/frontend/src/container/LogsExplorerViews/tests/LogsExplorerViews.test.tsx @@ -1,3 +1,4 @@ +import { PANEL_TYPES } from 'constants/queryBuilder'; import ROUTES from 'constants/routes'; import { useCopyLogLink } from 'hooks/logs/useCopyLogLink'; import { useGetExplorerQueryRange } from 'hooks/queryBuilder/useGetExplorerQueryRange'; @@ -261,6 +262,68 @@ describe('LogsExplorerViews -', () => { // Verify the total number of filters (original + 1 new activeLogId filter) expect(firstQuery.filters?.items.length).toBe(expectedFiltersLength); + + // Verify the filter expression + expect(firstQuery.filter?.expression).toBe(`id <= '${ACTIVE_LOG_ID}'`); + } + }); + }); + + it('should update filter expression with activeLogId when present with existing filter expression', async () => { + // Mock useCopyLogLink to return an activeLogId + (useCopyLogLink as jest.Mock).mockReturnValue({ + activeLogId: ACTIVE_LOG_ID, + }); + + // Create a custom QueryBuilderContext with an existing filter expression + const customContext = { + ...mockQueryBuilderContextValue, + panelType: PANEL_TYPES.LIST, + stagedQuery: { + ...mockQueryBuilderContextValue.stagedQuery, + builder: { + ...mockQueryBuilderContextValue.stagedQuery.builder, + queryData: [ + { + ...mockQueryBuilderContextValue.stagedQuery.builder.queryData[0], + filter: { expression: "service = 'frontend'" }, + }, + ], + }, + }, + }; + + lodsQueryServerRequest(); + + render( + + + {}} + listQueryKeyRef={{ current: {} }} + chartQueryKeyRef={{ current: {} }} + setWarning={(): void => {}} + showLiveLogs={false} + /> + + , + ); + + await waitFor(() => { + // Find the call made for LIST panel type (main logs list request) + const listCall = (useGetExplorerQueryRange as jest.Mock).mock.calls.find( + (call) => call[1] === PANEL_TYPES.LIST && call[0], + ); + + expect(listCall).toBeDefined(); + if (listCall) { + const queryArg = listCall[0]; + const firstQuery = queryArg.builder.queryData[0]; + // It should append the activeLogId condition to existing expression + expect(firstQuery.filter?.expression).toBe( + "service = 'frontend' id <= 'test-log-id'", + ); } }); });