Compare commits

...

32 Commits

Author SHA1 Message Date
SagarRajput-7
a5095bb96c Merge branch 'variable-update-queue' into limiting-api-via-keys 2024-12-17 17:52:45 +05:30
SagarRajput-7
cb91fee7c3 Merge branch 'develop' into variable-update-queue 2024-12-17 17:52:11 +05:30
Vibhu Pandey
14fbb1fcda fix(flag): add missing flag back (#6647) 2024-12-17 01:12:31 +05:30
Vikrant Gupta
96da21df05 fix: correlation time stamps for APM to traces and logs (#6632) 2024-12-17 00:00:15 +05:30
Vikrant Gupta
8608f02263 fix: timerange selected for traces to logs (#6634) 2024-12-16 22:35:25 +05:30
Vikrant Gupta
2701ae5c34 fix: unable to remove query tags from the beginning when similar tags present (#6645)
* fix: unable to remove query tags from the begining

* fix: focus only when the filters dropdown is opened

* fix: used auto focus prop
2024-12-16 22:25:37 +05:30
Vibhu Pandey
951593b0a3 feat(licenses): deprecate licenses v2 (#6626)
deprecate licenses v2
2024-12-16 10:23:23 +00:00
Srikanth Chekuri
e6766023dd chore: use tag attributes v2 table (#6616) 2024-12-16 09:59:16 +00:00
Srikanth Chekuri
bef5b96c5c chore: add queries with tag attributes v1 (#6643) 2024-12-16 13:04:55 +05:30
Shaheer Kochai
b29359dee0 fix: improve traces table ux by making the table scrollable (#6423)
* fix: improve traces table ux by making the table scrollable

* chore: remove explicit scroll.x and modify the existing scroll.x from true to max-content

* fix(Traces Explorer): make trace explorer card full-width when collapsed, resizable when expanded
2024-12-16 07:00:40 +00:00
Amlan Kumar Nandy
9a1cd65b73 feat: add functionality to export dashboard as json from listing page 2024-12-16 12:10:21 +05:30
SagarRajput-7
4b6e934510 Merge branch 'variable-update-queue' into limiting-api-via-keys 2024-12-16 12:03:14 +05:30
SagarRajput-7
99fb8c2a64 Merge branch 'develop' into variable-update-queue 2024-12-16 12:03:05 +05:30
Amlan Kumar Nandy
8ab0c066d6 Merge branch 'develop' into dashboard-list-export-json 2024-12-16 11:39:18 +05:30
Shaheer Kochai
b333aa3775 Feat: Timezone picker feature (#6474)
* feat: time picker hint and timezone picker UI with basic functionality + helper to get timezones

* feat: add support for esc keypress to close the timezone picker

* chore: add the selected timezone as url param and close timezone picker on select

* fix: overall improvement + add searchIndex to timezone

* feat: timezone preferences UI

* chore: improve timezone utils

* chore: change timezone item from div to button

* feat: display timezone in timepicker input

* chore: fix the typo

* fix: don't focus on time picker when timezone is clicked

* fix: fix the issue of timezone breaking for browser and utc timezones

* fix: display the timezone in timepicker hint 'You are at'

* feat: timezone basic functionality (#6492)

* chore: change div to fragment + change type to any as the ESLint complains otherwise

* chore: manage etc timezone filtering with an arg

* chore: update timezone wrapper class name

* fix: add timezone support to downloaded logs

* feat: add current timezone to dashboard list and configure metadata modal

* fix: add pencil icon next to timezone hint + change the copy to Current timezone

* fix: properly handle the escape button behavior for timezone picker

* chore: replace @vvo/tzdb with native Intl API for timezones

* feat: lightmode for timezone picker and timezone adaptation components

* fix: use normald tz in browser timezone

* fix: timezone picker lightmode fixes

* feat: display selected time range in 12 hour format

* chore: remove unnecessary optional chaining

* fix: fix the typo in css variable

* chore: add em dash and change icon for timezone hint in date/time picker

* chore: move pen line icon to the right of timezone offset

* fix: fix the failing tests

* feat: handle switching off the timezone adaptation
2024-12-16 11:27:20 +05:30
amlannandy
8a3319cdf5 chore: address comments 2024-12-16 11:24:57 +05:30
amlannandy
d09c4d947e feat: update unit test 2024-12-16 11:24:57 +05:30
amlannandy
2508e6f9f1 feat: update unit test 2024-12-16 11:24:57 +05:30
amlannandy
1b8213653a feat: update tests 2024-12-16 11:24:57 +05:30
amlannandy
b499b10333 feat: add unit test 2024-12-16 11:24:57 +05:30
amlannandy
b35b975798 chore: address comments 2024-12-16 11:24:57 +05:30
amlannandy
715f8a2363 feat: address comments 2024-12-16 11:24:57 +05:30
amlannandy
8d1c4491b7 feat: add functionality to export dashboard as json from listing page 2024-12-16 11:24:57 +05:30
dependabot[bot]
e3caa6a8f5 chore(deps): bump golang.org/x/crypto from 0.27.0 to 0.31.0 (#6638)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.27.0 to 0.31.0.
- [Commits](https://github.com/golang/crypto/compare/v0.27.0...v0.31.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-14 04:29:25 +00:00
Vibhu Pandey
a1059ed949 feat(signoz-ingestion-key): rename signoz-access-token to signoz-ingestion-key (#6633) 2024-12-13 18:18:12 +05:30
SagarRajput-7
8c46de8eac feat: added table column and row logic for the new api response structure for producer overview (#6433)
* feat: added table column and row logic for the new api response structure for prodcure overview

* feat: fixed typo in function name

* feat: consumed new 2 table merging logic for producer latency overview

* feat: added 3 digit precision to 'ingestion_rate' and 'byte_rate'in consumer overview
2024-12-13 11:25:39 +05:30
Nityananda Gohain
0fbfb6b22b fix: fix count aggregate attribute column name (#6613)
* fix: fix count aggregate attribute column name

* fix: add test for column as well

* fix: use new schema in threshold rule enrichment
2024-12-11 13:37:25 +05:30
SagarRajput-7
421d355e29 feat: fixed dropdown open triggering the api calls for single-select and misc 2024-12-11 13:18:56 +05:30
SagarRajput-7
eb75e636e8 feat: added API limiting to reduce unneccesary api call for dashboard variables 2024-12-10 11:20:34 +05:30
SagarRajput-7
f121240c82 Merge branch 'develop' into variable-update-queue 2024-12-10 11:18:28 +05:30
SagarRajput-7
a60dbf7f89 Merge branch 'develop' into variable-update-queue 2024-12-04 10:46:17 +05:30
SagarRajput-7
fa4aeae508 feat: updated the logic for variable update queue 2024-12-04 10:19:33 +05:30
399 changed files with 2664 additions and 1240 deletions

View File

@@ -41,7 +41,6 @@ type APIHandlerOptions struct {
FluxInterval time.Duration FluxInterval time.Duration
UseLogsNewSchema bool UseLogsNewSchema bool
UseTraceNewSchema bool UseTraceNewSchema bool
UseLicensesV3 bool
} }
type APIHandler struct { type APIHandler struct {
@@ -68,7 +67,6 @@ func NewAPIHandler(opts APIHandlerOptions) (*APIHandler, error) {
FluxInterval: opts.FluxInterval, FluxInterval: opts.FluxInterval,
UseLogsNewSchema: opts.UseLogsNewSchema, UseLogsNewSchema: opts.UseLogsNewSchema,
UseTraceNewSchema: opts.UseTraceNewSchema, UseTraceNewSchema: opts.UseTraceNewSchema,
UseLicensesV3: opts.UseLicensesV3,
}) })
if err != nil { if err != nil {

View File

@@ -84,13 +84,6 @@ func (ah *APIHandler) listLicenses(w http.ResponseWriter, r *http.Request) {
} }
func (ah *APIHandler) applyLicense(w http.ResponseWriter, r *http.Request) { func (ah *APIHandler) applyLicense(w http.ResponseWriter, r *http.Request) {
if ah.UseLicensesV3 {
// if the licenses v3 is toggled on then do not apply license in v2 and run the validator!
// TODO: remove after migration to v3 and deprecation from zeus
zap.L().Info("early return from apply license v2 call")
render.Success(w, http.StatusOK, nil)
return
}
var l model.License var l model.License
if err := json.NewDecoder(r.Body).Decode(&l); err != nil { if err := json.NewDecoder(r.Body).Decode(&l); err != nil {
@@ -102,7 +95,7 @@ func (ah *APIHandler) applyLicense(w http.ResponseWriter, r *http.Request) {
RespondError(w, model.BadRequest(fmt.Errorf("license key is required")), nil) RespondError(w, model.BadRequest(fmt.Errorf("license key is required")), nil)
return return
} }
license, apiError := ah.LM().Activate(r.Context(), l.Key) license, apiError := ah.LM().ActivateV3(r.Context(), l.Key)
if apiError != nil { if apiError != nil {
RespondError(w, apiError, nil) RespondError(w, apiError, nil)
return return
@@ -265,24 +258,12 @@ func convertLicenseV3ToLicenseV2(licenses []*model.LicenseV3) []model.License {
} }
func (ah *APIHandler) listLicensesV2(w http.ResponseWriter, r *http.Request) { func (ah *APIHandler) listLicensesV2(w http.ResponseWriter, r *http.Request) {
licensesV3, apierr := ah.LM().GetLicensesV3(r.Context())
var licenses []model.License if apierr != nil {
RespondError(w, apierr, nil)
if ah.UseLicensesV3 { return
licensesV3, err := ah.LM().GetLicensesV3(r.Context())
if err != nil {
RespondError(w, err, nil)
return
}
licenses = convertLicenseV3ToLicenseV2(licensesV3)
} else {
_licenses, apiError := ah.LM().GetLicenses(r.Context())
if apiError != nil {
RespondError(w, apiError, nil)
return
}
licenses = _licenses
} }
licenses := convertLicenseV3ToLicenseV2(licensesV3)
resp := model.Licenses{ resp := model.Licenses{
TrialStart: -1, TrialStart: -1,

View File

@@ -78,7 +78,6 @@ type ServerOptions struct {
GatewayUrl string GatewayUrl string
UseLogsNewSchema bool UseLogsNewSchema bool
UseTraceNewSchema bool UseTraceNewSchema bool
UseLicensesV3 bool
} }
// Server runs HTTP api service // Server runs HTTP api service
@@ -135,7 +134,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
} }
// initiate license manager // initiate license manager
lm, err := licensepkg.StartManager("sqlite", localDB, serverOptions.UseLicensesV3) lm, err := licensepkg.StartManager("sqlite", localDB)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -274,7 +273,6 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
Gateway: gatewayProxy, Gateway: gatewayProxy,
UseLogsNewSchema: serverOptions.UseLogsNewSchema, UseLogsNewSchema: serverOptions.UseLogsNewSchema,
UseTraceNewSchema: serverOptions.UseTraceNewSchema, UseTraceNewSchema: serverOptions.UseTraceNewSchema,
UseLicensesV3: serverOptions.UseLicensesV3,
} }
apiHandler, err := api.NewAPIHandler(apiOpts) apiHandler, err := api.NewAPIHandler(apiOpts)

View File

@@ -2,18 +2,6 @@ package signozio
type status string type status string
type ActivationResult struct {
Status status `json:"status"`
Data *ActivationResponse `json:"data,omitempty"`
ErrorType string `json:"errorType,omitempty"`
Error string `json:"error,omitempty"`
}
type ActivationResponse struct {
ActivationId string `json:"ActivationId"`
PlanDetails string `json:"PlanDetails"`
}
type ValidateLicenseResponse struct { type ValidateLicenseResponse struct {
Status status `json:"status"` Status status `json:"status"`
Data map[string]interface{} `json:"data"` Data map[string]interface{} `json:"data"`

View File

@@ -10,7 +10,6 @@ import (
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
"go.uber.org/zap"
"go.signoz.io/signoz/ee/query-service/constants" "go.signoz.io/signoz/ee/query-service/constants"
"go.signoz.io/signoz/ee/query-service/model" "go.signoz.io/signoz/ee/query-service/model"
@@ -39,86 +38,6 @@ func init() {
C = New() C = New()
} }
// ActivateLicense sends key to license.signoz.io and gets activation data
func ActivateLicense(key, siteId string) (*ActivationResponse, *model.ApiError) {
licenseReq := map[string]string{
"key": key,
"siteId": siteId,
}
reqString, _ := json.Marshal(licenseReq)
httpResponse, err := http.Post(C.Prefix+"/licenses/activate", APPLICATION_JSON, bytes.NewBuffer(reqString))
if err != nil {
zap.L().Error("failed to connect to license.signoz.io", zap.Error(err))
return nil, model.BadRequest(fmt.Errorf("unable to connect with license.signoz.io, please check your network connection"))
}
httpBody, err := io.ReadAll(httpResponse.Body)
if err != nil {
zap.L().Error("failed to read activation response from license.signoz.io", zap.Error(err))
return nil, model.BadRequest(fmt.Errorf("failed to read activation response from license.signoz.io"))
}
defer httpResponse.Body.Close()
// read api request result
result := ActivationResult{}
err = json.Unmarshal(httpBody, &result)
if err != nil {
zap.L().Error("failed to marshal activation response from license.signoz.io", zap.Error(err))
return nil, model.InternalError(errors.Wrap(err, "failed to marshal license activation response"))
}
switch httpResponse.StatusCode {
case 200, 201:
return result.Data, nil
case 400, 401:
return nil, model.BadRequest(fmt.Errorf(fmt.Sprintf("failed to activate: %s", result.Error)))
default:
return nil, model.InternalError(fmt.Errorf(fmt.Sprintf("failed to activate: %s", result.Error)))
}
}
// ValidateLicense validates the license key
func ValidateLicense(activationId string) (*ActivationResponse, *model.ApiError) {
validReq := map[string]string{
"activationId": activationId,
}
reqString, _ := json.Marshal(validReq)
response, err := http.Post(C.Prefix+"/licenses/validate", APPLICATION_JSON, bytes.NewBuffer(reqString))
if err != nil {
return nil, model.BadRequest(errors.Wrap(err, "unable to connect with license.signoz.io, please check your network connection"))
}
body, err := io.ReadAll(response.Body)
if err != nil {
return nil, model.BadRequest(errors.Wrap(err, "failed to read validation response from license.signoz.io"))
}
defer response.Body.Close()
switch response.StatusCode {
case 200, 201:
a := ActivationResult{}
err = json.Unmarshal(body, &a)
if err != nil {
return nil, model.BadRequest(errors.Wrap(err, "failed to marshal license validation response"))
}
return a.Data, nil
case 400, 401:
return nil, model.BadRequest(errors.Wrap(fmt.Errorf(string(body)),
"bad request error received from license.signoz.io"))
default:
return nil, model.InternalError(errors.Wrap(fmt.Errorf(string(body)),
"internal error received from license.signoz.io"))
}
}
func ValidateLicenseV3(licenseKey string) (*model.LicenseV3, *model.ApiError) { func ValidateLicenseV3(licenseKey string) (*model.LicenseV3, *model.ApiError) {
// Creating an HTTP client with a timeout for better control // Creating an HTTP client with a timeout for better control

View File

@@ -18,15 +18,13 @@ import (
// Repo is license repo. stores license keys in a secured DB // Repo is license repo. stores license keys in a secured DB
type Repo struct { type Repo struct {
db *sqlx.DB db *sqlx.DB
useLicensesV3 bool
} }
// NewLicenseRepo initiates a new license repo // NewLicenseRepo initiates a new license repo
func NewLicenseRepo(db *sqlx.DB, useLicensesV3 bool) Repo { func NewLicenseRepo(db *sqlx.DB) Repo {
return Repo{ return Repo{
db: db, db: db,
useLicensesV3: useLicensesV3,
} }
} }
@@ -112,26 +110,16 @@ func (r *Repo) GetActiveLicenseV2(ctx context.Context) (*model.License, *basemod
// GetActiveLicense fetches the latest active license from DB. // GetActiveLicense fetches the latest active license from DB.
// If the license is not present, expect a nil license and a nil error in the output. // If the license is not present, expect a nil license and a nil error in the output.
func (r *Repo) GetActiveLicense(ctx context.Context) (*model.License, *basemodel.ApiError) { func (r *Repo) GetActiveLicense(ctx context.Context) (*model.License, *basemodel.ApiError) {
if r.useLicensesV3 { activeLicenseV3, err := r.GetActiveLicenseV3(ctx)
zap.L().Info("Using licenses v3 for GetActiveLicense")
activeLicenseV3, err := r.GetActiveLicenseV3(ctx)
if err != nil {
return nil, basemodel.InternalError(fmt.Errorf("failed to get active licenses from db: %v", err))
}
if activeLicenseV3 == nil {
return nil, nil
}
activeLicenseV2 := model.ConvertLicenseV3ToLicenseV2(activeLicenseV3)
return activeLicenseV2, nil
}
active, err := r.GetActiveLicenseV2(ctx)
if err != nil { if err != nil {
return nil, err return nil, basemodel.InternalError(fmt.Errorf("failed to get active licenses from db: %v", err))
} }
return active, nil
if activeLicenseV3 == nil {
return nil, nil
}
activeLicenseV2 := model.ConvertLicenseV3ToLicenseV2(activeLicenseV3)
return activeLicenseV2, nil
} }
func (r *Repo) GetActiveLicenseV3(ctx context.Context) (*model.LicenseV3, error) { func (r *Repo) GetActiveLicenseV3(ctx context.Context) (*model.LicenseV3, error) {

View File

@@ -51,12 +51,12 @@ type Manager struct {
activeFeatures basemodel.FeatureSet activeFeatures basemodel.FeatureSet
} }
func StartManager(dbType string, db *sqlx.DB, useLicensesV3 bool, features ...basemodel.Feature) (*Manager, error) { func StartManager(dbType string, db *sqlx.DB, features ...basemodel.Feature) (*Manager, error) {
if LM != nil { if LM != nil {
return LM, nil return LM, nil
} }
repo := NewLicenseRepo(db, useLicensesV3) repo := NewLicenseRepo(db)
err := repo.InitDB(dbType) err := repo.InitDB(dbType)
if err != nil { if err != nil {
@@ -67,32 +67,7 @@ func StartManager(dbType string, db *sqlx.DB, useLicensesV3 bool, features ...ba
repo: &repo, repo: &repo,
} }
if useLicensesV3 { if err := m.start(features...); err != nil {
// get active license from the db
active, err := m.repo.GetActiveLicenseV2(context.Background())
if err != nil {
return m, err
}
// if we have an active license then need to fetch the complete details
if active != nil {
// fetch the new license structure from control plane
licenseV3, apiError := validate.ValidateLicenseV3(active.Key)
if apiError != nil {
return m, apiError
}
// insert the licenseV3 in sqlite db
apiError = m.repo.InsertLicenseV3(context.Background(), licenseV3)
// if the license already exists move ahead.
if apiError != nil && apiError.Typ != model.ErrorConflict {
return m, apiError
}
zap.L().Info("Successfully inserted license from v2 to v3 table")
}
}
if err := m.start(useLicensesV3, features...); err != nil {
return m, err return m, err
} }
LM = m LM = m
@@ -100,16 +75,8 @@ func StartManager(dbType string, db *sqlx.DB, useLicensesV3 bool, features ...ba
} }
// start loads active license in memory and initiates validator // start loads active license in memory and initiates validator
func (lm *Manager) start(useLicensesV3 bool, features ...basemodel.Feature) error { func (lm *Manager) start(features ...basemodel.Feature) error {
return lm.LoadActiveLicenseV3(features...)
var err error
if useLicensesV3 {
err = lm.LoadActiveLicenseV3(features...)
} else {
err = lm.LoadActiveLicense(features...)
}
return err
} }
func (lm *Manager) Stop() { func (lm *Manager) Stop() {
@@ -117,31 +84,6 @@ func (lm *Manager) Stop() {
<-lm.terminated <-lm.terminated
} }
func (lm *Manager) SetActive(l *model.License, features ...basemodel.Feature) {
lm.mutex.Lock()
defer lm.mutex.Unlock()
if l == nil {
return
}
lm.activeLicense = l
lm.activeFeatures = append(l.FeatureSet, features...)
// set default features
setDefaultFeatures(lm)
err := lm.InitFeatures(lm.activeFeatures)
if err != nil {
zap.L().Panic("Couldn't activate features", zap.Error(err))
}
if !lm.validatorRunning {
// we want to make sure only one validator runs,
// we already have lock() so good to go
lm.validatorRunning = true
go lm.Validator(context.Background())
}
}
func (lm *Manager) SetActiveV3(l *model.LicenseV3, features ...basemodel.Feature) { func (lm *Manager) SetActiveV3(l *model.LicenseV3, features ...basemodel.Feature) {
lm.mutex.Lock() lm.mutex.Lock()
defer lm.mutex.Unlock() defer lm.mutex.Unlock()
@@ -172,29 +114,6 @@ func setDefaultFeatures(lm *Manager) {
lm.activeFeatures = append(lm.activeFeatures, baseconstants.DEFAULT_FEATURE_SET...) lm.activeFeatures = append(lm.activeFeatures, baseconstants.DEFAULT_FEATURE_SET...)
} }
// LoadActiveLicense loads the most recent active license
func (lm *Manager) LoadActiveLicense(features ...basemodel.Feature) error {
active, err := lm.repo.GetActiveLicense(context.Background())
if err != nil {
return err
}
if active != nil {
lm.SetActive(active, features...)
} else {
zap.L().Info("No active license found, defaulting to basic plan")
// if no active license is found, we default to basic(free) plan with all default features
lm.activeFeatures = model.BasicPlan
setDefaultFeatures(lm)
err := lm.InitFeatures(lm.activeFeatures)
if err != nil {
zap.L().Error("Couldn't initialize features", zap.Error(err))
return err
}
}
return nil
}
func (lm *Manager) LoadActiveLicenseV3(features ...basemodel.Feature) error { func (lm *Manager) LoadActiveLicenseV3(features ...basemodel.Feature) error {
active, err := lm.repo.GetActiveLicenseV3(context.Background()) active, err := lm.repo.GetActiveLicenseV3(context.Background())
if err != nil { if err != nil {
@@ -265,31 +184,6 @@ func (lm *Manager) GetLicensesV3(ctx context.Context) (response []*model.License
return response, nil return response, nil
} }
// Validator validates license after an epoch of time
func (lm *Manager) Validator(ctx context.Context) {
zap.L().Info("Validator started!")
defer close(lm.terminated)
tick := time.NewTicker(validationFrequency)
defer tick.Stop()
lm.Validate(ctx)
for {
select {
case <-lm.done:
return
default:
select {
case <-lm.done:
return
case <-tick.C:
lm.Validate(ctx)
}
}
}
}
// Validator validates license after an epoch of time // Validator validates license after an epoch of time
func (lm *Manager) ValidatorV3(ctx context.Context) { func (lm *Manager) ValidatorV3(ctx context.Context) {
zap.L().Info("ValidatorV3 started!") zap.L().Info("ValidatorV3 started!")
@@ -315,73 +209,6 @@ func (lm *Manager) ValidatorV3(ctx context.Context) {
} }
} }
// Validate validates the current active license
func (lm *Manager) Validate(ctx context.Context) (reterr error) {
zap.L().Info("License validation started")
if lm.activeLicense == nil {
return nil
}
defer func() {
lm.mutex.Lock()
lm.lastValidated = time.Now().Unix()
if reterr != nil {
zap.L().Error("License validation completed with error", zap.Error(reterr))
atomic.AddUint64(&lm.failedAttempts, 1)
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_LICENSE_CHECK_FAILED,
map[string]interface{}{"err": reterr.Error()}, "", true, false)
} else {
zap.L().Info("License validation completed with no errors")
}
lm.mutex.Unlock()
}()
response, apiError := validate.ValidateLicense(lm.activeLicense.ActivationId)
if apiError != nil {
zap.L().Error("failed to validate license", zap.Error(apiError.Err))
return apiError.Err
}
if response.PlanDetails == lm.activeLicense.PlanDetails {
// license plan hasnt changed, nothing to do
return nil
}
if response.PlanDetails != "" {
// copy and replace the active license record
l := model.License{
Key: lm.activeLicense.Key,
CreatedAt: lm.activeLicense.CreatedAt,
PlanDetails: response.PlanDetails,
ValidationMessage: lm.activeLicense.ValidationMessage,
ActivationId: lm.activeLicense.ActivationId,
}
if err := l.ParsePlan(); err != nil {
zap.L().Error("failed to parse updated license", zap.Error(err))
return err
}
// updated plan is parsable, check if plan has changed
if lm.activeLicense.PlanDetails != response.PlanDetails {
err := lm.repo.UpdatePlanDetails(ctx, lm.activeLicense.Key, response.PlanDetails)
if err != nil {
// unexpected db write issue but we can let the user continue
// and wait for update to work in next cycle.
zap.L().Error("failed to validate license", zap.Error(err))
}
}
// activate the update license plan
lm.SetActive(&l)
}
return nil
}
func (lm *Manager) RefreshLicense(ctx context.Context) *model.ApiError { func (lm *Manager) RefreshLicense(ctx context.Context) *model.ApiError {
license, apiError := validate.ValidateLicenseV3(lm.activeLicenseV3.Key) license, apiError := validate.ValidateLicenseV3(lm.activeLicenseV3.Key)
@@ -429,50 +256,6 @@ func (lm *Manager) ValidateV3(ctx context.Context) (reterr error) {
return nil return nil
} }
// Activate activates a license key with signoz server
func (lm *Manager) Activate(ctx context.Context, key string) (licenseResponse *model.License, errResponse *model.ApiError) {
defer func() {
if errResponse != nil {
userEmail, err := auth.GetEmailFromJwt(ctx)
if err == nil {
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_LICENSE_ACT_FAILED,
map[string]interface{}{"err": errResponse.Err.Error()}, userEmail, true, false)
}
}
}()
response, apiError := validate.ActivateLicense(key, "")
if apiError != nil {
zap.L().Error("failed to activate license", zap.Error(apiError.Err))
return nil, apiError
}
l := &model.License{
Key: key,
ActivationId: response.ActivationId,
PlanDetails: response.PlanDetails,
}
// parse validity and features from the plan details
err := l.ParsePlan()
if err != nil {
zap.L().Error("failed to activate license", zap.Error(err))
return nil, model.InternalError(err)
}
// store the license before activating it
err = lm.repo.InsertLicense(ctx, l)
if err != nil {
zap.L().Error("failed to activate license", zap.Error(err))
return nil, model.InternalError(err)
}
// license is valid, activate it
lm.SetActive(l)
return l, nil
}
func (lm *Manager) ActivateV3(ctx context.Context, licenseKey string) (licenseResponse *model.LicenseV3, errResponse *model.ApiError) { func (lm *Manager) ActivateV3(ctx context.Context, licenseKey string) (licenseResponse *model.LicenseV3, errResponse *model.ApiError) {
defer func() { defer func() {
if errResponse != nil { if errResponse != nil {

View File

@@ -95,7 +95,6 @@ func main() {
var useLogsNewSchema bool var useLogsNewSchema bool
var useTraceNewSchema bool var useTraceNewSchema bool
var useLicensesV3 bool
var cacheConfigPath, fluxInterval string var cacheConfigPath, fluxInterval string
var enableQueryServiceLogOTLPExport bool var enableQueryServiceLogOTLPExport bool
var preferSpanMetrics bool var preferSpanMetrics bool
@@ -104,10 +103,10 @@ func main() {
var maxOpenConns int var maxOpenConns int
var dialTimeout time.Duration var dialTimeout time.Duration
var gatewayUrl string var gatewayUrl string
var useLicensesV3 bool
flag.BoolVar(&useLogsNewSchema, "use-logs-new-schema", false, "use logs_v2 schema for logs") flag.BoolVar(&useLogsNewSchema, "use-logs-new-schema", false, "use logs_v2 schema for logs")
flag.BoolVar(&useTraceNewSchema, "use-trace-new-schema", false, "use new schema for traces") flag.BoolVar(&useTraceNewSchema, "use-trace-new-schema", false, "use new schema for traces")
flag.BoolVar(&useLicensesV3, "use-licenses-v3", false, "use licenses_v3 schema for licenses")
flag.StringVar(&promConfigPath, "config", "./config/prometheus.yml", "(prometheus config to read metrics)") flag.StringVar(&promConfigPath, "config", "./config/prometheus.yml", "(prometheus config to read metrics)")
flag.StringVar(&skipTopLvlOpsPath, "skip-top-level-ops", "", "(config file to skip top level operations)") flag.StringVar(&skipTopLvlOpsPath, "skip-top-level-ops", "", "(config file to skip top level operations)")
flag.BoolVar(&disableRules, "rules.disable", false, "(disable rule evaluation)") flag.BoolVar(&disableRules, "rules.disable", false, "(disable rule evaluation)")
@@ -121,6 +120,7 @@ func main() {
flag.BoolVar(&enableQueryServiceLogOTLPExport, "enable.query.service.log.otlp.export", false, "(enable query service log otlp export)") flag.BoolVar(&enableQueryServiceLogOTLPExport, "enable.query.service.log.otlp.export", false, "(enable query service log otlp export)")
flag.StringVar(&cluster, "cluster", "cluster", "(cluster name - defaults to 'cluster')") flag.StringVar(&cluster, "cluster", "cluster", "(cluster name - defaults to 'cluster')")
flag.StringVar(&gatewayUrl, "gateway-url", "", "(url to the gateway)") flag.StringVar(&gatewayUrl, "gateway-url", "", "(url to the gateway)")
flag.BoolVar(&useLicensesV3, "use-licenses-v3", false, "use licenses_v3 schema for licenses")
flag.Parse() flag.Parse()
@@ -148,7 +148,6 @@ func main() {
GatewayUrl: gatewayUrl, GatewayUrl: gatewayUrl,
UseLogsNewSchema: useLogsNewSchema, UseLogsNewSchema: useLogsNewSchema,
UseTraceNewSchema: useTraceNewSchema, UseTraceNewSchema: useTraceNewSchema,
UseLicensesV3: useLicensesV3,
} }
// Read the jwt secret key // Read the jwt secret key

View File

@@ -1,10 +1,9 @@
import axios from 'api'; import axios from 'api';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { import {
MessagingQueueServicePayload, MessagingQueueServicePayload,
MessagingQueuesPayloadProps, MessagingQueuesPayloadProps,
} from './getConsumerLagDetails'; } from 'pages/MessagingQueues/MQDetails/MQTables/getConsumerLagDetails';
import { ErrorResponse, SuccessResponse } from 'types/api';
export const getTopicThroughputOverview = async ( export const getTopicThroughputOverview = async (
props: Omit<MessagingQueueServicePayload, 'variables'>, props: Omit<MessagingQueueServicePayload, 'variables'>,

View File

@@ -40,7 +40,7 @@
&.custom-time { &.custom-time {
input:not(:focus) { input:not(:focus) {
min-width: 240px; min-width: 280px;
} }
} }
@@ -119,3 +119,69 @@
color: var(--bg-slate-400) !important; color: var(--bg-slate-400) !important;
} }
} }
.date-time-popover__footer {
border-top: 1px solid var(--bg-ink-200);
padding: 8px 14px;
.timezone-container {
&,
.timezone {
font-family: Inter;
font-size: 12px;
line-height: 16px;
letter-spacing: -0.06px;
}
display: flex;
align-items: center;
color: var(--bg-vanilla-400);
gap: 6px;
.timezone {
display: flex;
align-items: center;
gap: 4px;
border-radius: 2px;
background: rgba(171, 189, 255, 0.04);
cursor: pointer;
padding: 0px 4px;
color: var(--bg-vanilla-100);
border: none;
}
}
}
.timezone-badge {
display: flex;
align-items: center;
justify-content: center;
padding: 0 4px;
border-radius: 2px;
background: rgba(171, 189, 255, 0.04);
color: var(--bg-vanilla-100);
font-size: 12px;
font-weight: 400;
line-height: 16px;
letter-spacing: -0.06px;
cursor: pointer;
}
.lightMode {
.date-time-popover__footer {
border-color: var(--bg-vanilla-400);
}
.timezone-container {
color: var(--bg-ink-400);
&__clock-icon {
stroke: var(--bg-ink-400);
}
.timezone {
color: var(--bg-ink-100);
background: rgb(179 179 179 / 15%);
&__icon {
stroke: var(--bg-ink-100);
}
}
}
.timezone-badge {
color: var(--bg-ink-100);
background: rgb(179 179 179 / 15%);
}
}

View File

@@ -15,11 +15,14 @@ import { isValidTimeFormat } from 'lib/getMinMax';
import { defaultTo, isFunction, noop } from 'lodash-es'; import { defaultTo, isFunction, noop } from 'lodash-es';
import debounce from 'lodash-es/debounce'; import debounce from 'lodash-es/debounce';
import { CheckCircle, ChevronDown, Clock } from 'lucide-react'; import { CheckCircle, ChevronDown, Clock } from 'lucide-react';
import { useTimezone } from 'providers/Timezone';
import { import {
ChangeEvent, ChangeEvent,
Dispatch, Dispatch,
SetStateAction, SetStateAction,
useCallback,
useEffect, useEffect,
useMemo,
useState, useState,
} from 'react'; } from 'react';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
@@ -28,6 +31,8 @@ import { popupContainer } from 'utils/selectPopupContainer';
import CustomTimePickerPopoverContent from './CustomTimePickerPopoverContent'; import CustomTimePickerPopoverContent from './CustomTimePickerPopoverContent';
const maxAllowedMinTimeInMonths = 6; const maxAllowedMinTimeInMonths = 6;
type ViewType = 'datetime' | 'timezone';
const DEFAULT_VIEW: ViewType = 'datetime';
interface CustomTimePickerProps { interface CustomTimePickerProps {
onSelect: (value: string) => void; onSelect: (value: string) => void;
@@ -81,11 +86,42 @@ function CustomTimePicker({
const location = useLocation(); const location = useLocation();
const [isInputFocused, setIsInputFocused] = useState(false); const [isInputFocused, setIsInputFocused] = useState(false);
const [activeView, setActiveView] = useState<ViewType>(DEFAULT_VIEW);
const { timezone, browserTimezone } = useTimezone();
const activeTimezoneOffset = timezone.offset;
const isTimezoneOverridden = useMemo(
() => timezone.offset !== browserTimezone.offset,
[timezone, browserTimezone],
);
const handleViewChange = useCallback(
(newView: 'timezone' | 'datetime'): void => {
if (activeView !== newView) {
setActiveView(newView);
}
setOpen(true);
},
[activeView, setOpen],
);
const [isOpenedFromFooter, setIsOpenedFromFooter] = useState(false);
const getSelectedTimeRangeLabel = ( const getSelectedTimeRangeLabel = (
selectedTime: string, selectedTime: string,
selectedTimeValue: string, selectedTimeValue: string,
): string => { ): string => {
if (selectedTime === 'custom') { if (selectedTime === 'custom') {
// Convert the date range string to 12-hour format
const dates = selectedTimeValue.split(' - ');
if (dates.length === 2) {
const startDate = dayjs(dates[0], 'DD/MM/YYYY HH:mm');
const endDate = dayjs(dates[1], 'DD/MM/YYYY HH:mm');
return `${startDate.format('DD/MM/YYYY hh:mm A')} - ${endDate.format(
'DD/MM/YYYY hh:mm A',
)}`;
}
return selectedTimeValue; return selectedTimeValue;
} }
@@ -131,6 +167,7 @@ function CustomTimePicker({
setOpen(newOpen); setOpen(newOpen);
if (!newOpen) { if (!newOpen) {
setCustomDTPickerVisible?.(false); setCustomDTPickerVisible?.(false);
setActiveView('datetime');
} }
}; };
@@ -244,6 +281,7 @@ function CustomTimePicker({
const handleFocus = (): void => { const handleFocus = (): void => {
setIsInputFocused(true); setIsInputFocused(true);
setActiveView('datetime');
}; };
const handleBlur = (): void => { const handleBlur = (): void => {
@@ -280,6 +318,10 @@ function CustomTimePicker({
handleGoLive={defaultTo(handleGoLive, noop)} handleGoLive={defaultTo(handleGoLive, noop)}
options={items} options={items}
selectedTime={selectedTime} selectedTime={selectedTime}
activeView={activeView}
setActiveView={setActiveView}
setIsOpenedFromFooter={setIsOpenedFromFooter}
isOpenedFromFooter={isOpenedFromFooter}
/> />
) : ( ) : (
content content
@@ -316,12 +358,24 @@ function CustomTimePicker({
) )
} }
suffix={ suffix={
<ChevronDown <>
size={14} {!!isTimezoneOverridden && activeTimezoneOffset && (
onClick={(): void => { <div
setOpen(!open); className="timezone-badge"
}} onClick={(e): void => {
/> e.stopPropagation();
handleViewChange('timezone');
setIsOpenedFromFooter(false);
}}
>
<span>{activeTimezoneOffset}</span>
</div>
)}
<ChevronDown
size={14}
onClick={(): void => handleViewChange('datetime')}
/>
</>
} }
/> />
</Popover> </Popover>

View File

@@ -1,5 +1,6 @@
import './CustomTimePicker.styles.scss'; import './CustomTimePicker.styles.scss';
import { Color } from '@signozhq/design-tokens';
import { Button } from 'antd'; import { Button } from 'antd';
import cx from 'classnames'; import cx from 'classnames';
import ROUTES from 'constants/routes'; import ROUTES from 'constants/routes';
@@ -9,10 +10,13 @@ import {
Option, Option,
RelativeDurationSuggestionOptions, RelativeDurationSuggestionOptions,
} from 'container/TopNav/DateTimeSelectionV2/config'; } from 'container/TopNav/DateTimeSelectionV2/config';
import { Clock, PenLine } from 'lucide-react';
import { useTimezone } from 'providers/Timezone';
import { Dispatch, SetStateAction, useMemo } from 'react'; import { Dispatch, SetStateAction, useMemo } from 'react';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import RangePickerModal from './RangePickerModal'; import RangePickerModal from './RangePickerModal';
import TimezonePicker from './TimezonePicker';
interface CustomTimePickerPopoverContentProps { interface CustomTimePickerPopoverContentProps {
options: any[]; options: any[];
@@ -26,8 +30,13 @@ interface CustomTimePickerPopoverContentProps {
onSelectHandler: (label: string, value: string) => void; onSelectHandler: (label: string, value: string) => void;
handleGoLive: () => void; handleGoLive: () => void;
selectedTime: string; selectedTime: string;
activeView: 'datetime' | 'timezone';
setActiveView: Dispatch<SetStateAction<'datetime' | 'timezone'>>;
isOpenedFromFooter: boolean;
setIsOpenedFromFooter: Dispatch<SetStateAction<boolean>>;
} }
// eslint-disable-next-line sonarjs/cognitive-complexity
function CustomTimePickerPopoverContent({ function CustomTimePickerPopoverContent({
options, options,
setIsOpen, setIsOpen,
@@ -37,12 +46,18 @@ function CustomTimePickerPopoverContent({
onSelectHandler, onSelectHandler,
handleGoLive, handleGoLive,
selectedTime, selectedTime,
activeView,
setActiveView,
isOpenedFromFooter,
setIsOpenedFromFooter,
}: CustomTimePickerPopoverContentProps): JSX.Element { }: CustomTimePickerPopoverContentProps): JSX.Element {
const { pathname } = useLocation(); const { pathname } = useLocation();
const isLogsExplorerPage = useMemo(() => pathname === ROUTES.LOGS_EXPLORER, [ const isLogsExplorerPage = useMemo(() => pathname === ROUTES.LOGS_EXPLORER, [
pathname, pathname,
]); ]);
const { timezone } = useTimezone();
const activeTimezoneOffset = timezone.offset;
function getTimeChips(options: Option[]): JSX.Element { function getTimeChips(options: Option[]): JSX.Element {
return ( return (
@@ -63,55 +78,99 @@ function CustomTimePickerPopoverContent({
); );
} }
const handleTimezoneHintClick = (): void => {
setActiveView('timezone');
setIsOpenedFromFooter(true);
};
if (activeView === 'timezone') {
return (
<div className="date-time-popover">
<TimezonePicker
setActiveView={setActiveView}
setIsOpen={setIsOpen}
isOpenedFromFooter={isOpenedFromFooter}
/>
</div>
);
}
return ( return (
<div className="date-time-popover"> <>
<div className="date-time-options"> <div className="date-time-popover">
{isLogsExplorerPage && ( <div className="date-time-options">
<Button className="data-time-live" type="text" onClick={handleGoLive}> {isLogsExplorerPage && (
Live <Button className="data-time-live" type="text" onClick={handleGoLive}>
</Button> Live
)} </Button>
{options.map((option) => ( )}
<Button {options.map((option) => (
type="text" <Button
key={option.label + option.value} type="text"
onClick={(): void => { key={option.label + option.value}
onSelectHandler(option.label, option.value); onClick={(): void => {
}} onSelectHandler(option.label, option.value);
className={cx( }}
'date-time-options-btn', className={cx(
customDateTimeVisible 'date-time-options-btn',
? option.value === 'custom' && 'active' customDateTimeVisible
: selectedTime === option.value && 'active', ? option.value === 'custom' && 'active'
)} : selectedTime === option.value && 'active',
> )}
{option.label} >
</Button> {option.label}
))} </Button>
))}
</div>
<div
className={cx(
'relative-date-time',
selectedTime === 'custom' || customDateTimeVisible
? 'date-picker'
: 'relative-times',
)}
>
{selectedTime === 'custom' || customDateTimeVisible ? (
<RangePickerModal
setCustomDTPickerVisible={setCustomDTPickerVisible}
setIsOpen={setIsOpen}
onCustomDateHandler={onCustomDateHandler}
selectedTime={selectedTime}
/>
) : (
<div className="relative-times-container">
<div className="time-heading">RELATIVE TIMES</div>
<div>{getTimeChips(RelativeDurationSuggestionOptions)}</div>
</div>
)}
</div>
</div> </div>
<div
className={cx( <div className="date-time-popover__footer">
'relative-date-time', <div className="timezone-container">
selectedTime === 'custom' || customDateTimeVisible <Clock
? 'date-picker' color={Color.BG_VANILLA_400}
: 'relative-times', className="timezone-container__clock-icon"
)} height={12}
> width={12}
{selectedTime === 'custom' || customDateTimeVisible ? (
<RangePickerModal
setCustomDTPickerVisible={setCustomDTPickerVisible}
setIsOpen={setIsOpen}
onCustomDateHandler={onCustomDateHandler}
selectedTime={selectedTime}
/> />
) : ( <span className="timezone__icon">Current timezone</span>
<div className="relative-times-container"> <div></div>
<div className="time-heading">RELATIVE TIMES</div> <button
<div>{getTimeChips(RelativeDurationSuggestionOptions)}</div> type="button"
</div> className="timezone"
)} onClick={handleTimezoneHintClick}
>
<span>{activeTimezoneOffset}</span>
<PenLine
color={Color.BG_VANILLA_100}
className="timezone__icon"
size={10}
/>
</button>
</div>
</div> </div>
</div> </>
); );
} }

View File

@@ -3,7 +3,8 @@ import './RangePickerModal.styles.scss';
import { DatePicker } from 'antd'; import { DatePicker } from 'antd';
import { DateTimeRangeType } from 'container/TopNav/CustomDateTimeModal'; import { DateTimeRangeType } from 'container/TopNav/CustomDateTimeModal';
import { LexicalContext } from 'container/TopNav/DateTimeSelectionV2/config'; import { LexicalContext } from 'container/TopNav/DateTimeSelectionV2/config';
import dayjs, { Dayjs } from 'dayjs'; import dayjs from 'dayjs';
import { useTimezone } from 'providers/Timezone';
import { Dispatch, SetStateAction } from 'react'; import { Dispatch, SetStateAction } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
@@ -31,7 +32,10 @@ function RangePickerModal(props: RangePickerModalProps): JSX.Element {
(state) => state.globalTime, (state) => state.globalTime,
); );
const disabledDate = (current: Dayjs): boolean => { // Using any type here because antd's DatePicker expects its own internal Dayjs type
// which conflicts with our project's Dayjs type that has additional plugins (tz, utc etc).
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
const disabledDate = (current: any): boolean => {
const currentDay = dayjs(current); const currentDay = dayjs(current);
return currentDay.isAfter(dayjs()); return currentDay.isAfter(dayjs());
}; };
@@ -49,16 +53,22 @@ function RangePickerModal(props: RangePickerModalProps): JSX.Element {
} }
onCustomDateHandler(date_time, LexicalContext.CUSTOM_DATE_PICKER); onCustomDateHandler(date_time, LexicalContext.CUSTOM_DATE_PICKER);
}; };
const { timezone } = useTimezone();
return ( return (
<div className="custom-date-picker"> <div className="custom-date-picker">
<RangePicker <RangePicker
disabledDate={disabledDate} disabledDate={disabledDate}
allowClear allowClear
showTime showTime
format="YYYY-MM-DD hh:mm A"
onOk={onModalOkHandler} onOk={onModalOkHandler}
// eslint-disable-next-line react/jsx-props-no-spreading // eslint-disable-next-line react/jsx-props-no-spreading
{...(selectedTime === 'custom' && { {...(selectedTime === 'custom' && {
defaultValue: [dayjs(minTime / 1000000), dayjs(maxTime / 1000000)], defaultValue: [
dayjs(minTime / 1000000).tz(timezone.value),
dayjs(maxTime / 1000000).tz(timezone.value),
],
})} })}
/> />
</div> </div>

View File

@@ -0,0 +1,166 @@
// Variables
$font-family: 'Inter';
$item-spacing: 8px;
:root {
--border-color: var(--bg-slate-400);
}
.lightMode {
--border-color: var(--bg-vanilla-400);
}
// Mixins
@mixin text-style-base {
font-family: $font-family;
font-style: normal;
font-weight: 400;
}
@mixin flex-center {
display: flex;
align-items: center;
}
.timezone-picker {
width: 532px;
color: var(--bg-vanilla-400);
font-family: $font-family;
&__search {
@include flex-center;
justify-content: space-between;
padding: 12px 14px;
border-bottom: 1px solid var(--border-color);
}
&__input-container {
@include flex-center;
gap: 6px;
width: -webkit-fill-available;
}
&__input {
@include text-style-base;
width: 100%;
background: transparent;
border: none;
outline: none;
color: var(--bg-vanilla-100);
font-size: 14px;
line-height: 20px;
letter-spacing: -0.07px;
padding: 0;
&.ant-input:focus {
box-shadow: none;
}
&::placeholder {
color: var(--bg-vanilla-400);
}
}
&__esc-key {
@include text-style-base;
font-size: 8px;
color: var(--bg-vanilla-400);
letter-spacing: -0.04px;
border-radius: 2.286px;
border: 1.143px solid var(--bg-ink-200);
border-bottom-width: 2.286px;
background: var(--bg-ink-400);
padding: 0 1px;
}
&__list {
max-height: 310px;
overflow-y: auto;
}
&__item {
@include flex-center;
justify-content: space-between;
padding: 7.5px 6px 7.5px $item-spacing;
margin: 4px $item-spacing;
cursor: pointer;
background: transparent;
border: none;
width: -webkit-fill-available;
color: var(--bg-vanilla-400);
font-family: $font-family;
&:hover,
&.selected {
border-radius: 2px;
background: rgba(171, 189, 255, 0.04);
color: var(--bg-vanilla-100);
}
&.has-divider {
position: relative;
&::after {
content: '';
position: absolute;
bottom: -2px;
left: -$item-spacing;
right: -$item-spacing;
border-bottom: 1px solid var(--border-color);
}
}
}
&__name {
@include text-style-base;
font-size: 14px;
line-height: 20px;
letter-spacing: -0.07px;
}
&__offset {
color: var(--bg-vanilla-100);
font-size: 12px;
line-height: 16px;
letter-spacing: -0.06px;
}
}
.timezone-name-wrapper {
@include flex-center;
gap: 6px;
&__selected-icon {
height: 15px;
width: 15px;
}
}
.lightMode {
.timezone-picker {
&__search {
.search-icon {
stroke: var(--bg-ink-400);
}
}
&__input {
color: var(--bg-ink-100);
}
&__esc-key {
background-color: var(--bg-vanilla-100);
border-color: var(--bg-vanilla-400);
color: var(--bg-ink-400);
}
&__item {
color: var(--bg-ink-400);
}
&__offset {
color: var(--bg-ink-100);
}
}
.timezone-name-wrapper {
&__selected-icon {
.check-icon {
stroke: var(--bg-ink-100);
}
}
}
}

View File

@@ -0,0 +1,201 @@
import './TimezonePicker.styles.scss';
import { Color } from '@signozhq/design-tokens';
import { Input } from 'antd';
import cx from 'classnames';
import { TimezonePickerShortcuts } from 'constants/shortcuts/TimezonePickerShortcuts';
import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys';
import { Check, Search } from 'lucide-react';
import { useTimezone } from 'providers/Timezone';
import {
Dispatch,
SetStateAction,
useCallback,
useEffect,
useState,
} from 'react';
import { Timezone, TIMEZONE_DATA } from './timezoneUtils';
interface SearchBarProps {
value: string;
onChange: (value: string) => void;
setIsOpen: Dispatch<SetStateAction<boolean>>;
setActiveView: Dispatch<SetStateAction<'datetime' | 'timezone'>>;
isOpenedFromFooter: boolean;
}
interface TimezoneItemProps {
timezone: Timezone;
isSelected?: boolean;
onClick?: () => void;
}
const ICON_SIZE = 14;
function SearchBar({
value,
onChange,
setIsOpen,
setActiveView,
isOpenedFromFooter = false,
}: SearchBarProps): JSX.Element {
const handleKeyDown = useCallback(
(e: React.KeyboardEvent): void => {
if (e.key === 'Escape') {
if (isOpenedFromFooter) {
setActiveView('datetime');
} else {
setIsOpen(false);
}
}
},
[setActiveView, setIsOpen, isOpenedFromFooter],
);
return (
<div className="timezone-picker__search">
<div className="timezone-picker__input-container">
<Search
color={Color.BG_VANILLA_400}
className="search-icon"
height={ICON_SIZE}
width={ICON_SIZE}
/>
<Input
type="text"
className="timezone-picker__input"
placeholder="Search timezones..."
value={value}
onChange={(e): void => onChange(e.target.value)}
onKeyDown={handleKeyDown}
tabIndex={0}
autoFocus
/>
</div>
<kbd className="timezone-picker__esc-key">esc</kbd>
</div>
);
}
function TimezoneItem({
timezone,
isSelected = false,
onClick,
}: TimezoneItemProps): JSX.Element {
return (
<button
type="button"
className={cx('timezone-picker__item', {
selected: isSelected,
'has-divider': timezone.hasDivider,
})}
onClick={onClick}
>
<div className="timezone-name-wrapper">
<div className="timezone-name-wrapper__selected-icon">
{isSelected && (
<Check
className="check-icon"
color={Color.BG_VANILLA_100}
height={ICON_SIZE}
width={ICON_SIZE}
/>
)}
</div>
<div className="timezone-picker__name">{timezone.name}</div>
</div>
<div className="timezone-picker__offset">{timezone.offset}</div>
</button>
);
}
TimezoneItem.defaultProps = {
isSelected: false,
onClick: undefined,
};
interface TimezonePickerProps {
setActiveView: Dispatch<SetStateAction<'datetime' | 'timezone'>>;
setIsOpen: Dispatch<SetStateAction<boolean>>;
isOpenedFromFooter: boolean;
}
function TimezonePicker({
setActiveView,
setIsOpen,
isOpenedFromFooter,
}: TimezonePickerProps): JSX.Element {
console.log({ isOpenedFromFooter });
const [searchTerm, setSearchTerm] = useState('');
const { timezone, updateTimezone } = useTimezone();
const [selectedTimezone, setSelectedTimezone] = useState<string>(
timezone.name ?? TIMEZONE_DATA[0].name,
);
const getFilteredTimezones = useCallback((searchTerm: string): Timezone[] => {
const normalizedSearch = searchTerm.toLowerCase();
return TIMEZONE_DATA.filter(
(tz) =>
tz.name.toLowerCase().includes(normalizedSearch) ||
tz.offset.toLowerCase().includes(normalizedSearch) ||
tz.searchIndex.toLowerCase().includes(normalizedSearch),
);
}, []);
const handleCloseTimezonePicker = useCallback(() => {
if (isOpenedFromFooter) {
setActiveView('datetime');
} else {
setIsOpen(false);
}
}, [isOpenedFromFooter, setActiveView, setIsOpen]);
const handleTimezoneSelect = useCallback(
(timezone: Timezone) => {
setSelectedTimezone(timezone.name);
updateTimezone(timezone);
handleCloseTimezonePicker();
setIsOpen(false);
},
[handleCloseTimezonePicker, setIsOpen, updateTimezone],
);
// Register keyboard shortcuts
const { registerShortcut, deregisterShortcut } = useKeyboardHotkeys();
useEffect(() => {
registerShortcut(
TimezonePickerShortcuts.CloseTimezonePicker,
handleCloseTimezonePicker,
);
return (): void => {
deregisterShortcut(TimezonePickerShortcuts.CloseTimezonePicker);
};
}, [deregisterShortcut, handleCloseTimezonePicker, registerShortcut]);
return (
<div className="timezone-picker">
<SearchBar
value={searchTerm}
onChange={setSearchTerm}
setIsOpen={setIsOpen}
setActiveView={setActiveView}
isOpenedFromFooter={isOpenedFromFooter}
/>
<div className="timezone-picker__list">
{getFilteredTimezones(searchTerm).map((timezone) => (
<TimezoneItem
key={timezone.value}
timezone={timezone}
isSelected={timezone.name === selectedTimezone}
onClick={(): void => handleTimezoneSelect(timezone)}
/>
))}
</div>
</div>
);
}
export default TimezonePicker;

View File

@@ -0,0 +1,152 @@
import dayjs from 'dayjs';
import timezone from 'dayjs/plugin/timezone';
import utc from 'dayjs/plugin/utc';
dayjs.extend(utc);
dayjs.extend(timezone);
export interface Timezone {
name: string;
value: string;
offset: string;
searchIndex: string;
hasDivider?: boolean;
}
const TIMEZONE_TYPES = {
BROWSER: 'BROWSER',
UTC: 'UTC',
STANDARD: 'STANDARD',
} as const;
type TimezoneType = typeof TIMEZONE_TYPES[keyof typeof TIMEZONE_TYPES];
export const UTC_TIMEZONE: Timezone = {
name: 'Coordinated Universal Time — UTC, GMT',
value: 'UTC',
offset: 'UTC',
searchIndex: 'UTC',
hasDivider: true,
};
const normalizeTimezoneName = (timezone: string): string => {
// https://github.com/tc39/proposal-temporal/issues/1076
if (timezone === 'Asia/Calcutta') {
return 'Asia/Kolkata';
}
return timezone;
};
const formatOffset = (offsetMinutes: number): string => {
if (offsetMinutes === 0) return 'UTC';
const hours = Math.floor(Math.abs(offsetMinutes) / 60);
const minutes = Math.abs(offsetMinutes) % 60;
const sign = offsetMinutes > 0 ? '+' : '-';
return `UTC ${sign} ${hours}${
minutes ? `:${minutes.toString().padStart(2, '0')}` : ':00'
}`;
};
const createTimezoneEntry = (
name: string,
offsetMinutes: number,
type: TimezoneType = TIMEZONE_TYPES.STANDARD,
hasDivider = false,
): Timezone => {
const offset = formatOffset(offsetMinutes);
let value = name;
let displayName = name;
switch (type) {
case TIMEZONE_TYPES.BROWSER:
displayName = `Browser time — ${name}`;
value = name;
break;
case TIMEZONE_TYPES.UTC:
displayName = 'Coordinated Universal Time — UTC, GMT';
value = 'UTC';
break;
case TIMEZONE_TYPES.STANDARD:
displayName = name;
value = name;
break;
default:
console.error(`Invalid timezone type: ${type}`);
}
return {
name: displayName,
value,
offset,
searchIndex: offset.replace(/ /g, ''),
...(hasDivider && { hasDivider }),
};
};
const getOffsetByTimezone = (timezone: string): number => {
const dayjsTimezone = dayjs().tz(timezone);
return dayjsTimezone.utcOffset();
};
export const getBrowserTimezone = (): Timezone => {
const browserTz = dayjs.tz.guess();
const normalizedTz = normalizeTimezoneName(browserTz);
const browserOffset = getOffsetByTimezone(normalizedTz);
return createTimezoneEntry(
normalizedTz,
browserOffset,
TIMEZONE_TYPES.BROWSER,
);
};
const filterAndSortTimezones = (
allTimezones: string[],
browserTzName?: string,
includeEtcTimezones = false,
): Timezone[] =>
allTimezones
.filter((tz) => {
const isNotBrowserTz = tz !== browserTzName;
const isNotEtcTz = includeEtcTimezones || !tz.startsWith('Etc/');
return isNotBrowserTz && isNotEtcTz;
})
.sort((a, b) => a.localeCompare(b))
.map((tz) => {
const normalizedTz = normalizeTimezoneName(tz);
const offset = getOffsetByTimezone(normalizedTz);
return createTimezoneEntry(normalizedTz, offset);
});
const generateTimezoneData = (includeEtcTimezones = false): Timezone[] => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const allTimezones = (Intl as any).supportedValuesOf('timeZone');
const timezones: Timezone[] = [];
// Add browser timezone
const browserTzObject = getBrowserTimezone();
timezones.push(browserTzObject);
// Add UTC timezone with divider
timezones.push(UTC_TIMEZONE);
timezones.push(
...filterAndSortTimezones(
allTimezones,
browserTzObject.value,
includeEtcTimezones,
),
);
return timezones;
};
export const getTimezoneObjectByTimezoneString = (
timezone: string,
): Timezone => {
const utcOffset = getOffsetByTimezone(timezone);
return createTimezoneEntry(timezone, utcOffset);
};
export const TIMEZONE_DATA = generateTimezoneData();

View File

@@ -1,4 +1,5 @@
import { import {
_adapters,
BarController, BarController,
BarElement, BarElement,
CategoryScale, CategoryScale,
@@ -18,8 +19,10 @@ import {
} from 'chart.js'; } from 'chart.js';
import annotationPlugin from 'chartjs-plugin-annotation'; import annotationPlugin from 'chartjs-plugin-annotation';
import { generateGridTitle } from 'container/GridPanelSwitch/utils'; import { generateGridTitle } from 'container/GridPanelSwitch/utils';
import dayjs from 'dayjs';
import { useIsDarkMode } from 'hooks/useDarkMode'; import { useIsDarkMode } from 'hooks/useDarkMode';
import isEqual from 'lodash-es/isEqual'; import isEqual from 'lodash-es/isEqual';
import { useTimezone } from 'providers/Timezone';
import { import {
forwardRef, forwardRef,
memo, memo,
@@ -62,6 +65,17 @@ Chart.register(
Tooltip.positioners.custom = TooltipPositionHandler; Tooltip.positioners.custom = TooltipPositionHandler;
// Map of Chart.js time formats to dayjs format strings
const formatMap = {
'HH:mm:ss': 'HH:mm:ss',
'HH:mm': 'HH:mm',
'MM/DD HH:mm': 'MM/DD HH:mm',
'MM/dd HH:mm': 'MM/DD HH:mm',
'MM/DD': 'MM/DD',
'YY-MM': 'YY-MM',
YY: 'YY',
};
const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>( const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>(
( (
{ {
@@ -80,11 +94,13 @@ const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>(
dragSelectColor, dragSelectColor,
}, },
ref, ref,
// eslint-disable-next-line sonarjs/cognitive-complexity
): JSX.Element => { ): JSX.Element => {
const nearestDatasetIndex = useRef<null | number>(null); const nearestDatasetIndex = useRef<null | number>(null);
const chartRef = useRef<HTMLCanvasElement>(null); const chartRef = useRef<HTMLCanvasElement>(null);
const isDarkMode = useIsDarkMode(); const isDarkMode = useIsDarkMode();
const gridTitle = useMemo(() => generateGridTitle(title), [title]); const gridTitle = useMemo(() => generateGridTitle(title), [title]);
const { timezone } = useTimezone();
const currentTheme = isDarkMode ? 'dark' : 'light'; const currentTheme = isDarkMode ? 'dark' : 'light';
const xAxisTimeUnit = useXAxisTimeUnit(data); // Computes the relevant time unit for x axis by analyzing the time stamp data const xAxisTimeUnit = useXAxisTimeUnit(data); // Computes the relevant time unit for x axis by analyzing the time stamp data
@@ -112,6 +128,22 @@ const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>(
return 'rgba(231,233,237,0.8)'; return 'rgba(231,233,237,0.8)';
}, [currentTheme]); }, [currentTheme]);
// Override Chart.js date adapter to use dayjs with timezone support
useEffect(() => {
_adapters._date.override({
format(time: number | Date, fmt: string) {
const dayjsTime = dayjs(time).tz(timezone.value);
const format = formatMap[fmt as keyof typeof formatMap];
if (!format) {
console.warn(`Missing datetime format for ${fmt}`);
return dayjsTime.format('YYYY-MM-DD HH:mm:ss'); // fallback format
}
return dayjsTime.format(format);
},
});
}, [timezone]);
const buildChart = useCallback(() => { const buildChart = useCallback(() => {
if (lineChartRef.current !== undefined) { if (lineChartRef.current !== undefined) {
lineChartRef.current.destroy(); lineChartRef.current.destroy();
@@ -132,6 +164,7 @@ const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>(
isStacked, isStacked,
onClickHandler, onClickHandler,
data, data,
timezone,
); );
const chartHasData = hasData(data); const chartHasData = hasData(data);
@@ -166,6 +199,7 @@ const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>(
isStacked, isStacked,
onClickHandler, onClickHandler,
data, data,
timezone,
name, name,
type, type,
]); ]);

View File

@@ -1,5 +1,6 @@
import { Chart, ChartConfiguration, ChartData, Color } from 'chart.js'; import { Chart, ChartConfiguration, ChartData, Color } from 'chart.js';
import * as chartjsAdapter from 'chartjs-adapter-date-fns'; import * as chartjsAdapter from 'chartjs-adapter-date-fns';
import { Timezone } from 'components/CustomTimePicker/timezoneUtils';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { MutableRefObject } from 'react'; import { MutableRefObject } from 'react';
@@ -50,6 +51,7 @@ export const getGraphOptions = (
isStacked: boolean | undefined, isStacked: boolean | undefined,
onClickHandler: GraphOnClickHandler | undefined, onClickHandler: GraphOnClickHandler | undefined,
data: ChartData, data: ChartData,
timezone: Timezone,
// eslint-disable-next-line sonarjs/cognitive-complexity // eslint-disable-next-line sonarjs/cognitive-complexity
): CustomChartOptions => ({ ): CustomChartOptions => ({
animation: { animation: {
@@ -97,7 +99,7 @@ export const getGraphOptions = (
callbacks: { callbacks: {
title(context): string | string[] { title(context): string | string[] {
const date = dayjs(context[0].parsed.x); const date = dayjs(context[0].parsed.x);
return date.format('MMM DD, YYYY, HH:mm:ss'); return date.tz(timezone.value).format('MMM DD, YYYY, HH:mm:ss');
}, },
label(context): string | string[] { label(context): string | string[] {
let label = context.dataset.label || ''; let label = context.dataset.label || '';

View File

@@ -8,13 +8,13 @@ import LogDetail from 'components/LogDetail';
import { VIEW_TYPES } from 'components/LogDetail/constants'; import { VIEW_TYPES } from 'components/LogDetail/constants';
import { unescapeString } from 'container/LogDetailedView/utils'; import { unescapeString } from 'container/LogDetailedView/utils';
import { FontSize } from 'container/OptionsMenu/types'; import { FontSize } from 'container/OptionsMenu/types';
import dayjs from 'dayjs';
import dompurify from 'dompurify'; import dompurify from 'dompurify';
import { useActiveLog } from 'hooks/logs/useActiveLog'; import { useActiveLog } from 'hooks/logs/useActiveLog';
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink'; import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
import { useIsDarkMode } from 'hooks/useDarkMode'; import { useIsDarkMode } from 'hooks/useDarkMode';
// utils // utils
import { FlatLogData } from 'lib/logs/flatLogData'; import { FlatLogData } from 'lib/logs/flatLogData';
import { useTimezone } from 'providers/Timezone';
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
// interfaces // interfaces
import { IField } from 'types/api/logs/fields'; import { IField } from 'types/api/logs/fields';
@@ -174,12 +174,20 @@ function ListLogView({
[selectedFields], [selectedFields],
); );
const { formatTimezoneAdjustedTimestamp } = useTimezone();
const timestampValue = useMemo( const timestampValue = useMemo(
() => () =>
typeof flattenLogData.timestamp === 'string' typeof flattenLogData.timestamp === 'string'
? dayjs(flattenLogData.timestamp).format('YYYY-MM-DD HH:mm:ss.SSS') ? formatTimezoneAdjustedTimestamp(
: dayjs(flattenLogData.timestamp / 1e6).format('YYYY-MM-DD HH:mm:ss.SSS'), flattenLogData.timestamp,
[flattenLogData.timestamp], 'YYYY-MM-DD HH:mm:ss.SSS',
)
: formatTimezoneAdjustedTimestamp(
flattenLogData.timestamp / 1e6,
'YYYY-MM-DD HH:mm:ss.SSS',
),
[flattenLogData.timestamp, formatTimezoneAdjustedTimestamp],
); );
const logType = getLogIndicatorType(logData); const logType = getLogIndicatorType(logData);

View File

@@ -6,7 +6,6 @@ import LogDetail from 'components/LogDetail';
import { VIEW_TYPES, VIEWS } from 'components/LogDetail/constants'; import { VIEW_TYPES, VIEWS } from 'components/LogDetail/constants';
import { unescapeString } from 'container/LogDetailedView/utils'; import { unescapeString } from 'container/LogDetailedView/utils';
import LogsExplorerContext from 'container/LogsExplorerContext'; import LogsExplorerContext from 'container/LogsExplorerContext';
import dayjs from 'dayjs';
import dompurify from 'dompurify'; import dompurify from 'dompurify';
import { useActiveLog } from 'hooks/logs/useActiveLog'; import { useActiveLog } from 'hooks/logs/useActiveLog';
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink'; import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
@@ -14,6 +13,7 @@ import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
import { useIsDarkMode } from 'hooks/useDarkMode'; import { useIsDarkMode } from 'hooks/useDarkMode';
import { FlatLogData } from 'lib/logs/flatLogData'; import { FlatLogData } from 'lib/logs/flatLogData';
import { isEmpty, isNumber, isUndefined } from 'lodash-es'; import { isEmpty, isNumber, isUndefined } from 'lodash-es';
import { useTimezone } from 'providers/Timezone';
import { import {
KeyboardEvent, KeyboardEvent,
MouseEvent, MouseEvent,
@@ -89,16 +89,24 @@ function RawLogView({
attributesText += ' | '; attributesText += ' | ';
} }
const { formatTimezoneAdjustedTimestamp } = useTimezone();
const text = useMemo(() => { const text = useMemo(() => {
const date = const date =
typeof data.timestamp === 'string' typeof data.timestamp === 'string'
? dayjs(data.timestamp) ? formatTimezoneAdjustedTimestamp(data.timestamp, 'YYYY-MM-DD HH:mm:ss.SSS')
: dayjs(data.timestamp / 1e6); : formatTimezoneAdjustedTimestamp(
data.timestamp / 1e6,
'YYYY-MM-DD HH:mm:ss.SSS',
);
return `${date.format('YYYY-MM-DD HH:mm:ss.SSS')} | ${attributesText} ${ return `${date} | ${attributesText} ${data.body}`;
data.body }, [
}`; data.timestamp,
}, [data.timestamp, data.body, attributesText]); data.body,
attributesText,
formatTimezoneAdjustedTimestamp,
]);
const handleClickExpand = useCallback(() => { const handleClickExpand = useCallback(() => {
if (activeContextLog || isReadOnly) return; if (activeContextLog || isReadOnly) return;

View File

@@ -5,10 +5,10 @@ import { Typography } from 'antd';
import { ColumnsType } from 'antd/es/table'; import { ColumnsType } from 'antd/es/table';
import cx from 'classnames'; import cx from 'classnames';
import { unescapeString } from 'container/LogDetailedView/utils'; import { unescapeString } from 'container/LogDetailedView/utils';
import dayjs from 'dayjs';
import dompurify from 'dompurify'; import dompurify from 'dompurify';
import { useIsDarkMode } from 'hooks/useDarkMode'; import { useIsDarkMode } from 'hooks/useDarkMode';
import { FlatLogData } from 'lib/logs/flatLogData'; import { FlatLogData } from 'lib/logs/flatLogData';
import { useTimezone } from 'providers/Timezone';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { FORBID_DOM_PURIFY_TAGS } from 'utils/app'; import { FORBID_DOM_PURIFY_TAGS } from 'utils/app';
@@ -44,6 +44,8 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
logs, logs,
]); ]);
const { formatTimezoneAdjustedTimestamp } = useTimezone();
const columns: ColumnsType<Record<string, unknown>> = useMemo(() => { const columns: ColumnsType<Record<string, unknown>> = useMemo(() => {
const fieldColumns: ColumnsType<Record<string, unknown>> = fields const fieldColumns: ColumnsType<Record<string, unknown>> = fields
.filter((e) => e.name !== 'id') .filter((e) => e.name !== 'id')
@@ -81,8 +83,11 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
render: (field, item): ColumnTypeRender<Record<string, unknown>> => { render: (field, item): ColumnTypeRender<Record<string, unknown>> => {
const date = const date =
typeof field === 'string' typeof field === 'string'
? dayjs(field).format('YYYY-MM-DD HH:mm:ss.SSS') ? formatTimezoneAdjustedTimestamp(field, 'YYYY-MM-DD HH:mm:ss.SSS')
: dayjs(field / 1e6).format('YYYY-MM-DD HH:mm:ss.SSS'); : formatTimezoneAdjustedTimestamp(
field / 1e6,
'YYYY-MM-DD HH:mm:ss.SSS',
);
return { return {
children: ( children: (
<div className="table-timestamp"> <div className="table-timestamp">
@@ -125,7 +130,15 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
}, },
...(appendTo === 'end' ? fieldColumns : []), ...(appendTo === 'end' ? fieldColumns : []),
]; ];
}, [fields, isListViewPanel, appendTo, isDarkMode, linesPerRow, fontSize]); }, [
fields,
isListViewPanel,
appendTo,
isDarkMode,
linesPerRow,
fontSize,
formatTimezoneAdjustedTimestamp,
]);
return { columns, dataSource: flattenLogData }; return { columns, dataSource: flattenLogData };
}; };

View File

@@ -1,11 +1,13 @@
import { Typography } from 'antd'; import { Typography } from 'antd';
import convertDateToAmAndPm from 'lib/convertDateToAmAndPm'; import { useTimezone } from 'providers/Timezone';
import getFormattedDate from 'lib/getFormatedDate';
function Time({ CreatedOrUpdateTime }: DateProps): JSX.Element { function Time({ CreatedOrUpdateTime }: DateProps): JSX.Element {
const { formatTimezoneAdjustedTimestamp } = useTimezone();
const time = new Date(CreatedOrUpdateTime); const time = new Date(CreatedOrUpdateTime);
const date = getFormattedDate(time); const timeString = formatTimezoneAdjustedTimestamp(
const timeString = `${date} ${convertDateToAmAndPm(time)}`; time,
'MM/DD/YYYY hh:mm:ss A (UTC Z)',
);
return <Typography>{timeString}</Typography>; return <Typography>{timeString}</Typography>;
} }

View File

@@ -21,4 +21,5 @@ export enum LOCALSTORAGE {
THEME_ANALYTICS_V1 = 'THEME_ANALYTICS_V1', THEME_ANALYTICS_V1 = 'THEME_ANALYTICS_V1',
LAST_USED_SAVED_VIEWS = 'LAST_USED_SAVED_VIEWS', LAST_USED_SAVED_VIEWS = 'LAST_USED_SAVED_VIEWS',
SHOW_LOGS_QUICK_FILTERS = 'SHOW_LOGS_QUICK_FILTERS', SHOW_LOGS_QUICK_FILTERS = 'SHOW_LOGS_QUICK_FILTERS',
PREFERRED_TIMEZONE = 'PREFERRED_TIMEZONE',
} }

View File

@@ -0,0 +1,3 @@
export const TimezonePickerShortcuts = {
CloseTimezonePicker: 'escape',
};

View File

@@ -7,6 +7,7 @@ import useUrlQuery from 'hooks/useUrlQuery';
import history from 'lib/history'; import history from 'lib/history';
import heatmapPlugin from 'lib/uPlotLib/plugins/heatmapPlugin'; import heatmapPlugin from 'lib/uPlotLib/plugins/heatmapPlugin';
import timelinePlugin from 'lib/uPlotLib/plugins/timelinePlugin'; import timelinePlugin from 'lib/uPlotLib/plugins/timelinePlugin';
import { useTimezone } from 'providers/Timezone';
import { useMemo, useRef } from 'react'; import { useMemo, useRef } from 'react';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { UpdateTimeInterval } from 'store/actions'; import { UpdateTimeInterval } from 'store/actions';
@@ -48,6 +49,7 @@ function HorizontalTimelineGraph({
const urlQuery = useUrlQuery(); const urlQuery = useUrlQuery();
const dispatch = useDispatch(); const dispatch = useDispatch();
const { timezone } = useTimezone();
const options: uPlot.Options = useMemo( const options: uPlot.Options = useMemo(
() => ({ () => ({
@@ -116,8 +118,18 @@ function HorizontalTimelineGraph({
}), }),
] ]
: [], : [],
tzDate: (timestamp: number): Date =>
uPlot.tzDate(new Date(timestamp * 1e3), timezone.value),
}), }),
[width, isDarkMode, transformedData.length, urlQuery, dispatch], [
width,
isDarkMode,
transformedData.length,
urlQuery,
dispatch,
timezone.value,
],
); );
return <Uplot data={transformedData} options={options} />; return <Uplot data={transformedData} options={options} />;
} }

View File

@@ -7,6 +7,7 @@ import {
useGetAlertRuleDetailsTimelineTable, useGetAlertRuleDetailsTimelineTable,
useTimelineTable, useTimelineTable,
} from 'pages/AlertDetails/hooks'; } from 'pages/AlertDetails/hooks';
import { useTimezone } from 'providers/Timezone';
import { HTMLAttributes, useMemo, useState } from 'react'; import { HTMLAttributes, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { AlertRuleTimelineTableResponse } from 'types/api/alerts/def'; import { AlertRuleTimelineTableResponse } from 'types/api/alerts/def';
@@ -41,6 +42,8 @@ function TimelineTable(): JSX.Element {
const { t } = useTranslation('common'); const { t } = useTranslation('common');
const { formatTimezoneAdjustedTimestamp } = useTimezone();
if (isError || !isValidRuleId || !ruleId) { if (isError || !isValidRuleId || !ruleId) {
return <div>{t('something_went_wrong')}</div>; return <div>{t('something_went_wrong')}</div>;
} }
@@ -64,6 +67,7 @@ function TimelineTable(): JSX.Element {
filters, filters,
labels: labels ?? {}, labels: labels ?? {},
setFilters, setFilters,
formatTimezoneAdjustedTimestamp,
})} })}
onRow={handleRowClick} onRow={handleRowClick}
dataSource={timelineData} dataSource={timelineData}

View File

@@ -8,6 +8,7 @@ import ClientSideQBSearch, {
import { ConditionalAlertPopover } from 'container/AlertHistory/AlertPopover/AlertPopover'; import { ConditionalAlertPopover } from 'container/AlertHistory/AlertPopover/AlertPopover';
import { transformKeyValuesToAttributeValuesMap } from 'container/QueryBuilder/filters/utils'; import { transformKeyValuesToAttributeValuesMap } from 'container/QueryBuilder/filters/utils';
import { useIsDarkMode } from 'hooks/useDarkMode'; import { useIsDarkMode } from 'hooks/useDarkMode';
import { TimestampInput } from 'hooks/useTimezoneFormatter/useTimezoneFormatter';
import { Search } from 'lucide-react'; import { Search } from 'lucide-react';
import AlertLabels, { import AlertLabels, {
AlertLabelsProps, AlertLabelsProps,
@@ -16,7 +17,6 @@ import AlertState from 'pages/AlertDetails/AlertHeader/AlertState/AlertState';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { AlertRuleTimelineTableResponse } from 'types/api/alerts/def'; import { AlertRuleTimelineTableResponse } from 'types/api/alerts/def';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData'; import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { formatEpochTimestamp } from 'utils/timeUtils';
const transformLabelsToQbKeys = ( const transformLabelsToQbKeys = (
labels: AlertRuleTimelineTableResponse['labels'], labels: AlertRuleTimelineTableResponse['labels'],
@@ -74,10 +74,15 @@ export const timelineTableColumns = ({
filters, filters,
labels, labels,
setFilters, setFilters,
formatTimezoneAdjustedTimestamp,
}: { }: {
filters: TagFilter; filters: TagFilter;
labels: AlertLabelsProps['labels']; labels: AlertLabelsProps['labels'];
setFilters: (filters: TagFilter) => void; setFilters: (filters: TagFilter) => void;
formatTimezoneAdjustedTimestamp: (
input: TimestampInput,
format?: string,
) => string;
}): ColumnsType<AlertRuleTimelineTableResponse> => [ }): ColumnsType<AlertRuleTimelineTableResponse> => [
{ {
title: 'STATE', title: 'STATE',
@@ -106,7 +111,9 @@ export const timelineTableColumns = ({
dataIndex: 'unixMilli', dataIndex: 'unixMilli',
width: 200, width: 200,
render: (value): JSX.Element => ( render: (value): JSX.Element => (
<div className="alert-rule__created-at">{formatEpochTimestamp(value)}</div> <div className="alert-rule__created-at">
{formatTimezoneAdjustedTimestamp(value, 'MMM D, YYYY ⎯ HH:mm:ss')}
</div>
), ),
}, },
{ {

View File

@@ -17,14 +17,15 @@ import getAll from 'api/errors/getAll';
import getErrorCounts from 'api/errors/getErrorCounts'; import getErrorCounts from 'api/errors/getErrorCounts';
import { ResizeTable } from 'components/ResizeTable'; import { ResizeTable } from 'components/ResizeTable';
import ROUTES from 'constants/routes'; import ROUTES from 'constants/routes';
import dayjs from 'dayjs';
import { useNotifications } from 'hooks/useNotifications'; import { useNotifications } from 'hooks/useNotifications';
import useResourceAttribute from 'hooks/useResourceAttribute'; import useResourceAttribute from 'hooks/useResourceAttribute';
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils'; import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
import { TimestampInput } from 'hooks/useTimezoneFormatter/useTimezoneFormatter';
import useUrlQuery from 'hooks/useUrlQuery'; import useUrlQuery from 'hooks/useUrlQuery';
import createQueryParams from 'lib/createQueryParams'; import createQueryParams from 'lib/createQueryParams';
import history from 'lib/history'; import history from 'lib/history';
import { isUndefined } from 'lodash-es'; import { isUndefined } from 'lodash-es';
import { useTimezone } from 'providers/Timezone';
import { useCallback, useEffect, useMemo, useRef } from 'react'; import { useCallback, useEffect, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useQueries } from 'react-query'; import { useQueries } from 'react-query';
@@ -155,8 +156,16 @@ function AllErrors(): JSX.Element {
} }
}, [data?.error, data?.payload, t, notifications]); }, [data?.error, data?.payload, t, notifications]);
const getDateValue = (value: string): JSX.Element => ( const getDateValue = (
<Typography>{dayjs(value).format('DD/MM/YYYY HH:mm:ss A')}</Typography> value: string,
formatTimezoneAdjustedTimestamp: (
input: TimestampInput,
format?: string,
) => string,
): JSX.Element => (
<Typography>
{formatTimezoneAdjustedTimestamp(value, 'DD/MM/YYYY hh:mm:ss A')}
</Typography>
); );
const filterIcon = useCallback(() => <SearchOutlined />, []); const filterIcon = useCallback(() => <SearchOutlined />, []);
@@ -283,6 +292,8 @@ function AllErrors(): JSX.Element {
[filterIcon, filterDropdownWrapper], [filterIcon, filterDropdownWrapper],
); );
const { formatTimezoneAdjustedTimestamp } = useTimezone();
const columns: ColumnsType<Exception> = [ const columns: ColumnsType<Exception> = [
{ {
title: 'Exception Type', title: 'Exception Type',
@@ -342,7 +353,8 @@ function AllErrors(): JSX.Element {
dataIndex: 'lastSeen', dataIndex: 'lastSeen',
width: 80, width: 80,
key: 'lastSeen', key: 'lastSeen',
render: getDateValue, render: (value): JSX.Element =>
getDateValue(value, formatTimezoneAdjustedTimestamp),
sorter: true, sorter: true,
defaultSortOrder: getDefaultOrder( defaultSortOrder: getDefaultOrder(
getUpdatedParams, getUpdatedParams,
@@ -355,7 +367,8 @@ function AllErrors(): JSX.Element {
dataIndex: 'firstSeen', dataIndex: 'firstSeen',
width: 80, width: 80,
key: 'firstSeen', key: 'firstSeen',
render: getDateValue, render: (value): JSX.Element =>
getDateValue(value, formatTimezoneAdjustedTimestamp),
sorter: true, sorter: true,
defaultSortOrder: getDefaultOrder( defaultSortOrder: getDefaultOrder(
getUpdatedParams, getUpdatedParams,

View File

@@ -10,6 +10,7 @@ import getAxes from 'lib/uPlotLib/utils/getAxes';
import { getUplotChartDataForAnomalyDetection } from 'lib/uPlotLib/utils/getUplotChartData'; import { getUplotChartDataForAnomalyDetection } from 'lib/uPlotLib/utils/getUplotChartData';
import { getYAxisScaleForAnomalyDetection } from 'lib/uPlotLib/utils/getYAxisScale'; import { getYAxisScaleForAnomalyDetection } from 'lib/uPlotLib/utils/getYAxisScale';
import { LineChart } from 'lucide-react'; import { LineChart } from 'lucide-react';
import { useTimezone } from 'providers/Timezone';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import uPlot from 'uplot'; import uPlot from 'uplot';
@@ -148,10 +149,12 @@ function AnomalyAlertEvaluationView({
] ]
: []; : [];
const { timezone } = useTimezone();
const options = { const options = {
width: dimensions.width, width: dimensions.width,
height: dimensions.height - 36, height: dimensions.height - 36,
plugins: [bandsPlugin, tooltipPlugin(isDarkMode)], plugins: [bandsPlugin, tooltipPlugin(isDarkMode, timezone.value)],
focus: { focus: {
alpha: 0.3, alpha: 0.3,
}, },
@@ -256,6 +259,8 @@ function AnomalyAlertEvaluationView({
show: true, show: true,
}, },
axes: getAxes(isDarkMode, yAxisUnit), axes: getAxes(isDarkMode, yAxisUnit),
tzDate: (timestamp: number): Date =>
uPlot.tzDate(new Date(timestamp * 1e3), timezone.value),
}; };
const handleSearch = (searchText: string): void => { const handleSearch = (searchText: string): void => {

View File

@@ -1,8 +1,10 @@
import { themeColors } from 'constants/theme'; import { themeColors } from 'constants/theme';
import dayjs from 'dayjs';
import { generateColor } from 'lib/uPlotLib/utils/generateColor'; import { generateColor } from 'lib/uPlotLib/utils/generateColor';
const tooltipPlugin = ( const tooltipPlugin = (
isDarkMode: boolean, isDarkMode: boolean,
timezone: string,
): { hooks: { init: (u: any) => void } } => { ): { hooks: { init: (u: any) => void } } => {
let tooltip: HTMLDivElement; let tooltip: HTMLDivElement;
const tooltipLeftOffset = 10; const tooltipLeftOffset = 10;
@@ -17,7 +19,7 @@ const tooltipPlugin = (
return value.toFixed(3); return value.toFixed(3);
} }
if (value instanceof Date) { if (value instanceof Date) {
return value.toLocaleString(); return dayjs(value).tz(timezone).format('MM/DD/YYYY, h:mm:ss A');
} }
if (value == null) { if (value == null) {
return 'N/A'; return 'N/A';

View File

@@ -6,12 +6,12 @@ import getNextPrevId from 'api/errors/getNextPrevId';
import Editor from 'components/Editor'; import Editor from 'components/Editor';
import { ResizeTable } from 'components/ResizeTable'; import { ResizeTable } from 'components/ResizeTable';
import { getNanoSeconds } from 'container/AllError/utils'; import { getNanoSeconds } from 'container/AllError/utils';
import dayjs from 'dayjs';
import { useNotifications } from 'hooks/useNotifications'; import { useNotifications } from 'hooks/useNotifications';
import createQueryParams from 'lib/createQueryParams'; import createQueryParams from 'lib/createQueryParams';
import history from 'lib/history'; import history from 'lib/history';
import { isUndefined } from 'lodash-es'; import { isUndefined } from 'lodash-es';
import { urlKey } from 'pages/ErrorDetails/utils'; import { urlKey } from 'pages/ErrorDetails/utils';
import { useTimezone } from 'providers/Timezone';
import { useEffect, useMemo, useRef, useState } from 'react'; import { useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query'; import { useQuery } from 'react-query';
@@ -103,8 +103,6 @@ function ErrorDetails(props: ErrorDetailsProps): JSX.Element {
} }
}; };
const timeStamp = dayjs(errorDetail.timestamp);
const data: { key: string; value: string }[] = Object.keys(errorDetail) const data: { key: string; value: string }[] = Object.keys(errorDetail)
.filter((e) => !keyToExclude.includes(e)) .filter((e) => !keyToExclude.includes(e))
.map((key) => ({ .map((key) => ({
@@ -136,6 +134,8 @@ function ErrorDetails(props: ErrorDetailsProps): JSX.Element {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [data]); }, [data]);
const { formatTimezoneAdjustedTimestamp } = useTimezone();
return ( return (
<> <>
<Typography>{errorDetail.exceptionType}</Typography> <Typography>{errorDetail.exceptionType}</Typography>
@@ -145,7 +145,12 @@ function ErrorDetails(props: ErrorDetailsProps): JSX.Element {
<EventContainer> <EventContainer>
<div> <div>
<Typography>Event {errorDetail.errorId}</Typography> <Typography>Event {errorDetail.errorId}</Typography>
<Typography>{timeStamp.format('MMM DD YYYY hh:mm:ss A')}</Typography> <Typography>
{formatTimezoneAdjustedTimestamp(
errorDetail.timestamp,
'DD/MM/YYYY hh:mm:ss A (UTC Z)',
)}
</Typography>
</div> </div>
<div> <div>
<Space align="end" direction="horizontal"> <Space align="end" direction="horizontal">

View File

@@ -25,6 +25,7 @@ import getTimeString from 'lib/getTimeString';
import history from 'lib/history'; import history from 'lib/history';
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions'; import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData'; import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
import { useTimezone } from 'providers/Timezone';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
@@ -35,6 +36,7 @@ import { AlertDef } from 'types/api/alerts/def';
import { Query } from 'types/api/queryBuilder/queryBuilderData'; import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard'; import { EQueryType } from 'types/common/dashboard';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import uPlot from 'uplot';
import { getGraphType } from 'utils/getGraphType'; import { getGraphType } from 'utils/getGraphType';
import { getSortedSeriesData } from 'utils/getSortedSeriesData'; import { getSortedSeriesData } from 'utils/getSortedSeriesData';
import { getTimeRange } from 'utils/getTimeRange'; import { getTimeRange } from 'utils/getTimeRange';
@@ -201,6 +203,8 @@ function ChartPreview({
[dispatch, location.pathname, urlQuery], [dispatch, location.pathname, urlQuery],
); );
const { timezone } = useTimezone();
const options = useMemo( const options = useMemo(
() => () =>
getUPlotChartOptions({ getUPlotChartOptions({
@@ -236,6 +240,9 @@ function ChartPreview({
softMax: null, softMax: null,
softMin: null, softMin: null,
panelType: graphType, panelType: graphType,
tzDate: (timestamp: number) =>
uPlot.tzDate(new Date(timestamp * 1e3), timezone.value),
timezone: timezone.value,
}), }),
[ [
yAxisUnit, yAxisUnit,
@@ -250,6 +257,7 @@ function ChartPreview({
optionName, optionName,
alertDef?.condition.targetUnit, alertDef?.condition.targetUnit,
graphType, graphType,
timezone.value,
], ],
); );

View File

@@ -4,6 +4,7 @@ import { Popover, Typography } from 'antd';
import { convertTimeToRelevantUnit } from 'container/TraceDetail/utils'; import { convertTimeToRelevantUnit } from 'container/TraceDetail/utils';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useIsDarkMode } from 'hooks/useDarkMode'; import { useIsDarkMode } from 'hooks/useDarkMode';
import { useTimezone } from 'providers/Timezone';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { toFixed } from 'utils/toFixed'; import { toFixed } from 'utils/toFixed';
@@ -32,13 +33,17 @@ function Span(props: SpanLengthProps): JSX.Element {
const isDarkMode = useIsDarkMode(); const isDarkMode = useIsDarkMode();
const { time, timeUnitName } = convertTimeToRelevantUnit(inMsCount); const { time, timeUnitName } = convertTimeToRelevantUnit(inMsCount);
const { timezone } = useTimezone();
useEffect(() => { useEffect(() => {
document.documentElement.scrollTop = document.documentElement.clientHeight; document.documentElement.scrollTop = document.documentElement.clientHeight;
document.documentElement.scrollLeft = document.documentElement.clientWidth; document.documentElement.scrollLeft = document.documentElement.clientWidth;
}, []); }, []);
const getContent = (): JSX.Element => { const getContent = (): JSX.Element => {
const timeStamp = dayjs(startTime).format('h:mm:ss:SSS A'); const timeStamp = dayjs(startTime)
.tz(timezone.value)
.format('h:mm:ss:SSS A (UTC Z)');
const startTimeInMs = startTime - globalStart; const startTimeInMs = startTime - globalStart;
return ( return (
<div> <div>

View File

@@ -31,7 +31,7 @@ import { AxiosError } from 'axios';
import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig'; import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig';
import Tags from 'components/Tags/Tags'; import Tags from 'components/Tags/Tags';
import { SOMETHING_WENT_WRONG } from 'constants/api'; import { SOMETHING_WENT_WRONG } from 'constants/api';
import dayjs, { Dayjs } from 'dayjs'; import dayjs from 'dayjs';
import { useGetAllIngestionsKeys } from 'hooks/IngestionKeys/useGetAllIngestionKeys'; import { useGetAllIngestionsKeys } from 'hooks/IngestionKeys/useGetAllIngestionKeys';
import useDebouncedFn from 'hooks/useDebouncedFunction'; import useDebouncedFn from 'hooks/useDebouncedFunction';
import { useNotifications } from 'hooks/useNotifications'; import { useNotifications } from 'hooks/useNotifications';
@@ -51,6 +51,7 @@ import {
Trash2, Trash2,
X, X,
} from 'lucide-react'; } from 'lucide-react';
import { useTimezone } from 'providers/Timezone';
import { ChangeEvent, useEffect, useState } from 'react'; import { ChangeEvent, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useMutation } from 'react-query'; import { useMutation } from 'react-query';
@@ -70,7 +71,10 @@ const { Option } = Select;
const BYTES = 1073741824; const BYTES = 1073741824;
export const disabledDate = (current: Dayjs): boolean => // Using any type here because antd's DatePicker expects its own internal Dayjs type
// which conflicts with our project's Dayjs type that has additional plugins (tz, utc etc).
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
export const disabledDate = (current: any): boolean =>
// Disable all dates before today // Disable all dates before today
current && current < dayjs().endOf('day'); current && current < dayjs().endOf('day');
@@ -393,8 +397,11 @@ function MultiIngestionSettings(): JSX.Element {
const gbToBytes = (gb: number): number => Math.round(gb * 1024 ** 3); const gbToBytes = (gb: number): number => Math.round(gb * 1024 ** 3);
const getFormattedTime = (date: string): string => const getFormattedTime = (
dayjs(date).format('MMM DD,YYYY, hh:mm a'); date: string,
formatTimezoneAdjustedTimestamp: (date: string, format: string) => string,
): string =>
formatTimezoneAdjustedTimestamp(date, 'MMM DD,YYYY, hh:mm a (UTC Z)');
const showDeleteLimitModal = ( const showDeleteLimitModal = (
APIKey: IngestionKeyProps, APIKey: IngestionKeyProps,
@@ -544,17 +551,27 @@ function MultiIngestionSettings(): JSX.Element {
} }
}; };
const { formatTimezoneAdjustedTimestamp } = useTimezone();
const columns: AntDTableProps<IngestionKeyProps>['columns'] = [ const columns: AntDTableProps<IngestionKeyProps>['columns'] = [
{ {
title: 'Ingestion Key', title: 'Ingestion Key',
key: 'ingestion-key', key: 'ingestion-key',
// eslint-disable-next-line sonarjs/cognitive-complexity // eslint-disable-next-line sonarjs/cognitive-complexity
render: (APIKey: IngestionKeyProps): JSX.Element => { render: (APIKey: IngestionKeyProps): JSX.Element => {
const createdOn = getFormattedTime(APIKey.created_at); const createdOn = getFormattedTime(
APIKey.created_at,
formatTimezoneAdjustedTimestamp,
);
const formattedDateAndTime = const formattedDateAndTime =
APIKey && APIKey?.expires_at && getFormattedTime(APIKey?.expires_at); APIKey &&
APIKey?.expires_at &&
getFormattedTime(APIKey?.expires_at, formatTimezoneAdjustedTimestamp);
const updatedOn = getFormattedTime(APIKey?.updated_at); const updatedOn = getFormattedTime(
APIKey?.updated_at,
formatTimezoneAdjustedTimestamp,
);
const limits: { [key: string]: LimitProps } = {}; const limits: { [key: string]: LimitProps } = {};

View File

@@ -1,8 +1,20 @@
import { Typography } from 'antd';
import { ColumnsType } from 'antd/lib/table'; import { ColumnsType } from 'antd/lib/table';
import { ResizeTable } from 'components/ResizeTable'; import { ResizeTable } from 'components/ResizeTable';
import { useTimezone } from 'providers/Timezone';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { License } from 'types/api/licenses/def'; import { License } from 'types/api/licenses/def';
function ValidityColumn({ value }: { value: string }): JSX.Element {
const { formatTimezoneAdjustedTimestamp } = useTimezone();
return (
<Typography>
{formatTimezoneAdjustedTimestamp(value, 'YYYY-MM-DD HH:mm:ss (UTC Z)')}
</Typography>
);
}
function ListLicenses({ licenses }: ListLicensesProps): JSX.Element { function ListLicenses({ licenses }: ListLicensesProps): JSX.Element {
const { t } = useTranslation(['licenses']); const { t } = useTranslation(['licenses']);
@@ -23,12 +35,14 @@ function ListLicenses({ licenses }: ListLicensesProps): JSX.Element {
title: t('column_valid_from'), title: t('column_valid_from'),
dataIndex: 'ValidFrom', dataIndex: 'ValidFrom',
key: 'valid from', key: 'valid from',
render: (value: string): JSX.Element => ValidityColumn({ value }),
width: 80, width: 80,
}, },
{ {
title: t('column_valid_until'), title: t('column_valid_until'),
dataIndex: 'ValidUntil', dataIndex: 'ValidUntil',
key: 'valid until', key: 'valid until',
render: (value: string): JSX.Element => ValidityColumn({ value }),
width: 80, width: 80,
}, },
]; ];

View File

@@ -867,7 +867,7 @@
.configure-metadata-root { .configure-metadata-root {
.ant-modal-content { .ant-modal-content {
width: 400px; width: 500px;
flex-shrink: 0; flex-shrink: 0;
border-radius: 4px; border-radius: 4px;
border: 1px solid var(--Slate-500, #161922); border: 1px solid var(--Slate-500, #161922);
@@ -1039,7 +1039,6 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
width: 336px;
padding: 0px 0px 0px 14.634px; padding: 0px 0px 0px 14.634px;
.left { .left {

View File

@@ -27,6 +27,8 @@ import { AxiosError } from 'axios';
import cx from 'classnames'; import cx from 'classnames';
import { ENTITY_VERSION_V4 } from 'constants/app'; import { ENTITY_VERSION_V4 } from 'constants/app';
import ROUTES from 'constants/routes'; import ROUTES from 'constants/routes';
import { sanitizeDashboardData } from 'container/NewDashboard/DashboardDescription';
import { downloadObjectAsJson } from 'container/NewDashboard/DashboardDescription/utils';
import { Base64Icons } from 'container/NewDashboard/DashboardSettings/General/utils'; import { Base64Icons } from 'container/NewDashboard/DashboardSettings/General/utils';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useGetAllDashboard } from 'hooks/dashboard/useGetAllDashboard'; import { useGetAllDashboard } from 'hooks/dashboard/useGetAllDashboard';
@@ -44,6 +46,7 @@ import {
EllipsisVertical, EllipsisVertical,
Expand, Expand,
ExternalLink, ExternalLink,
FileJson,
Github, Github,
HdmiPort, HdmiPort,
LayoutGrid, LayoutGrid,
@@ -57,6 +60,7 @@ import {
// see more: https://github.com/lucide-icons/lucide/issues/94 // see more: https://github.com/lucide-icons/lucide/issues/94
import { handleContactSupport } from 'pages/Integrations/utils'; import { handleContactSupport } from 'pages/Integrations/utils';
import { useDashboard } from 'providers/Dashboard/Dashboard'; import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useTimezone } from 'providers/Timezone';
import { import {
ChangeEvent, ChangeEvent,
Key, Key,
@@ -66,12 +70,18 @@ import {
useRef, useRef,
useState, useState,
} from 'react'; } from 'react';
import { Layout } from 'react-grid-layout';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { generatePath, Link } from 'react-router-dom'; import { generatePath, Link } from 'react-router-dom';
import { useCopyToClipboard } from 'react-use'; import { useCopyToClipboard } from 'react-use';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { Dashboard } from 'types/api/dashboard/getAll'; import {
Dashboard,
IDashboardVariable,
WidgetRow,
Widgets,
} from 'types/api/dashboard/getAll';
import AppReducer from 'types/reducer/app'; import AppReducer from 'types/reducer/app';
import { isCloudUser } from 'utils/app'; import { isCloudUser } from 'utils/app';
@@ -260,6 +270,11 @@ function DashboardsList(): JSX.Element {
isLocked: !!e.isLocked || false, isLocked: !!e.isLocked || false,
lastUpdatedBy: e.updated_by, lastUpdatedBy: e.updated_by,
image: e.data.image || Base64Icons[0], image: e.data.image || Base64Icons[0],
variables: e.data.variables,
widgets: e.data.widgets,
layout: e.data.layout,
panelMap: e.data.panelMap,
version: e.data.version,
refetchDashboardList, refetchDashboardList,
})) || []; })) || [];
@@ -343,31 +358,13 @@ function DashboardsList(): JSX.Element {
} }
}, [state.error, state.value, t, notifications]); }, [state.error, state.value, t, notifications]);
const { formatTimezoneAdjustedTimestamp } = useTimezone();
function getFormattedTime(dashboard: Dashboard, option: string): string { function getFormattedTime(dashboard: Dashboard, option: string): string {
const timeOptions: Intl.DateTimeFormatOptions = { return formatTimezoneAdjustedTimestamp(
hour: '2-digit', get(dashboard, option, ''),
minute: '2-digit', 'MMM D, YYYY ⎯ hh:mm:ss A (UTC Z)',
second: '2-digit',
hour12: false,
};
const formattedTime = new Date(get(dashboard, option, '')).toLocaleTimeString(
'en-US',
timeOptions,
); );
const dateOptions: Intl.DateTimeFormatOptions = {
month: 'short',
day: 'numeric',
year: 'numeric',
};
const formattedDate = new Date(get(dashboard, option, '')).toLocaleDateString(
'en-US',
dateOptions,
);
// Combine time and date
return `${formattedDate}${formattedTime}`;
} }
const onLastUpdated = (time: string): string => { const onLastUpdated = (time: string): string => {
@@ -410,31 +407,11 @@ function DashboardsList(): JSX.Element {
title: 'Dashboards', title: 'Dashboards',
key: 'dashboard', key: 'dashboard',
render: (dashboard: Data, _, index): JSX.Element => { render: (dashboard: Data, _, index): JSX.Element => {
const timeOptions: Intl.DateTimeFormatOptions = { const formattedDateAndTime = formatTimezoneAdjustedTimestamp(
hour: '2-digit', dashboard.createdAt,
minute: '2-digit', 'MMM D, YYYY ⎯ hh:mm:ss A (UTC Z)',
second: '2-digit',
hour12: false,
};
const formattedTime = new Date(dashboard.createdAt).toLocaleTimeString(
'en-US',
timeOptions,
); );
const dateOptions: Intl.DateTimeFormatOptions = {
month: 'short',
day: 'numeric',
year: 'numeric',
};
const formattedDate = new Date(dashboard.createdAt).toLocaleDateString(
'en-US',
dateOptions,
);
// Combine time and date
const formattedDateAndTime = `${formattedDate}${formattedTime}`;
const getLink = (): string => `${ROUTES.ALL_DASHBOARD}/${dashboard.id}`; const getLink = (): string => `${ROUTES.ALL_DASHBOARD}/${dashboard.id}`;
const onClickHandler = (event: React.MouseEvent<HTMLElement>): void => { const onClickHandler = (event: React.MouseEvent<HTMLElement>): void => {
@@ -450,6 +427,15 @@ function DashboardsList(): JSX.Element {
}); });
}; };
const handleJsonExport = (event: React.MouseEvent<HTMLElement>): void => {
event.stopPropagation();
event.preventDefault();
downloadObjectAsJson(
sanitizeDashboardData({ ...dashboard, title: dashboard.name }),
dashboard.name,
);
};
return ( return (
<div className="dashboard-list-item" onClick={onClickHandler}> <div className="dashboard-list-item" onClick={onClickHandler}>
<div className="title-with-action"> <div className="title-with-action">
@@ -523,6 +509,14 @@ function DashboardsList(): JSX.Element {
> >
Copy Link Copy Link
</Button> </Button>
<Button
type="text"
className="action-btn"
icon={<FileJson size={12} />}
onClick={handleJsonExport}
>
Export JSON
</Button>
</section> </section>
<section className="section-2"> <section className="section-2">
<DeleteButton <DeleteButton
@@ -541,6 +535,7 @@ function DashboardsList(): JSX.Element {
<EllipsisVertical <EllipsisVertical
className="dashboard-action-icon" className="dashboard-action-icon"
size={14} size={14}
data-testid="dashboard-action-icon"
onClick={(e): void => { onClick={(e): void => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
@@ -1105,6 +1100,11 @@ export interface Data {
isLocked: boolean; isLocked: boolean;
id: string; id: string;
image?: string; image?: string;
widgets?: Array<WidgetRow | Widgets>;
layout?: Layout[];
panelMap?: Record<string, { widgets: Layout[]; collapsed: boolean }>;
variables: Record<string, IDashboardVariable>;
version?: string;
} }
export default DashboardsList; export default DashboardsList;

View File

@@ -8,10 +8,12 @@ import { useResizeObserver } from 'hooks/useDimensions';
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions'; import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData'; import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
import { useTimezone } from 'providers/Timezone';
import { useMemo, useRef } from 'react'; import { useMemo, useRef } from 'react';
import { useQueries, UseQueryResult } from 'react-query'; import { useQueries, UseQueryResult } from 'react-query';
import { SuccessResponse } from 'types/api'; import { SuccessResponse } from 'types/api';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import uPlot from 'uplot';
import { import {
getHostQueryPayload, getHostQueryPayload,
@@ -73,6 +75,8 @@ function NodeMetrics({
[queries], [queries],
); );
const { timezone } = useTimezone();
const options = useMemo( const options = useMemo(
() => () =>
queries.map(({ data }, idx) => queries.map(({ data }, idx) =>
@@ -86,6 +90,9 @@ function NodeMetrics({
minTimeScale: start, minTimeScale: start,
maxTimeScale: end, maxTimeScale: end,
verticalLineTimestamp, verticalLineTimestamp,
tzDate: (timestamp: number) =>
uPlot.tzDate(new Date(timestamp * 1e3), timezone.value),
timezone: timezone.value,
}), }),
), ),
[ [
@@ -96,6 +103,7 @@ function NodeMetrics({
start, start,
verticalLineTimestamp, verticalLineTimestamp,
end, end,
timezone.value,
], ],
); );

View File

@@ -8,10 +8,12 @@ import { useResizeObserver } from 'hooks/useDimensions';
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions'; import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData'; import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
import { useTimezone } from 'providers/Timezone';
import { useMemo, useRef } from 'react'; import { useMemo, useRef } from 'react';
import { useQueries, UseQueryResult } from 'react-query'; import { useQueries, UseQueryResult } from 'react-query';
import { SuccessResponse } from 'types/api'; import { SuccessResponse } from 'types/api';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import uPlot from 'uplot';
import { getPodQueryPayload, podWidgetInfo } from './constants'; import { getPodQueryPayload, podWidgetInfo } from './constants';
@@ -60,6 +62,7 @@ function PodMetrics({
() => queries.map(({ data }) => getUPlotChartData(data?.payload)), () => queries.map(({ data }) => getUPlotChartData(data?.payload)),
[queries], [queries],
); );
const { timezone } = useTimezone();
const options = useMemo( const options = useMemo(
() => () =>
@@ -74,9 +77,20 @@ function PodMetrics({
minTimeScale: start, minTimeScale: start,
maxTimeScale: end, maxTimeScale: end,
verticalLineTimestamp, verticalLineTimestamp,
tzDate: (timestamp: number) =>
uPlot.tzDate(new Date(timestamp * 1e3), timezone.value),
timezone: timezone.value,
}), }),
), ),
[queries, isDarkMode, dimensions, start, verticalLineTimestamp, end], [
queries,
isDarkMode,
dimensions,
start,
end,
verticalLineTimestamp,
timezone.value,
],
); );
const renderCardContent = ( const renderCardContent = (

View File

@@ -11,7 +11,8 @@ import ROUTES from 'constants/routes';
import dompurify from 'dompurify'; import dompurify from 'dompurify';
import { isEmpty } from 'lodash-es'; import { isEmpty } from 'lodash-es';
import { ArrowDownToDot, ArrowUpFromDot, Ellipsis } from 'lucide-react'; import { ArrowDownToDot, ArrowUpFromDot, Ellipsis } from 'lucide-react';
import { useMemo, useState } from 'react'; import { useTimezone } from 'providers/Timezone';
import React, { useMemo, useState } from 'react';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { FORBID_DOM_PURIFY_TAGS } from 'utils/app'; import { FORBID_DOM_PURIFY_TAGS } from 'utils/app';
@@ -68,6 +69,8 @@ export function TableViewActions(
const [isOpen, setIsOpen] = useState<boolean>(false); const [isOpen, setIsOpen] = useState<boolean>(false);
const { formatTimezoneAdjustedTimestamp } = useTimezone();
if (record.field === 'body') { if (record.field === 'body') {
const parsedBody = recursiveParseJSON(fieldData.value); const parsedBody = recursiveParseJSON(fieldData.value);
if (!isEmpty(parsedBody)) { if (!isEmpty(parsedBody)) {
@@ -100,33 +103,44 @@ export function TableViewActions(
); );
} }
let cleanTimestamp: string;
if (record.field === 'timestamp') {
cleanTimestamp = fieldData.value.replace(/^["']|["']$/g, '');
}
const renderFieldContent = (): JSX.Element => {
const commonStyles: React.CSSProperties = {
color: Color.BG_SIENNA_400,
whiteSpace: 'pre-wrap',
tabSize: 4,
};
switch (record.field) {
case 'body':
return <span style={commonStyles} dangerouslySetInnerHTML={bodyHtml} />;
case 'timestamp':
return (
<span style={commonStyles}>
{formatTimezoneAdjustedTimestamp(
cleanTimestamp,
'MM/DD/YYYY, HH:mm:ss.SSS (UTC Z)',
)}
</span>
);
default:
return (
<span style={commonStyles}>{removeEscapeCharacters(fieldData.value)}</span>
);
}
};
return ( return (
<div className={cx('value-field', isOpen ? 'open-popover' : '')}> <div className={cx('value-field', isOpen ? 'open-popover' : '')}>
{record.field === 'body' ? ( <CopyClipboardHOC entityKey={fieldFilterKey} textToCopy={textToCopy}>
<CopyClipboardHOC entityKey={fieldFilterKey} textToCopy={textToCopy}> {renderFieldContent()}
<span </CopyClipboardHOC>
style={{
color: Color.BG_SIENNA_400,
whiteSpace: 'pre-wrap',
tabSize: 4,
}}
dangerouslySetInnerHTML={bodyHtml}
/>
</CopyClipboardHOC>
) : (
<CopyClipboardHOC entityKey={fieldFilterKey} textToCopy={textToCopy}>
<span
style={{
color: Color.BG_SIENNA_400,
whiteSpace: 'pre-wrap',
tabSize: 4,
}}
>
{removeEscapeCharacters(fieldData.value)}
</span>
</CopyClipboardHOC>
)}
{!isListViewPanel && ( {!isListViewPanel && (
<span className="action-btn"> <span className="action-btn">
<Tooltip title="Filter for value"> <Tooltip title="Filter for value">

View File

@@ -50,6 +50,7 @@ import {
} from 'lodash-es'; } from 'lodash-es';
import { Sliders } from 'lucide-react'; import { Sliders } from 'lucide-react';
import { SELECTED_VIEWS } from 'pages/LogsExplorer/utils'; import { SELECTED_VIEWS } from 'pages/LogsExplorer/utils';
import { useTimezone } from 'providers/Timezone';
import { import {
memo, memo,
MutableRefObject, MutableRefObject,
@@ -669,13 +670,19 @@ function LogsExplorerViews({
setIsLoadingQueries, setIsLoadingQueries,
]); ]);
const { timezone } = useTimezone();
const flattenLogData = useMemo( const flattenLogData = useMemo(
() => () =>
logs.map((log) => { logs.map((log) => {
const timestamp = const timestamp =
typeof log.timestamp === 'string' typeof log.timestamp === 'string'
? dayjs(log.timestamp).format('YYYY-MM-DD HH:mm:ss.SSS') ? dayjs(log.timestamp)
: dayjs(log.timestamp / 1e6).format('YYYY-MM-DD HH:mm:ss.SSS'); .tz(timezone.value)
.format('YYYY-MM-DD HH:mm:ss.SSS')
: dayjs(log.timestamp / 1e6)
.tz(timezone.value)
.format('YYYY-MM-DD HH:mm:ss.SSS');
return FlatLogData({ return FlatLogData({
timestamp, timestamp,
@@ -683,7 +690,7 @@ function LogsExplorerViews({
...omit(log, 'timestamp', 'body'), ...omit(log, 'timestamp', 'body'),
}); });
}), }),
[logs], [logs, timezone.value],
); );
return ( return (

View File

@@ -7,6 +7,7 @@ import { rest } from 'msw';
import { SELECTED_VIEWS } from 'pages/LogsExplorer/utils'; import { SELECTED_VIEWS } from 'pages/LogsExplorer/utils';
import { QueryBuilderProvider } from 'providers/QueryBuilder'; import { QueryBuilderProvider } from 'providers/QueryBuilder';
import MockQueryClientProvider from 'providers/test/MockQueryClientProvider'; import MockQueryClientProvider from 'providers/test/MockQueryClientProvider';
import TimezoneProvider from 'providers/Timezone';
import { I18nextProvider } from 'react-i18next'; import { I18nextProvider } from 'react-i18next';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { MemoryRouter } from 'react-router-dom'; import { MemoryRouter } from 'react-router-dom';
@@ -91,17 +92,19 @@ const renderer = (): RenderResult =>
<I18nextProvider i18n={i18n}> <I18nextProvider i18n={i18n}>
<MockQueryClientProvider> <MockQueryClientProvider>
<QueryBuilderProvider> <QueryBuilderProvider>
<VirtuosoMockContext.Provider <TimezoneProvider>
value={{ viewportHeight: 300, itemHeight: 100 }} <VirtuosoMockContext.Provider
> value={{ viewportHeight: 300, itemHeight: 100 }}
<LogsExplorerViews >
selectedView={SELECTED_VIEWS.SEARCH} <LogsExplorerViews
showFrequencyChart selectedView={SELECTED_VIEWS.SEARCH}
setIsLoadingQueries={(): void => {}} showFrequencyChart
listQueryKeyRef={{ current: {} }} setIsLoadingQueries={(): void => {}}
chartQueryKeyRef={{ current: {} }} listQueryKeyRef={{ current: {} }}
/> chartQueryKeyRef={{ current: {} }}
</VirtuosoMockContext.Provider> />
</VirtuosoMockContext.Provider>
</TimezoneProvider>
</QueryBuilderProvider> </QueryBuilderProvider>
</MockQueryClientProvider> </MockQueryClientProvider>
</I18nextProvider> </I18nextProvider>

View File

@@ -15,6 +15,7 @@ import { useLogsData } from 'hooks/useLogsData';
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import { FlatLogData } from 'lib/logs/flatLogData'; import { FlatLogData } from 'lib/logs/flatLogData';
import { RowData } from 'lib/query/createTableColumnsFromQuery'; import { RowData } from 'lib/query/createTableColumnsFromQuery';
import { useTimezone } from 'providers/Timezone';
import { import {
Dispatch, Dispatch,
HTMLAttributes, HTMLAttributes,
@@ -76,7 +77,12 @@ function LogsPanelComponent({
}); });
}; };
const columns = getLogPanelColumnsList(widget.selectedLogFields); const { formatTimezoneAdjustedTimestamp } = useTimezone();
const columns = getLogPanelColumnsList(
widget.selectedLogFields,
formatTimezoneAdjustedTimestamp,
);
const dataLength = const dataLength =
queryResponse.data?.payload?.data?.newResult?.data?.result[0]?.list?.length; queryResponse.data?.payload?.data?.newResult?.data?.result[0]?.list?.length;

View File

@@ -1,6 +1,7 @@
import { ColumnsType } from 'antd/es/table'; import { ColumnsType } from 'antd/es/table';
import { Typography } from 'antd/lib'; import { Typography } from 'antd/lib';
import { OPERATORS } from 'constants/queryBuilder'; import { OPERATORS } from 'constants/queryBuilder';
import { TimestampInput } from 'hooks/useTimezoneFormatter/useTimezoneFormatter';
// import Typography from 'antd/es/typography/Typography'; // import Typography from 'antd/es/typography/Typography';
import { RowData } from 'lib/query/createTableColumnsFromQuery'; import { RowData } from 'lib/query/createTableColumnsFromQuery';
import { ReactNode } from 'react'; import { ReactNode } from 'react';
@@ -13,18 +14,31 @@ import { v4 as uuid } from 'uuid';
export const getLogPanelColumnsList = ( export const getLogPanelColumnsList = (
selectedLogFields: Widgets['selectedLogFields'], selectedLogFields: Widgets['selectedLogFields'],
formatTimezoneAdjustedTimestamp: (
input: TimestampInput,
format?: string,
) => string,
): ColumnsType<RowData> => { ): ColumnsType<RowData> => {
const initialColumns: ColumnsType<RowData> = []; const initialColumns: ColumnsType<RowData> = [];
const columns: ColumnsType<RowData> = const columns: ColumnsType<RowData> =
selectedLogFields?.map((field: IField) => { selectedLogFields?.map((field: IField) => {
const { name } = field; const { name } = field;
return { return {
title: name, title: name,
dataIndex: name, dataIndex: name,
key: name, key: name,
width: name === 'body' ? 350 : 100, width: name === 'body' ? 350 : 100,
render: (value: ReactNode): JSX.Element => { render: (value: ReactNode): JSX.Element => {
if (name === 'timestamp') {
return (
<Typography.Text>
{formatTimezoneAdjustedTimestamp(value as string)}
</Typography.Text>
);
}
if (name === 'body') { if (name === 'body') {
return ( return (
<Typography.Paragraph ellipsis={{ rows: 1 }} data-testid={name}> <Typography.Paragraph ellipsis={{ rows: 1 }} data-testid={name}>

View File

@@ -185,6 +185,7 @@ function Application(): JSX.Element {
panelTypes: PANEL_TYPES.TIME_SERIES, panelTypes: PANEL_TYPES.TIME_SERIES,
yAxisUnit: '%', yAxisUnit: '%',
id: SERVICE_CHART_ID.errorPercentage, id: SERVICE_CHART_ID.errorPercentage,
fillSpans: true,
}), }),
[servicename, tagFilterItems, topLevelOperationsRoute], [servicename, tagFilterItems, topLevelOperationsRoute],
); );
@@ -222,12 +223,11 @@ function Application(): JSX.Element {
apmToTraceQuery: Query, apmToTraceQuery: Query,
isViewLogsClicked?: boolean, isViewLogsClicked?: boolean,
): (() => void) => (): void => { ): (() => void) => (): void => {
const currentTime = timestamp; const endTime = timestamp;
const endTime = timestamp + stepInterval; const startTime = timestamp - stepInterval;
console.log(endTime, stepInterval);
const urlParams = new URLSearchParams(search); const urlParams = new URLSearchParams(search);
urlParams.set(QueryParams.startTime, currentTime.toString()); urlParams.set(QueryParams.startTime, startTime.toString());
urlParams.set(QueryParams.endTime, endTime.toString()); urlParams.set(QueryParams.endTime, endTime.toString());
urlParams.delete(QueryParams.relativeTime); urlParams.delete(QueryParams.relativeTime);
const avialableParams = routeConfig[ROUTES.TRACE]; const avialableParams = routeConfig[ROUTES.TRACE];

View File

@@ -65,11 +65,11 @@ export function onViewTracePopupClick({
stepInterval, stepInterval,
}: OnViewTracePopupClickProps): VoidFunction { }: OnViewTracePopupClickProps): VoidFunction {
return (): void => { return (): void => {
const currentTime = timestamp; const endTime = timestamp;
const endTime = timestamp + (stepInterval || 60); const startTime = timestamp - (stepInterval || 60);
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
urlParams.set(QueryParams.startTime, currentTime.toString()); urlParams.set(QueryParams.startTime, startTime.toString());
urlParams.set(QueryParams.endTime, endTime.toString()); urlParams.set(QueryParams.endTime, endTime.toString());
urlParams.delete(QueryParams.relativeTime); urlParams.delete(QueryParams.relativeTime);
const avialableParams = routeConfig[ROUTES.TRACE]; const avialableParams = routeConfig[ROUTES.TRACE];

View File

@@ -0,0 +1,96 @@
.timezone-adaption {
padding: 16px;
background: var(--bg-ink-400);
border: 1px solid var(--bg-ink-500);
border-radius: 4px;
&__header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
&__title {
color: var(--bg-vanilla-300);
font-size: 14px;
font-weight: 500;
margin: 0;
}
&__description {
color: var(--bg-vanilla-400);
font-size: 14px;
line-height: 20px;
margin: 0 0 12px 0;
}
&__note {
display: flex;
align-items: center;
justify-content: space-between;
padding: 7.5px 12px;
background: rgba(78, 116, 248, 0.1);
border: 1px solid rgba(78, 116, 248, 0.1);
border-radius: 4px;
}
&__bullet {
color: var(--bg-robin-400);
font-size: 16px;
line-height: 20px;
}
&__note-text-container {
display: flex;
align-items: center;
gap: 10px;
}
&__note-text {
display: flex;
align-items: center;
gap: 4px;
color: var(--bg-robin-400);
font-size: 14px;
line-height: 20px;
}
&__note-text-overridden {
display: flex;
align-items: center;
padding: 0 2px;
background: rgba(171, 189, 255, 0.04);
border-radius: 2px;
font-size: 12px;
line-height: 16px;
color: var(--bg-vanilla-100);
}
&__clear-override {
display: flex;
align-items: center;
gap: 6px;
background: transparent;
border: none;
padding: 0;
color: var(--bg-robin-300);
font-size: 12px;
line-height: 16px; /* 133.333% */
letter-spacing: 0.12px;
cursor: pointer;
}
}
.lightMode {
.timezone-adaption {
background-color: var(--bg-vanilla-100);
border-color: var(--bg-vanilla-300);
&__title {
color: var(--text-ink-300);
}
&__description {
color: var(--text-ink-400);
}
}
}

View File

@@ -0,0 +1,82 @@
import './TimezoneAdaptation.styles.scss';
import { Color } from '@signozhq/design-tokens';
import { Switch } from 'antd';
import { Delete } from 'lucide-react';
import { useTimezone } from 'providers/Timezone';
import { useMemo } from 'react';
function TimezoneAdaptation(): JSX.Element {
const {
timezone,
browserTimezone,
updateTimezone,
isAdaptationEnabled,
setIsAdaptationEnabled,
} = useTimezone();
const isTimezoneOverridden = useMemo(
() => timezone.offset !== browserTimezone.offset,
[timezone, browserTimezone],
);
const getSwitchStyles = (): React.CSSProperties => ({
backgroundColor:
isAdaptationEnabled && isTimezoneOverridden ? Color.BG_AMBER_400 : undefined,
});
const handleOverrideClear = (): void => {
updateTimezone(browserTimezone);
};
return (
<div className="timezone-adaption">
<div className="timezone-adaption__header">
<h2 className="timezone-adaption__title">Adapt to my timezone</h2>
<Switch
checked={isAdaptationEnabled}
onChange={setIsAdaptationEnabled}
style={getSwitchStyles()}
/>
</div>
<p className="timezone-adaption__description">
Adapt the timestamps shown in the SigNoz console to my active timezone.
</p>
<div className="timezone-adaption__note">
<div className="timezone-adaption__note-text-container">
<span className="timezone-adaption__bullet"></span>
<span className="timezone-adaption__note-text">
{isTimezoneOverridden ? (
<>
Your current timezone is overridden to
<span className="timezone-adaption__note-text-overridden">
{timezone.offset}
</span>
</>
) : (
<>
You can override the timezone adaption for any view with the time
picker.
</>
)}
</span>
</div>
{!!isTimezoneOverridden && (
<button
type="button"
className="timezone-adaption__clear-override"
onClick={handleOverrideClear}
>
<Delete height={12} width={12} color={Color.BG_ROBIN_300} />
Clear override
</button>
)}
</div>
</div>
);
}
export default TimezoneAdaptation;

View File

@@ -7,6 +7,7 @@ import { LogOut, Moon, Sun } from 'lucide-react';
import { useState } from 'react'; import { useState } from 'react';
import Password from './Password'; import Password from './Password';
import TimezoneAdaptation from './TimezoneAdaptation/TimezoneAdaptation';
import UserInfo from './UserInfo'; import UserInfo from './UserInfo';
function MySettings(): JSX.Element { function MySettings(): JSX.Element {
@@ -78,6 +79,8 @@ function MySettings(): JSX.Element {
<Password /> <Password />
</div> </div>
<TimezoneAdaptation />
<Button <Button
className="flexBtn" className="flexBtn"
onClick={(): void => Logout()} onClick={(): void => Logout()}

View File

@@ -65,7 +65,7 @@ interface DashboardDescriptionProps {
handle: FullScreenHandle; handle: FullScreenHandle;
} }
function sanitizeDashboardData( export function sanitizeDashboardData(
selectedData: DashboardData, selectedData: DashboardData,
): Omit<DashboardData, 'uuid'> { ): Omit<DashboardData, 'uuid'> {
if (!selectedData?.variables) { if (!selectedData?.variables) {

View File

@@ -1,9 +1,15 @@
import { Row } from 'antd'; import { Row } from 'antd';
import { isNull } from 'lodash-es';
import { useDashboard } from 'providers/Dashboard/Dashboard'; import { useDashboard } from 'providers/Dashboard/Dashboard';
import { memo, useEffect, useState } from 'react'; import { memo, useEffect, useRef, useState } from 'react';
import { IDashboardVariable } from 'types/api/dashboard/getAll'; import { IDashboardVariable } from 'types/api/dashboard/getAll';
import {
buildDependencies,
buildDependencyGraph,
buildParentDependencyGraph,
onUpdateVariableNode,
VariableGraph,
} from './util';
import VariableItem from './VariableItem'; import VariableItem from './VariableItem';
function DashboardVariableSelection(): JSX.Element | null { function DashboardVariableSelection(): JSX.Element | null {
@@ -21,6 +27,12 @@ function DashboardVariableSelection(): JSX.Element | null {
const [variablesTableData, setVariablesTableData] = useState<any>([]); const [variablesTableData, setVariablesTableData] = useState<any>([]);
const [dependencyData, setDependencyData] = useState<{
order: string[];
graph: VariableGraph;
parentDependencyGraph: VariableGraph;
} | null>(null);
useEffect(() => { useEffect(() => {
if (variables) { if (variables) {
const tableRowData = []; const tableRowData = [];
@@ -43,35 +55,28 @@ function DashboardVariableSelection(): JSX.Element | null {
} }
}, [variables]); }, [variables]);
const onVarChanged = (name: string): void => { const initializationRef = useRef(false);
/**
* this function takes care of adding the dependent variables to current update queue and removing useEffect(() => {
* the updated variable name from the queue if (variablesTableData.length > 0 && !initializationRef.current) {
*/ const depGrp = buildDependencies(variablesTableData);
const dependentVariables = variablesTableData const { order, graph } = buildDependencyGraph(depGrp);
?.map((variable: any) => { const parentDependencyGraph = buildParentDependencyGraph(graph);
if (variable.type === 'QUERY') { setDependencyData({
const re = new RegExp(`\\{\\{\\s*?\\.${name}\\s*?\\}\\}`); // regex for `{{.var}}` order,
const queryValue = variable.queryValue || ''; graph,
const dependVarReMatch = queryValue.match(re); parentDependencyGraph,
if (dependVarReMatch !== null && dependVarReMatch.length > 0) { });
return variable.name; initializationRef.current = true;
} }
} }, [variablesTableData]);
return null;
})
.filter((val: string | null) => !isNull(val));
setVariablesToGetUpdated((prev) => [
...prev.filter((v) => v !== name),
...dependentVariables,
]);
};
const onValueUpdate = ( const onValueUpdate = (
name: string, name: string,
id: string, id: string,
value: IDashboardVariable['selectedValue'], value: IDashboardVariable['selectedValue'],
allSelected: boolean, allSelected: boolean,
isMountedCall?: boolean,
// eslint-disable-next-line sonarjs/cognitive-complexity // eslint-disable-next-line sonarjs/cognitive-complexity
): void => { ): void => {
if (id) { if (id) {
@@ -111,7 +116,18 @@ function DashboardVariableSelection(): JSX.Element | null {
}); });
} }
onVarChanged(name); if (dependencyData && !isMountedCall) {
const updatedVariables: string[] = [];
onUpdateVariableNode(
name,
dependencyData.graph,
dependencyData.order,
(node) => updatedVariables.push(node),
);
setVariablesToGetUpdated(updatedVariables.filter((v) => v !== name));
} else if (isMountedCall) {
setVariablesToGetUpdated((prev) => prev.filter((v) => v !== name));
}
} }
}; };
@@ -139,6 +155,7 @@ function DashboardVariableSelection(): JSX.Element | null {
onValueUpdate={onValueUpdate} onValueUpdate={onValueUpdate}
variablesToGetUpdated={variablesToGetUpdated} variablesToGetUpdated={variablesToGetUpdated}
setVariablesToGetUpdated={setVariablesToGetUpdated} setVariablesToGetUpdated={setVariablesToGetUpdated}
dependencyData={dependencyData}
/> />
))} ))}
</Row> </Row>

View File

@@ -24,7 +24,7 @@ import { commaValuesParser } from 'lib/dashbaordVariables/customCommaValuesParse
import sortValues from 'lib/dashbaordVariables/sortVariableValues'; import sortValues from 'lib/dashbaordVariables/sortVariableValues';
import { debounce, isArray, isString } from 'lodash-es'; import { debounce, isArray, isString } from 'lodash-es';
import map from 'lodash-es/map'; import map from 'lodash-es/map';
import { ChangeEvent, memo, useEffect, useMemo, useState } from 'react'; import { ChangeEvent, memo, useEffect, useMemo, useRef, useState } from 'react';
import { useQuery } from 'react-query'; import { useQuery } from 'react-query';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
@@ -35,12 +35,15 @@ import { popupContainer } from 'utils/selectPopupContainer';
import { variablePropsToPayloadVariables } from '../utils'; import { variablePropsToPayloadVariables } from '../utils';
import { SelectItemStyle } from './styles'; import { SelectItemStyle } from './styles';
import { areArraysEqual } from './util'; import {
areArraysEqual,
checkAPIInvocation,
onUpdateVariableNode,
VariableGraph,
} from './util';
const ALL_SELECT_VALUE = '__ALL__'; const ALL_SELECT_VALUE = '__ALL__';
const variableRegexPattern = /\{\{\s*?\.([^\s}]+)\s*?\}\}/g;
enum ToggleTagValue { enum ToggleTagValue {
Only = 'Only', Only = 'Only',
All = 'All', All = 'All',
@@ -54,9 +57,15 @@ interface VariableItemProps {
id: string, id: string,
arg1: IDashboardVariable['selectedValue'], arg1: IDashboardVariable['selectedValue'],
allSelected: boolean, allSelected: boolean,
isMountedCall?: boolean,
) => void; ) => void;
variablesToGetUpdated: string[]; variablesToGetUpdated: string[];
setVariablesToGetUpdated: React.Dispatch<React.SetStateAction<string[]>>; setVariablesToGetUpdated: React.Dispatch<React.SetStateAction<string[]>>;
dependencyData: {
order: string[];
graph: VariableGraph;
parentDependencyGraph: VariableGraph;
} | null;
} }
const getSelectValue = ( const getSelectValue = (
@@ -79,6 +88,7 @@ function VariableItem({
onValueUpdate, onValueUpdate,
variablesToGetUpdated, variablesToGetUpdated,
setVariablesToGetUpdated, setVariablesToGetUpdated,
dependencyData,
}: VariableItemProps): JSX.Element { }: VariableItemProps): JSX.Element {
const [optionsData, setOptionsData] = useState<(string | number | boolean)[]>( const [optionsData, setOptionsData] = useState<(string | number | boolean)[]>(
[], [],
@@ -88,60 +98,29 @@ function VariableItem({
(state) => state.globalTime, (state) => state.globalTime,
); );
// logic to detect if its a rerender or a new render/mount
const isMounted = useRef(false);
useEffect(() => { useEffect(() => {
if (variableData.allSelected && variableData.type === 'QUERY') { isMounted.current = true;
setVariablesToGetUpdated((prev) => { }, []);
const variablesQueue = [...prev.filter((v) => v !== variableData.name)];
if (variableData.name) { const validVariableUpdate = (): boolean => {
variablesQueue.push(variableData.name); if (!variableData.name) {
} return false;
return variablesQueue;
});
} }
// eslint-disable-next-line react-hooks/exhaustive-deps if (!isMounted.current) {
}, [minTime, maxTime]); // variableData.name is present as the top element or next in the queue - variablesToGetUpdated
return Boolean(
variablesToGetUpdated.length &&
variablesToGetUpdated[0] === variableData.name,
);
}
return variablesToGetUpdated.includes(variableData.name);
};
const [errorMessage, setErrorMessage] = useState<null | string>(null); const [errorMessage, setErrorMessage] = useState<null | string>(null);
const getDependentVariables = (queryValue: string): string[] => {
const matches = queryValue.match(variableRegexPattern);
// Extract variable names from the matches array without {{ . }}
return matches
? matches.map((match) => match.replace(variableRegexPattern, '$1'))
: [];
};
const getQueryKey = (variableData: IDashboardVariable): string[] => {
let dependentVariablesStr = '';
const dependentVariables = getDependentVariables(
variableData.queryValue || '',
);
const variableName = variableData.name || '';
dependentVariables?.forEach((element) => {
const [, variable] =
Object.entries(existingVariables).find(
([, value]) => value.name === element,
) || [];
dependentVariablesStr += `${element}${variable?.selectedValue}`;
});
const variableKey = dependentVariablesStr.replace(/\s/g, '');
// added this time dependency for variables query as API respects the passed time range now
return [
REACT_QUERY_KEY.DASHBOARD_BY_ID,
variableName,
variableKey,
`${minTime}`,
`${maxTime}`,
];
};
// eslint-disable-next-line sonarjs/cognitive-complexity // eslint-disable-next-line sonarjs/cognitive-complexity
const getOptions = (variablesRes: VariableResponseProps | null): void => { const getOptions = (variablesRes: VariableResponseProps | null): void => {
if (variablesRes && variableData.type === 'QUERY') { if (variablesRes && variableData.type === 'QUERY') {
@@ -184,9 +163,7 @@ function VariableItem({
if ( if (
variableData.type === 'QUERY' && variableData.type === 'QUERY' &&
variableData.name && variableData.name &&
(variablesToGetUpdated.includes(variableData.name) || (validVariableUpdate() || valueNotInList || variableData.allSelected)
valueNotInList ||
variableData.allSelected)
) { ) {
let value = variableData.selectedValue; let value = variableData.selectedValue;
let allSelected = false; let allSelected = false;
@@ -200,7 +177,16 @@ function VariableItem({
} }
if (variableData && variableData?.name && variableData?.id) { if (variableData && variableData?.name && variableData?.id) {
onValueUpdate(variableData.name, variableData.id, value, allSelected); onValueUpdate(
variableData.name,
variableData.id,
value,
allSelected,
isMounted.current,
);
setVariablesToGetUpdated((prev) =>
prev.filter((name) => name !== variableData.name),
);
} }
} }
@@ -224,36 +210,75 @@ function VariableItem({
} }
}; };
const { isLoading } = useQuery(getQueryKey(variableData), { const { isLoading } = useQuery(
enabled: variableData && variableData.type === 'QUERY', [
queryFn: () => REACT_QUERY_KEY.DASHBOARD_BY_ID,
dashboardVariablesQuery({ variableData.name || '',
query: variableData.queryValue || '', `${minTime}`,
variables: variablePropsToPayloadVariables(existingVariables), `${maxTime}`,
}), ],
refetchOnWindowFocus: false, {
onSuccess: (response) => { enabled:
getOptions(response.payload); variableData &&
}, variableData.type === 'QUERY' &&
onError: (error: { checkAPIInvocation(
details: { variablesToGetUpdated,
error: string; variableData,
}; dependencyData?.parentDependencyGraph,
}) => { ),
const { details } = error; queryFn: () =>
dashboardVariablesQuery({
if (details.error) { query: variableData.queryValue || '',
let message = details.error; variables: variablePropsToPayloadVariables(existingVariables),
if (details.error.includes('Syntax error:')) { }),
message = refetchOnWindowFocus: false,
'Please make sure query is valid and dependent variables are selected'; onSuccess: (response) => {
getOptions(response.payload);
if (
dependencyData?.parentDependencyGraph[variableData.name || ''].length === 0
) {
const updatedVariables: string[] = [];
onUpdateVariableNode(
variableData.name || '',
dependencyData.graph,
dependencyData.order,
(node) => updatedVariables.push(node),
);
setVariablesToGetUpdated((prev) => [
...prev,
...updatedVariables.filter((v) => v !== variableData.name),
]);
} }
setErrorMessage(message); },
} onError: (error: {
details: {
error: string;
};
}) => {
const { details } = error;
if (details.error) {
let message = details.error;
if (details.error.includes('Syntax error:')) {
message =
'Please make sure query is valid and dependent variables are selected';
}
setErrorMessage(message);
}
},
}, },
}); );
const handleChange = (value: string | string[]): void => { const handleChange = (value: string | string[]): void => {
// if value is equal to selected value then return
if (
value === variableData.selectedValue ||
(Array.isArray(value) &&
Array.isArray(variableData.selectedValue) &&
areArraysEqual(value, variableData.selectedValue))
) {
return;
}
if (variableData.name) { if (variableData.name) {
if ( if (
value === ALL_SELECT_VALUE || value === ALL_SELECT_VALUE ||

View File

@@ -1,3 +1,4 @@
import { isEmpty } from 'lodash-es';
import { Dashboard, IDashboardVariable } from 'types/api/dashboard/getAll'; import { Dashboard, IDashboardVariable } from 'types/api/dashboard/getAll';
export function areArraysEqual( export function areArraysEqual(
@@ -29,3 +30,158 @@ export const convertVariablesToDbFormat = (
result[id] = obj; result[id] = obj;
return result; return result;
}, {}); }, {});
const getDependentVariables = (queryValue: string): string[] => {
const variableRegexPattern = /\{\{\s*?\.([^\s}]+)\s*?\}\}/g;
const matches = queryValue.match(variableRegexPattern);
// Extract variable names from the matches array without {{ . }}
return matches
? matches.map((match) => match.replace(variableRegexPattern, '$1'))
: [];
};
export type VariableGraph = Record<string, string[]>;
export const buildDependencies = (
variables: IDashboardVariable[],
): VariableGraph => {
const graph: VariableGraph = {};
// Initialize empty arrays for all variables first
variables.forEach((variable) => {
if (variable.name) {
graph[variable.name] = [];
}
});
// For each QUERY variable, add it as a dependent to its referenced variables
variables.forEach((variable) => {
if (variable.type === 'QUERY' && variable.name) {
const dependentVariables = getDependentVariables(variable.queryValue || '');
// For each referenced variable, add the current query as a dependent
dependentVariables.forEach((referencedVar) => {
if (graph[referencedVar]) {
graph[referencedVar].push(variable.name as string);
} else {
graph[referencedVar] = [variable.name as string];
}
});
}
});
return graph;
};
// Function to build the dependency graph
export const buildDependencyGraph = (
dependencies: VariableGraph,
): { order: string[]; graph: VariableGraph } => {
const inDegree: Record<string, number> = {};
const adjList: VariableGraph = {};
// Initialize in-degree and adjacency list
Object.keys(dependencies).forEach((node) => {
if (!inDegree[node]) inDegree[node] = 0;
if (!adjList[node]) adjList[node] = [];
dependencies[node].forEach((child) => {
if (!inDegree[child]) inDegree[child] = 0;
inDegree[child]++;
adjList[node].push(child);
});
});
// Topological sort using Kahn's Algorithm
const queue: string[] = Object.keys(inDegree).filter(
(node) => inDegree[node] === 0,
);
const topologicalOrder: string[] = [];
while (queue.length > 0) {
const current = queue.shift();
if (current === undefined) {
break;
}
topologicalOrder.push(current);
adjList[current].forEach((neighbor) => {
inDegree[neighbor]--;
if (inDegree[neighbor] === 0) queue.push(neighbor);
});
}
if (topologicalOrder.length !== Object.keys(dependencies).length) {
throw new Error('Cycle detected in the dependency graph!');
}
return { order: topologicalOrder, graph: adjList };
};
export const onUpdateVariableNode = (
nodeToUpdate: string,
graph: VariableGraph,
topologicalOrder: string[],
callback: (node: string) => void,
): void => {
const visited = new Set<string>();
// Start processing from the node to update
topologicalOrder.forEach((node) => {
if (node === nodeToUpdate || visited.has(node)) {
visited.add(node);
callback(node);
(graph[node] || []).forEach((child) => {
visited.add(child);
});
}
});
};
export const buildParentDependencyGraph = (
graph: VariableGraph,
): VariableGraph => {
const parentGraph: VariableGraph = {};
// Initialize empty arrays for all nodes
Object.keys(graph).forEach((node) => {
parentGraph[node] = [];
});
// For each node and its children in the original graph
Object.entries(graph).forEach(([node, children]) => {
// For each child, add the current node as its parent
children.forEach((child) => {
parentGraph[child].push(node);
});
});
return parentGraph;
};
export const checkAPIInvocation = (
variablesToGetUpdated: string[],
variableData: IDashboardVariable,
parentDependencyGraph?: VariableGraph,
): boolean => {
if (isEmpty(variableData.name)) {
return false;
}
if (isEmpty(parentDependencyGraph)) {
return true;
}
// if no dependency then true
const haveDependency =
parentDependencyGraph?.[variableData.name || '']?.length > 0;
if (!haveDependency) {
return true;
}
// if variable is in the list and has dependency then check if its the top element in the queue then true else false
return (
variablesToGetUpdated.length > 0 &&
variablesToGetUpdated[0] === variableData.name
);
};

View File

@@ -48,7 +48,7 @@ builder.Services.AddOpenTelemetry()
otlpOptions.Protocol = OtlpExportProtocol.Grpc; otlpOptions.Protocol = OtlpExportProtocol.Grpc;
//SigNoz Cloud account Ingestion key //SigNoz Cloud account Ingestion key
string headerKey = "signoz-access-token"; string headerKey = "signoz-ingestion-key";
string headerValue = "{{SIGNOZ_INGESTION_KEY}}"; string headerValue = "{{SIGNOZ_INGESTION_KEY}}";
string formattedHeader = $"{headerKey}={headerValue}"; string formattedHeader = $"{headerKey}={headerValue}";

View File

@@ -48,7 +48,7 @@ builder.Services.AddOpenTelemetry()
otlpOptions.Protocol = OtlpExportProtocol.Grpc; otlpOptions.Protocol = OtlpExportProtocol.Grpc;
//SigNoz Cloud account Ingestion key //SigNoz Cloud account Ingestion key
string headerKey = "signoz-access-token"; string headerKey = "signoz-ingestion-key";
string headerValue = "{{SIGNOZ_INGESTION_KEY}}"; string headerValue = "{{SIGNOZ_INGESTION_KEY}}";
string formattedHeader = $"{headerKey}={headerValue}"; string formattedHeader = $"{headerKey}={headerValue}";

View File

@@ -68,7 +68,7 @@ exporters:
tls: tls:
insecure: false insecure: false
headers: headers:
"signoz-access-token": "{{SIGNOZ_INGESTION_KEY}}" "signoz-ingestion-key": "{{SIGNOZ_INGESTION_KEY}}"
logging: logging:
verbosity: normal verbosity: normal
service: service:

View File

@@ -48,7 +48,7 @@ builder.Services.AddOpenTelemetry()
otlpOptions.Protocol = OtlpExportProtocol.Grpc; otlpOptions.Protocol = OtlpExportProtocol.Grpc;
//SigNoz Cloud account Ingestion key //SigNoz Cloud account Ingestion key
string headerKey = "signoz-access-token"; string headerKey = "signoz-ingestion-key";
string headerValue = "{{SIGNOZ_INGESTION_KEY}}"; string headerValue = "{{SIGNOZ_INGESTION_KEY}}";
string formattedHeader = $"{headerKey}={headerValue}"; string formattedHeader = $"{headerKey}={headerValue}";

View File

@@ -69,7 +69,7 @@ exporters:
tls: tls:
insecure: false insecure: false
headers: headers:
"signoz-access-token": "{{SIGNOZ_INGESTION_KEY}}" "signoz-ingestion-key": "{{SIGNOZ_INGESTION_KEY}}"
logging: logging:
verbosity: normal verbosity: normal
service: service:

View File

@@ -48,7 +48,7 @@ builder.Services.AddOpenTelemetry()
otlpOptions.Protocol = OtlpExportProtocol.Grpc; otlpOptions.Protocol = OtlpExportProtocol.Grpc;
//SigNoz Cloud account Ingestion key //SigNoz Cloud account Ingestion key
string headerKey = "signoz-access-token"; string headerKey = "signoz-ingestion-key";
string headerValue = "{{SIGNOZ_INGESTION_KEY}}"; string headerValue = "{{SIGNOZ_INGESTION_KEY}}";
string formattedHeader = $"{headerKey}={headerValue}"; string formattedHeader = $"{headerKey}={headerValue}";

View File

@@ -67,7 +67,7 @@ exporters:
tls: tls:
insecure: false insecure: false
headers: headers:
"signoz-access-token": "{{SIGNOZ_INGESTION_KEY}}" "signoz-ingestion-key": "{{SIGNOZ_INGESTION_KEY}}"
logging: logging:
verbosity: normal verbosity: normal
service: service:

View File

@@ -48,7 +48,7 @@ builder.Services.AddOpenTelemetry()
otlpOptions.Protocol = OtlpExportProtocol.Grpc; otlpOptions.Protocol = OtlpExportProtocol.Grpc;
//SigNoz Cloud account Ingestion key //SigNoz Cloud account Ingestion key
string headerKey = "signoz-access-token"; string headerKey = "signoz-ingestion-key";
string headerValue = "{{SIGNOZ_INGESTION_KEY}}"; string headerValue = "{{SIGNOZ_INGESTION_KEY}}";
string formattedHeader = $"{headerKey}={headerValue}"; string formattedHeader = $"{headerKey}={headerValue}";

View File

@@ -68,7 +68,7 @@ exporters:
tls: tls:
insecure: false insecure: false
headers: headers:
"signoz-access-token": "{{SIGNOZ_INGESTION_KEY}}" "signoz-ingestion-key": "{{SIGNOZ_INGESTION_KEY}}"
logging: logging:
verbosity: normal verbosity: normal
service: service:

View File

@@ -37,7 +37,7 @@ builder.Services.AddOpenTelemetry()
otlpOptions.Protocol = OtlpExportProtocol.Grpc; otlpOptions.Protocol = OtlpExportProtocol.Grpc;
//SigNoz Cloud account Ingestion key //SigNoz Cloud account Ingestion key
string headerKey = "signoz-access-token"; string headerKey = "signoz-ingestion-key";
string headerValue = "{{SIGNOZ_INGESTION_KEY}}"; string headerValue = "{{SIGNOZ_INGESTION_KEY}}";
string formattedHeader = $"{headerKey}={headerValue}"; string formattedHeader = $"{headerKey}={headerValue}";

View File

@@ -53,7 +53,7 @@ config :opentelemetry, :processors,
%{ %{
endpoints: ["https://ingest.{{REGION}}.signoz.cloud:443"], endpoints: ["https://ingest.{{REGION}}.signoz.cloud:443"],
headers: [ headers: [
{"signoz-access-token", {{SIGNOZ_ACCESS_TOKEN}} } {"signoz-ingestion-key", {{SIGNOZ_ACCESS_TOKEN}} }
] ]
} }
} }

View File

@@ -53,7 +53,7 @@ config :opentelemetry, :processors,
%{ %{
endpoints: ["https://ingest.{{REGION}}.signoz.cloud:443"], endpoints: ["https://ingest.{{REGION}}.signoz.cloud:443"],
headers: [ headers: [
{"signoz-access-token", {{SIGNOZ_ACCESS_TOKEN}} } {"signoz-ingestion-key", {{SIGNOZ_ACCESS_TOKEN}} }
] ]
} }
} }

View File

@@ -66,7 +66,7 @@ exporters:
tls: tls:
insecure: false insecure: false
headers: headers:
"signoz-access-token": "{{SIGNOZ_INGESTION_KEY}}" "signoz-ingestion-key": "{{SIGNOZ_INGESTION_KEY}}"
logging: logging:
verbosity: normal verbosity: normal
service: service:

View File

@@ -53,7 +53,7 @@ config :opentelemetry, :processors,
%{ %{
endpoints: ["https://ingest.{{REGION}}.signoz.cloud:443"], endpoints: ["https://ingest.{{REGION}}.signoz.cloud:443"],
headers: [ headers: [
{"signoz-access-token", {{SIGNOZ_ACCESS_TOKEN}} } {"signoz-ingestion-key", {{SIGNOZ_ACCESS_TOKEN}} }
] ]
} }
} }

View File

@@ -66,7 +66,7 @@ exporters:
tls: tls:
insecure: false insecure: false
headers: headers:
"signoz-access-token": "{{SIGNOZ_INGESTION_KEY}}" "signoz-ingestion-key": "{{SIGNOZ_INGESTION_KEY}}"
logging: logging:
verbosity: normal verbosity: normal
service: service:

View File

@@ -53,7 +53,7 @@ config :opentelemetry, :processors,
%{ %{
endpoints: ["https://ingest.{{REGION}}.signoz.cloud:443"], endpoints: ["https://ingest.{{REGION}}.signoz.cloud:443"],
headers: [ headers: [
{"signoz-access-token", {{SIGNOZ_ACCESS_TOKEN}} } {"signoz-ingestion-key", {{SIGNOZ_ACCESS_TOKEN}} }
] ]
} }
} }

View File

@@ -66,7 +66,7 @@ exporters:
tls: tls:
insecure: false insecure: false
headers: headers:
"signoz-access-token": "{{SIGNOZ_INGESTION_KEY}}" "signoz-ingestion-key": "{{SIGNOZ_INGESTION_KEY}}"
logging: logging:
verbosity: normal verbosity: normal
service: service:

View File

@@ -53,7 +53,7 @@ config :opentelemetry, :processors,
%{ %{
endpoints: ["https://ingest.{{REGION}}.signoz.cloud:443"], endpoints: ["https://ingest.{{REGION}}.signoz.cloud:443"],
headers: [ headers: [
{"signoz-access-token", {{SIGNOZ_ACCESS_TOKEN}} } {"signoz-ingestion-key", {{SIGNOZ_ACCESS_TOKEN}} }
] ]
} }
} }

View File

@@ -66,7 +66,7 @@ exporters:
tls: tls:
insecure: false insecure: false
headers: headers:
"signoz-access-token": "{{SIGNOZ_INGESTION_KEY}}" "signoz-ingestion-key": "{{SIGNOZ_INGESTION_KEY}}"
logging: logging:
verbosity: normal verbosity: normal
service: service:

View File

@@ -53,7 +53,7 @@ config :opentelemetry, :processors,
%{ %{
endpoints: ["https://ingest.{{REGION}}.signoz.cloud:443"], endpoints: ["https://ingest.{{REGION}}.signoz.cloud:443"],
headers: [ headers: [
{"signoz-access-token", {{SIGNOZ_ACCESS_TOKEN}} } {"signoz-ingestion-key", {{SIGNOZ_ACCESS_TOKEN}} }
] ]
} }
} }

View File

@@ -75,7 +75,7 @@ exporters:
tls: tls:
insecure: false insecure: false
headers: headers:
"signoz-access-token": "{{SIGNOZ_INGESTION_KEY}}" "signoz-ingestion-key": "{{SIGNOZ_INGESTION_KEY}}"
logging: logging:
verbosity: normal verbosity: normal
service: service:

View File

@@ -134,7 +134,7 @@ From VMs, there are two ways to send data to SigNoz Cloud.
The run command must have some environment variables to send data to SigNoz cloud. The run command: The run command must have some environment variables to send data to SigNoz cloud. The run command:
```bash ```bash
SERVICE_NAME={{MYAPP}} INSECURE_MODE=false OTEL_EXPORTER_OTLP_HEADERS=signoz-access-token={{SIGNOZ_INGESTION_KEY}} OTEL_EXPORTER_OTLP_ENDPOINT=ingest.{{REGION}}.signoz.cloud:443 go run main.go SERVICE_NAME={{MYAPP}} INSECURE_MODE=false OTEL_EXPORTER_OTLP_HEADERS=signoz-ingestion-key={{SIGNOZ_INGESTION_KEY}} OTEL_EXPORTER_OTLP_ENDPOINT=ingest.{{REGION}}.signoz.cloud:443 go run main.go
``` ```
If you want to update your `service_name`, you can modify the `SERVICE_NAME` variable. If you want to update your `service_name`, you can modify the `SERVICE_NAME` variable.

View File

@@ -128,7 +128,7 @@ Set the environment variables in your Dockerfile.
# Set environment variables # Set environment variables
ENV SERVICE_NAME={{MYAPP}} \ ENV SERVICE_NAME={{MYAPP}} \
INSECURE_MODE=false \ INSECURE_MODE=false \
OTEL_EXPORTER_OTLP_HEADERS="signoz-access-token=b{{SIGNOZ_INGESTION_KEY}}" \ OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=b{{SIGNOZ_INGESTION_KEY}}" \
OTEL_EXPORTER_OTLP_ENDPOINT=ingest.{{REGION}}.signoz.cloud:443 OTEL_EXPORTER_OTLP_ENDPOINT=ingest.{{REGION}}.signoz.cloud:443
... ...
``` ```

View File

@@ -2,5 +2,5 @@
To run your Go Gin application, use the below command : To run your Go Gin application, use the below command :
```bash ```bash
SERVICE_NAME={{MYAPP}} INSECURE_MODE=false OTEL_EXPORTER_OTLP_HEADERS=signoz-access-token={{SIGNOZ_INGESTION_KEY}} OTEL_EXPORTER_OTLP_ENDPOINT=ingest.{{REGION}}.signoz.cloud:443 go run main.go SERVICE_NAME={{MYAPP}} INSECURE_MODE=false OTEL_EXPORTER_OTLP_HEADERS=signoz-ingestion-key={{SIGNOZ_INGESTION_KEY}} OTEL_EXPORTER_OTLP_ENDPOINT=ingest.{{REGION}}.signoz.cloud:443 go run main.go
``` ```

View File

@@ -66,7 +66,7 @@ exporters:
tls: tls:
insecure: false insecure: false
headers: headers:
"signoz-access-token": "{{SIGNOZ_INGESTION_KEY}}" "signoz-ingestion-key": "{{SIGNOZ_INGESTION_KEY}}"
logging: logging:
verbosity: normal verbosity: normal
service: service:

View File

@@ -2,5 +2,5 @@
To run your Go Gin application, use the below command : To run your Go Gin application, use the below command :
```bash ```bash
SERVICE_NAME={{MYAPP}} INSECURE_MODE=false OTEL_EXPORTER_OTLP_HEADERS=signoz-access-token={{SIGNOZ_INGESTION_KEY}} OTEL_EXPORTER_OTLP_ENDPOINT=ingest.{{REGION}}.signoz.cloud:443 go run main.go SERVICE_NAME={{MYAPP}} INSECURE_MODE=false OTEL_EXPORTER_OTLP_HEADERS=signoz-ingestion-key={{SIGNOZ_INGESTION_KEY}} OTEL_EXPORTER_OTLP_ENDPOINT=ingest.{{REGION}}.signoz.cloud:443 go run main.go
``` ```

View File

@@ -69,7 +69,7 @@ exporters:
tls: tls:
insecure: false insecure: false
headers: headers:
"signoz-access-token": "{{SIGNOZ_INGESTION_KEY}}" "signoz-ingestion-key": "{{SIGNOZ_INGESTION_KEY}}"
logging: logging:
verbosity: normal verbosity: normal
service: service:

View File

@@ -2,5 +2,5 @@
To run your Go Gin application, use the below command : To run your Go Gin application, use the below command :
```bash ```bash
SERVICE_NAME={{MYAPP}} INSECURE_MODE=false OTEL_EXPORTER_OTLP_HEADERS=signoz-access-token={{SIGNOZ_INGESTION_KEY}} OTEL_EXPORTER_OTLP_ENDPOINT=ingest.{{REGION}}.signoz.cloud:443 go run main.go SERVICE_NAME={{MYAPP}} INSECURE_MODE=false OTEL_EXPORTER_OTLP_HEADERS=signoz-ingestion-key={{SIGNOZ_INGESTION_KEY}} OTEL_EXPORTER_OTLP_ENDPOINT=ingest.{{REGION}}.signoz.cloud:443 go run main.go
``` ```

View File

@@ -66,7 +66,7 @@ exporters:
tls: tls:
insecure: false insecure: false
headers: headers:
"signoz-access-token": "{{SIGNOZ_INGESTION_KEY}}" "signoz-ingestion-key": "{{SIGNOZ_INGESTION_KEY}}"
logging: logging:
verbosity: normal verbosity: normal
service: service:

View File

@@ -2,5 +2,5 @@
To run your Go Gin application, use the below command : To run your Go Gin application, use the below command :
```bash ```bash
SERVICE_NAME={{MYAPP}} INSECURE_MODE=false OTEL_EXPORTER_OTLP_HEADERS=signoz-access-token={{SIGNOZ_INGESTION_KEY}} OTEL_EXPORTER_OTLP_ENDPOINT=ingest.{{REGION}}.signoz.cloud:443 go run main.go SERVICE_NAME={{MYAPP}} INSECURE_MODE=false OTEL_EXPORTER_OTLP_HEADERS=signoz-ingestion-key={{SIGNOZ_INGESTION_KEY}} OTEL_EXPORTER_OTLP_ENDPOINT=ingest.{{REGION}}.signoz.cloud:443 go run main.go
``` ```

View File

@@ -64,7 +64,7 @@ exporters:
tls: tls:
insecure: false insecure: false
headers: headers:
"signoz-access-token": "{{SIGNOZ_INGESTION_KEY}}" "signoz-ingestion-key": "{{SIGNOZ_INGESTION_KEY}}"
logging: logging:
verbosity: normal verbosity: normal
service: service:

View File

@@ -22,7 +22,7 @@
var ( var (
serviceName = "{{MYAPP}}") serviceName = "{{MYAPP}}")
collectorURL = "https://ingest.{{REGION}}.signoz.cloud:443" collectorURL = "https://ingest.{{REGION}}.signoz.cloud:443"
headers="signoz-access-token={{SIGNOZ_INGESTION_KEY}}" headers="signoz-ingestion-key={{SIGNOZ_INGESTION_KEY}}"
insecure = os.Getenv("INSECURE_MODE") insecure = os.Getenv("INSECURE_MODE")
) )
``` ```

View File

@@ -18,7 +18,7 @@ RUN wget https://github.com/open-telemetry/opentelemetry-java-instrumentation/re
# Open and update the configuration file # Open and update the configuration file
RUN sed -i 's/\(JAVA_OPTS=".*\)/\1 -javaagent:\/opt\/jboss-eap-7.1\/opentelemetry-javaagent.jar \ RUN sed -i 's/\(JAVA_OPTS=".*\)/\1 -javaagent:\/opt\/jboss-eap-7.1\/opentelemetry-javaagent.jar \
-Dotel.exporter.otlp.endpoint=https:\/\/ingest.{{REGION}}.signoz.cloud:443 \ -Dotel.exporter.otlp.endpoint=https:\/\/ingest.{{REGION}}.signoz.cloud:443 \
-Dotel.exporter.otlp.headers="signoz-access-token={{SIGNOZ_INGESTION_KEY}}" \ -Dotel.exporter.otlp.headers="signoz-ingestion-key={{SIGNOZ_INGESTION_KEY}}" \
-Dotel.resource.attributes="service.name={{MYAPP}}"/' /opt/jboss-eap-7.1/bin/standalone.conf -Dotel.resource.attributes="service.name={{MYAPP}}"/' /opt/jboss-eap-7.1/bin/standalone.conf
... ...
``` ```

View File

@@ -10,7 +10,7 @@ Update `JAVA_OPTS` environment variable with configurations required to send dat
```bash ```bash
JAVA_OPTS="-javaagent:/<path>/opentelemetry-javaagent.jar JAVA_OPTS="-javaagent:/<path>/opentelemetry-javaagent.jar
-Dotel.exporter.otlp.endpoint=https://ingest.{{REGION}}.signoz.cloud:443 -Dotel.exporter.otlp.endpoint=https://ingest.{{REGION}}.signoz.cloud:443
-Dotel.exporter.otlp.headers="signoz-access-token={{SIGNOZ_INGESTION_KEY}}" -Dotel.exporter.otlp.headers="signoz-ingestion-key={{SIGNOZ_INGESTION_KEY}}"
-Dotel.resource.attributes="service.name={{MYAPP}}"" -Dotel.resource.attributes="service.name={{MYAPP}}""
``` ```

View File

@@ -66,7 +66,7 @@ exporters:
tls: tls:
insecure: false insecure: false
headers: headers:
"signoz-access-token": "{{SIGNOZ_INGESTION_KEY}}" "signoz-ingestion-key": "{{SIGNOZ_INGESTION_KEY}}"
logging: logging:
verbosity: normal verbosity: normal
service: service:

View File

@@ -10,7 +10,7 @@ Update `JAVA_OPTS` environment variable with configurations required to send dat
```bash ```bash
JAVA_OPTS="-javaagent:/<path>/opentelemetry-javaagent.jar JAVA_OPTS="-javaagent:/<path>/opentelemetry-javaagent.jar
-Dotel.exporter.otlp.endpoint=https://ingest.{{REGION}}.signoz.cloud:443 -Dotel.exporter.otlp.endpoint=https://ingest.{{REGION}}.signoz.cloud:443
-Dotel.exporter.otlp.headers="signoz-access-token={{SIGNOZ_INGESTION_KEY}}" -Dotel.exporter.otlp.headers="signoz-ingestion-key={{SIGNOZ_INGESTION_KEY}}"
-Dotel.resource.attributes="service.name={{MYAPP}}"" -Dotel.resource.attributes="service.name={{MYAPP}}""
``` ```

View File

@@ -66,7 +66,7 @@ exporters:
tls: tls:
insecure: false insecure: false
headers: headers:
"signoz-access-token": "{{SIGNOZ_INGESTION_KEY}}" "signoz-ingestion-key": "{{SIGNOZ_INGESTION_KEY}}"
logging: logging:
verbosity: normal verbosity: normal
service: service:

View File

@@ -10,7 +10,7 @@ Update `JAVA_OPTS` environment variable with configurations required to send dat
```bash ```bash
JAVA_OPTS="-javaagent:/<path>/opentelemetry-javaagent.jar JAVA_OPTS="-javaagent:/<path>/opentelemetry-javaagent.jar
-Dotel.exporter.otlp.endpoint=https://ingest.{{REGION}}.signoz.cloud:443 -Dotel.exporter.otlp.endpoint=https://ingest.{{REGION}}.signoz.cloud:443
-Dotel.exporter.otlp.headers="signoz-access-token={{SIGNOZ_INGESTION_KEY}}" -Dotel.exporter.otlp.headers="signoz-ingestion-key={{SIGNOZ_INGESTION_KEY}}"
-Dotel.resource.attributes="service.name={{MYAPP}}"" -Dotel.resource.attributes="service.name={{MYAPP}}""
``` ```

View File

@@ -66,7 +66,7 @@ exporters:
tls: tls:
insecure: false insecure: false
headers: headers:
"signoz-access-token": "{{SIGNOZ_INGESTION_KEY}}" "signoz-ingestion-key": "{{SIGNOZ_INGESTION_KEY}}"
logging: logging:
verbosity: normal verbosity: normal
service: service:

View File

@@ -10,7 +10,7 @@ Update `JAVA_OPTS` environment variable with configurations required to send dat
```bash ```bash
JAVA_OPTS="-javaagent:/<path>/opentelemetry-javaagent.jar JAVA_OPTS="-javaagent:/<path>/opentelemetry-javaagent.jar
-Dotel.exporter.otlp.endpoint=https://ingest.{{REGION}}.signoz.cloud:443 -Dotel.exporter.otlp.endpoint=https://ingest.{{REGION}}.signoz.cloud:443
-Dotel.exporter.otlp.headers="signoz-access-token={{SIGNOZ_INGESTION_KEY}}" -Dotel.exporter.otlp.headers="signoz-ingestion-key={{SIGNOZ_INGESTION_KEY}}"
-Dotel.resource.attributes="service.name={{MYAPP}}"" -Dotel.resource.attributes="service.name={{MYAPP}}""
``` ```

View File

@@ -66,7 +66,7 @@ exporters:
tls: tls:
insecure: false insecure: false
headers: headers:
"signoz-access-token": "{{SIGNOZ_INGESTION_KEY}}" "signoz-ingestion-key": "{{SIGNOZ_INGESTION_KEY}}"
logging: logging:
verbosity: normal verbosity: normal
service: service:

View File

@@ -17,7 +17,7 @@ Update `JAVA_OPTS` environment variable with configurations required to send dat
```bash ```bash
set JAVA_OPTS=-javaagent:C:\path\to\opentelemetry-javaagent.jar set JAVA_OPTS=-javaagent:C:\path\to\opentelemetry-javaagent.jar
set JAVA_OPTS=%JAVA_OPTS% -Dotel.exporter.otlp.endpoint=https://ingest.{{REGION}}.signoz.cloud:443 set JAVA_OPTS=%JAVA_OPTS% -Dotel.exporter.otlp.endpoint=https://ingest.{{REGION}}.signoz.cloud:443
set JAVA_OPTS=%JAVA_OPTS% -Dotel.exporter.otlp.headers="signoz-access-token={{SIGNOZ_INGESTION_KEY}}" set JAVA_OPTS=%JAVA_OPTS% -Dotel.exporter.otlp.headers="signoz-ingestion-key={{SIGNOZ_INGESTION_KEY}}"
set JAVA_OPTS=%JAVA_OPTS% -Dotel.resource.attributes="service.name={{MYAPP}}" set JAVA_OPTS=%JAVA_OPTS% -Dotel.resource.attributes="service.name={{MYAPP}}"
``` ```
&nbsp; &nbsp;

View File

@@ -13,7 +13,7 @@ RUN wget https://github.com/open-telemetry/opentelemetry-java-instrumentation/re
# Set environment variables and run your Spring Boot application # Set environment variables and run your Spring Boot application
ENV OTEL_RESOURCE_ATTRIBUTES="service.name={{MYAPP}}" \ ENV OTEL_RESOURCE_ATTRIBUTES="service.name={{MYAPP}}" \
OTEL_EXPORTER_OTLP_HEADERS="signoz-access-token={{SIGNOZ_INGESTION_KEY}}" \ OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key={{SIGNOZ_INGESTION_KEY}}" \
OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.{{REGION}}.signoz.cloud:443" OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.{{REGION}}.signoz.cloud:443"
# Copy the Spring Boot application JAR file into the container # Copy the Spring Boot application JAR file into the container

View File

@@ -3,7 +3,7 @@ Once you are done intrumenting your Java application, you can run it using the b
```bash ```bash
OTEL_RESOURCE_ATTRIBUTES=service.name={{MYAPP}} \ OTEL_RESOURCE_ATTRIBUTES=service.name={{MYAPP}} \
OTEL_EXPORTER_OTLP_HEADERS="signoz-access-token={{SIGNOZ_INGESTION_KEY}}" \ OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key={{SIGNOZ_INGESTION_KEY}}" \
OTEL_EXPORTER_OTLP_ENDPOINT=https://ingest.{{REGION}}.signoz.cloud:443 \ OTEL_EXPORTER_OTLP_ENDPOINT=https://ingest.{{REGION}}.signoz.cloud:443 \
java -javaagent:<path>/opentelemetry-javaagent.jar -jar <my-app>.jar java -javaagent:<path>/opentelemetry-javaagent.jar -jar <my-app>.jar
``` ```

View File

@@ -66,7 +66,7 @@ exporters:
tls: tls:
insecure: false insecure: false
headers: headers:
"signoz-access-token": "{{SIGNOZ_INGESTION_KEY}}" "signoz-ingestion-key": "{{SIGNOZ_INGESTION_KEY}}"
logging: logging:
verbosity: normal verbosity: normal
service: service:

View File

@@ -3,7 +3,7 @@ Once you are done intrumenting your Java application, you can run it using the b
```bash ```bash
OTEL_RESOURCE_ATTRIBUTES=service.name={{MYAPP}} \ OTEL_RESOURCE_ATTRIBUTES=service.name={{MYAPP}} \
OTEL_EXPORTER_OTLP_HEADERS="signoz-access-token={{SIGNOZ_INGESTION_KEY}}" \ OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key={{SIGNOZ_INGESTION_KEY}}" \
OTEL_EXPORTER_OTLP_ENDPOINT=https://ingest.{{REGION}}.signoz.cloud:443 \ OTEL_EXPORTER_OTLP_ENDPOINT=https://ingest.{{REGION}}.signoz.cloud:443 \
java -javaagent:<path>/opentelemetry-javaagent.jar -jar <my-app>.jar java -javaagent:<path>/opentelemetry-javaagent.jar -jar <my-app>.jar
``` ```

View File

@@ -66,7 +66,7 @@ exporters:
tls: tls:
insecure: false insecure: false
headers: headers:
"signoz-access-token": "{{SIGNOZ_INGESTION_KEY}}" "signoz-ingestion-key": "{{SIGNOZ_INGESTION_KEY}}"
logging: logging:
verbosity: normal verbosity: normal
service: service:

Some files were not shown because too many files have changed in this diff Show More