Merge pull request #6662 from SigNoz/release/v0.63.x

Release/v0.63.x
This commit is contained in:
Prashant Shahi 2024-12-18 15:41:24 +05:30 committed by GitHub
commit 46bc7c7a21
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
399 changed files with 2376 additions and 1149 deletions

View File

@ -146,7 +146,7 @@ services:
condition: on-failure condition: on-failure
query-service: query-service:
image: signoz/query-service:0.62.0 image: signoz/query-service:0.63.0
command: command:
[ [
"-config=/root/config/prometheus.yml", "-config=/root/config/prometheus.yml",
@ -186,7 +186,7 @@ services:
<<: *db-depend <<: *db-depend
frontend: frontend:
image: signoz/frontend:0.62.0 image: signoz/frontend:0.63.0
deploy: deploy:
restart_policy: restart_policy:
condition: on-failure condition: on-failure
@ -199,7 +199,7 @@ services:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf - ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector: otel-collector:
image: signoz/signoz-otel-collector:0.111.15 image: signoz/signoz-otel-collector:0.111.16
command: command:
[ [
"--config=/etc/otel-collector-config.yaml", "--config=/etc/otel-collector-config.yaml",
@ -237,7 +237,7 @@ services:
- query-service - query-service
otel-collector-migrator: otel-collector-migrator:
image: signoz/signoz-schema-migrator:0.111.15 image: signoz/signoz-schema-migrator:0.111.16
deploy: deploy:
restart_policy: restart_policy:
condition: on-failure condition: on-failure

View File

@ -69,7 +69,7 @@ services:
- --storage.path=/data - --storage.path=/data
otel-collector-migrator: otel-collector-migrator:
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.15} image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.16}
container_name: otel-migrator container_name: otel-migrator
command: command:
- "sync" - "sync"
@ -86,7 +86,7 @@ services:
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md` # Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
otel-collector: otel-collector:
container_name: signoz-otel-collector container_name: signoz-otel-collector
image: signoz/signoz-otel-collector:0.111.15 image: signoz/signoz-otel-collector:0.111.16
command: command:
[ [
"--config=/etc/otel-collector-config.yaml", "--config=/etc/otel-collector-config.yaml",

View File

@ -162,7 +162,7 @@ services:
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md` # Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
query-service: query-service:
image: signoz/query-service:${DOCKER_TAG:-0.62.0} image: signoz/query-service:${DOCKER_TAG:-0.63.0}
container_name: signoz-query-service container_name: signoz-query-service
command: command:
[ [
@ -201,7 +201,7 @@ services:
<<: *db-depend <<: *db-depend
frontend: frontend:
image: signoz/frontend:${DOCKER_TAG:-0.62.0} image: signoz/frontend:${DOCKER_TAG:-0.63.0}
container_name: signoz-frontend container_name: signoz-frontend
restart: on-failure restart: on-failure
depends_on: depends_on:
@ -213,7 +213,7 @@ services:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf - ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector-migrator-sync: otel-collector-migrator-sync:
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.15} image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.16}
container_name: otel-migrator-sync container_name: otel-migrator-sync
command: command:
- "sync" - "sync"
@ -228,7 +228,7 @@ services:
# condition: service_healthy # condition: service_healthy
otel-collector-migrator-async: otel-collector-migrator-async:
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.15} image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.16}
container_name: otel-migrator-async container_name: otel-migrator-async
command: command:
- "async" - "async"
@ -245,7 +245,7 @@ services:
# condition: service_healthy # condition: service_healthy
otel-collector: otel-collector:
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.15} image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.16}
container_name: signoz-otel-collector container_name: signoz-otel-collector
command: command:
[ [

View File

@ -167,7 +167,7 @@ services:
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md` # Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
query-service: query-service:
image: signoz/query-service:${DOCKER_TAG:-0.62.0} image: signoz/query-service:${DOCKER_TAG:-0.63.0}
container_name: signoz-query-service container_name: signoz-query-service
command: command:
[ [
@ -208,7 +208,7 @@ services:
<<: *db-depend <<: *db-depend
frontend: frontend:
image: signoz/frontend:${DOCKER_TAG:-0.62.0} image: signoz/frontend:${DOCKER_TAG:-0.63.0}
container_name: signoz-frontend container_name: signoz-frontend
restart: on-failure restart: on-failure
depends_on: depends_on:
@ -220,7 +220,7 @@ services:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf - ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector-migrator: otel-collector-migrator:
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.15} image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.16}
container_name: otel-migrator container_name: otel-migrator
command: command:
- "--dsn=tcp://clickhouse:9000" - "--dsn=tcp://clickhouse:9000"
@ -234,7 +234,7 @@ services:
otel-collector: otel-collector:
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.15} image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.16}
container_name: signoz-otel-collector container_name: signoz-otel-collector
command: command:
[ [

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

@ -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
``` ```

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