From fc7a94fa66f49b6581dc981c1a416455a7fdb1f3 Mon Sep 17 00:00:00 2001 From: Vishal Sharma Date: Thu, 19 Sep 2024 18:48:37 +0530 Subject: [PATCH 1/4] chore: update dashboard contributing doc and issue template (#6029) * chore: update dashboard contributing doc and issue template * chore: update issue template --- .github/ISSUE_TEMPLATE/request_dashboard.md | 49 +++++++++------------ CONTRIBUTING.md | 13 +++++- 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/request_dashboard.md b/.github/ISSUE_TEMPLATE/request_dashboard.md index 7972f40625..ba68449103 100644 --- a/.github/ISSUE_TEMPLATE/request_dashboard.md +++ b/.github/ISSUE_TEMPLATE/request_dashboard.md @@ -1,58 +1,49 @@ --- name: Request Dashboard about: Request a new dashboard for the SigNoz Dashboards repository -title: '' +title: '[Dashboard Request] ' labels: 'dashboard-template' assignees: '' --- -## 📝 Dashboard Request Template + -*Use this template to request a new dashboard for the SigNoz Dashboards repository. Please provide as much detail as possible to help us understand your needs.* +## Dashboard Name ---- - -### 1. Dashboard Name + -Name of the requested dashboard (e.g., MySQL Monitoring Dashboard): +## Expected Dashboard Sections and Panels ---- +(Can be tweaked (add or remove panels/sections) according to available metrics) -### 2. Expected Dashboard Sections and Panels +### Section Name -#### Section Name + -Brief description of the section (e.g., "Resource usage metrics for MySQL database"). +### Panel Name -#### Panel Name + -Panel description (e.g., "Value-type panels displaying current CPU usage, memory usage, etc."). - -- **Example:** + ---- + -### 3. Expected Variables +## Expected Dashboard Variables -List any variables you expect to use in the dashboard (e.g., `deployment.environment`, `hostname`, etc.). + ---- +## Additional Comments or Requirements -### 4. Additional Comments or Requirements + -Any additional details or special requirements for the dashboard? +## References or Screenshots ---- + -### 📋 Notes +## 📋 Notes Please review the [CONTRIBUTING.md](https://github.com/SigNoz/dashboards/blob/main/CONTRIBUTING.md) for guidelines on dashboard structure, naming conventions, and how to submit a pull request. - ---- -Thank you for your request! We will review it and provide feedback or guidance as necessary. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cc1c4399d8..632a4e98e8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,6 +30,7 @@ Also, have a look at these [good first issues label](https://github.com/SigNoz/s - [To run ClickHouse setup](#41-to-run-clickhouse-setup-recommended-for-local-development) - [Contribute to SigNoz Helm Chart](#5-contribute-to-signoz-helm-chart-) - [To run helm chart for local development](#51-to-run-helm-chart-for-local-development) +- [Contribute to Dashboards](#6-contribute-to-dashboards-) - [Other Ways to Contribute](#other-ways-to-contribute) # 1. General Instructions 📝 @@ -369,6 +370,17 @@ curl -sL https://github.com/SigNoz/signoz/raw/develop/sample-apps/hotrod/hotrod- --- +# 6. Contribute to Dashboards 📈 + +**Need to Update: [https://github.com/SigNoz/dashboards](https://github.com/SigNoz/dashboards)** + +To contribute a new dashboard template for any service, follow the contribution guidelines in the [Dashboard Contributing Guide](https://github.com/SigNoz/dashboards/blob/main/CONTRIBUTING.md). In brief: + +1. Create a dashboard JSON file. +2. Add a README file explaining the dashboard, the metrics ingested, and the configurations needed. +3. Include screenshots of the dashboard in the `assets/` directory. +4. Submit a pull request for review. + ## Other Ways to Contribute There are many other ways to get involved with the community and to participate in this project: @@ -379,7 +391,6 @@ There are many other ways to get involved with the community and to participate - Help answer questions on forums such as Stack Overflow and [SigNoz Community Slack Channel](https://signoz.io/slack). - Tell others about the project on Twitter, your blog, etc. - Again, Feel free to ping us on [`#contributing`](https://signoz-community.slack.com/archives/C01LWQ8KS7M) or [`#contributing-frontend`](https://signoz-community.slack.com/archives/C027134DM8B) on our slack community if you need any help on this :) Thank You! From 70fb5af19f0c5cad25670f4993436336d88b9a4e Mon Sep 17 00:00:00 2001 From: SagarRajput-7 <162284829+SagarRajput-7@users.noreply.github.com> Date: Thu, 19 Sep 2024 19:18:37 +0530 Subject: [PATCH 2/4] chore: removed empty signoz-core-ui folder (#6030) --- signoz-core-ui | 1 - 1 file changed, 1 deletion(-) delete mode 160000 signoz-core-ui diff --git a/signoz-core-ui b/signoz-core-ui deleted file mode 160000 index f8c925d842..0000000000 --- a/signoz-core-ui +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f8c925d842922f8a30063012a7bfb688a3bf0f36 From 2f7d208eb56d36f4ab02082a74931d194cecde5b Mon Sep 17 00:00:00 2001 From: Vikrant Gupta Date: Thu, 19 Sep 2024 19:23:12 +0530 Subject: [PATCH 3/4] fix: added time range key for query and local storage handling (#6009) * fix: added time range key for query and local storage handling * chore: fix jest test cases * fix: send single element array for only variable option as well * fix: intermediate stale data should not be shown --- .../GridCardLayout/GridCard/index.tsx | 21 ++++++++++ .../VariableItem.test.tsx | 8 +--- .../VariableItem.tsx | 40 +++++++++++++++++-- 3 files changed, 58 insertions(+), 11 deletions(-) diff --git a/frontend/src/container/GridCardLayout/GridCard/index.tsx b/frontend/src/container/GridCardLayout/GridCard/index.tsx index a618f807a5..66ce70fb86 100644 --- a/frontend/src/container/GridCardLayout/GridCard/index.tsx +++ b/frontend/src/container/GridCardLayout/GridCard/index.tsx @@ -11,6 +11,7 @@ import { isEqual } from 'lodash-es'; import isEmpty from 'lodash-es/isEmpty'; import { useDashboard } from 'providers/Dashboard/Dashboard'; import { memo, useEffect, useRef, useState } from 'react'; +import { useQueryClient } from 'react-query'; import { useDispatch, useSelector } from 'react-redux'; import { UpdateTimeInterval } from 'store/actions'; import { AppState } from 'store/reducers'; @@ -48,6 +49,7 @@ function GridCardGraph({ AppState, GlobalReducer >((state) => state.globalTime); + const queryClient = useQueryClient(); const handleBackNavigation = (): void => { const searchParams = new URLSearchParams(window.location.search); @@ -136,6 +138,25 @@ function GridCardGraph({ }; }); + // TODO [vikrantgupta25] remove this useEffect with refactor as this is prone to race condition + // this is added to tackle the case of async communication between VariableItem.tsx and GridCard.tsx + useEffect(() => { + if (variablesToGetUpdated.length > 0) { + queryClient.cancelQueries([ + maxTime, + minTime, + globalSelectedInterval, + variables, + widget?.query, + widget?.panelTypes, + widget.timePreferance, + widget.fillSpans, + requestData, + ]); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [variablesToGetUpdated]); + useEffect(() => { if (!isEqual(updatedQuery, requestData.query)) { setRequestData((prev) => ({ diff --git a/frontend/src/container/NewDashboard/DashboardVariablesSelection/VariableItem.test.tsx b/frontend/src/container/NewDashboard/DashboardVariablesSelection/VariableItem.test.tsx index 0c8fbd51ae..1cb89d6b95 100644 --- a/frontend/src/container/NewDashboard/DashboardVariablesSelection/VariableItem.test.tsx +++ b/frontend/src/container/NewDashboard/DashboardVariablesSelection/VariableItem.test.tsx @@ -1,14 +1,8 @@ import '@testing-library/jest-dom/extend-expect'; -import { - act, - fireEvent, - render, - screen, - waitFor, -} from '@testing-library/react'; import MockQueryClientProvider from 'providers/test/MockQueryClientProvider'; import React, { useEffect } from 'react'; +import { act, fireEvent, render, screen, waitFor } from 'tests/test-utils'; import { IDashboardVariable } from 'types/api/dashboard/getAll'; import VariableItem from './VariableItem'; diff --git a/frontend/src/container/NewDashboard/DashboardVariablesSelection/VariableItem.tsx b/frontend/src/container/NewDashboard/DashboardVariablesSelection/VariableItem.tsx index baa8228b3c..e14162d0ce 100644 --- a/frontend/src/container/NewDashboard/DashboardVariablesSelection/VariableItem.tsx +++ b/frontend/src/container/NewDashboard/DashboardVariablesSelection/VariableItem.tsx @@ -1,3 +1,4 @@ +/* eslint-disable sonarjs/cognitive-complexity */ /* eslint-disable jsx-a11y/click-events-have-key-events */ /* eslint-disable jsx-a11y/no-static-element-interactions */ /* eslint-disable @typescript-eslint/no-explicit-any */ @@ -25,8 +26,11 @@ import { debounce, isArray, isString } from 'lodash-es'; import map from 'lodash-es/map'; import { ChangeEvent, memo, useEffect, useMemo, useState } from 'react'; import { useQuery } from 'react-query'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; import { IDashboardVariable } from 'types/api/dashboard/getAll'; import { VariableResponseProps } from 'types/api/dashboard/variables/query'; +import { GlobalReducer } from 'types/reducer/globalTime'; import { popupContainer } from 'utils/selectPopupContainer'; import { variablePropsToPayloadVariables } from '../utils'; @@ -80,6 +84,23 @@ function VariableItem({ [], ); + const { maxTime, minTime } = useSelector( + (state) => state.globalTime, + ); + + useEffect(() => { + if (variableData.allSelected && variableData.type === 'QUERY') { + setVariablesToGetUpdated((prev) => { + const variablesQueue = [...prev.filter((v) => v !== variableData.name)]; + if (variableData.name) { + variablesQueue.push(variableData.name); + } + return variablesQueue; + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [minTime, maxTime]); + const [errorMessage, setErrorMessage] = useState(null); const getDependentVariables = (queryValue: string): string[] => { @@ -111,7 +132,14 @@ function VariableItem({ const variableKey = dependentVariablesStr.replace(/\s/g, ''); - return [REACT_QUERY_KEY.DASHBOARD_BY_ID, variableName, variableKey]; + // added this time dependency for variables query as API respects the passed time range now + return [ + REACT_QUERY_KEY.DASHBOARD_BY_ID, + variableName, + variableKey, + `${minTime}`, + `${maxTime}`, + ]; }; // eslint-disable-next-line sonarjs/cognitive-complexity @@ -151,10 +179,14 @@ function VariableItem({ valueNotInList = true; } } + // variablesData.allSelected is added for the case where on change of options we need to update the + // local storage if ( variableData.type === 'QUERY' && variableData.name && - (variablesToGetUpdated.includes(variableData.name) || valueNotInList) + (variablesToGetUpdated.includes(variableData.name) || + valueNotInList || + variableData.allSelected) ) { let value = variableData.selectedValue; let allSelected = false; @@ -338,8 +370,8 @@ function VariableItem({ (Array.isArray(selectValue) && selectValue?.includes(option.toString())); if (isChecked) { - if (mode === ToggleTagValue.Only) { - handleChange(option.toString()); + if (mode === ToggleTagValue.Only && variableData.multiSelect) { + handleChange([option.toString()]); } else if (!variableData.multiSelect) { handleChange(option.toString()); } else { From 8eb2cf144e3e66e758621c772396a95e4e9b8220 Mon Sep 17 00:00:00 2001 From: Nityananda Gohain Date: Thu, 19 Sep 2024 21:20:57 +0530 Subject: [PATCH 4/4] fix: issues with like and ilike fixed in v4 qb (#6018) --- .../app/logs/v4/query_builder.go | 16 ++++++----- .../app/logs/v4/query_builder_test.go | 4 +-- .../app/logs/v4/resource_query_builder.go | 17 +++++++++--- .../logs/v4/resource_query_builder_test.go | 27 ++++++++++++++++--- 4 files changed, 48 insertions(+), 16 deletions(-) diff --git a/pkg/query-service/app/logs/v4/query_builder.go b/pkg/query-service/app/logs/v4/query_builder.go index 47fda73c2a..e906c605a1 100644 --- a/pkg/query-service/app/logs/v4/query_builder.go +++ b/pkg/query-service/app/logs/v4/query_builder.go @@ -146,6 +146,7 @@ func buildAttributeFilter(item v3.FilterItem) (string, error) { return fmt.Sprintf(logsOp, keyName, fmtVal), nil case v3.FilterOperatorContains, v3.FilterOperatorNotContains: + // we also want to treat %, _ as literals for contains val := utils.QuoteEscapedStringForContains(fmt.Sprintf("%s", item.Value)) // for body the contains is case insensitive if keyName == BODY { @@ -153,14 +154,15 @@ func buildAttributeFilter(item v3.FilterItem) (string, error) { } else { return fmt.Sprintf("%s %s '%%%s%%'", keyName, logsOp, val), nil } - default: - // for use lower for like and ilike - if op == v3.FilterOperatorLike || op == v3.FilterOperatorNotLike { - if keyName == BODY { - keyName = fmt.Sprintf("lower(%s)", keyName) - fmtVal = fmt.Sprintf("lower(%s)", fmtVal) - } + case v3.FilterOperatorLike, v3.FilterOperatorNotLike: + // for body use lower for like and ilike + val := utils.QuoteEscapedString(fmt.Sprintf("%s", item.Value)) + if keyName == BODY { + return fmt.Sprintf("lower(%s) %s lower('%s')", keyName, logsOp, val), nil + } else { + return fmt.Sprintf("%s %s '%s'", keyName, logsOp, val), nil } + default: return fmt.Sprintf("%s %s %s", keyName, logsOp, fmtVal), nil } } else { diff --git a/pkg/query-service/app/logs/v4/query_builder_test.go b/pkg/query-service/app/logs/v4/query_builder_test.go index 1b24a6aac6..34ea7e1f6f 100644 --- a/pkg/query-service/app/logs/v4/query_builder_test.go +++ b/pkg/query-service/app/logs/v4/query_builder_test.go @@ -277,10 +277,10 @@ func Test_buildAttributeFilter(t *testing.T) { Type: v3.AttributeKeyTypeResource, }, Operator: v3.FilterOperatorLike, - Value: "test", + Value: "test%", }, }, - want: "resources_string['service.name'] LIKE 'test'", + want: "resources_string['service.name'] LIKE 'test%'", }, { name: "build attribute filter like-body", diff --git a/pkg/query-service/app/logs/v4/resource_query_builder.go b/pkg/query-service/app/logs/v4/resource_query_builder.go index 12d6c1a36a..2a56549b43 100644 --- a/pkg/query-service/app/logs/v4/resource_query_builder.go +++ b/pkg/query-service/app/logs/v4/resource_query_builder.go @@ -23,8 +23,13 @@ func buildResourceFilter(logsOp string, key string, op v3.FilterOperator, value return fmt.Sprintf(logsOp, searchKey, chFmtVal) case v3.FilterOperatorContains, v3.FilterOperatorNotContains: // this is required as clickhouseFormattedValue add's quotes to the string + // we also want to treat %, _ as literals for contains escapedStringValue := utils.QuoteEscapedStringForContains(fmt.Sprintf("%s", value)) return fmt.Sprintf("%s %s '%%%s%%'", searchKey, logsOp, escapedStringValue) + case v3.FilterOperatorLike, v3.FilterOperatorNotLike: + // this is required as clickhouseFormattedValue add's quotes to the string + escapedStringValue := utils.QuoteEscapedString(fmt.Sprintf("%s", value)) + return fmt.Sprintf("%s %s '%s'", searchKey, logsOp, escapedStringValue) default: return fmt.Sprintf("%s %s %s", searchKey, logsOp, chFmtVal) } @@ -74,13 +79,19 @@ func buildIndexFilterForInOperator(key string, op v3.FilterOperator, value inter // example:= x like '%john%' = labels like '%x%john%' func buildResourceIndexFilter(key string, op v3.FilterOperator, value interface{}) string { // not using clickhouseFormattedValue as we don't wan't the quotes - formattedValueEscaped := utils.QuoteEscapedStringForContains(fmt.Sprintf("%s", value)) + strVal := fmt.Sprintf("%s", value) + formattedValueEscapedForContains := utils.QuoteEscapedStringForContains(strVal) + formattedValueEscaped := utils.QuoteEscapedString(strVal) // add index filters switch op { - case v3.FilterOperatorContains, v3.FilterOperatorEqual, v3.FilterOperatorLike: + case v3.FilterOperatorContains: + return fmt.Sprintf("labels like '%%%s%%%s%%'", key, formattedValueEscapedForContains) + case v3.FilterOperatorNotContains: + return fmt.Sprintf("labels not like '%%%s%%%s%%'", key, formattedValueEscapedForContains) + case v3.FilterOperatorLike, v3.FilterOperatorEqual: return fmt.Sprintf("labels like '%%%s%%%s%%'", key, formattedValueEscaped) - case v3.FilterOperatorNotContains, v3.FilterOperatorNotEqual, v3.FilterOperatorNotLike: + case v3.FilterOperatorNotLike, v3.FilterOperatorNotEqual: return fmt.Sprintf("labels not like '%%%s%%%s%%'", key, formattedValueEscaped) case v3.FilterOperatorNotRegex: return fmt.Sprintf("labels not like '%%%s%%'", key) diff --git a/pkg/query-service/app/logs/v4/resource_query_builder_test.go b/pkg/query-service/app/logs/v4/resource_query_builder_test.go index 130fd9e98c..e315f739a3 100644 --- a/pkg/query-service/app/logs/v4/resource_query_builder_test.go +++ b/pkg/query-service/app/logs/v4/resource_query_builder_test.go @@ -61,9 +61,9 @@ func Test_buildResourceFilter(t *testing.T) { logsOp: "=", key: "service.name", op: v3.FilterOperatorEqual, - value: "Application", + value: "Application%", }, - want: `simpleJSONExtractString(labels, 'service.name') = 'Application'`, + want: `simpleJSONExtractString(labels, 'service.name') = 'Application%'`, }, { name: "test value with quotes", @@ -75,6 +75,16 @@ func Test_buildResourceFilter(t *testing.T) { }, want: `simpleJSONExtractString(labels, 'service.name') = 'Application\'s'`, }, + { + name: "test like", + args: args{ + logsOp: "LIKE", + key: "service.name", + op: v3.FilterOperatorLike, + value: "Application%_", + }, + want: `simpleJSONExtractString(labels, 'service.name') LIKE 'Application%_'`, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -119,9 +129,9 @@ func Test_buildIndexFilterForInOperator(t *testing.T) { args: args{ key: "service.name", op: v3.FilterOperatorIn, - value: "application", + value: "application%", }, - want: `(labels like '%"service.name":"application"%')`, + want: `(labels like '%"service.name":"application\%"%')`, }, { name: "test nin string", @@ -180,6 +190,15 @@ func Test_buildResourceIndexFilter(t *testing.T) { }, want: `labels not like '%service.name%application\%\_test%'`, }, + { + name: "test like with % and _", + args: args{ + key: "service.name", + op: v3.FilterOperatorLike, + value: "application%_test", + }, + want: `labels like '%service.name%application%_test%'`, + }, { name: "test not regex", args: args{