From 4875652ecb32e6f92689e5c2bcc1b4b554e14846 Mon Sep 17 00:00:00 2001 From: Ankit Nayan Date: Tue, 6 Sep 2022 19:29:07 +0530 Subject: [PATCH 01/17] chore: added group analytics --- pkg/query-service/telemetry/telemetry.go | 31 +++++++++++++++++++----- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/pkg/query-service/telemetry/telemetry.go b/pkg/query-service/telemetry/telemetry.go index 43d27484e5..0774b7bb91 100644 --- a/pkg/query-service/telemetry/telemetry.go +++ b/pkg/query-service/telemetry/telemetry.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "net/http" "os" + "strings" "sync" "time" @@ -35,12 +36,13 @@ var telemetry *Telemetry var once sync.Once type Telemetry struct { - operator analytics.Client - ipAddress string - isEnabled bool - isAnonymous bool - distinctId string - reader interfaces.Reader + operator analytics.Client + ipAddress string + isEnabled bool + isAnonymous bool + distinctId string + reader interfaces.Reader + companyDomain string } func createTelemetry() { @@ -106,6 +108,7 @@ func (a *Telemetry) IdentifyUser(user *model.User) { if !a.isTelemetryEnabled() || a.isTelemetryAnonymous() { return } + a.setCompanyDomain(user.Email) a.operator.Enqueue(analytics.Identify{ UserId: a.ipAddress, @@ -113,6 +116,21 @@ func (a *Telemetry) IdentifyUser(user *model.User) { }) } + +func (a *Telemetry) setCompanyDomain(email string) { + + email_split := strings.Split(email, "@") + if len(email_split) != 2 { + a.companyDomain = email + } + a.companyDomain = email_split[1] + +} + +func (a *Telemetry) getCompanyDomain() string { + return a.companyDomain +} + func (a *Telemetry) checkEvents(event string) bool { sendEvent := true if event == TELEMETRY_EVENT_USER && a.isTelemetryAnonymous() { @@ -136,6 +154,7 @@ func (a *Telemetry) SendEvent(event string, data map[string]interface{}) { properties := analytics.NewProperties() properties.Set("version", version.GetVersion()) properties.Set("deploymentType", getDeploymentType()) + properties.Set("companyDomain", a.getCompanyDomain()) for k, v := range data { properties.Set(k, v) From 99c0c97c1e93c213999cc315916b387ff5c5cd9f Mon Sep 17 00:00:00 2001 From: Ankit Nayan Date: Tue, 6 Sep 2022 19:55:01 +0530 Subject: [PATCH 02/17] chore: added sampling in analytics --- pkg/query-service/app/server.go | 7 ++++--- pkg/query-service/telemetry/telemetry.go | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/pkg/query-service/app/server.go b/pkg/query-service/app/server.go index dd00aea804..a34bb0b477 100644 --- a/pkg/query-service/app/server.go +++ b/pkg/query-service/app/server.go @@ -232,9 +232,10 @@ func (s *Server) analyticsMiddleware(next http.Handler) http.Handler { next.ServeHTTP(lrw, r) data := map[string]interface{}{"path": path, "statusCode": lrw.statusCode} - - if _, ok := telemetry.IgnoredPaths()[path]; !ok { - telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_PATH, data) + if telemetry.GetInstance().IsSampled() { + if _, ok := telemetry.IgnoredPaths()[path]; !ok { + telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_PATH, data) + } } }) diff --git a/pkg/query-service/telemetry/telemetry.go b/pkg/query-service/telemetry/telemetry.go index 0774b7bb91..1d2fa5429e 100644 --- a/pkg/query-service/telemetry/telemetry.go +++ b/pkg/query-service/telemetry/telemetry.go @@ -3,6 +3,7 @@ package telemetry import ( "context" "io/ioutil" + "math/rand" "net/http" "os" "strings" @@ -23,6 +24,7 @@ const ( TELEMETRY_EVENT_NUMBER_OF_SERVICES = "Number of Services" TELEMETRY_EVENT_HEART_BEAT = "Heart Beat" TELEMETRY_EVENT_ORG_SETTINGS = "Org Settings" + DEFAULT_SAMPLING = 0.1 ) const api_key = "4Gmoa4ixJAUHx2BpJxsjwA1bEfnwEeRz" @@ -35,6 +37,18 @@ const HEART_BEAT_DURATION = 6 * time.Hour var telemetry *Telemetry var once sync.Once +func (a *Telemetry) IsSampled() bool { + + number := a.minRandInt + rand.Intn(a.maxRandInt-a.minRandInt) + + if (number % a.maxRandInt) == 0 { + return true + } else { + return false + } + +} + type Telemetry struct { operator analytics.Client ipAddress string @@ -43,6 +57,8 @@ type Telemetry struct { distinctId string reader interfaces.Reader companyDomain string + minRandInt int + maxRandInt int } func createTelemetry() { @@ -50,6 +66,10 @@ func createTelemetry() { operator: analytics.New(api_key), ipAddress: getOutboundIP(), } + telemetry.minRandInt = 0 + telemetry.maxRandInt = int(1 / DEFAULT_SAMPLING) + + rand.Seed(time.Now().UnixNano()) data := map[string]interface{}{} From 578dafd1ff56ea95ad73a8ae59dc6811c64ae127 Mon Sep 17 00:00:00 2001 From: Ankit Nayan Date: Wed, 7 Sep 2022 15:20:56 +0530 Subject: [PATCH 03/17] chore: fixed random number generation to match to maxRandInt --- pkg/query-service/telemetry/telemetry.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/query-service/telemetry/telemetry.go b/pkg/query-service/telemetry/telemetry.go index 1d2fa5429e..687f8dddec 100644 --- a/pkg/query-service/telemetry/telemetry.go +++ b/pkg/query-service/telemetry/telemetry.go @@ -39,9 +39,9 @@ var once sync.Once func (a *Telemetry) IsSampled() bool { - number := a.minRandInt + rand.Intn(a.maxRandInt-a.minRandInt) + random_number := a.minRandInt + rand.Intn(a.maxRandInt-a.minRandInt) + 1 - if (number % a.maxRandInt) == 0 { + if (random_number % a.maxRandInt) == 0 { return true } else { return false From 9e6d9019f7eb603ceb9872922de3928b2c2e9d39 Mon Sep 17 00:00:00 2001 From: Ankit Nayan Date: Thu, 8 Sep 2022 21:05:54 +0530 Subject: [PATCH 04/17] chore: added group analytics --- pkg/query-service/go.mod | 1 + pkg/query-service/go.sum | 6 ++++ pkg/query-service/telemetry/telemetry.go | 43 +++++++++++++++++++----- 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/pkg/query-service/go.mod b/pkg/query-service/go.mod index 34ddceace9..5e608f0d8b 100644 --- a/pkg/query-service/go.mod +++ b/pkg/query-service/go.mod @@ -34,6 +34,7 @@ require ( github.com/minio/md5-simd v1.1.0 // indirect github.com/minio/sha256-simd v0.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/posthog/posthog-go v0.0.0-20220817142604-0b0bbf0f9c0f // indirect gopkg.in/ini.v1 v1.42.0 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) diff --git a/pkg/query-service/go.sum b/pkg/query-service/go.sum index 4fbb2b1c25..e0db02686a 100644 --- a/pkg/query-service/go.sum +++ b/pkg/query-service/go.sum @@ -93,6 +93,7 @@ github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/cmux v0.0.0-20170110192607-30d10be49292/go.mod h1:qRiX68mZX1lGBkTWyp3CLcenw9I94W2dLeRvMzcn9N4= github.com/cockroachdb/cockroach v0.0.0-20170608034007-84bc9597164f/go.mod h1:xeT/CQ0qZHangbYbWShlCGAx31aV4AjGswDUjhKS6HQ= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -378,6 +379,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posthog/posthog-go v0.0.0-20220817142604-0b0bbf0f9c0f h1:h0p1aZ9F5d6IXOygysob3g4B07b+HuVUQC0VJKD8wA4= +github.com/posthog/posthog-go v0.0.0-20220817142604-0b0bbf0f9c0f/go.mod h1:oa2sAs9tGai3VldabTV0eWejt/O4/OOD7azP8GaikqU= github.com/prometheus/client_golang v0.9.0-pre1.0.20181001174001-0a8115f42e03 h1:hqNopISksxji/N5zEy1xMN7TrnSyVG/LymiwnkXi6/Q= github.com/prometheus/client_golang v0.9.0-pre1.0.20181001174001-0a8115f42e03/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= @@ -393,6 +396,7 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/samuel/go-zookeeper v0.0.0-20161028232340-1d7be4effb13/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da h1:p3Vo3i64TCLY7gIfzeQaUJ+kppEO5WQG3cL8iE8tGHU= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= @@ -406,6 +410,7 @@ github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/vfsgen v0.0.0-20180711163814-62bca832be04/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= @@ -433,6 +438,7 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c h1:3lbZUMbMiGUW/LMkfsEABsc5zNT9+b1CvsJx47JzJ8g= diff --git a/pkg/query-service/telemetry/telemetry.go b/pkg/query-service/telemetry/telemetry.go index 687f8dddec..62560d97f9 100644 --- a/pkg/query-service/telemetry/telemetry.go +++ b/pkg/query-service/telemetry/telemetry.go @@ -10,6 +10,7 @@ import ( "sync" "time" + ph "github.com/posthog/posthog-go" "go.signoz.io/query-service/constants" "go.signoz.io/query-service/interfaces" "go.signoz.io/query-service/model" @@ -18,16 +19,19 @@ import ( ) const ( - TELEMETRY_EVENT_PATH = "API Call" - TELEMETRY_EVENT_USER = "User" - TELEMETRY_EVENT_INPRODUCT_FEEDBACK = "InProduct Feeback Submitted" - TELEMETRY_EVENT_NUMBER_OF_SERVICES = "Number of Services" - TELEMETRY_EVENT_HEART_BEAT = "Heart Beat" - TELEMETRY_EVENT_ORG_SETTINGS = "Org Settings" - DEFAULT_SAMPLING = 0.1 + TELEMETRY_EVENT_PATH = "API Call" + TELEMETRY_EVENT_USER = "User" + TELEMETRY_EVENT_INPRODUCT_FEEDBACK = "InProduct Feeback Submitted" + TELEMETRY_EVENT_NUMBER_OF_SERVICES = "Number of Services" + TELEMETRY_EVENT_NUMBER_OF_SERVICES_PH = "Number of Services V2" + TELEMETRY_EVENT_HEART_BEAT = "Heart Beat" + TELEMETRY_EVENT_ORG_SETTINGS = "Org Settings" + DEFAULT_SAMPLING = 0.1 ) const api_key = "4Gmoa4ixJAUHx2BpJxsjwA1bEfnwEeRz" +const ph_api_key = "H-htDCae7CR3RV57gUzmol6IAKtm5IMCvbcm_fwnL-w" + const IP_NOT_FOUND_PLACEHOLDER = "NA" const HEART_BEAT_DURATION = 6 * time.Hour @@ -51,6 +55,7 @@ func (a *Telemetry) IsSampled() bool { type Telemetry struct { operator analytics.Client + phOperator ph.Client ipAddress string isEnabled bool isAnonymous bool @@ -63,8 +68,9 @@ type Telemetry struct { func createTelemetry() { telemetry = &Telemetry{ - operator: analytics.New(api_key), - ipAddress: getOutboundIP(), + operator: analytics.New(api_key), + phOperator: ph.New(ph_api_key), + ipAddress: getOutboundIP(), } telemetry.minRandInt = 0 telemetry.maxRandInt = int(1 / DEFAULT_SAMPLING) @@ -134,6 +140,13 @@ func (a *Telemetry) IdentifyUser(user *model.User) { UserId: a.ipAddress, Traits: analytics.NewTraits().SetName(user.Name).SetEmail(user.Email).Set("ip", a.ipAddress), }) + // Updating a groups properties + a.phOperator.Enqueue(ph.GroupIdentify{ + Type: "companyDomain", + Key: a.getCompanyDomain(), + Properties: ph.NewProperties(). + Set("companyDomain", a.getCompanyDomain()), + }) } @@ -190,6 +203,18 @@ func (a *Telemetry) SendEvent(event string, data map[string]interface{}) { UserId: userId, Properties: properties, }) + + if event == TELEMETRY_EVENT_NUMBER_OF_SERVICES { + + a.phOperator.Enqueue(ph.Capture{ + DistinctId: userId, + Event: TELEMETRY_EVENT_NUMBER_OF_SERVICES_PH, + Properties: ph.Properties(properties), + Groups: ph.NewGroups(). + Set("companyDomain", a.getCompanyDomain()), + }) + + } } func (a *Telemetry) GetDistinctId() string { From 461a15d52d2840cd6e50e237cd3f8ab9860321a7 Mon Sep 17 00:00:00 2001 From: Pranshu Chittora Date: Fri, 9 Sep 2022 17:43:25 +0530 Subject: [PATCH 05/17] feat: dashboard variables (#1552) * feat: dashboard variables * fix: variable wipe on few instances * feat: error handling states * feat: eslint and tsc fixes --- frontend/src/api/dashboard/variables/query.ts | 26 ++ .../Graph/FullView/index.metricsBuilder.tsx | 2 + .../container/GridGraphLayout/Graph/index.tsx | 13 + .../src/container/GridGraphLayout/index.tsx | 2 + .../src/container/GridGraphLayout/utils.ts | 1 + .../SearchFilter/__tests__/utils.test.ts | 3 + .../General}/AddTags/index.tsx | 0 .../General}/AddTags/styles.ts | 0 .../General}/Description/index.tsx | 0 .../General}/Description/styles.ts | 0 .../DashboardSettings/General/index.tsx | 112 ++++++ .../DashboardSettings/General/styles.ts | 20 + .../Variables/VariableItem/VariableItem.tsx | 349 ++++++++++++++++++ .../Variables/VariableItem/styles.ts | 11 + .../DashboardSettings/Variables/index.tsx | 188 ++++++++++ .../DashboardSettings/Variables/types.ts | 1 + .../NewDashboard/DashboardSettings/index.tsx | 22 ++ .../VariableItem.tsx | 137 +++++++ .../DashboardVariablesSelection/index.tsx | 72 ++++ .../DashboardVariablesSelection/styles.ts | 19 + .../DescriptionOfDashboard/SettingsDrawer.tsx | 37 ++ .../DescriptionOfDashboard/index.tsx | 159 ++------ .../DescriptionOfDashboard/styles.ts | 9 +- frontend/src/container/NewWidget/index.tsx | 2 + .../customCommaValuesParser.ts | 20 + .../getDashboardVariables.ts | 38 ++ .../dashbaordVariables/sortVariableValues.ts | 15 + .../store/actions/dashboard/deleteWidget.ts | 1 + .../actions/dashboard/getQueryResults.ts | 22 +- .../dashboard/updatedDashboardVariables.ts | 38 ++ frontend/src/store/reducers/dashboard.ts | 20 +- frontend/src/types/actions/dashboard.ts | 16 +- frontend/src/types/api/dashboard/getAll.ts | 26 ++ .../types/api/dashboard/variables/query.ts | 7 + .../src/types/api/metrics/getQueryRange.ts | 1 + 35 files changed, 1254 insertions(+), 135 deletions(-) create mode 100644 frontend/src/api/dashboard/variables/query.ts rename frontend/src/container/NewDashboard/{DescriptionOfDashboard => DashboardSettings/General}/AddTags/index.tsx (100%) rename frontend/src/container/NewDashboard/{DescriptionOfDashboard => DashboardSettings/General}/AddTags/styles.ts (100%) rename frontend/src/container/NewDashboard/{DescriptionOfDashboard => DashboardSettings/General}/Description/index.tsx (100%) rename frontend/src/container/NewDashboard/{DescriptionOfDashboard => DashboardSettings/General}/Description/styles.ts (100%) create mode 100644 frontend/src/container/NewDashboard/DashboardSettings/General/index.tsx create mode 100644 frontend/src/container/NewDashboard/DashboardSettings/General/styles.ts create mode 100644 frontend/src/container/NewDashboard/DashboardSettings/Variables/VariableItem/VariableItem.tsx create mode 100644 frontend/src/container/NewDashboard/DashboardSettings/Variables/VariableItem/styles.ts create mode 100644 frontend/src/container/NewDashboard/DashboardSettings/Variables/index.tsx create mode 100644 frontend/src/container/NewDashboard/DashboardSettings/Variables/types.ts create mode 100644 frontend/src/container/NewDashboard/DashboardSettings/index.tsx create mode 100644 frontend/src/container/NewDashboard/DashboardVariablesSelection/VariableItem.tsx create mode 100644 frontend/src/container/NewDashboard/DashboardVariablesSelection/index.tsx create mode 100644 frontend/src/container/NewDashboard/DashboardVariablesSelection/styles.ts create mode 100644 frontend/src/container/NewDashboard/DescriptionOfDashboard/SettingsDrawer.tsx create mode 100644 frontend/src/lib/dashbaordVariables/customCommaValuesParser.ts create mode 100644 frontend/src/lib/dashbaordVariables/getDashboardVariables.ts create mode 100644 frontend/src/lib/dashbaordVariables/sortVariableValues.ts create mode 100644 frontend/src/store/actions/dashboard/updatedDashboardVariables.ts create mode 100644 frontend/src/types/api/dashboard/variables/query.ts diff --git a/frontend/src/api/dashboard/variables/query.ts b/frontend/src/api/dashboard/variables/query.ts new file mode 100644 index 0000000000..61693ba4c0 --- /dev/null +++ b/frontend/src/api/dashboard/variables/query.ts @@ -0,0 +1,26 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { PayloadProps, Props } from 'types/api/dashboard/variables/query'; + +const query = async ( + props: Props, +): Promise | ErrorResponse> => { + try { + const response = await axios.get( + `/variables/query?query=${encodeURIComponent(props.query)}`, + ); + + return { + statusCode: 200, + error: null, + message: response.data.status, + payload: response.data.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; + +export default query; diff --git a/frontend/src/container/GridGraphLayout/Graph/FullView/index.metricsBuilder.tsx b/frontend/src/container/GridGraphLayout/Graph/FullView/index.metricsBuilder.tsx index 8fe3b259c1..113049a295 100644 --- a/frontend/src/container/GridGraphLayout/Graph/FullView/index.metricsBuilder.tsx +++ b/frontend/src/container/GridGraphLayout/Graph/FullView/index.metricsBuilder.tsx @@ -7,6 +7,7 @@ import { timeItems, timePreferance, } from 'container/NewWidget/RightContainer/timeItems'; +import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables'; import getChartData from 'lib/getChartData'; import React, { useCallback, useState } from 'react'; import { useQuery } from 'react-query'; @@ -52,6 +53,7 @@ function FullView({ graphType: widget.panelTypes, query: widget.query, globalSelectedInterval: globalSelectedTime, + variables: getDashboardVariables(), }), ); diff --git a/frontend/src/container/GridGraphLayout/Graph/index.tsx b/frontend/src/container/GridGraphLayout/Graph/index.tsx index f39d64cb76..4d654a0222 100644 --- a/frontend/src/container/GridGraphLayout/Graph/index.tsx +++ b/frontend/src/container/GridGraphLayout/Graph/index.tsx @@ -3,6 +3,7 @@ import { AxiosError } from 'axios'; import { ChartData } from 'chart.js'; import Spinner from 'components/Spinner'; import GridGraphComponent from 'container/GridGraphComponent'; +import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables'; import getChartData from 'lib/getChartData'; import isEmpty from 'lodash-es/isEmpty'; import React, { memo, useCallback, useEffect, useState } from 'react'; @@ -104,11 +105,18 @@ function GridCardGraph({ useEffect(() => { (async (): Promise => { try { + setState((state) => ({ + ...state, + error: false, + errorMessage: '', + loading: true, + })); const response = await GetMetricQueryRange({ selectedTime: widget.timePreferance, graphType: widget.panelTypes, query: widget.query, globalSelectedInterval, + variables: getDashboardVariables(), }); const isError = response.error; @@ -144,6 +152,11 @@ function GridCardGraph({ errorMessage: (error as AxiosError).toString(), loading: false, })); + } finally { + setState((state) => ({ + ...state, + loading: false, + })); } })(); }, [widget, maxTime, minTime, globalSelectedInterval]); diff --git a/frontend/src/container/GridGraphLayout/index.tsx b/frontend/src/container/GridGraphLayout/index.tsx index d80b12f7a1..234b10ceb7 100644 --- a/frontend/src/container/GridGraphLayout/index.tsx +++ b/frontend/src/container/GridGraphLayout/index.tsx @@ -121,6 +121,7 @@ function GridGraph(props: Props): JSX.Element { name: data.name, tags: data.tags, widgets: data.widgets, + variables: data.variables, layout, }, uuid: selectedDashboard.uuid, @@ -157,6 +158,7 @@ function GridGraph(props: Props): JSX.Element { data.name, data.tags, data.title, + data.variables, data.widgets, dispatch, saveLayoutPermission, diff --git a/frontend/src/container/GridGraphLayout/utils.ts b/frontend/src/container/GridGraphLayout/utils.ts index 0d7320f6f5..d06c69c757 100644 --- a/frontend/src/container/GridGraphLayout/utils.ts +++ b/frontend/src/container/GridGraphLayout/utils.ts @@ -27,6 +27,7 @@ export const UpdateDashboard = async ({ description: data.description, name: data.name, tags: data.tags, + variables: data.variables, widgets: [ ...(data.widgets || []), { diff --git a/frontend/src/container/ListOfDashboard/SearchFilter/__tests__/utils.test.ts b/frontend/src/container/ListOfDashboard/SearchFilter/__tests__/utils.test.ts index db9825b677..369a0dffa3 100644 --- a/frontend/src/container/ListOfDashboard/SearchFilter/__tests__/utils.test.ts +++ b/frontend/src/container/ListOfDashboard/SearchFilter/__tests__/utils.test.ts @@ -12,6 +12,7 @@ describe('executeSearchQueries', () => { updated_at: '', data: { title: 'first dashboard', + variables: {}, }, }; const secondDashboard: Dashboard = { @@ -21,6 +22,7 @@ describe('executeSearchQueries', () => { updated_at: '', data: { title: 'second dashboard', + variables: {}, }, }; const thirdDashboard: Dashboard = { @@ -30,6 +32,7 @@ describe('executeSearchQueries', () => { updated_at: '', data: { title: 'third dashboard (with special characters +?\\)', + variables: {}, }, }; const dashboards = [firstDashboard, secondDashboard, thirdDashboard]; diff --git a/frontend/src/container/NewDashboard/DescriptionOfDashboard/AddTags/index.tsx b/frontend/src/container/NewDashboard/DashboardSettings/General/AddTags/index.tsx similarity index 100% rename from frontend/src/container/NewDashboard/DescriptionOfDashboard/AddTags/index.tsx rename to frontend/src/container/NewDashboard/DashboardSettings/General/AddTags/index.tsx diff --git a/frontend/src/container/NewDashboard/DescriptionOfDashboard/AddTags/styles.ts b/frontend/src/container/NewDashboard/DashboardSettings/General/AddTags/styles.ts similarity index 100% rename from frontend/src/container/NewDashboard/DescriptionOfDashboard/AddTags/styles.ts rename to frontend/src/container/NewDashboard/DashboardSettings/General/AddTags/styles.ts diff --git a/frontend/src/container/NewDashboard/DescriptionOfDashboard/Description/index.tsx b/frontend/src/container/NewDashboard/DashboardSettings/General/Description/index.tsx similarity index 100% rename from frontend/src/container/NewDashboard/DescriptionOfDashboard/Description/index.tsx rename to frontend/src/container/NewDashboard/DashboardSettings/General/Description/index.tsx diff --git a/frontend/src/container/NewDashboard/DescriptionOfDashboard/Description/styles.ts b/frontend/src/container/NewDashboard/DashboardSettings/General/Description/styles.ts similarity index 100% rename from frontend/src/container/NewDashboard/DescriptionOfDashboard/Description/styles.ts rename to frontend/src/container/NewDashboard/DashboardSettings/General/Description/styles.ts diff --git a/frontend/src/container/NewDashboard/DashboardSettings/General/index.tsx b/frontend/src/container/NewDashboard/DashboardSettings/General/index.tsx new file mode 100644 index 0000000000..4a8fce57a2 --- /dev/null +++ b/frontend/src/container/NewDashboard/DashboardSettings/General/index.tsx @@ -0,0 +1,112 @@ +import { SaveOutlined } from '@ant-design/icons'; +import { Col, Divider, Input, Space, Typography } from 'antd'; +import AddTags from 'container/NewDashboard/DashboardSettings/General/AddTags'; +import React, { useCallback, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { connect, useSelector } from 'react-redux'; +import { bindActionCreators, Dispatch } from 'redux'; +import { ThunkDispatch } from 'redux-thunk'; +import { + UpdateDashboardTitleDescriptionTags, + UpdateDashboardTitleDescriptionTagsProps, +} from 'store/actions'; +import { AppState } from 'store/reducers'; +import AppActions from 'types/actions'; +import DashboardReducer from 'types/reducer/dashboards'; + +import { Button } from './styles'; + +function GeneralDashboardSettings({ + updateDashboardTitleDescriptionTags, +}: DescriptionOfDashboardProps): JSX.Element { + const { dashboards } = useSelector( + (state) => state.dashboards, + ); + + const [selectedDashboard] = dashboards; + const selectedData = selectedDashboard.data; + const { title } = selectedData; + const { tags } = selectedData; + const { description } = selectedData; + + const [updatedTitle, setUpdatedTitle] = useState(title); + const [updatedTags, setUpdatedTags] = useState(tags || []); + const [updatedDescription, setUpdatedDescription] = useState( + description || '', + ); + + const { t } = useTranslation('common'); + + const onSaveHandler = useCallback(() => { + const dashboard = selectedDashboard; + // @TODO need to update this function to take title,description,tags only + updateDashboardTitleDescriptionTags({ + dashboard: { + ...dashboard, + data: { + ...dashboard.data, + description: updatedDescription, + tags: updatedTags, + title: updatedTitle, + }, + }, + }); + }, [ + updatedTitle, + updatedTags, + updatedDescription, + selectedDashboard, + updateDashboardTitleDescriptionTags, + ]); + + return ( + + +
+ Name + setUpdatedTitle(e.target.value)} + /> +
+ +
+ Description + setUpdatedDescription(e.target.value)} + /> +
+
+ Tags + +
+
+ + +
+
+ + ); +} + +interface DispatchProps { + updateDashboardTitleDescriptionTags: ( + props: UpdateDashboardTitleDescriptionTagsProps, + ) => (dispatch: Dispatch) => void; +} + +const mapDispatchToProps = ( + dispatch: ThunkDispatch, +): DispatchProps => ({ + updateDashboardTitleDescriptionTags: bindActionCreators( + UpdateDashboardTitleDescriptionTags, + dispatch, + ), +}); + +type DescriptionOfDashboardProps = DispatchProps; + +export default connect(null, mapDispatchToProps)(GeneralDashboardSettings); diff --git a/frontend/src/container/NewDashboard/DashboardSettings/General/styles.ts b/frontend/src/container/NewDashboard/DashboardSettings/General/styles.ts new file mode 100644 index 0000000000..43bcccef51 --- /dev/null +++ b/frontend/src/container/NewDashboard/DashboardSettings/General/styles.ts @@ -0,0 +1,20 @@ +import { Button as ButtonComponent, Drawer } from 'antd'; +import styled from 'styled-components'; + +export const Container = styled.div` + margin-top: 0.5rem; +`; + +export const Button = styled(ButtonComponent)` + &&& { + display: flex; + align-items: center; + } +`; + +export const DrawerContainer = styled(Drawer)` + .ant-drawer-header { + padding: 0; + border: none; + } +`; diff --git a/frontend/src/container/NewDashboard/DashboardSettings/Variables/VariableItem/VariableItem.tsx b/frontend/src/container/NewDashboard/DashboardSettings/Variables/VariableItem/VariableItem.tsx new file mode 100644 index 0000000000..31d443abff --- /dev/null +++ b/frontend/src/container/NewDashboard/DashboardSettings/Variables/VariableItem/VariableItem.tsx @@ -0,0 +1,349 @@ +/* eslint-disable sonarjs/cognitive-complexity */ +import { orange } from '@ant-design/colors'; +import { + Button, + Col, + Divider, + Input, + Select, + Switch, + Tag, + Typography, +} from 'antd'; +import query from 'api/dashboard/variables/query'; +import Editor from 'components/Editor'; +import { commaValuesParser } from 'lib/dashbaordVariables/customCommaValuesParser'; +import sortValues from 'lib/dashbaordVariables/sortVariableValues'; +import { map } from 'lodash-es'; +import React, { useEffect, useState } from 'react'; +import { + IDashboardVariable, + TSortVariableValuesType, + TVariableQueryType, + VariableQueryTypeArr, + VariableSortTypeArr, +} from 'types/api/dashboard/getAll'; +import { v4 } from 'uuid'; + +import { TVariableViewMode } from '../types'; +import { LabelContainer, VariableItemRow } from './styles'; + +const { Option } = Select; + +interface VariableItemProps { + variableData: IDashboardVariable; + onCancel: () => void; + onSave: (name: string, arg0: IDashboardVariable) => void; + validateName: (arg0: string) => boolean; + variableViewMode: TVariableViewMode; +} +function VariableItem({ + variableData, + onCancel, + onSave, + validateName, + variableViewMode, +}: VariableItemProps): JSX.Element { + const [variableName, setVariableName] = useState( + variableData.name || '', + ); + const [variableDescription, setVariableDescription] = useState( + variableData.description || '', + ); + const [queryType, setQueryType] = useState( + variableData.type || 'QUERY', + ); + const [variableQueryValue, setVariableQueryValue] = useState( + variableData.queryValue || '', + ); + const [variableCustomValue, setVariableCustomValue] = useState( + variableData.customValue || '', + ); + const [variableTextboxValue, setVariableTextboxValue] = useState( + variableData.textboxValue || '', + ); + const [ + variableSortType, + setVariableSortType, + ] = useState( + variableData.sort || VariableSortTypeArr[0], + ); + const [variableMultiSelect, setVariableMultiSelect] = useState( + variableData.multiSelect || false, + ); + const [variableShowALLOption, setVariableShowALLOption] = useState( + variableData.showALLOption || false, + ); + const [previewValues, setPreviewValues] = useState([]); + + // Internal states + const [previewLoading, setPreviewLoading] = useState(false); + // Error messages + const [errorName, setErrorName] = useState(false); + const [errorPreview, setErrorPreview] = useState(null); + + useEffect(() => { + setPreviewValues([]); + if (queryType === 'CUSTOM') { + setPreviewValues( + sortValues( + commaValuesParser(variableCustomValue), + variableSortType, + ) as never, + ); + } + }, [ + queryType, + variableCustomValue, + variableData.customValue, + variableData.type, + variableSortType, + ]); + + const handleSave = (): void => { + const newVariableData: IDashboardVariable = { + name: variableName, + description: variableDescription, + type: queryType, + queryValue: variableQueryValue, + customValue: variableCustomValue, + textboxValue: variableTextboxValue, + multiSelect: variableMultiSelect, + showALLOption: variableShowALLOption, + sort: variableSortType, + ...(queryType === 'TEXTBOX' && { + selectedValue: (variableData.selectedValue || + variableTextboxValue) as never, + }), + modificationUUID: v4(), + }; + onSave( + (variableViewMode === 'EDIT' ? variableData.name : variableName) as string, + newVariableData, + ); + onCancel(); + }; + + // Fetches the preview values for the SQL variable query + const handleQueryResult = async (): Promise => { + setPreviewLoading(true); + setErrorPreview(null); + try { + const variableQueryResponse = await query({ + query: variableQueryValue, + }); + setPreviewLoading(false); + if (variableQueryResponse.error) { + setErrorPreview(variableQueryResponse.error); + return; + } + if (variableQueryResponse.payload?.variableValues) + setPreviewValues( + sortValues( + variableQueryResponse.payload?.variableValues || [], + variableSortType, + ) as never, + ); + } catch (e) { + console.error(e); + } + }; + return ( + + {/* Add Variable */} + + + Name + +
+ { + setVariableName(e.target.value); + setErrorName(!validateName(e.target.value)); + }} + /> +
+ + {errorName ? 'Variable name already exists' : ''} + +
+
+
+ + + Description + + + setVariableDescription(e.target.value)} + /> + + + + Type + + + + + + Options + + {queryType === 'QUERY' && ( + + + Query + + +
+ setVariableQueryValue(e)} + height="300px" + /> + +
+
+ )} + {queryType === 'CUSTOM' && ( + + + Values separated by comma + + { + setVariableCustomValue(e.target.value); + setPreviewValues( + sortValues( + commaValuesParser(e.target.value), + variableSortType, + ) as never, + ); + }} + /> + + )} + {queryType === 'TEXTBOX' && ( + + + Default Value + + { + setVariableTextboxValue(e.target.value); + }} + placeholder="Default value if any" + style={{ width: 400 }} + /> + + )} + {(queryType === 'QUERY' || queryType === 'CUSTOM') && ( + <> + + + Preview of Values + +
+ {errorPreview ? ( + {errorPreview} + ) : ( + map(previewValues, (value, idx) => ( + {value.toString()} + )) + )} +
+
+ + + Sort + + + + + + + Enable multiple values to be checked + + { + setVariableMultiSelect(e); + if (!e) { + setVariableShowALLOption(false); + } + }} + /> + + {variableMultiSelect && ( + + + Include an option for ALL values + + setVariableShowALLOption(e)} + /> + + )} + + )} + + + + + + + ); +} + +export default VariableItem; diff --git a/frontend/src/container/NewDashboard/DashboardSettings/Variables/VariableItem/styles.ts b/frontend/src/container/NewDashboard/DashboardSettings/Variables/VariableItem/styles.ts new file mode 100644 index 0000000000..2d603ede18 --- /dev/null +++ b/frontend/src/container/NewDashboard/DashboardSettings/Variables/VariableItem/styles.ts @@ -0,0 +1,11 @@ +import { Row } from 'antd'; +import styled from 'styled-components'; + +export const VariableItemRow = styled(Row)` + gap: 1rem; + margin-bottom: 1rem; +`; + +export const LabelContainer = styled.div` + width: 200px; +`; diff --git a/frontend/src/container/NewDashboard/DashboardSettings/Variables/index.tsx b/frontend/src/container/NewDashboard/DashboardSettings/Variables/index.tsx new file mode 100644 index 0000000000..f070752ac4 --- /dev/null +++ b/frontend/src/container/NewDashboard/DashboardSettings/Variables/index.tsx @@ -0,0 +1,188 @@ +import { blue, red } from '@ant-design/colors'; +import { PlusOutlined } from '@ant-design/icons'; +import { Button, Modal, Row, Space, Table, Tag } from 'antd'; +import React, { useRef, useState } from 'react'; +import { connect, useSelector } from 'react-redux'; +import { bindActionCreators, Dispatch } from 'redux'; +import { ThunkDispatch } from 'redux-thunk'; +import { UpdateDashboardVariables } from 'store/actions/dashboard/updatedDashboardVariables'; +import { AppState } from 'store/reducers'; +import AppActions from 'types/actions'; +import { IDashboardVariable } from 'types/api/dashboard/getAll'; +import DashboardReducer from 'types/reducer/dashboards'; + +import { TVariableViewMode } from './types'; +import VariableItem from './VariableItem/VariableItem'; + +function VariablesSetting({ + updateDashboardVariables, +}: DispatchProps): JSX.Element { + const variableToDelete = useRef(null); + const [deleteVariableModal, setDeleteVariableModal] = useState(false); + + const { dashboards } = useSelector( + (state) => state.dashboards, + ); + + const [selectedDashboard] = dashboards; + + const { + data: { variables = {} }, + } = selectedDashboard; + + const variablesTableData = Object.keys(variables).map((variableName) => ({ + key: variableName, + name: variableName, + ...variables[variableName], + })); + + const [ + variableViewMode, + setVariableViewMode, + ] = useState(null); + + const [ + variableEditData, + setVariableEditData, + ] = useState(null); + + const onDoneVariableViewMode = (): void => { + setVariableViewMode(null); + setVariableEditData(null); + }; + + const onVariableViewModeEnter = ( + viewType: TVariableViewMode, + varData: IDashboardVariable, + ): void => { + setVariableEditData(varData); + setVariableViewMode(viewType); + }; + + const onVariableSaveHandler = ( + name: string, + variableData: IDashboardVariable, + ): void => { + if (!variableData.name) { + return; + } + const newVariables = { ...variables }; + newVariables[variableData.name] = variableData; + if (variableViewMode === 'EDIT') delete newVariables[name]; + updateDashboardVariables(newVariables); + onDoneVariableViewMode(); + }; + + const onVariableDeleteHandler = (variableName: string): void => { + variableToDelete.current = variableName; + setDeleteVariableModal(true); + }; + + const handleDeleteConfirm = (): void => { + const newVariables = { ...variables }; + if (variableToDelete?.current) delete newVariables[variableToDelete?.current]; + updateDashboardVariables(newVariables); + variableToDelete.current = null; + setDeleteVariableModal(false); + }; + const handleDeleteCancel = (): void => { + variableToDelete.current = null; + setDeleteVariableModal(false); + }; + + const validateVariableName = (name: string): boolean => { + return !variables[name]; + }; + + const columns = [ + { + title: 'Variable', + dataIndex: 'name', + key: 'name', + }, + { + title: 'Definition', + dataIndex: 'description', + key: 'description', + }, + { + title: 'Actions', + key: 'action', + render: (_: IDashboardVariable): JSX.Element => ( + + + + + ), + }, + ]; + + return ( + <> + {variableViewMode ? ( + + ) : ( + <> + + + + + + )} + + Are you sure you want to delete variable{' '} + {variableToDelete.current}? + + + ); +} + +interface DispatchProps { + updateDashboardVariables: ( + props: Record, + ) => (dispatch: Dispatch) => void; +} + +const mapDispatchToProps = ( + dispatch: ThunkDispatch, +): DispatchProps => ({ + updateDashboardVariables: bindActionCreators( + UpdateDashboardVariables, + dispatch, + ), +}); + +export default connect(null, mapDispatchToProps)(VariablesSetting); diff --git a/frontend/src/container/NewDashboard/DashboardSettings/Variables/types.ts b/frontend/src/container/NewDashboard/DashboardSettings/Variables/types.ts new file mode 100644 index 0000000000..877372f653 --- /dev/null +++ b/frontend/src/container/NewDashboard/DashboardSettings/Variables/types.ts @@ -0,0 +1 @@ +export type TVariableViewMode = 'EDIT' | 'ADD'; diff --git a/frontend/src/container/NewDashboard/DashboardSettings/index.tsx b/frontend/src/container/NewDashboard/DashboardSettings/index.tsx new file mode 100644 index 0000000000..d8cfa57ff6 --- /dev/null +++ b/frontend/src/container/NewDashboard/DashboardSettings/index.tsx @@ -0,0 +1,22 @@ +import { Tabs } from 'antd'; +import React from 'react'; + +import GeneralDashboardSettings from './General'; +import VariablesSetting from './Variables'; + +const { TabPane } = Tabs; + +function DashboardSettingsContent(): JSX.Element { + return ( + + + + + + + + + ); +} + +export default DashboardSettingsContent; diff --git a/frontend/src/container/NewDashboard/DashboardVariablesSelection/VariableItem.tsx b/frontend/src/container/NewDashboard/DashboardVariablesSelection/VariableItem.tsx new file mode 100644 index 0000000000..b40d113be4 --- /dev/null +++ b/frontend/src/container/NewDashboard/DashboardVariablesSelection/VariableItem.tsx @@ -0,0 +1,137 @@ +import { orange } from '@ant-design/colors'; +import { WarningOutlined } from '@ant-design/icons'; +import { Input, Popover, Select, Typography } from 'antd'; +import query from 'api/dashboard/variables/query'; +import { commaValuesParser } from 'lib/dashbaordVariables/customCommaValuesParser'; +import sortValues from 'lib/dashbaordVariables/sortVariableValues'; +import { map } from 'lodash-es'; +import React, { useCallback, useEffect, useState } from 'react'; +import { IDashboardVariable } from 'types/api/dashboard/getAll'; + +import { VariableContainer, VariableName } from './styles'; + +const { Option } = Select; + +const ALL_SELECT_VALUE = '__ALL__'; + +interface VariableItemProps { + variableData: IDashboardVariable; + onValueUpdate: (name: string | undefined, arg1: string | string[]) => void; + onAllSelectedUpdate: (name: string | undefined, arg1: boolean) => void; +} +function VariableItem({ + variableData, + onValueUpdate, + onAllSelectedUpdate, +}: VariableItemProps): JSX.Element { + const [optionsData, setOptionsData] = useState([]); + const [isLoading, setIsLoading] = useState(false); + + const [errorMessage, setErrorMessage] = useState(null); + const getOptions = useCallback(async (): Promise => { + if (variableData.type === 'QUERY') { + try { + setErrorMessage(null); + setIsLoading(true); + + const response = await query({ + query: variableData.queryValue || '', + }); + + setIsLoading(false); + if (response.error) { + setErrorMessage(response.error); + return; + } + if (response.payload?.variableValues) + setOptionsData( + sortValues(response.payload?.variableValues, variableData.sort) as never, + ); + } catch (e) { + console.error(e); + } + } else if (variableData.type === 'CUSTOM') { + setOptionsData( + sortValues( + commaValuesParser(variableData.customValue || ''), + variableData.sort, + ) as never, + ); + } + }, [ + variableData.customValue, + variableData.queryValue, + variableData.sort, + variableData.type, + ]); + + useEffect(() => { + getOptions(); + }, [getOptions]); + + const handleChange = (value: string | string[]): void => { + if ( + value === ALL_SELECT_VALUE || + (Array.isArray(value) && value.includes(ALL_SELECT_VALUE)) + ) { + onValueUpdate(variableData.name, optionsData); + onAllSelectedUpdate(variableData.name, true); + } else { + onValueUpdate(variableData.name, value); + onAllSelectedUpdate(variableData.name, false); + } + }; + return ( + + ${variableData.name} + {variableData.type === 'TEXTBOX' ? ( + { + handleChange(e.target.value || ''); + }} + style={{ + width: 50 + ((variableData.selectedValue?.length || 0) * 7 || 50), + }} + /> + ) : ( + + )} + {errorMessage && ( + + {errorMessage}}> + + + + )} + + ); +} + +export default VariableItem; diff --git a/frontend/src/container/NewDashboard/DashboardVariablesSelection/index.tsx b/frontend/src/container/NewDashboard/DashboardVariablesSelection/index.tsx new file mode 100644 index 0000000000..c3aad577bf --- /dev/null +++ b/frontend/src/container/NewDashboard/DashboardVariablesSelection/index.tsx @@ -0,0 +1,72 @@ +import { Row } from 'antd'; +import { map, sortBy } from 'lodash-es'; +import React from 'react'; +import { connect, useSelector } from 'react-redux'; +import { bindActionCreators, Dispatch } from 'redux'; +import { ThunkDispatch } from 'redux-thunk'; +import { UpdateDashboardVariables } from 'store/actions/dashboard/updatedDashboardVariables'; +import { AppState } from 'store/reducers'; +import AppActions from 'types/actions'; +import { IDashboardVariable } from 'types/api/dashboard/getAll'; +import DashboardReducer from 'types/reducer/dashboards'; + +import VariableItem from './VariableItem'; + +function DashboardVariableSelection({ + updateDashboardVariables, +}: DispatchProps): JSX.Element { + const { dashboards } = useSelector( + (state) => state.dashboards, + ); + const [selectedDashboard] = dashboards; + const { + data: { variables = {} }, + } = selectedDashboard; + + const onValueUpdate = ( + name: string, + value: IDashboardVariable['selectedValue'], + ): void => { + const updatedVariablesData = { ...variables }; + updatedVariablesData[name].selectedValue = value; + updateDashboardVariables(updatedVariablesData); + }; + const onAllSelectedUpdate = ( + name: string, + value: IDashboardVariable['allSelected'], + ): void => { + const updatedVariablesData = { ...variables }; + updatedVariablesData[name].allSelected = value; + updateDashboardVariables(updatedVariablesData); + }; + + return ( + + {map(sortBy(Object.keys(variables)), (variableName) => ( + + ))} + + ); +} + +interface DispatchProps { + updateDashboardVariables: ( + props: Parameters[0], + ) => (dispatch: Dispatch) => void; +} + +const mapDispatchToProps = ( + dispatch: ThunkDispatch, +): DispatchProps => ({ + updateDashboardVariables: bindActionCreators( + UpdateDashboardVariables, + dispatch, + ), +}); + +export default connect(null, mapDispatchToProps)(DashboardVariableSelection); diff --git a/frontend/src/container/NewDashboard/DashboardVariablesSelection/styles.ts b/frontend/src/container/NewDashboard/DashboardVariablesSelection/styles.ts new file mode 100644 index 0000000000..9b4b1d3322 --- /dev/null +++ b/frontend/src/container/NewDashboard/DashboardVariablesSelection/styles.ts @@ -0,0 +1,19 @@ +import { grey } from '@ant-design/colors'; +import { Typography } from 'antd'; +import styled from 'styled-components'; + +export const VariableContainer = styled.div` + border: 1px solid ${grey[1]}66; + border-radius: 2px; + padding: 0; + padding-left: 0.5rem; + display: flex; + align-items: center; + margin-bottom: 0.3rem; +`; + +export const VariableName = styled(Typography)` + font-size: 0.8rem; + font-style: italic; + color: ${grey[0]}; +`; diff --git a/frontend/src/container/NewDashboard/DescriptionOfDashboard/SettingsDrawer.tsx b/frontend/src/container/NewDashboard/DescriptionOfDashboard/SettingsDrawer.tsx new file mode 100644 index 0000000000..b8f6617204 --- /dev/null +++ b/frontend/src/container/NewDashboard/DescriptionOfDashboard/SettingsDrawer.tsx @@ -0,0 +1,37 @@ +import { SettingOutlined } from '@ant-design/icons'; +import { Button } from 'antd'; +import React, { useState } from 'react'; + +import DashboardSettingsContent from '../DashboardSettings'; +import { DrawerContainer } from './styles'; + +function SettingsDrawer(): JSX.Element { + const [visible, setVisible] = useState(false); // TODO Make it False + + const showDrawer = (): void => { + setVisible(true); + }; + + const onClose = (): void => { + setVisible(false); + }; + + return ( + <> + + + + + + ); +} + +export default SettingsDrawer; diff --git a/frontend/src/container/NewDashboard/DescriptionOfDashboard/index.tsx b/frontend/src/container/NewDashboard/DescriptionOfDashboard/index.tsx index bcaa553e62..c1a98cce8f 100644 --- a/frontend/src/container/NewDashboard/DescriptionOfDashboard/index.tsx +++ b/frontend/src/container/NewDashboard/DescriptionOfDashboard/index.tsx @@ -1,135 +1,69 @@ -import { - EditOutlined, - SaveOutlined, - ShareAltOutlined, -} from '@ant-design/icons'; -import { Card, Col, Row, Space, Tag, Typography } from 'antd'; -import AddTags from 'container/NewDashboard/DescriptionOfDashboard/AddTags'; -import NameOfTheDashboard from 'container/NewDashboard/DescriptionOfDashboard/NameOfTheDashboard'; +import { ShareAltOutlined } from '@ant-design/icons'; +import { Button, Card, Col, Row, Space, Tag, Typography } from 'antd'; import useComponentPermission from 'hooks/useComponentPermission'; -import React, { useCallback, useState } from 'react'; +import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { connect, useSelector } from 'react-redux'; -import { bindActionCreators, Dispatch } from 'redux'; -import { ThunkDispatch } from 'redux-thunk'; -import { - ToggleEditMode, - UpdateDashboardTitleDescriptionTags, - UpdateDashboardTitleDescriptionTagsProps, -} from 'store/actions'; +import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; -import AppActions from 'types/actions'; import AppReducer from 'types/reducer/app'; import DashboardReducer from 'types/reducer/dashboards'; -import Description from './Description'; +import DashboardVariableSelection from '../DashboardVariablesSelection'; +import SettingsDrawer from './SettingsDrawer'; import ShareModal from './ShareModal'; -import { Button, Container } from './styles'; -function DescriptionOfDashboard({ - updateDashboardTitleDescriptionTags, - toggleEditMode, -}: DescriptionOfDashboardProps): JSX.Element { - const { dashboards, isEditMode } = useSelector( +function DescriptionOfDashboard(): JSX.Element { + const { dashboards } = useSelector( (state) => state.dashboards, ); const [selectedDashboard] = dashboards; const selectedData = selectedDashboard.data; - const { title } = selectedData; - const { tags } = selectedData; - const { description } = selectedData; + const { title, tags, description } = selectedData; - const [updatedTitle, setUpdatedTitle] = useState(title); - const [updatedTags, setUpdatedTags] = useState(tags || []); - const [updatedDescription, setUpdatedDescription] = useState( - description || '', - ); const [isJSONModalVisible, isIsJSONModalVisible] = useState(false); const { t } = useTranslation('common'); const { role } = useSelector((state) => state.app); const [editDashboard] = useComponentPermission(['edit_dashboard'], role); - const onClickEditHandler = useCallback(() => { - if (isEditMode) { - const dashboard = selectedDashboard; - // @TODO need to update this function to take title,description,tags only - updateDashboardTitleDescriptionTags({ - dashboard: { - ...dashboard, - data: { - ...dashboard.data, - description: updatedDescription, - tags: updatedTags, - title: updatedTitle, - }, - }, - }); - } else { - toggleEditMode(); - } - }, [ - isEditMode, - updatedTitle, - updatedTags, - updatedDescription, - selectedDashboard, - toggleEditMode, - updateDashboardTitleDescriptionTags, - ]); - const onToggleHandler = (): void => { isIsJSONModalVisible((state) => !state); }; return ( - - {!isEditMode ? ( - - {title} - - {tags?.map((e) => ( - {e} - ))} - - - {description} - - - ) : ( - - - - - - )} - - - + + + + {title} + + {description} +
+ {tags?.map((e) => ( + {e} + ))} +
+ + + - - {editDashboard && ( - - )} @@ -137,23 +71,4 @@ function DescriptionOfDashboard({ ); } -interface DispatchProps { - updateDashboardTitleDescriptionTags: ( - props: UpdateDashboardTitleDescriptionTagsProps, - ) => (dispatch: Dispatch) => void; - toggleEditMode: () => void; -} - -const mapDispatchToProps = ( - dispatch: ThunkDispatch, -): DispatchProps => ({ - updateDashboardTitleDescriptionTags: bindActionCreators( - UpdateDashboardTitleDescriptionTags, - dispatch, - ), - toggleEditMode: bindActionCreators(ToggleEditMode, dispatch), -}); - -type DescriptionOfDashboardProps = DispatchProps; - -export default connect(null, mapDispatchToProps)(DescriptionOfDashboard); +export default DescriptionOfDashboard; diff --git a/frontend/src/container/NewDashboard/DescriptionOfDashboard/styles.ts b/frontend/src/container/NewDashboard/DescriptionOfDashboard/styles.ts index 74794e163c..43bcccef51 100644 --- a/frontend/src/container/NewDashboard/DescriptionOfDashboard/styles.ts +++ b/frontend/src/container/NewDashboard/DescriptionOfDashboard/styles.ts @@ -1,4 +1,4 @@ -import { Button as ButtonComponent } from 'antd'; +import { Button as ButtonComponent, Drawer } from 'antd'; import styled from 'styled-components'; export const Container = styled.div` @@ -11,3 +11,10 @@ export const Button = styled(ButtonComponent)` align-items: center; } `; + +export const DrawerContainer = styled(Drawer)` + .ant-drawer-header { + padding: 0; + border: none; + } +`; diff --git a/frontend/src/container/NewWidget/index.tsx b/frontend/src/container/NewWidget/index.tsx index 82627f8d22..eb38d54ae4 100644 --- a/frontend/src/container/NewWidget/index.tsx +++ b/frontend/src/container/NewWidget/index.tsx @@ -1,6 +1,7 @@ import { Button, Modal, Typography } from 'antd'; import ROUTES from 'constants/routes'; import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; +import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables'; import history from 'lib/history'; import { DashboardWidgetPageParams } from 'pages/DashboardWidget'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; @@ -143,6 +144,7 @@ function NewWidget({ widgetId: selectedWidget?.id || '', graphType: selectedGraph, globalSelectedInterval, + variables: getDashboardVariables(), }); } }, [ diff --git a/frontend/src/lib/dashbaordVariables/customCommaValuesParser.ts b/frontend/src/lib/dashbaordVariables/customCommaValuesParser.ts new file mode 100644 index 0000000000..01b5ccc2eb --- /dev/null +++ b/frontend/src/lib/dashbaordVariables/customCommaValuesParser.ts @@ -0,0 +1,20 @@ +export const commaValuesParser = (query: string): (string | number)[] => { + if (!query) { + return []; + } + const match = query.match(/(?:\\,|[^,])+/g) ?? []; + + const options: string[] = match.map((text) => { + // eslint-disable-next-line no-param-reassign + text = text.replace(/\\,/g, ','); + const textMatch = /^(.+)\s:\s(.+)$/g.exec(text) ?? []; + if (textMatch.length === 3) { + const [, , value] = textMatch; + return value.trim(); + } + return text.trim(); + }); + return options.map((option): string | number => + Number.isNaN(Number(option)) ? option : Number(option), + ); +}; diff --git a/frontend/src/lib/dashbaordVariables/getDashboardVariables.ts b/frontend/src/lib/dashbaordVariables/getDashboardVariables.ts new file mode 100644 index 0000000000..1f5a66a2a2 --- /dev/null +++ b/frontend/src/lib/dashbaordVariables/getDashboardVariables.ts @@ -0,0 +1,38 @@ +import GetMinMax from 'lib/getMinMax'; +import GetStartAndEndTime from 'lib/getStartAndEndTime'; +import store from 'store'; + +export const getDashboardVariables = (): Record => { + try { + const { + globalTime, + dashboards: { dashboards }, + } = store.getState(); + const [selectedDashboard] = dashboards; + const { + data: { variables }, + } = selectedDashboard; + + const minMax = GetMinMax(globalTime.selectedTime, [ + globalTime.minTime / 1000000, + globalTime.maxTime / 1000000, + ]); + + const { start, end } = GetStartAndEndTime({ + type: 'GLOBAL_TIME', + minTime: minMax.minTime, + maxTime: minMax.maxTime, + }); + const variablesTuple: Record = { + SIGNOZ_START_TIME: parseInt(start, 10) * 1e3, + SIGNOZ_END_TIME: parseInt(end, 10) * 1e3, + }; + Object.keys(variables).forEach((key) => { + variablesTuple[key] = variables[key].selectedValue; + }); + return variablesTuple; + } catch (e) { + console.error(e); + } + return {}; +}; diff --git a/frontend/src/lib/dashbaordVariables/sortVariableValues.ts b/frontend/src/lib/dashbaordVariables/sortVariableValues.ts new file mode 100644 index 0000000000..0937f70412 --- /dev/null +++ b/frontend/src/lib/dashbaordVariables/sortVariableValues.ts @@ -0,0 +1,15 @@ +import { sortBy } from 'lodash-es'; +import { TSortVariableValuesType } from 'types/api/dashboard/getAll'; + +type TValuesDataType = (string | number | boolean)[]; +const sortValues = ( + values: TValuesDataType, + sortType: TSortVariableValuesType, +): TValuesDataType => { + if (sortType === 'ASC') return sortBy(values); + if (sortType === 'DESC') return sortBy(values).reverse(); + + return values; +}; + +export default sortValues; diff --git a/frontend/src/store/actions/dashboard/deleteWidget.ts b/frontend/src/store/actions/dashboard/deleteWidget.ts index 3aea4902b8..8509cdbc46 100644 --- a/frontend/src/store/actions/dashboard/deleteWidget.ts +++ b/frontend/src/store/actions/dashboard/deleteWidget.ts @@ -30,6 +30,7 @@ export const DeleteWidget = ({ tags: selectedDashboard.data.tags, widgets: updatedWidgets, layout: updatedLayout, + variables: selectedDashboard.data.variables, }, uuid: selectedDashboard.uuid, }; diff --git a/frontend/src/store/actions/dashboard/getQueryResults.ts b/frontend/src/store/actions/dashboard/getQueryResults.ts index 88cd3bbf07..1090c57bea 100644 --- a/frontend/src/store/actions/dashboard/getQueryResults.ts +++ b/frontend/src/store/actions/dashboard/getQueryResults.ts @@ -19,7 +19,7 @@ import { Dispatch } from 'redux'; import store from 'store'; import AppActions from 'types/actions'; import { ErrorResponse, SuccessResponse } from 'types/api'; -import { Query } from 'types/api/dashboard/getAll'; +import { IDashboardVariable, Query } from 'types/api/dashboard/getAll'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; import { EDataSource, EPanelType, EQueryType } from 'types/common/dashboard'; import { GlobalReducer } from 'types/reducer/globalTime'; @@ -29,11 +29,13 @@ export async function GetMetricQueryRange({ globalSelectedInterval, graphType, selectedTime, + variables = {}, }: { query: Query; graphType: GRAPH_TYPES; selectedTime: timePreferenceType; globalSelectedInterval: Time; + variables?: Record; }): Promise | ErrorResponse> { const { queryType } = query; const queryKey: Record = @@ -138,6 +140,7 @@ export async function GetMetricQueryRange({ start: parseInt(start, 10) * 1e3, end: parseInt(end, 10) * 1e3, step: getStep({ start, end, inputFormat: 'ms' }), + variables, ...QueryPayload, }); if (response.statusCode >= 400) { @@ -173,6 +176,14 @@ export const GetQueryResults = ( ): ((dispatch: Dispatch) => void) => { return async (dispatch: Dispatch): Promise => { try { + dispatch({ + type: 'QUERY_ERROR', + payload: { + errorMessage: '', + widgetId: props.widgetId, + errorBoolean: false, + }, + }); const response = await GetMetricQueryRange(props); const isError = response.error; @@ -199,14 +210,6 @@ export const GetQueryResults = ( }, }, }); - dispatch({ - type: 'QUERY_ERROR', - payload: { - errorMessage: '', - widgetId: props.widgetId, - errorBoolean: false, - }, - }); } catch (error) { dispatch({ type: 'QUERY_ERROR', @@ -226,4 +229,5 @@ export interface GetQueryResultsProps { query: Query; graphType: ITEMS; globalSelectedInterval: GlobalReducer['selectedTime']; + variables: Record; } diff --git a/frontend/src/store/actions/dashboard/updatedDashboardVariables.ts b/frontend/src/store/actions/dashboard/updatedDashboardVariables.ts new file mode 100644 index 0000000000..20bbbd0a06 --- /dev/null +++ b/frontend/src/store/actions/dashboard/updatedDashboardVariables.ts @@ -0,0 +1,38 @@ +import { notification } from 'antd'; +import update from 'api/dashboard/update'; +import { Dispatch } from 'redux'; +import store from 'store/index'; +import AppActions from 'types/actions'; +import { UPDATE_DASHBOARD_VARIABLES } from 'types/actions/dashboard'; +import { IDashboardVariable } from 'types/api/dashboard/getAll'; + +export const UpdateDashboardVariables = ( + variables: Record, +): ((dispatch: Dispatch) => void) => { + return async (dispatch: Dispatch): Promise => { + try { + dispatch({ + type: UPDATE_DASHBOARD_VARIABLES, + payload: variables, + }); + + const reduxStoreState = store.getState(); + const [dashboard] = reduxStoreState.dashboards.dashboards; + + const response = await update({ + data: { + ...dashboard.data, + }, + uuid: dashboard.uuid, + }); + + if (response.statusCode !== 200) { + notification.error({ + message: response.error, + }); + } + } catch (error) { + console.error(error); + } + }; +}; diff --git a/frontend/src/store/reducers/dashboard.ts b/frontend/src/store/reducers/dashboard.ts index 61c5cc7351..0ef474ea32 100644 --- a/frontend/src/store/reducers/dashboard.ts +++ b/frontend/src/store/reducers/dashboard.ts @@ -18,6 +18,7 @@ import { SAVE_SETTING_TO_PANEL_SUCCESS, TOGGLE_EDIT_MODE, UPDATE_DASHBOARD, + UPDATE_DASHBOARD_VARIABLES, UPDATE_QUERY, UPDATE_TITLE_DESCRIPTION_TAGS_SUCCESS, } from 'types/actions/dashboard'; @@ -170,7 +171,6 @@ const dashboard = ( case QUERY_ERROR: { const { widgetId, errorMessage, errorBoolean = true } = action.payload; - const [selectedDashboard] = state.dashboards; const { data } = selectedDashboard; @@ -397,7 +397,25 @@ const dashboard = ( ], }; } + case UPDATE_DASHBOARD_VARIABLES: { + const variablesData = action.payload; + const { dashboards } = state; + const [selectedDashboard] = dashboards; + const { data } = selectedDashboard; + return { + ...state, + dashboards: [ + { + ...selectedDashboard, + data: { + ...data, + variables: variablesData, + }, + }, + ], + }; + } default: return state; } diff --git a/frontend/src/types/actions/dashboard.ts b/frontend/src/types/actions/dashboard.ts index 3330f028d4..4745771cc0 100644 --- a/frontend/src/types/actions/dashboard.ts +++ b/frontend/src/types/actions/dashboard.ts @@ -1,6 +1,11 @@ import { Layout } from 'react-grid-layout'; import { ApplySettingsToPanelProps } from 'store/actions/dashboard/applySettingsToPanel'; -import { Dashboard, Query, Widgets } from 'types/api/dashboard/getAll'; +import { + Dashboard, + IDashboardVariable, + Query, + Widgets, +} from 'types/api/dashboard/getAll'; import { QueryData } from 'types/api/widgets/getQuery'; export const GET_DASHBOARD = 'GET_DASHBOARD'; @@ -42,6 +47,8 @@ export const IS_ADD_WIDGET = 'IS_ADD_WIDGET'; export const DELETE_QUERY = 'DELETE_QUERY'; export const FLUSH_DASHBOARD = 'FLUSH_DASHBOARD'; +export const UPDATE_DASHBOARD_VARIABLES = 'UPDATE_DASHBOARD_VARIABLES'; + interface GetDashboard { type: typeof GET_DASHBOARD; payload: Dashboard; @@ -174,6 +181,10 @@ interface DeleteQuery { interface FlushDashboard { type: typeof FLUSH_DASHBOARD; } +interface UpdateDashboardVariables { + type: typeof UPDATE_DASHBOARD_VARIABLES; + payload: Record; +} export type DashboardActions = | GetDashboard @@ -194,4 +205,5 @@ export type DashboardActions = | IsAddWidget | UpdateQuery | DeleteQuery - | FlushDashboard; + | FlushDashboard + | UpdateDashboardVariables; diff --git a/frontend/src/types/api/dashboard/getAll.ts b/frontend/src/types/api/dashboard/getAll.ts index c158bff826..2a2617bb99 100644 --- a/frontend/src/types/api/dashboard/getAll.ts +++ b/frontend/src/types/api/dashboard/getAll.ts @@ -11,6 +11,31 @@ import { QueryData } from '../widgets/getQuery'; export type PayloadProps = Dashboard[]; +export const VariableQueryTypeArr = ['QUERY', 'TEXTBOX', 'CUSTOM'] as const; +export type TVariableQueryType = typeof VariableQueryTypeArr[number]; + +export const VariableSortTypeArr = ['DISABLED', 'ASC', 'DESC'] as const; +export type TSortVariableValuesType = typeof VariableSortTypeArr[number]; + +export interface IDashboardVariable { + name?: string; // key will be the source of truth + description: string; + type: TVariableQueryType; + // Query + queryValue?: string; + // Custom + customValue?: string; + // Textbox + textboxValue?: string; + + sort: TSortVariableValuesType; + multiSelect: boolean; + showALLOption: boolean; + selectedValue?: null | string | string[]; + // Internal use + modificationUUID?: string; + allSelected?: boolean; +} export interface Dashboard { id: number; uuid: string; @@ -26,6 +51,7 @@ export interface DashboardData { widgets?: Widgets[]; title: string; layout?: Layout[]; + variables: Record; } export interface IBaseWidget { diff --git a/frontend/src/types/api/dashboard/variables/query.ts b/frontend/src/types/api/dashboard/variables/query.ts new file mode 100644 index 0000000000..e715378c66 --- /dev/null +++ b/frontend/src/types/api/dashboard/variables/query.ts @@ -0,0 +1,7 @@ +export type Props = { + query: string; +}; + +export type PayloadProps = { + variableValues: string[] | number[]; +}; diff --git a/frontend/src/types/api/metrics/getQueryRange.ts b/frontend/src/types/api/metrics/getQueryRange.ts index da28970542..bbe9c697a9 100644 --- a/frontend/src/types/api/metrics/getQueryRange.ts +++ b/frontend/src/types/api/metrics/getQueryRange.ts @@ -5,5 +5,6 @@ export interface MetricRangePayloadProps { data: { result: QueryData[]; resultType: string; + variables: Record; }; } From 8556c87d465366f3b6b871ada8373b26a24d4a68 Mon Sep 17 00:00:00 2001 From: Srikanth Chekuri Date: Sun, 11 Sep 2022 03:34:02 +0530 Subject: [PATCH 06/17] feat: add support for dashboard variables (#1557) --- .../app/clickhouseReader/reader.go | 38 +++++++- pkg/query-service/app/http_handler.go | 94 +++++++++++++++---- .../app/metrics/query_builder.go | 48 ++++++++-- pkg/query-service/app/parser/metrics.go | 39 ++++++++ pkg/query-service/interfaces/interface.go | 2 + pkg/query-service/model/queryParams.go | 11 ++- pkg/query-service/model/response.go | 4 + 7 files changed, 203 insertions(+), 33 deletions(-) diff --git a/pkg/query-service/app/clickhouseReader/reader.go b/pkg/query-service/app/clickhouseReader/reader.go index 464ba3e272..024cb1cd65 100644 --- a/pkg/query-service/app/clickhouseReader/reader.go +++ b/pkg/query-service/app/clickhouseReader/reader.go @@ -2813,7 +2813,7 @@ func (r *ClickHouseReader) GetMetricResult(ctx context.Context, query string) ([ if err != nil { zap.S().Debug("Error in processing query: ", err) - return nil, fmt.Errorf("error in processing query") + return nil, err } var ( @@ -3239,3 +3239,39 @@ func (r *ClickHouseReader) AggregateLogs(ctx context.Context, params *model.Logs return &aggregateResponse, nil } + +func (r *ClickHouseReader) QueryDashboardVars(ctx context.Context, query string) (*model.DashboardVar, error) { + var result model.DashboardVar + rows, err := r.db.Query(ctx, query) + + zap.S().Info(query) + + if err != nil { + zap.S().Debug("Error in processing sql query: ", err) + return nil, err + } + + var ( + columnTypes = rows.ColumnTypes() + vars = make([]interface{}, len(columnTypes)) + ) + for i := range columnTypes { + vars[i] = reflect.New(columnTypes[i].ScanType()).Interface() + } + + defer rows.Close() + for rows.Next() { + if err := rows.Scan(vars...); err != nil { + return nil, err + } + for _, v := range vars { + switch v := v.(type) { + case *string, *int8, *int16, *int32, *int64, *uint8, *uint16, *uint32, *uint64, *float32, *float64, *time.Time, *bool: + result.VariableValues = append(result.VariableValues, reflect.ValueOf(v).Elem().Interface()) + default: + return nil, fmt.Errorf("unsupported value type encountered") + } + } + } + return &result, nil +} diff --git a/pkg/query-service/app/http_handler.go b/pkg/query-service/app/http_handler.go index 0a63e38c6b..4ee4702028 100644 --- a/pkg/query-service/app/http_handler.go +++ b/pkg/query-service/app/http_handler.go @@ -9,7 +9,9 @@ import ( "io/ioutil" "net/http" "strconv" + "strings" "sync" + "text/template" "time" "github.com/gorilla/mux" @@ -320,6 +322,7 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router) { router.HandleFunc("/api/v1/dashboards/{uuid}", ViewAccess(aH.getDashboard)).Methods(http.MethodGet) router.HandleFunc("/api/v1/dashboards/{uuid}", EditAccess(aH.updateDashboard)).Methods(http.MethodPut) router.HandleFunc("/api/v1/dashboards/{uuid}", EditAccess(aH.deleteDashboard)).Methods(http.MethodDelete) + router.HandleFunc("/api/v1/variables/query", ViewAccess(aH.queryDashboardVars)).Methods(http.MethodGet) router.HandleFunc("/api/v1/feedback", OpenAccess(aH.submitFeedback)).Methods(http.MethodPost) // router.HandleFunc("/api/v1/get_percentiles", aH.getApplicationPercentiles).Methods(http.MethodGet) @@ -483,9 +486,11 @@ func (aH *APIHandler) queryRangeMetricsV2(w http.ResponseWriter, r *http.Request type channelResult struct { Series []*model.Series Err error + Name string + Query string } - execClickHouseQueries := func(queries map[string]string) ([]*model.Series, error) { + execClickHouseQueries := func(queries map[string]string) ([]*model.Series, error, map[string]string) { var seriesList []*model.Series ch := make(chan channelResult, len(queries)) var wg sync.WaitGroup @@ -500,7 +505,7 @@ func (aH *APIHandler) queryRangeMetricsV2(w http.ResponseWriter, r *http.Request } if err != nil { - ch <- channelResult{Err: fmt.Errorf("error in query-%s: %v", name, err)} + ch <- channelResult{Err: fmt.Errorf("error in query-%s: %v", name, err), Name: name, Query: query} return } ch <- channelResult{Series: seriesList} @@ -511,21 +516,23 @@ func (aH *APIHandler) queryRangeMetricsV2(w http.ResponseWriter, r *http.Request close(ch) var errs []error + errQuriesByName := make(map[string]string) // read values from the channel for r := range ch { if r.Err != nil { errs = append(errs, r.Err) + errQuriesByName[r.Name] = r.Query continue } seriesList = append(seriesList, r.Series...) } if len(errs) != 0 { - return nil, fmt.Errorf("encountered multiple errors: %s", metrics.FormatErrs(errs, "\n")) + return nil, fmt.Errorf("encountered multiple errors: %s", metrics.FormatErrs(errs, "\n")), errQuriesByName } - return seriesList, nil + return seriesList, nil, nil } - execPromQueries := func(metricsQueryRangeParams *model.QueryRangeParamsV2) ([]*model.Series, error) { + execPromQueries := func(metricsQueryRangeParams *model.QueryRangeParamsV2) ([]*model.Series, error, map[string]string) { var seriesList []*model.Series ch := make(chan channelResult, len(metricsQueryRangeParams.CompositeMetricQuery.PromQueries)) var wg sync.WaitGroup @@ -538,6 +545,19 @@ func (aH *APIHandler) queryRangeMetricsV2(w http.ResponseWriter, r *http.Request go func(name string, query *model.PromQuery) { var seriesList []*model.Series defer wg.Done() + tmpl := template.New("promql-query") + tmpl, tmplErr := tmpl.Parse(query.Query) + if tmplErr != nil { + ch <- channelResult{Err: fmt.Errorf("error in parsing query-%s: %v", name, tmplErr), Name: name, Query: query.Query} + return + } + var queryBuf bytes.Buffer + tmplErr = tmpl.Execute(&queryBuf, metricsQueryRangeParams.Variables) + if tmplErr != nil { + ch <- channelResult{Err: fmt.Errorf("error in parsing query-%s: %v", name, tmplErr), Name: name, Query: query.Query} + return + } + query.Query = queryBuf.String() queryModel := model.QueryRangeParams{ Start: time.UnixMilli(metricsQueryRangeParams.Start), End: time.UnixMilli(metricsQueryRangeParams.End), @@ -546,7 +566,7 @@ func (aH *APIHandler) queryRangeMetricsV2(w http.ResponseWriter, r *http.Request } promResult, _, err := (*aH.reader).GetQueryRangeResult(r.Context(), &queryModel) if err != nil { - ch <- channelResult{Err: fmt.Errorf("error in query-%s: %v", name, err)} + ch <- channelResult{Err: fmt.Errorf("error in query-%s: %v", name, err), Name: name, Query: query.Query} return } matrix, _ := promResult.Matrix() @@ -567,22 +587,25 @@ func (aH *APIHandler) queryRangeMetricsV2(w http.ResponseWriter, r *http.Request close(ch) var errs []error + errQuriesByName := make(map[string]string) // read values from the channel for r := range ch { if r.Err != nil { errs = append(errs, r.Err) + errQuriesByName[r.Name] = r.Query continue } seriesList = append(seriesList, r.Series...) } if len(errs) != 0 { - return nil, fmt.Errorf("encountered multiple errors: %s", metrics.FormatErrs(errs, "\n")) + return nil, fmt.Errorf("encountered multiple errors: %s", metrics.FormatErrs(errs, "\n")), errQuriesByName } - return seriesList, nil + return seriesList, nil, nil } var seriesList []*model.Series var err error + var errQuriesByName map[string]string switch metricsQueryRangeParams.CompositeMetricQuery.QueryType { case model.QUERY_BUILDER: runQueries := metrics.PrepareBuilderMetricQueries(metricsQueryRangeParams, constants.SIGNOZ_TIMESERIES_TABLENAME) @@ -590,7 +613,7 @@ func (aH *APIHandler) queryRangeMetricsV2(w http.ResponseWriter, r *http.Request respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: runQueries.Err}, nil) return } - seriesList, err = execClickHouseQueries(runQueries.Queries) + seriesList, err, errQuriesByName = execClickHouseQueries(runQueries.Queries) case model.CLICKHOUSE: queries := make(map[string]string) @@ -598,20 +621,32 @@ func (aH *APIHandler) queryRangeMetricsV2(w http.ResponseWriter, r *http.Request if chQuery.Disabled { continue } - queries[name] = chQuery.Query + tmpl := template.New("clickhouse-query") + tmpl, err := tmpl.Parse(chQuery.Query) + if err != nil { + respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil) + return + } + var query bytes.Buffer + err = tmpl.Execute(&query, metricsQueryRangeParams.Variables) + if err != nil { + respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil) + return + } + queries[name] = query.String() } - seriesList, err = execClickHouseQueries(queries) + seriesList, err, errQuriesByName = execClickHouseQueries(queries) case model.PROM: - seriesList, err = execPromQueries(metricsQueryRangeParams) + seriesList, err, errQuriesByName = execPromQueries(metricsQueryRangeParams) default: err = fmt.Errorf("invalid query type") - respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil) + respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, errQuriesByName) return } if err != nil { apiErrObj := &model.ApiError{Typ: model.ErrorBadData, Err: err} - respondError(w, apiErrObj, nil) + respondError(w, apiErrObj, errQuriesByName) return } if metricsQueryRangeParams.CompositeMetricQuery.PanelType == model.QUERY_VALUE && @@ -707,6 +742,25 @@ func (aH *APIHandler) deleteDashboard(w http.ResponseWriter, r *http.Request) { } +func (aH *APIHandler) queryDashboardVars(w http.ResponseWriter, r *http.Request) { + + query := r.URL.Query().Get("query") + if query == "" { + respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("query is required")}, nil) + return + } + if strings.Contains(strings.ToLower(query), "alter table") { + respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("query shouldn't alter data")}, nil) + return + } + dashboardVars, err := (*aH.reader).QueryDashboardVars(r.Context(), query) + if err != nil { + respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil) + return + } + aH.respond(w, dashboardVars) +} + func (aH *APIHandler) updateDashboard(w http.ResponseWriter, r *http.Request) { uuid := mux.Vars(r)["uuid"] @@ -1034,11 +1088,11 @@ func (aH *APIHandler) queryRangeMetrics(w http.ResponseWriter, r *http.Request) if res.Err != nil { switch res.Err.(type) { case promql.ErrQueryCanceled: - respondError(w, &model.ApiError{model.ErrorCanceled, res.Err}, nil) + respondError(w, &model.ApiError{Typ: model.ErrorCanceled, Err: res.Err}, nil) case promql.ErrQueryTimeout: - respondError(w, &model.ApiError{model.ErrorTimeout, res.Err}, nil) + respondError(w, &model.ApiError{Typ: model.ErrorTimeout, Err: res.Err}, nil) } - respondError(w, &model.ApiError{model.ErrorExec, res.Err}, nil) + respondError(w, &model.ApiError{Typ: model.ErrorExec, Err: res.Err}, nil) } response_data := &model.QueryData{ @@ -1088,11 +1142,11 @@ func (aH *APIHandler) queryMetrics(w http.ResponseWriter, r *http.Request) { if res.Err != nil { switch res.Err.(type) { case promql.ErrQueryCanceled: - respondError(w, &model.ApiError{model.ErrorCanceled, res.Err}, nil) + respondError(w, &model.ApiError{Typ: model.ErrorCanceled, Err: res.Err}, nil) case promql.ErrQueryTimeout: - respondError(w, &model.ApiError{model.ErrorTimeout, res.Err}, nil) + respondError(w, &model.ApiError{Typ: model.ErrorTimeout, Err: res.Err}, nil) } - respondError(w, &model.ApiError{model.ErrorExec, res.Err}, nil) + respondError(w, &model.ApiError{Typ: model.ErrorExec, Err: res.Err}, nil) } response_data := &model.QueryData{ diff --git a/pkg/query-service/app/metrics/query_builder.go b/pkg/query-service/app/metrics/query_builder.go index 26f57261b9..72d7ee183d 100644 --- a/pkg/query-service/app/metrics/query_builder.go +++ b/pkg/query-service/app/metrics/query_builder.go @@ -8,6 +8,7 @@ import ( "github.com/SigNoz/govaluate" "go.signoz.io/query-service/constants" "go.signoz.io/query-service/model" + "go.uber.org/zap" ) type RunQueries struct { @@ -50,8 +51,8 @@ func GoValuateFuncs() map[string]govaluate.ExpressionFunction { return GoValuateFuncs } -// formattedValue formats the value to be used in clickhouse query -func formattedValue(v interface{}) string { +// FormattedValue formats the value to be used in clickhouse query +func FormattedValue(v interface{}) string { switch x := v.(type) { case int: return fmt.Sprintf("%d", x) @@ -62,6 +63,9 @@ func formattedValue(v interface{}) string { case bool: return fmt.Sprintf("%v", x) case []interface{}: + if len(x) == 0 { + return "" + } switch x[0].(type) { case string: str := "[" @@ -75,10 +79,12 @@ func formattedValue(v interface{}) string { return str case int, float32, float64, bool: return strings.Join(strings.Fields(fmt.Sprint(x)), ",") + default: + zap.L().Error("invalid type for formatted value", zap.Any("type", reflect.TypeOf(x[0]))) + return "" } - return "" default: - // may be log the warning here? + zap.L().Error("invalid type for formatted value", zap.Any("type", reflect.TypeOf(x))) return "" } } @@ -87,7 +93,7 @@ func formattedValue(v interface{}) string { // timeseries based on search criteria func BuildMetricsTimeSeriesFilterQuery(fs *model.FilterSet, groupTags []string, metricName string, aggregateOperator model.AggregateOperator) (string, error) { var conditions []string - conditions = append(conditions, fmt.Sprintf("metric_name = %s", formattedValue(metricName))) + conditions = append(conditions, fmt.Sprintf("metric_name = %s", FormattedValue(metricName))) if fs != nil && len(fs.Items) != 0 { for _, item := range fs.Items { toFormat := item.Value @@ -102,7 +108,7 @@ func BuildMetricsTimeSeriesFilterQuery(fs *model.FilterSet, groupTags []string, toFormat = x[0] } } - fmtVal := formattedValue(toFormat) + fmtVal := FormattedValue(toFormat) switch op { case "eq": conditions = append(conditions, fmt.Sprintf("labels_object.%s = %s", item.Key, fmtVal)) @@ -152,7 +158,7 @@ func BuildMetricQuery(qp *model.QueryRangeParamsV2, mq *model.MetricQuery, table return "", err } - samplesTableTimeFilter := fmt.Sprintf("metric_name = %s AND timestamp_ms >= %d AND timestamp_ms <= %d", formattedValue(mq.MetricName), qp.Start, qp.End) + samplesTableTimeFilter := fmt.Sprintf("metric_name = %s AND timestamp_ms >= %d AND timestamp_ms <= %d", FormattedValue(mq.MetricName), qp.Start, qp.End) // Select the aggregate value for interval queryTmpl := @@ -419,3 +425,31 @@ func PrepareBuilderMetricQueries(qp *model.QueryRangeParamsV2, tableName string) } return &RunQueries{Queries: namedQueries} } + +// PromFormattedValue formats the value to be used in promql +func PromFormattedValue(v interface{}) string { + switch x := v.(type) { + case int: + return fmt.Sprintf("%d", x) + case float32, float64: + return fmt.Sprintf("%f", x) + case string: + return fmt.Sprintf("%s", x) + case bool: + return fmt.Sprintf("%v", x) + case []interface{}: + if len(x) == 0 { + return "" + } + switch x[0].(type) { + case string, int, float32, float64, bool: + return strings.Trim(strings.Join(strings.Fields(fmt.Sprint(x)), "|"), "[]") + default: + zap.L().Error("invalid type for prom formatted value", zap.Any("type", reflect.TypeOf(x[0]))) + return "" + } + default: + zap.L().Error("invalid type for prom formatted value", zap.Any("type", reflect.TypeOf(x))) + return "" + } +} diff --git a/pkg/query-service/app/parser/metrics.go b/pkg/query-service/app/parser/metrics.go index ce4d079fa5..c8ec53a322 100644 --- a/pkg/query-service/app/parser/metrics.go +++ b/pkg/query-service/app/parser/metrics.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "net/http" + "strings" "go.signoz.io/query-service/app/metrics" "go.signoz.io/query-service/model" @@ -36,6 +37,44 @@ func ParseMetricQueryRangeParams(r *http.Request) (*model.QueryRangeParamsV2, *m if err := validateQueryRangeParamsV2(postData); err != nil { return nil, &model.ApiError{Typ: model.ErrorBadData, Err: err} } + // prepare the variables for the corrspnding query type + formattedVars := make(map[string]interface{}) + for name, value := range postData.Variables { + if postData.CompositeMetricQuery.QueryType == model.PROM { + formattedVars[name] = metrics.PromFormattedValue(value) + } else if postData.CompositeMetricQuery.QueryType == model.CLICKHOUSE { + formattedVars[name] = metrics.FormattedValue(value) + } + } + // replace the variables in metrics builder filter item with actual value + if postData.CompositeMetricQuery.QueryType == model.QUERY_BUILDER { + for _, query := range postData.CompositeMetricQuery.BuilderQueries { + for idx := range query.TagFilters.Items { + item := &query.TagFilters.Items[idx] + value := item.Value + if value != nil { + switch x := value.(type) { + case string: + variableName := strings.Trim(x, "{{ . }}") + if _, ok := postData.Variables[variableName]; ok { + item.Value = postData.Variables[variableName] + } + case []interface{}: + if len(x) > 0 { + switch x[0].(type) { + case string: + variableName := strings.Trim(x[0].(string), "{{ . }}") + if _, ok := postData.Variables[variableName]; ok { + item.Value = postData.Variables[variableName] + } + } + } + } + } + } + } + } + postData.Variables = formattedVars return postData, nil } diff --git a/pkg/query-service/interfaces/interface.go b/pkg/query-service/interfaces/interface.go index 5367021500..9dc8b23b07 100644 --- a/pkg/query-service/interfaces/interface.go +++ b/pkg/query-service/interfaces/interface.go @@ -70,4 +70,6 @@ type Reader interface { // Connection needed for rules, not ideal but required GetConn() clickhouse.Conn + + QueryDashboardVars(ctx context.Context, query string) (*model.DashboardVar, error) } diff --git a/pkg/query-service/model/queryParams.go b/pkg/query-service/model/queryParams.go index 33827d63c1..06d416537a 100644 --- a/pkg/query-service/model/queryParams.go +++ b/pkg/query-service/model/queryParams.go @@ -118,11 +118,12 @@ const ( ) type QueryRangeParamsV2 struct { - DataSource DataSource `json:"dataSource"` - Start int64 `json:"start"` - End int64 `json:"end"` - Step int64 `json:"step"` - CompositeMetricQuery *CompositeMetricQuery `json:"compositeMetricQuery"` + DataSource DataSource `json:"dataSource"` + Start int64 `json:"start"` + End int64 `json:"end"` + Step int64 `json:"step"` + CompositeMetricQuery *CompositeMetricQuery `json:"compositeMetricQuery"` + Variables map[string]interface{} `json:"variables,omitempty"` } // Metric auto complete types diff --git a/pkg/query-service/model/response.go b/pkg/query-service/model/response.go index bc07bea356..a78f93d8c0 100644 --- a/pkg/query-service/model/response.go +++ b/pkg/query-service/model/response.go @@ -492,3 +492,7 @@ func (s *ServiceItem) MarshalJSON() ([]byte, error) { Alias: (*Alias)(s), }) } + +type DashboardVar struct { + VariableValues []interface{} `json:"variableValues"` +} From ac86d840f970e10f20733c5ee2633eea8b39502e Mon Sep 17 00:00:00 2001 From: Srikanth Chekuri Date: Mon, 12 Sep 2022 12:30:36 +0530 Subject: [PATCH 07/17] fix: reuse the query engine and storage for alerts pqlEngine (#1558) --- pkg/query-service/app/clickhouseReader/reader.go | 13 ++++++++++++- pkg/query-service/app/server.go | 6 ++++-- pkg/query-service/interfaces/interface.go | 3 +++ pkg/query-service/pqlEngine/engine.go | 11 ++++++++++- 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/pkg/query-service/app/clickhouseReader/reader.go b/pkg/query-service/app/clickhouseReader/reader.go index 024cb1cd65..cabb84f3a4 100644 --- a/pkg/query-service/app/clickhouseReader/reader.go +++ b/pkg/query-service/app/clickhouseReader/reader.go @@ -94,6 +94,7 @@ type ClickHouseReader struct { logsResourceKeys string queryEngine *promql.Engine remoteStorage *remote.Storage + fanoutStorage *storage.Storage promConfigFile string promConfig *config.Config @@ -143,7 +144,7 @@ func NewReader(localDB *sqlx.DB, configFile string) *ClickHouseReader { } } -func (r *ClickHouseReader) Start() { +func (r *ClickHouseReader) Start(readerReady chan bool) { logLevel := promlog.AllowedLevel{} logLevel.Set("debug") // allowedFormat := promlog.AllowedFormat{} @@ -311,6 +312,8 @@ func (r *ClickHouseReader) Start() { } r.queryEngine = queryEngine r.remoteStorage = remoteStorage + r.fanoutStorage = &fanoutStorage + readerReady <- true if err := g.Run(); err != nil { level.Error(logger).Log("err", err) @@ -319,6 +322,14 @@ func (r *ClickHouseReader) Start() { } +func (r *ClickHouseReader) GetQueryEngine() *promql.Engine { + return r.queryEngine +} + +func (r *ClickHouseReader) GetFanoutStorage() *storage.Storage { + return r.fanoutStorage +} + func reloadConfig(filename string, logger log.Logger, rls ...func(*config.Config) error) (promConfig *config.Config, err error) { level.Info(logger).Log("msg", "Loading configuration file", "filename", filename) diff --git a/pkg/query-service/app/server.go b/pkg/query-service/app/server.go index a34bb0b477..2a04302ef7 100644 --- a/pkg/query-service/app/server.go +++ b/pkg/query-service/app/server.go @@ -77,18 +77,20 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) { } localDB.SetMaxOpenConns(10) + readerReady := make(chan bool) var reader interfaces.Reader storage := os.Getenv("STORAGE") if storage == "clickhouse" { zap.S().Info("Using ClickHouse as datastore ...") clickhouseReader := clickhouseReader.NewReader(localDB, serverOptions.PromConfigPath) - go clickhouseReader.Start() + go clickhouseReader.Start(readerReady) reader = clickhouseReader } else { return nil, fmt.Errorf("Storage type: %s is not supported in query service", storage) } + <-readerReady rm, err := makeRulesManager(serverOptions.PromConfigPath, constants.GetAlertManagerApiPrefix(), serverOptions.RuleRepoURL, localDB, reader, serverOptions.DisableRules) if err != nil { return nil, err @@ -362,7 +364,7 @@ func makeRulesManager( disableRules bool) (*rules.Manager, error) { // create engine - pqle, err := pqle.FromConfigPath(promConfigPath) + pqle, err := pqle.FromReader(ch) if err != nil { return nil, fmt.Errorf("failed to create pql engine : %v", err) } diff --git a/pkg/query-service/interfaces/interface.go b/pkg/query-service/interfaces/interface.go index 9dc8b23b07..4f4da853c6 100644 --- a/pkg/query-service/interfaces/interface.go +++ b/pkg/query-service/interfaces/interface.go @@ -5,6 +5,7 @@ import ( "github.com/ClickHouse/clickhouse-go/v2" "github.com/prometheus/prometheus/promql" + "github.com/prometheus/prometheus/storage" "github.com/prometheus/prometheus/util/stats" am "go.signoz.io/query-service/integrations/alertManager" "go.signoz.io/query-service/model" @@ -70,6 +71,8 @@ type Reader interface { // Connection needed for rules, not ideal but required GetConn() clickhouse.Conn + GetQueryEngine() *promql.Engine + GetFanoutStorage() *storage.Storage QueryDashboardVars(ctx context.Context, query string) (*model.DashboardVar, error) } diff --git a/pkg/query-service/pqlEngine/engine.go b/pkg/query-service/pqlEngine/engine.go index e9a45ad542..47bde314ee 100644 --- a/pkg/query-service/pqlEngine/engine.go +++ b/pkg/query-service/pqlEngine/engine.go @@ -3,6 +3,8 @@ package promql import ( "context" "fmt" + "time" + "github.com/go-kit/log" pmodel "github.com/prometheus/common/model" plog "github.com/prometheus/common/promlog" @@ -11,7 +13,7 @@ import ( pql "github.com/prometheus/prometheus/promql" pstorage "github.com/prometheus/prometheus/storage" premote "github.com/prometheus/prometheus/storage/remote" - "time" + "go.signoz.io/query-service/interfaces" ) type PqlEngine struct { @@ -29,6 +31,13 @@ func FromConfigPath(promConfigPath string) (*PqlEngine, error) { return NewPqlEngine(c) } +func FromReader(ch interfaces.Reader) (*PqlEngine, error) { + return &PqlEngine{ + engine: ch.GetQueryEngine(), + fanoutStorage: *ch.GetFanoutStorage(), + }, nil +} + func NewPqlEngine(config *pconfig.Config) (*PqlEngine, error) { logLevel := plog.AllowedLevel{} From 0ccd7777bfc7bd530c88a43d2979b2e77aaacfa1 Mon Sep 17 00:00:00 2001 From: Srikanth Chekuri Date: Mon, 12 Sep 2022 16:11:55 +0530 Subject: [PATCH 08/17] fix: make widget plot work with missing data points (#1559) --- frontend/src/lib/getChartData.ts | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/frontend/src/lib/getChartData.ts b/frontend/src/lib/getChartData.ts index c3eefb0055..9f5c0c1a61 100644 --- a/frontend/src/lib/getChartData.ts +++ b/frontend/src/lib/getChartData.ts @@ -6,6 +6,16 @@ import convertIntoEpoc from './covertIntoEpoc'; import { colors } from './getRandomColor'; const getChartData = ({ queryData }: GetChartDataProps): ChartData => { + const uniqueTimeLabels = new Set(); + queryData.forEach((data) => { + data.queryData.forEach((query) => { + query.values.forEach((value) => { + uniqueTimeLabels.add(value[0]); + }); + }); + }); + const labels = Array.from(uniqueTimeLabels).sort((a, b) => a - b); + const response = queryData.map( ({ queryData, query: queryG, legend: legendG }) => { return queryData.map((e) => { @@ -22,11 +32,24 @@ const getChartData = ({ queryData }: GetChartDataProps): ChartData => { second: Number(parseFloat(second)), }; }); + // Fill the missing data with null + const filledDataValues = Array.from(labels).map((e) => { + const td1 = new Date(parseInt(convertIntoEpoc(e * 1000), 10)); + const data = dataValue.find((e1) => { + return e1.first.getTime() === td1.getTime(); + }); + return ( + data || { + first: new Date(parseInt(convertIntoEpoc(e * 1000), 10)), + second: null, + } + ); + }); return { label: labelNames !== 'undefined' ? labelNames : '', - first: dataValue.map((e) => e.first), - second: dataValue.map((e) => e.second), + first: filledDataValues.map((e) => e.first), + second: filledDataValues.map((e) => e.second), }; }); }, From 1ec92489755db5acb37df4a0582a360bea2d8d08 Mon Sep 17 00:00:00 2001 From: Pranshu Chittora Date: Mon, 12 Sep 2022 16:26:01 +0530 Subject: [PATCH 09/17] feat: getting started page (#1560) Co-authored-by: palashgdev --- frontend/public/Logos/elixir.png | Bin 0 -> 4523 bytes frontend/public/Logos/go.png | Bin 0 -> 2968 bytes frontend/public/Logos/java.png | Bin 0 -> 3259 bytes frontend/public/Logos/javascript.png | Bin 0 -> 1682 bytes frontend/public/Logos/ms-net-framework.png | Bin 0 -> 4252 bytes frontend/public/Logos/php.png | Bin 0 -> 3476 bytes frontend/public/Logos/python.png | Bin 0 -> 2722 bytes frontend/public/Logos/rails.png | Bin 0 -> 1705 bytes frontend/public/Logos/rust.png | Bin 0 -> 2302 bytes frontend/src/constants/routes.ts | 2 +- frontend/src/container/SideNav/Slack.tsx | 14 +- frontend/src/container/SideNav/menuItems.ts | 2 +- .../container/TopNav/Breadcrumbs/index.tsx | 2 +- .../src/pages/AddInstrumentation/DocCard.tsx | 30 +++ .../src/pages/AddInstrumentation/Section.tsx | 43 +++++ .../src/pages/AddInstrumentation/index.tsx | 42 +---- .../pages/AddInstrumentation/renderConfig.tsx | 175 ++++++++++++++++++ .../src/pages/AddInstrumentation/styles.ts | 12 +- .../src/pages/AddInstrumentation/types.ts | 10 + .../src/pages/AddInstrumentation/utmParams.ts | 3 + 20 files changed, 295 insertions(+), 40 deletions(-) create mode 100644 frontend/public/Logos/elixir.png create mode 100644 frontend/public/Logos/go.png create mode 100644 frontend/public/Logos/java.png create mode 100644 frontend/public/Logos/javascript.png create mode 100644 frontend/public/Logos/ms-net-framework.png create mode 100644 frontend/public/Logos/php.png create mode 100644 frontend/public/Logos/python.png create mode 100644 frontend/public/Logos/rails.png create mode 100644 frontend/public/Logos/rust.png create mode 100644 frontend/src/pages/AddInstrumentation/DocCard.tsx create mode 100644 frontend/src/pages/AddInstrumentation/Section.tsx create mode 100644 frontend/src/pages/AddInstrumentation/renderConfig.tsx create mode 100644 frontend/src/pages/AddInstrumentation/types.ts create mode 100644 frontend/src/pages/AddInstrumentation/utmParams.ts diff --git a/frontend/public/Logos/elixir.png b/frontend/public/Logos/elixir.png new file mode 100644 index 0000000000000000000000000000000000000000..909a736e0f07d0b771dd63c713e3947e8570b7fc GIT binary patch literal 4523 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 JPDHLkV1hZ3oKOG& literal 0 HcmV?d00001 diff --git a/frontend/public/Logos/go.png b/frontend/public/Logos/go.png new file mode 100644 index 0000000000000000000000000000000000000000..70f7bd9728a60137acf5db06995e4d827f39d946 GIT binary patch literal 2968 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 literal 0 HcmV?d00001 diff --git a/frontend/public/Logos/python.png b/frontend/public/Logos/python.png new file mode 100644 index 0000000000000000000000000000000000000000..664f75d425fc7027bde93c4b39beb0105b2ef7e0 GIT binary patch literal 2722 zcmV;T3SISyP)J0Hl+(nqIUEB98|?M&dXG8Y>zm7S?94LjFjSHDlSZ>| zcIWuM-}&AwKnW$3P(leMlu$wmjzDmGAl-+NwsFm0D66a%)*eb3et;E@nM@CMAH$DUtVHL-aw^7$GlAtZaX zL=ZHx^*cE22*M1ywneGeR#>> z?$|yT7=1k{65oJ5|kU0^Vil}aKuS{W-F22qRkN?@2;vjAXU zJv{P-Pm^TiR=BpdHh^p9nyy1z9K@&NHx-y*5lvbB(8f@8A2C`H*eR_;Nh@M)9wK7RbV`A|fb)35L(Z;sT>6v4zLz|2bBZx56-=8|N=k>EY zzW>~jE-2DvMiUOcWshxN9h)?JJ8g&WB!&lzrC!o)zp-2EAQ0Vt6DO-a!5*&dou)bLn@a1b>=@TI2hEiJVnC;~-^ zP%~>~q-yf~E!39Prc#EDU7vmHYaU~;olIFjW2`1bJN`=kkOh$MEyzMtM18A&`Ao}V zC?eF05dlrV_sh$u2Q0ULaEI~+*j^CXgP@={L}J2m@lcJmkL< zT;>JWB21mOvO?0rE*XMGUd`56C1>pvfe>>^B*rpmm_b^A`CJ=w>F|z?>nCkt5so-| zffgckUsvO6a!Jj7pMWC1DIhPAf$5>m7S^Z7K+Mx(x)5@-_E{ZALTH>@Zf-?D3|$(gmPMM21F_ci{P2*- z()H6$K9zU{XELX9B-cX?B`Dpk2HrfB=wXbwT!!)Act7!zZ&u8>=kA%m35U>fy&OtJ z4doHASp<6A??Yq3C0u)gkWFVKW<;7GlLCvQnuRgW@yWrLDzCo!i)~NT&2G3OvN#y< z-&^n|k}HEkDx2lXL!mkIXah8zmOa)?@@67n8s~!RwFkE%P_t&QW5wKzeFJ0`B|X@; z{l4-&e{S8j=*4K%zl!>|^SuaBiGb2-(=8g3mJ>w>qM|Kv{g?=%fcwEETqnlf?rYtY zoRF98aensaxlCJa{~!0ZerMs!(Q;~iM*)aV%Crgji4PYji+ShL8Pza%#U-^OMCd)3 zBLaUVzrn#)uOO+Oj-URlwRvqcQZ~==g2YJx=h9~q06PmHZM}V&PH+kL%~D54MilsV z0+xQj>J?Z_4xE1bL_=-)dix6zdjYUUOwdEX))T~=dX69p$-Zm3C!#Pob9|ikRWKeL1#q&qV2E)ix5qZZsOPBTY+5+_{N6;jTVlnCK$4fw z{F5$*3_)H#&_%(b)XdYGYVFmCan`3=4_mA`^1a~b=Jv-0L&%8a!sX1a0|zd0E_oFv z!s)-h_%WAG?k_w~uzH|f>P8WqiWY&Kd;516uAV*mbupJ| z!%8b`st|hbPmC@Kh22^cL@3}51#$$Ev?j@OJ=te}@Iv>W;5Mu=id}E)c(1Ff>AtyZ zzP8F=5u4#-12HUhIMnJQ#}8Ea1Dx`J0M;hP+SqWM*posUbEyOOf4f6kTjY!6GwSyaA`EmTPVN5jKCqdtuH7_qjAA);F%^_})#BDT z;lYIG1YY=vzOoeMiBB^OUhU}Pl{0Wypc9qv3e!TY@oaIFiNsc@bF!vQ3U63u$>T@#zY@`1P?{PdQMQY z_XtUwsju;vz|z+W;RQfw=gVe}$?q||gm)rF)(|1|6{P@V=n&xHQ$YHD3oO1Ob%K0HcXrEHp6)ydpCJ)o-+wL* zEnQgt{)C#~T>8Sa4uM^jFeF3+7{~&{e?s>e5Ck%KvNQF=r=IKn{rTQ(20VagB2bXs zf4h8nAcbF>(Ga{&SIc7brARd;%p$<%{=s~jylS80$;Eh5c=erQgHNt{xcyBkKpH&4 z?fP%CZ~gM>aO>lZvnSVu7SdN~i^FB4fn`x8O-fNJM_R;WIFlhgxt!R3b&&tyzh{#t zA9?27Rgf1My#0Uk?GnKVM%y+&TFb7P6Jh^xdRW+dxL0UzAL&5V#-fB0N+_X(5=tnc cg#SDK2UN38o*A2hx=6E$&3++&ouM;~3I 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 Y1!w-R)&)lw_W%F@07*qoM6N<$g4dL2UjP6A literal 0 HcmV?d00001 diff --git a/frontend/src/constants/routes.ts b/frontend/src/constants/routes.ts index c9fc9fe90a..6fe138d33b 100644 --- a/frontend/src/constants/routes.ts +++ b/frontend/src/constants/routes.ts @@ -6,7 +6,7 @@ const ROUTES = { TRACE: '/trace', TRACE_DETAIL: '/trace/:id', SETTINGS: '/settings', - INSTRUMENTATION: '/add-instrumentation', + INSTRUMENTATION: '/get-started', USAGE_EXPLORER: '/usage-explorer', APPLICATION: '/application', ALL_DASHBOARD: '/dashboard', diff --git a/frontend/src/container/SideNav/Slack.tsx b/frontend/src/container/SideNav/Slack.tsx index c0abe5d3ba..f4f1e8e5c6 100644 --- a/frontend/src/container/SideNav/Slack.tsx +++ b/frontend/src/container/SideNav/Slack.tsx @@ -1,10 +1,14 @@ import React from 'react'; -function Slack(): JSX.Element { +interface ISlackProps { + width?: number; + height?: number; +} +function Slack({ width, height }: ISlackProps): JSX.Element { return ( ); } +Slack.defaultProps = { + width: 28, + height: 28, +}; export default Slack; diff --git a/frontend/src/container/SideNav/menuItems.ts b/frontend/src/container/SideNav/menuItems.ts index d3e400dedf..f811c333a3 100644 --- a/frontend/src/container/SideNav/menuItems.ts +++ b/frontend/src/container/SideNav/menuItems.ts @@ -62,7 +62,7 @@ const menus: SidebarMenu[] = [ { Icon: ApiOutlined, to: ROUTES.INSTRUMENTATION, - name: 'Add instrumentation', + name: 'Get Started', }, ]; diff --git a/frontend/src/container/TopNav/Breadcrumbs/index.tsx b/frontend/src/container/TopNav/Breadcrumbs/index.tsx index a79e5e3885..85cb0227a1 100644 --- a/frontend/src/container/TopNav/Breadcrumbs/index.tsx +++ b/frontend/src/container/TopNav/Breadcrumbs/index.tsx @@ -8,7 +8,7 @@ const breadcrumbNameMap = { [ROUTES.TRACE]: 'Traces', [ROUTES.SERVICE_MAP]: 'Service Map', [ROUTES.USAGE_EXPLORER]: 'Usage Explorer', - [ROUTES.INSTRUMENTATION]: 'Add instrumentation', + [ROUTES.INSTRUMENTATION]: 'Get Started', [ROUTES.SETTINGS]: 'Settings', [ROUTES.DASHBOARD]: 'Dashboard', [ROUTES.ALL_ERROR]: 'Exceptions', diff --git a/frontend/src/pages/AddInstrumentation/DocCard.tsx b/frontend/src/pages/AddInstrumentation/DocCard.tsx new file mode 100644 index 0000000000..38bf63c16d --- /dev/null +++ b/frontend/src/pages/AddInstrumentation/DocCard.tsx @@ -0,0 +1,30 @@ +import { Typography } from 'antd'; +import React from 'react'; +import { useSelector } from 'react-redux'; +import { Link } from 'react-router-dom'; +import { AppState } from 'store/reducers'; +import AppReducer from 'types/reducer/app'; + +import { DocCardContainer } from './styles'; +import { TGetStartedContentDoc } from './types'; +import UTMParams from './utmParams'; + +interface IDocCardProps { + text: TGetStartedContentDoc['title']; + icon: TGetStartedContentDoc['icon']; + url: TGetStartedContentDoc['url']; +} +function DocCard({ icon, text, url }: IDocCardProps): JSX.Element { + const { isDarkMode } = useSelector((state) => state.app); + + return ( + + + {icon} + {text} + + + ); +} + +export default DocCard; diff --git a/frontend/src/pages/AddInstrumentation/Section.tsx b/frontend/src/pages/AddInstrumentation/Section.tsx new file mode 100644 index 0000000000..4a90b746de --- /dev/null +++ b/frontend/src/pages/AddInstrumentation/Section.tsx @@ -0,0 +1,43 @@ +import { Col, Row, Typography } from 'antd'; +import { map } from 'lodash-es'; +import React from 'react'; + +import DocCard from './DocCard'; +import { TGetStartedContentSection } from './types'; + +interface IDocSectionProps { + sectionData: TGetStartedContentSection; +} + +function DocSection({ sectionData }: IDocSectionProps): JSX.Element { + return ( +
+ {sectionData.heading} + + {sectionData.description && ( +
+ {sectionData.description} + + )} + {map(sectionData.items, (item, idx) => ( + + + + ))} + + + ); +} + +export default DocSection; diff --git a/frontend/src/pages/AddInstrumentation/index.tsx b/frontend/src/pages/AddInstrumentation/index.tsx index f4adc07b2c..5703239b51 100644 --- a/frontend/src/pages/AddInstrumentation/index.tsx +++ b/frontend/src/pages/AddInstrumentation/index.tsx @@ -1,43 +1,19 @@ import { Typography } from 'antd'; import React from 'react'; -import { useSelector } from 'react-redux'; -import { AppState } from 'store/reducers'; -import AppReducer from 'types/reducer/app'; -import { Container, Heading } from './styles'; +import { GetStartedContent } from './renderConfig'; +import DocSection from './Section'; function InstrumentationPage(): JSX.Element { - const { isDarkMode } = useSelector((state) => state.app); - return ( <> - Instrument your application - - Congrats, you have successfully installed SigNoz!{' '} - - To start seeing YOUR application data here, follow the instructions in the - docs - - - - https://signoz.io/docs/instrumentation/overview - -  If you face any issues, join our - -  slack community  - - to ask any questions or mail us at  - - support@signoz.io - - + + Congrats, you have successfully installed SigNoz! Now lets get some data in + and start deriving insights from them + + {GetStartedContent().map((section) => { + return ; + })} ); } diff --git a/frontend/src/pages/AddInstrumentation/renderConfig.tsx b/frontend/src/pages/AddInstrumentation/renderConfig.tsx new file mode 100644 index 0000000000..86aa6b5ed7 --- /dev/null +++ b/frontend/src/pages/AddInstrumentation/renderConfig.tsx @@ -0,0 +1,175 @@ +import { + AlertFilled, + AlignLeftOutlined, + ApiFilled, + BarChartOutlined, + DashboardFilled, + SoundFilled, +} from '@ant-design/icons'; +import { Typography } from 'antd'; +import Slack from 'container/SideNav/Slack'; +import React from 'react'; +import store from 'store'; + +import { TGetStartedContentSection } from './types'; + +export const GetStartedContent = (): TGetStartedContentSection[] => { + const { + app: { currentVersion }, + } = store.getState(); + return [ + { + heading: 'Send data from your applications to SigNoz', + items: [ + { + title: 'Instrument your Java Application', + icon: ( + + ), + url: 'https://signoz.io/docs/instrumentation/java/', + }, + { + title: 'Instrument your Python Application', + icon: ( + + ), + url: 'https://signoz.io/docs/instrumentation/python/', + }, + { + title: 'Instrument your JS Application', + icon: ( + + ), + url: 'https://signoz.io/docs/instrumentation/javascript/', + }, + { + title: 'Instrument your Go Application', + icon: ( + + ), + url: 'https://signoz.io/docs/instrumentation/golang/', + }, + { + title: 'Instrument your .NET Application', + icon: ( + + ), + url: 'https://signoz.io/docs/instrumentation/dotnet/', + }, + { + title: 'Instrument your PHP Application', + icon: ( + + ), + url: 'https://signoz.io/docs/instrumentation/php/', + }, + { + title: 'Instrument your Rails Application', + icon: ( + + ), + url: 'https://signoz.io/docs/instrumentation/ruby-on-rails/', + }, + { + title: 'Instrument your Rust Application', + icon: ( + + ), + url: 'https://signoz.io/docs/instrumentation/rust/', + }, + { + title: 'Instrument your Elixir Application', + icon: ( + + ), + url: 'https://signoz.io/docs/instrumentation/elixir/', + }, + ], + }, + { + heading: 'Send Metrics from your Infrastructure & create Dashboards', + items: [ + { + title: 'Send metrics to SigNoz', + icon: , + url: 'https://signoz.io/docs/userguide/send-metrics/', + }, + { + title: 'Create and Manage Dashboards', + icon: , + url: 'https://signoz.io/docs/userguide/manage-dashboards-and-panels/', + }, + ], + }, + { + heading: 'Send your logs to SigNoz', + items: [ + { + title: 'Send your logs to SigNoz', + icon: , + url: 'https://signoz.io/docs/userguide/logs/', + }, + { + title: 'Existing log collectors to SigNoz', + icon: , + url: 'https://signoz.io/docs/userguide/fluentbit_to_signoz/', + }, + ], + }, + { + heading: 'Create alerts on Metrics', + items: [ + { + title: 'Create alert rules on metrics', + icon: , + url: 'https://signoz.io/docs/userguide/alerts-management/', + }, + { + title: 'Configure alert notification channels', + icon: , + url: + 'https://signoz.io/docs/userguide/alerts-management/#setting-up-a-notification-channel', + }, + ], + }, + { + heading: 'Need help?', + description: ( + <> + {'Join our slack community and ask any question you may have on '} + + #support + + {' or '} + + #general + + + ), + + items: [ + { + title: 'Join SigNoz slack community ', + icon: ( +
+ +
+ ), + url: 'https://signoz.io/slack', + }, + ], + }, + ]; +}; diff --git a/frontend/src/pages/AddInstrumentation/styles.ts b/frontend/src/pages/AddInstrumentation/styles.ts index af4a0bfb29..6e1ec7ad44 100644 --- a/frontend/src/pages/AddInstrumentation/styles.ts +++ b/frontend/src/pages/AddInstrumentation/styles.ts @@ -1,4 +1,4 @@ -import { Card, Typography } from 'antd'; +import { Card, Row, Typography } from 'antd'; import styled from 'styled-components'; interface Props { @@ -18,3 +18,13 @@ export const Heading = styled(Typography)` margin-bottom: 1rem; } `; + +export const DocCardContainer = styled(Row)<{ + isDarkMode: boolean; +}>` + display: flex; + border: 1px solid ${({ isDarkMode }): string => (isDarkMode ? '#444' : '#ccc')}; + border-radius: 0.2rem; + align-items: center; + padding: 0.5rem 0.25rem; +`; diff --git a/frontend/src/pages/AddInstrumentation/types.ts b/frontend/src/pages/AddInstrumentation/types.ts new file mode 100644 index 0000000000..b544254038 --- /dev/null +++ b/frontend/src/pages/AddInstrumentation/types.ts @@ -0,0 +1,10 @@ +export type TGetStartedContentDoc = { + title: string; + icon: JSX.Element; + url: string; +}; +export type TGetStartedContentSection = { + heading: string; + description?: string | JSX.Element; + items: TGetStartedContentDoc[]; +}; diff --git a/frontend/src/pages/AddInstrumentation/utmParams.ts b/frontend/src/pages/AddInstrumentation/utmParams.ts new file mode 100644 index 0000000000..1aa01f503b --- /dev/null +++ b/frontend/src/pages/AddInstrumentation/utmParams.ts @@ -0,0 +1,3 @@ +const UTMParams = + '?utm_source=instrumentation_page&utm_medium=frontend&utm_term=language'; +export default UTMParams; From eaadc3bb953ed9636621dbe651dcc8fca16345cb Mon Sep 17 00:00:00 2001 From: Vishal Sharma Date: Mon, 12 Sep 2022 19:35:31 +0530 Subject: [PATCH 10/17] feat: introduce search trace ID component (#1551) * feat: searchTraceID checkpoint * feat: filter spans using TraceID from trace filter page Co-authored-by: palashgdev Co-authored-by: Srikanth Chekuri --- .../Panel/PanelBody/SearchTraceID/index.tsx | 127 ++++++++++++++++++ .../Trace/Filters/Panel/PanelBody/index.tsx | 19 ++- .../src/container/Trace/Filters/index.tsx | 1 + frontend/src/store/reducers/trace.ts | 1 + frontend/src/types/reducer/trace.ts | 7 +- .../app/clickhouseReader/reader.go | 17 +++ pkg/query-service/constants/constants.go | 1 + pkg/query-service/model/queryParams.go | 4 + 8 files changed, 170 insertions(+), 7 deletions(-) create mode 100644 frontend/src/container/Trace/Filters/Panel/PanelBody/SearchTraceID/index.tsx diff --git a/frontend/src/container/Trace/Filters/Panel/PanelBody/SearchTraceID/index.tsx b/frontend/src/container/Trace/Filters/Panel/PanelBody/SearchTraceID/index.tsx new file mode 100644 index 0000000000..d98bbf18fe --- /dev/null +++ b/frontend/src/container/Trace/Filters/Panel/PanelBody/SearchTraceID/index.tsx @@ -0,0 +1,127 @@ +import { Input, notification } from 'antd'; +import getFilters from 'api/trace/getFilters'; +import { AxiosError } from 'axios'; +import React, { useEffect, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { Dispatch } from 'redux'; +import { getFilter, updateURL } from 'store/actions/trace/util'; +import { AppState } from 'store/reducers'; +import AppActions from 'types/actions'; +import { UPDATE_ALL_FILTERS } from 'types/actions/trace'; +import { GlobalReducer } from 'types/reducer/globalTime'; +import { TraceReducer } from 'types/reducer/trace'; + +const { Search } = Input; + +function TraceID(): JSX.Element { + const { + selectedFilter, + filterToFetchData, + spansAggregate, + selectedTags, + userSelectedFilter, + isFilterExclude, + } = useSelector((state) => state.traces); + const dispatch = useDispatch>(); + const globalTime = useSelector( + (state) => state.globalTime, + ); + const [isLoading, setIsLoading] = useState(false); + const [userEnteredValue, setUserEnteredValue] = useState(''); + useEffect(() => { + setUserEnteredValue(selectedFilter.get('traceID')?.[0] || ''); + }, [selectedFilter]); + const onSearch = async (value: string): Promise => { + try { + setIsLoading(true); + const preSelectedFilter = new Map(selectedFilter); + const preUserSelected = new Map(userSelectedFilter); + + if (value !== '') { + preUserSelected.set('traceID', [value]); + preSelectedFilter.set('traceID', [value]); + } else { + preUserSelected.delete('traceID'); + preSelectedFilter.delete('traceID'); + } + const response = await getFilters({ + other: Object.fromEntries(preSelectedFilter), + end: String(globalTime.maxTime), + start: String(globalTime.minTime), + getFilters: filterToFetchData, + isFilterExclude, + }); + + if (response.statusCode === 200) { + const preFilter = getFilter(response.payload); + preFilter.set('traceID', { traceID: value }); + preFilter.forEach((value, key) => { + const values = Object.keys(value); + if (key !== 'duration' && values.length) { + preUserSelected.set(key, values); + } + }); + + dispatch({ + type: UPDATE_ALL_FILTERS, + payload: { + current: spansAggregate.currentPage, + filter: preFilter, + filterToFetchData, + selectedFilter: preSelectedFilter, + selectedTags, + userSelected: preUserSelected, + isFilterExclude, + order: spansAggregate.order, + pageSize: spansAggregate.pageSize, + orderParam: spansAggregate.orderParam, + }, + }); + + updateURL( + preSelectedFilter, + filterToFetchData, + spansAggregate.currentPage, + selectedTags, + isFilterExclude, + userSelectedFilter, + spansAggregate.order, + spansAggregate.pageSize, + spansAggregate.orderParam, + ); + } + } catch (error) { + notification.error({ + message: (error as AxiosError).toString() || 'Something went wrong', + }); + } finally { + setIsLoading(false); + } + }; + const onChange = (e: React.ChangeEvent): void => { + setUserEnteredValue(e.target.value); + }; + const onBlur = (): void => { + if (userEnteredValue !== selectedFilter.get('traceID')?.[0]) { + onSearch(userEnteredValue); + } + }; + return ( +
+ +
+ ); +} + +export default TraceID; diff --git a/frontend/src/container/Trace/Filters/Panel/PanelBody/index.tsx b/frontend/src/container/Trace/Filters/Panel/PanelBody/index.tsx index 5782a02f04..fda3599305 100644 --- a/frontend/src/container/Trace/Filters/Panel/PanelBody/index.tsx +++ b/frontend/src/container/Trace/Filters/Panel/PanelBody/index.tsx @@ -1,3 +1,4 @@ +/* eslint-disable no-nested-ternary */ import { Card } from 'antd'; import Spinner from 'components/Spinner'; import React from 'react'; @@ -7,6 +8,7 @@ import { TraceFilterEnum, TraceReducer } from 'types/reducer/trace'; import CommonCheckBox from './CommonCheckBox'; import Duration from './Duration'; +import TraceID from './SearchTraceID'; function PanelBody(props: PanelBodyProps): JSX.Element { const { type } = props; @@ -22,12 +24,17 @@ function PanelBody(props: PanelBodyProps): JSX.Element { ); } - - return ( - - {type === 'duration' ? : } - - ); + const renderBody = (type: TraceFilterEnum): JSX.Element => { + switch (type) { + case 'traceID': + return ; + case 'duration': + return ; + default: + return ; + } + }; + return {renderBody(type)}; } interface PanelBodyProps { diff --git a/frontend/src/container/Trace/Filters/index.tsx b/frontend/src/container/Trace/Filters/index.tsx index 95f73f4ed9..36edd206fe 100644 --- a/frontend/src/container/Trace/Filters/index.tsx +++ b/frontend/src/container/Trace/Filters/index.tsx @@ -16,6 +16,7 @@ export const AllTraceFilterEnum: TraceFilterEnum[] = [ 'httpMethod', 'httpRoute', 'httpUrl', + 'traceID', ]; function Filters(): JSX.Element { diff --git a/frontend/src/store/reducers/trace.ts b/frontend/src/store/reducers/trace.ts index 9b2b837478..ea6b9a7671 100644 --- a/frontend/src/store/reducers/trace.ts +++ b/frontend/src/store/reducers/trace.ts @@ -68,6 +68,7 @@ const initialValue: TraceReducer = { ['responseStatusCode', INITIAL_FILTER_VALUE], ['serviceName', INITIAL_FILTER_VALUE], ['status', INITIAL_FILTER_VALUE], + ['traceID', INITIAL_FILTER_VALUE], ]), }; diff --git a/frontend/src/types/reducer/trace.ts b/frontend/src/types/reducer/trace.ts index fed82dd0be..fa37924f5e 100644 --- a/frontend/src/types/reducer/trace.ts +++ b/frontend/src/types/reducer/trace.ts @@ -71,7 +71,8 @@ export type TraceFilterEnum = | 'serviceName' | 'status' | 'responseStatusCode' - | 'rpcMethod'; + | 'rpcMethod' + | 'traceID'; export const AllPanelHeading: { key: TraceFilterEnum; @@ -125,4 +126,8 @@ export const AllPanelHeading: { key: 'status', displayValue: 'Status', }, + { + key: 'traceID', + displayValue: 'Trace ID', + }, ]; diff --git a/pkg/query-service/app/clickhouseReader/reader.go b/pkg/query-service/app/clickhouseReader/reader.go index cabb84f3a4..b6bcc7b55e 100644 --- a/pkg/query-service/app/clickhouseReader/reader.go +++ b/pkg/query-service/app/clickhouseReader/reader.go @@ -936,6 +936,9 @@ func (r *ClickHouseReader) GetSpanFilters(ctx context.Context, queryParams *mode } args := []interface{}{clickhouse.Named("timestampL", strconv.FormatInt(queryParams.Start.UnixNano(), 10)), clickhouse.Named("timestampU", strconv.FormatInt(queryParams.End.UnixNano(), 10))} + if len(queryParams.TraceID) > 0 { + args = buildFilterArrayQuery(ctx, excludeMap, queryParams.TraceID, constants.TraceID, &query, args) + } if len(queryParams.ServiceName) > 0 { args = buildFilterArrayQuery(ctx, excludeMap, queryParams.ServiceName, constants.ServiceName, &query, args) } @@ -995,6 +998,8 @@ func (r *ClickHouseReader) GetSpanFilters(ctx context.Context, queryParams *mode for _, e := range queryParams.GetFilters { switch e { + case constants.TraceID: + continue case constants.ServiceName: finalQuery := fmt.Sprintf("SELECT serviceName, count() as count FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", r.traceDB, r.indexTable) finalQuery += query @@ -1271,6 +1276,9 @@ func (r *ClickHouseReader) GetFilteredSpans(ctx context.Context, queryParams *mo var query string args := []interface{}{clickhouse.Named("timestampL", strconv.FormatInt(queryParams.Start.UnixNano(), 10)), clickhouse.Named("timestampU", strconv.FormatInt(queryParams.End.UnixNano(), 10))} + if len(queryParams.TraceID) > 0 { + args = buildFilterArrayQuery(ctx, excludeMap, queryParams.TraceID, constants.TraceID, &query, args) + } if len(queryParams.ServiceName) > 0 { args = buildFilterArrayQuery(ctx, excludeMap, queryParams.ServiceName, constants.ServiceName, &query, args) } @@ -1461,6 +1469,9 @@ func (r *ClickHouseReader) GetTagFilters(ctx context.Context, queryParams *model var query string args := []interface{}{clickhouse.Named("timestampL", strconv.FormatInt(queryParams.Start.UnixNano(), 10)), clickhouse.Named("timestampU", strconv.FormatInt(queryParams.End.UnixNano(), 10))} + if len(queryParams.TraceID) > 0 { + args = buildFilterArrayQuery(ctx, excludeMap, queryParams.TraceID, constants.TraceID, &query, args) + } if len(queryParams.ServiceName) > 0 { args = buildFilterArrayQuery(ctx, excludeMap, queryParams.ServiceName, constants.ServiceName, &query, args) } @@ -1557,6 +1568,9 @@ func (r *ClickHouseReader) GetTagValues(ctx context.Context, queryParams *model. var query string args := []interface{}{clickhouse.Named("timestampL", strconv.FormatInt(queryParams.Start.UnixNano(), 10)), clickhouse.Named("timestampU", strconv.FormatInt(queryParams.End.UnixNano(), 10))} + if len(queryParams.TraceID) > 0 { + args = buildFilterArrayQuery(ctx, excludeMap, queryParams.TraceID, constants.TraceID, &query, args) + } if len(queryParams.ServiceName) > 0 { args = buildFilterArrayQuery(ctx, excludeMap, queryParams.ServiceName, constants.ServiceName, &query, args) } @@ -1864,6 +1878,9 @@ func (r *ClickHouseReader) GetFilteredSpansAggregates(ctx context.Context, query query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d minute) as time, %s FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", queryParams.StepSeconds/60, aggregation_query, r.traceDB, r.indexTable) } + if len(queryParams.TraceID) > 0 { + args = buildFilterArrayQuery(ctx, excludeMap, queryParams.TraceID, constants.TraceID, &query, args) + } if len(queryParams.ServiceName) > 0 { args = buildFilterArrayQuery(ctx, excludeMap, queryParams.ServiceName, constants.ServiceName, &query, args) } diff --git a/pkg/query-service/constants/constants.go b/pkg/query-service/constants/constants.go index 2e01c976cb..3c6e8f6317 100644 --- a/pkg/query-service/constants/constants.go +++ b/pkg/query-service/constants/constants.go @@ -41,6 +41,7 @@ var AmChannelApiPath = GetOrDefaultEnv("ALERTMANAGER_API_CHANNEL_PATH", "v1/rout var RELATIONAL_DATASOURCE_PATH = GetOrDefaultEnv("SIGNOZ_LOCAL_DB_PATH", "/var/lib/signoz/signoz.db") const ( + TraceID = "traceID" ServiceName = "serviceName" HttpRoute = "httpRoute" HttpCode = "httpCode" diff --git a/pkg/query-service/model/queryParams.go b/pkg/query-service/model/queryParams.go index 06d416537a..6d78e7438f 100644 --- a/pkg/query-service/model/queryParams.go +++ b/pkg/query-service/model/queryParams.go @@ -182,6 +182,7 @@ type TagQuery struct { } type GetFilteredSpansParams struct { + TraceID []string `json:"traceID"` ServiceName []string `json:"serviceName"` Operation []string `json:"operation"` Kind string `json:"kind"` @@ -209,6 +210,7 @@ type GetFilteredSpansParams struct { } type GetFilteredSpanAggregatesParams struct { + TraceID []string `json:"traceID"` ServiceName []string `json:"serviceName"` Operation []string `json:"operation"` Kind string `json:"kind"` @@ -237,6 +239,7 @@ type GetFilteredSpanAggregatesParams struct { } type SpanFilterParams struct { + TraceID []string `json:"traceID"` Status []string `json:"status"` ServiceName []string `json:"serviceName"` HttpRoute []string `json:"httpRoute"` @@ -259,6 +262,7 @@ type SpanFilterParams struct { } type TagFilterParams struct { + TraceID []string `json:"traceID"` Status []string `json:"status"` ServiceName []string `json:"serviceName"` HttpRoute []string `json:"httpRoute"` From c43dabdb0b8a84658edd2f17c094ca8b1dcec22d Mon Sep 17 00:00:00 2001 From: Pranshu Chittora Date: Mon, 12 Sep 2022 23:24:45 +0530 Subject: [PATCH 11/17] fix: dashboard variable getting deleted on edit instances (#1561) --- .../Variables/VariableItem/VariableItem.tsx | 11 ++++++++--- .../DashboardSettings/Variables/index.tsx | 10 ++++++++-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/frontend/src/container/NewDashboard/DashboardSettings/Variables/VariableItem/VariableItem.tsx b/frontend/src/container/NewDashboard/DashboardSettings/Variables/VariableItem/VariableItem.tsx index 31d443abff..f8a08f2677 100644 --- a/frontend/src/container/NewDashboard/DashboardSettings/Variables/VariableItem/VariableItem.tsx +++ b/frontend/src/container/NewDashboard/DashboardSettings/Variables/VariableItem/VariableItem.tsx @@ -33,7 +33,7 @@ const { Option } = Select; interface VariableItemProps { variableData: IDashboardVariable; onCancel: () => void; - onSave: (name: string, arg0: IDashboardVariable) => void; + onSave: (name: string, arg0: IDashboardVariable, arg1: string) => void; validateName: (arg0: string) => boolean; variableViewMode: TVariableViewMode; } @@ -118,8 +118,11 @@ function VariableItem({ modificationUUID: v4(), }; onSave( - (variableViewMode === 'EDIT' ? variableData.name : variableName) as string, + variableName, newVariableData, + (variableViewMode === 'EDIT' && variableName !== variableData.name + ? variableData.name + : '') as string, ); onCancel(); }; @@ -162,7 +165,9 @@ function VariableItem({ value={variableName} onChange={(e): void => { setVariableName(e.target.value); - setErrorName(!validateName(e.target.value)); + setErrorName( + !validateName(e.target.value) && e.target.value !== variableData.name, + ); }} />
diff --git a/frontend/src/container/NewDashboard/DashboardSettings/Variables/index.tsx b/frontend/src/container/NewDashboard/DashboardSettings/Variables/index.tsx index f070752ac4..02f2d3e9ae 100644 --- a/frontend/src/container/NewDashboard/DashboardSettings/Variables/index.tsx +++ b/frontend/src/container/NewDashboard/DashboardSettings/Variables/index.tsx @@ -62,13 +62,19 @@ function VariablesSetting({ const onVariableSaveHandler = ( name: string, variableData: IDashboardVariable, + oldName: string, ): void => { if (!variableData.name) { return; } + const newVariables = { ...variables }; - newVariables[variableData.name] = variableData; - if (variableViewMode === 'EDIT') delete newVariables[name]; + newVariables[name] = variableData; + + if (oldName) { + delete newVariables[oldName]; + } + updateDashboardVariables(newVariables); onDoneVariableViewMode(); }; From 080639781666ec1e07b6fad752dc67ab770b4097 Mon Sep 17 00:00:00 2001 From: palashgdev Date: Tue, 13 Sep 2022 12:00:09 +0530 Subject: [PATCH 12/17] feat: webpack chunk name is updated (#1562) --- frontend/src/AppRoutes/pageComponents.ts | 7 ++----- frontend/src/AppRoutes/routes.ts | 4 ++-- .../{AddInstrumentation => GettingStarted}/DocCard.tsx | 0 .../{AddInstrumentation => GettingStarted}/Section.tsx | 0 .../pages/{AddInstrumentation => GettingStarted}/index.tsx | 0 .../renderConfig.tsx | 0 .../pages/{AddInstrumentation => GettingStarted}/styles.ts | 0 .../pages/{AddInstrumentation => GettingStarted}/types.ts | 0 .../{AddInstrumentation => GettingStarted}/utmParams.ts | 0 9 files changed, 4 insertions(+), 7 deletions(-) rename frontend/src/pages/{AddInstrumentation => GettingStarted}/DocCard.tsx (100%) rename frontend/src/pages/{AddInstrumentation => GettingStarted}/Section.tsx (100%) rename frontend/src/pages/{AddInstrumentation => GettingStarted}/index.tsx (100%) rename frontend/src/pages/{AddInstrumentation => GettingStarted}/renderConfig.tsx (100%) rename frontend/src/pages/{AddInstrumentation => GettingStarted}/styles.ts (100%) rename frontend/src/pages/{AddInstrumentation => GettingStarted}/types.ts (100%) rename frontend/src/pages/{AddInstrumentation => GettingStarted}/utmParams.ts (100%) diff --git a/frontend/src/AppRoutes/pageComponents.ts b/frontend/src/AppRoutes/pageComponents.ts index 46cf8ec34a..c83dfcd991 100644 --- a/frontend/src/AppRoutes/pageComponents.ts +++ b/frontend/src/AppRoutes/pageComponents.ts @@ -35,11 +35,8 @@ export const SettingsPage = Loadable( () => import(/* webpackChunkName: "SettingsPage" */ 'pages/Settings'), ); -export const InstrumentationPage = Loadable( - () => - import( - /* webpackChunkName: "InstrumentationPage" */ 'pages/AddInstrumentation' - ), +export const GettingStarted = Loadable( + () => import(/* webpackChunkName: "GettingStarted" */ 'pages/GettingStarted'), ); export const DashboardPage = Loadable( diff --git a/frontend/src/AppRoutes/routes.ts b/frontend/src/AppRoutes/routes.ts index 876ece4af1..9bf52b39de 100644 --- a/frontend/src/AppRoutes/routes.ts +++ b/frontend/src/AppRoutes/routes.ts @@ -11,7 +11,7 @@ import { EditAlertChannelsAlerts, EditRulesPage, ErrorDetails, - InstrumentationPage, + GettingStarted, ListAllALertsPage, Login, Logs, @@ -85,7 +85,7 @@ const routes: AppRoutes[] = [ { path: ROUTES.INSTRUMENTATION, exact: true, - component: InstrumentationPage, + component: GettingStarted, isPrivate: true, key: 'INSTRUMENTATION', }, diff --git a/frontend/src/pages/AddInstrumentation/DocCard.tsx b/frontend/src/pages/GettingStarted/DocCard.tsx similarity index 100% rename from frontend/src/pages/AddInstrumentation/DocCard.tsx rename to frontend/src/pages/GettingStarted/DocCard.tsx diff --git a/frontend/src/pages/AddInstrumentation/Section.tsx b/frontend/src/pages/GettingStarted/Section.tsx similarity index 100% rename from frontend/src/pages/AddInstrumentation/Section.tsx rename to frontend/src/pages/GettingStarted/Section.tsx diff --git a/frontend/src/pages/AddInstrumentation/index.tsx b/frontend/src/pages/GettingStarted/index.tsx similarity index 100% rename from frontend/src/pages/AddInstrumentation/index.tsx rename to frontend/src/pages/GettingStarted/index.tsx diff --git a/frontend/src/pages/AddInstrumentation/renderConfig.tsx b/frontend/src/pages/GettingStarted/renderConfig.tsx similarity index 100% rename from frontend/src/pages/AddInstrumentation/renderConfig.tsx rename to frontend/src/pages/GettingStarted/renderConfig.tsx diff --git a/frontend/src/pages/AddInstrumentation/styles.ts b/frontend/src/pages/GettingStarted/styles.ts similarity index 100% rename from frontend/src/pages/AddInstrumentation/styles.ts rename to frontend/src/pages/GettingStarted/styles.ts diff --git a/frontend/src/pages/AddInstrumentation/types.ts b/frontend/src/pages/GettingStarted/types.ts similarity index 100% rename from frontend/src/pages/AddInstrumentation/types.ts rename to frontend/src/pages/GettingStarted/types.ts diff --git a/frontend/src/pages/AddInstrumentation/utmParams.ts b/frontend/src/pages/GettingStarted/utmParams.ts similarity index 100% rename from frontend/src/pages/AddInstrumentation/utmParams.ts rename to frontend/src/pages/GettingStarted/utmParams.ts From c9139c5236640ad122d1d81741e55f6c8b0fdea7 Mon Sep 17 00:00:00 2001 From: palashgdev Date: Tue, 13 Sep 2022 21:19:01 +0530 Subject: [PATCH 13/17] feat: height is updated (#1563) --- frontend/src/container/AppLayout/styles.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/container/AppLayout/styles.ts b/frontend/src/container/AppLayout/styles.ts index 71547d1592..5db7517c66 100644 --- a/frontend/src/container/AppLayout/styles.ts +++ b/frontend/src/container/AppLayout/styles.ts @@ -3,7 +3,7 @@ import styled from 'styled-components'; export const Layout = styled(LayoutComponent)` &&& { - min-height: 91vh; + min-height: 92vh; display: flex; position: relative; } From 05de0ccba5cdd9b379d717f630a7653da2c392f3 Mon Sep 17 00:00:00 2001 From: Prashant Shahi Date: Tue, 13 Sep 2022 22:37:39 +0530 Subject: [PATCH 14/17] =?UTF-8?q?chore:=20=F0=9F=94=A7=20Add=20414=20issue?= =?UTF-8?q?=20fix=20for=20Frontend=20default.conf=20and=20Swarm?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Prashant Shahi --- deploy/docker-swarm/common/nginx-config.conf | 8 ++++++-- frontend/conf/default.conf | 10 +++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/deploy/docker-swarm/common/nginx-config.conf b/deploy/docker-swarm/common/nginx-config.conf index 3153dff62f..d822e68c40 100644 --- a/deploy/docker-swarm/common/nginx-config.conf +++ b/deploy/docker-swarm/common/nginx-config.conf @@ -3,13 +3,17 @@ server { server_name _; gzip on; - gzip_static on; + gzip_static on; gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; gzip_proxied any; gzip_vary on; gzip_comp_level 6; gzip_buffers 16 8k; - gzip_http_version 1.1; + gzip_http_version 1.1; + + # to handle uri issue 414 from nginx + client_max_body_size 24M; + large_client_header_buffers 8 16k; location / { if ( $uri = '/index.html' ) { diff --git a/frontend/conf/default.conf b/frontend/conf/default.conf index dc0475eaf4..37b77463d4 100644 --- a/frontend/conf/default.conf +++ b/frontend/conf/default.conf @@ -1,15 +1,19 @@ server { listen 3301; server_name _; - + gzip on; - gzip_static on; + gzip_static on; gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; gzip_proxied any; gzip_vary on; gzip_comp_level 6; gzip_buffers 16 8k; - gzip_http_version 1.1; + gzip_http_version 1.1; + + # to handle uri issue 414 from nginx + client_max_body_size 24M; + large_client_header_buffers 8 16k; location / { root /usr/share/nginx/html; From 745fd07bd87b5f1651a55f9955937ea71b941169 Mon Sep 17 00:00:00 2001 From: Prashant Shahi Date: Tue, 13 Sep 2022 22:44:24 +0530 Subject: [PATCH 15/17] =?UTF-8?q?fix(lint):=20=F0=9F=9A=A8=20format=20prom?= =?UTF-8?q?etheus=20config=20YAML=20and=20remove=20trailing=20spaces?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Prashant Shahi --- deploy/docker-swarm/clickhouse-setup/prometheus.yml | 3 +-- deploy/docker/clickhouse-setup/prometheus.yml | 3 +-- deploy/docker/common/nginx-config.conf | 3 +-- frontend/Dockerfile | 2 +- pkg/query-service/config/prometheus.yml | 3 +-- pkg/query-service/tests/test-deploy/prometheus.yml | 3 +-- 6 files changed, 6 insertions(+), 11 deletions(-) diff --git a/deploy/docker-swarm/clickhouse-setup/prometheus.yml b/deploy/docker-swarm/clickhouse-setup/prometheus.yml index 16e65ff18c..6a796ea1d0 100644 --- a/deploy/docker-swarm/clickhouse-setup/prometheus.yml +++ b/deploy/docker-swarm/clickhouse-setup/prometheus.yml @@ -19,8 +19,7 @@ rule_files: # A scrape configuration containing exactly one endpoint to scrape: # Here it's Prometheus itself. -scrape_configs: - +scrape_configs: [] remote_read: - url: tcp://clickhouse:9000/?database=signoz_metrics diff --git a/deploy/docker/clickhouse-setup/prometheus.yml b/deploy/docker/clickhouse-setup/prometheus.yml index 16e65ff18c..6a796ea1d0 100644 --- a/deploy/docker/clickhouse-setup/prometheus.yml +++ b/deploy/docker/clickhouse-setup/prometheus.yml @@ -19,8 +19,7 @@ rule_files: # A scrape configuration containing exactly one endpoint to scrape: # Here it's Prometheus itself. -scrape_configs: - +scrape_configs: [] remote_read: - url: tcp://clickhouse:9000/?database=signoz_metrics diff --git a/deploy/docker/common/nginx-config.conf b/deploy/docker/common/nginx-config.conf index 99615f1f60..d822e68c40 100644 --- a/deploy/docker/common/nginx-config.conf +++ b/deploy/docker/common/nginx-config.conf @@ -3,7 +3,7 @@ server { server_name _; gzip on; - gzip_static on; + gzip_static on; gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; gzip_proxied any; gzip_vary on; @@ -13,7 +13,6 @@ server { # to handle uri issue 414 from nginx client_max_body_size 24M; - large_client_header_buffers 8 16k; location / { diff --git a/frontend/Dockerfile b/frontend/Dockerfile index d7fff6c0bb..71188e71c9 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -13,7 +13,7 @@ WORKDIR /frontend COPY package.json ./ # Install the dependencies and make the folder -RUN CI=1 yarn install +RUN CI=1 yarn install COPY . . diff --git a/pkg/query-service/config/prometheus.yml b/pkg/query-service/config/prometheus.yml index d7c0ce6911..88ee92961b 100644 --- a/pkg/query-service/config/prometheus.yml +++ b/pkg/query-service/config/prometheus.yml @@ -19,8 +19,7 @@ rule_files: # A scrape configuration containing exactly one endpoint to scrape: # Here it's Prometheus itself. -scrape_configs: - +scrape_configs: [] remote_read: - url: tcp://localhost:9000/?database=signoz_metrics diff --git a/pkg/query-service/tests/test-deploy/prometheus.yml b/pkg/query-service/tests/test-deploy/prometheus.yml index 16e65ff18c..6a796ea1d0 100644 --- a/pkg/query-service/tests/test-deploy/prometheus.yml +++ b/pkg/query-service/tests/test-deploy/prometheus.yml @@ -19,8 +19,7 @@ rule_files: # A scrape configuration containing exactly one endpoint to scrape: # Here it's Prometheus itself. -scrape_configs: - +scrape_configs: [] remote_read: - url: tcp://clickhouse:9000/?database=signoz_metrics From f1fdf78dc5e01210efa3cc51ca82395d68034bfb Mon Sep 17 00:00:00 2001 From: Prashant Shahi Date: Tue, 13 Sep 2022 22:46:00 +0530 Subject: [PATCH 16/17] =?UTF-8?q?chore(release):=20=F0=9F=93=8C=20pin=20ve?= =?UTF-8?q?rsions:=20SigNoz=200.11.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Prashant Shahi --- deploy/docker-swarm/clickhouse-setup/docker-compose.yaml | 4 ++-- deploy/docker/clickhouse-setup/docker-compose-prod.yaml | 4 ++-- deploy/docker/clickhouse-setup/docker-compose.yaml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml index d1c1568f61..6059e95718 100644 --- a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml +++ b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml @@ -40,7 +40,7 @@ services: condition: on-failure query-service: - image: signoz/query-service:0.11.0 + image: signoz/query-service:0.11.1 command: ["-config=/root/config/prometheus.yml"] # ports: # - "6060:6060" # pprof port @@ -70,7 +70,7 @@ services: - clickhouse frontend: - image: signoz/frontend:0.11.0 + image: signoz/frontend:0.11.1 deploy: restart_policy: condition: on-failure diff --git a/deploy/docker/clickhouse-setup/docker-compose-prod.yaml b/deploy/docker/clickhouse-setup/docker-compose-prod.yaml index d86510e849..f2a4d73269 100644 --- a/deploy/docker/clickhouse-setup/docker-compose-prod.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose-prod.yaml @@ -2,7 +2,7 @@ version: "2.4" services: query-service: - image: signoz/query-service:0.11.0 + image: signoz/query-service:0.11.1 container_name: query-service command: ["-config=/root/config/prometheus.yml"] # ports: @@ -31,7 +31,7 @@ services: condition: service_healthy frontend: - image: signoz/frontend:0.11.0 + image: signoz/frontend:0.11.1 container_name: frontend restart: on-failure depends_on: diff --git a/deploy/docker/clickhouse-setup/docker-compose.yaml b/deploy/docker/clickhouse-setup/docker-compose.yaml index e085f1dba3..099d28f63d 100644 --- a/deploy/docker/clickhouse-setup/docker-compose.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose.yaml @@ -39,7 +39,7 @@ services: # Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md` query-service: - image: signoz/query-service:0.11.0 + image: signoz/query-service:0.11.1 container_name: query-service command: ["-config=/root/config/prometheus.yml"] # ports: @@ -68,7 +68,7 @@ services: condition: service_healthy frontend: - image: signoz/frontend:0.11.0 + image: signoz/frontend:0.11.1 container_name: frontend restart: on-failure depends_on: From ed2bbc5035b9971e6b8324fcbd4db0e8c13193c2 Mon Sep 17 00:00:00 2001 From: Prashant Shahi Date: Wed, 14 Sep 2022 00:03:07 +0530 Subject: [PATCH 17/17] =?UTF-8?q?chore(release):=20=F0=9F=93=8C=20pin=20ve?= =?UTF-8?q?rsions:=20SigNoz-Otel-Collector=200.55.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Prashant Shahi --- deploy/docker-swarm/clickhouse-setup/docker-compose.yaml | 4 ++-- deploy/docker/clickhouse-setup/docker-compose-core.yaml | 4 ++-- deploy/docker/clickhouse-setup/docker-compose.yaml | 4 ++-- pkg/query-service/tests/test-deploy/docker-compose.yaml | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml index 6059e95718..88b022d34a 100644 --- a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml +++ b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml @@ -83,7 +83,7 @@ services: - ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf otel-collector: - image: signoz-otel-collector:0.55.0 + image: signoz-otel-collector:0.55.1 command: ["--config=/etc/otel-collector-config.yaml"] user: root # required for reading docker container logs volumes: @@ -111,7 +111,7 @@ services: - clickhouse otel-collector-metrics: - image: signoz-otel-collector:0.55.0 + image: signoz-otel-collector:0.55.1 command: ["--config=/etc/otel-collector-metrics-config.yaml"] volumes: - ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml diff --git a/deploy/docker/clickhouse-setup/docker-compose-core.yaml b/deploy/docker/clickhouse-setup/docker-compose-core.yaml index ead470cefb..da338d1dd7 100644 --- a/deploy/docker/clickhouse-setup/docker-compose-core.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose-core.yaml @@ -41,7 +41,7 @@ services: # Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md` otel-collector: container_name: otel-collector - image: signoz/signoz-otel-collector:0.55.0 + image: signoz/signoz-otel-collector:0.55.1 command: ["--config=/etc/otel-collector-config.yaml"] # user: root # required for reading docker container logs volumes: @@ -67,7 +67,7 @@ services: otel-collector-metrics: container_name: otel-collector-metrics - image: signoz/signoz-otel-collector:0.55.0 + image: signoz/signoz-otel-collector:0.55.1 command: ["--config=/etc/otel-collector-metrics-config.yaml"] volumes: - ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml diff --git a/deploy/docker/clickhouse-setup/docker-compose.yaml b/deploy/docker/clickhouse-setup/docker-compose.yaml index 099d28f63d..ae330e363f 100644 --- a/deploy/docker/clickhouse-setup/docker-compose.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose.yaml @@ -80,7 +80,7 @@ services: - ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf otel-collector: - image: signoz/signoz-otel-collector:0.55.0 + image: signoz/signoz-otel-collector:0.55.1 command: ["--config=/etc/otel-collector-config.yaml"] user: root # required for reading docker container logs volumes: @@ -106,7 +106,7 @@ services: condition: service_healthy otel-collector-metrics: - image: signoz/signoz-otel-collector:0.55.0 + image: signoz/signoz-otel-collector:0.55.1 command: ["--config=/etc/otel-collector-metrics-config.yaml"] volumes: - ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml diff --git a/pkg/query-service/tests/test-deploy/docker-compose.yaml b/pkg/query-service/tests/test-deploy/docker-compose.yaml index a8bae4a159..fef98d2007 100644 --- a/pkg/query-service/tests/test-deploy/docker-compose.yaml +++ b/pkg/query-service/tests/test-deploy/docker-compose.yaml @@ -61,7 +61,7 @@ services: condition: service_healthy otel-collector: - image: signoz-otel-collector:0.55.0 + image: signoz-otel-collector:0.55.1 command: ["--config=/etc/otel-collector-config.yaml"] user: root # required for reading docker container logs volumes: @@ -77,7 +77,7 @@ services: condition: service_healthy otel-collector-metrics: - image: signoz-otel-collector:0.55.0 + image: signoz-otel-collector:0.55.1 command: ["--config=/etc/otel-collector-metrics-config.yaml"] volumes: - ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml