mirror of
https://github.com/SigNoz/signoz.git
synced 2026-03-17 10:22:11 +00:00
Compare commits
16 Commits
feat/authz
...
partial-un
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
deed85ba2d | ||
|
|
7bf57b2ed5 | ||
|
|
60ceb1b45b | ||
|
|
e26690eae9 | ||
|
|
b0c977716a | ||
|
|
3e35b250c2 | ||
|
|
a6b8911885 | ||
|
|
e5eb62e45b | ||
|
|
7371dcacf0 | ||
|
|
3cdf3e06f3 | ||
|
|
f8c38df2bf | ||
|
|
cab4a56694 | ||
|
|
78041fe457 | ||
|
|
09b6382820 | ||
|
|
9689b847f0 | ||
|
|
15e5938e95 |
10
.github/CODEOWNERS
vendored
10
.github/CODEOWNERS
vendored
@@ -1,8 +1,6 @@
|
||||
# CODEOWNERS info: https://help.github.com/en/articles/about-code-owners
|
||||
|
||||
# Owners are automatically requested for review for PRs that changes code
|
||||
|
||||
# that they own.
|
||||
# Owners are automatically requested for review for PRs that changes code that they own.
|
||||
|
||||
/frontend/ @SigNoz/frontend-maintainers
|
||||
|
||||
@@ -11,8 +9,10 @@
|
||||
/frontend/src/container/OnboardingV2Container/onboarding-configs/onboarding-config-with-links.json @makeavish
|
||||
/frontend/src/container/OnboardingV2Container/AddDataSource/AddDataSource.tsx @makeavish
|
||||
|
||||
/deploy/ @SigNoz/devops
|
||||
.github @SigNoz/devops
|
||||
# CI
|
||||
/deploy/ @therealpandey
|
||||
.github @therealpandey
|
||||
go.mod @therealpandey
|
||||
|
||||
# Scaffold Owners
|
||||
|
||||
|
||||
60
.github/workflows/mergequeueci.yaml
vendored
Normal file
60
.github/workflows/mergequeueci.yaml
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
name: mergequeueci
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- dequeued
|
||||
|
||||
jobs:
|
||||
notify:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.merged == false
|
||||
steps:
|
||||
- name: alert
|
||||
uses: slackapi/slack-github-action@v2.1.1
|
||||
with:
|
||||
webhook: ${{ secrets.SLACK_MERGE_QUEUE_WEBHOOK }}
|
||||
webhook-type: incoming-webhook
|
||||
payload: |
|
||||
{
|
||||
"text": ":x: PR removed from merge queue",
|
||||
"blocks": [
|
||||
{
|
||||
"type": "header",
|
||||
"text": {
|
||||
"type": "plain_text",
|
||||
"text": ":x: PR Removed from Merge Queue"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": "*<${{ github.event.pull_request.html_url }}|PR #${{ github.event.pull_request.number }}: ${{ github.event.pull_request.title }}>*"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "divider"
|
||||
},
|
||||
{
|
||||
"type": "section",
|
||||
"fields": [
|
||||
{
|
||||
"type": "mrkdwn",
|
||||
"text": "*Author*\n@${{ github.event.pull_request.user.login }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
- name: comment
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
|
||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||
run: |
|
||||
gh api repos/${{ github.repository }}/issues/$PR_NUMBER/comments \
|
||||
-f body="> :x: **PR removed from merge queue**
|
||||
>
|
||||
> @$PR_AUTHOR your PR was removed from the merge queue. Fix the issue and re-queue when ready."
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/version"
|
||||
"github.com/spf13/cobra"
|
||||
"go.uber.org/zap" //nolint:depguard
|
||||
)
|
||||
|
||||
var RootCmd = &cobra.Command{
|
||||
@@ -19,12 +18,6 @@ var RootCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func Execute(logger *slog.Logger) {
|
||||
zapLogger := newZapLogger()
|
||||
zap.ReplaceGlobals(zapLogger)
|
||||
defer func() {
|
||||
_ = zapLogger.Sync()
|
||||
}()
|
||||
|
||||
err := RootCmd.Execute()
|
||||
if err != nil {
|
||||
logger.ErrorContext(RootCmd.Context(), "error running command", "error", err)
|
||||
|
||||
110
cmd/zap.go
110
cmd/zap.go
@@ -1,110 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"go.uber.org/zap" //nolint:depguard
|
||||
"go.uber.org/zap/zapcore" //nolint:depguard
|
||||
)
|
||||
|
||||
// Deprecated: Use `NewLogger` from `pkg/instrumentation` instead.
|
||||
func newZapLogger() *zap.Logger {
|
||||
config := zap.NewProductionConfig()
|
||||
config.EncoderConfig.TimeKey = "timestamp"
|
||||
config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
|
||||
|
||||
// Extract sampling config before building the logger.
|
||||
// We need to disable sampling in the config and apply it manually later
|
||||
// to ensure correct core ordering. See filteringCore documentation for details.
|
||||
samplerConfig := config.Sampling
|
||||
config.Sampling = nil
|
||||
|
||||
logger, _ := config.Build()
|
||||
|
||||
// Wrap with custom core wrapping to filter certain log entries.
|
||||
// The order of wrapping is important:
|
||||
// 1. First wrap with filteringCore
|
||||
// 2. Then wrap with sampler
|
||||
//
|
||||
// This creates the call chain: sampler -> filteringCore -> ioCore
|
||||
//
|
||||
// During logging:
|
||||
// - sampler.Check decides whether to sample the log entry
|
||||
// - If sampled, filteringCore.Check is called
|
||||
// - filteringCore adds itself to CheckedEntry.cores
|
||||
// - All cores in CheckedEntry.cores have their Write method called
|
||||
// - filteringCore.Write can now filter the entry before passing to ioCore
|
||||
//
|
||||
// If we didn't disable the sampler above, filteringCore would have wrapped
|
||||
// sampler. By calling sampler.Check we would have allowed it to call
|
||||
// ioCore.Check that adds itself to CheckedEntry.cores. Then ioCore.Write
|
||||
// would have bypassed our checks, making filtering impossible.
|
||||
return logger.WithOptions(zap.WrapCore(func(core zapcore.Core) zapcore.Core {
|
||||
core = &filteringCore{core}
|
||||
if samplerConfig != nil {
|
||||
core = zapcore.NewSamplerWithOptions(
|
||||
core,
|
||||
time.Second,
|
||||
samplerConfig.Initial,
|
||||
samplerConfig.Thereafter,
|
||||
)
|
||||
}
|
||||
return core
|
||||
}))
|
||||
}
|
||||
|
||||
// filteringCore wraps a zapcore.Core to filter out log entries based on a
|
||||
// custom logic.
|
||||
//
|
||||
// Note: This core must be positioned before the sampler in the core chain
|
||||
// to ensure Write is called. See newZapLogger for ordering details.
|
||||
type filteringCore struct {
|
||||
zapcore.Core
|
||||
}
|
||||
|
||||
// filter determines whether a log entry should be written based on its fields.
|
||||
// Returns false if the entry should be suppressed, true otherwise.
|
||||
//
|
||||
// Current filters:
|
||||
// - context.Canceled: These are expected errors from cancelled operations,
|
||||
// and create noise in logs.
|
||||
func (c *filteringCore) filter(fields []zapcore.Field) bool {
|
||||
for _, field := range fields {
|
||||
if field.Type == zapcore.ErrorType {
|
||||
if loggedErr, ok := field.Interface.(error); ok {
|
||||
// Suppress logs containing context.Canceled errors
|
||||
if errors.Is(loggedErr, context.Canceled) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// With implements zapcore.Core.With
|
||||
// It returns a new copy with the added context.
|
||||
func (c *filteringCore) With(fields []zapcore.Field) zapcore.Core {
|
||||
return &filteringCore{c.Core.With(fields)}
|
||||
}
|
||||
|
||||
// Check implements zapcore.Core.Check.
|
||||
// It adds this core to the CheckedEntry if the log level is enabled,
|
||||
// ensuring that Write will be called for this entry.
|
||||
func (c *filteringCore) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
|
||||
if c.Enabled(ent.Level) {
|
||||
return ce.AddCore(ent, c)
|
||||
}
|
||||
return ce
|
||||
}
|
||||
|
||||
// Write implements zapcore.Core.Write.
|
||||
// It filters log entries based on their fields before delegating to the wrapped core.
|
||||
func (c *filteringCore) Write(ent zapcore.Entry, fields []zapcore.Field) error {
|
||||
if !c.filter(fields) {
|
||||
return nil
|
||||
}
|
||||
return c.Core.Write(ent, fields)
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
version: "3"
|
||||
x-common: &common
|
||||
networks:
|
||||
- signoz-net
|
||||
extra_hosts:
|
||||
- host.docker.internal:host-gateway
|
||||
logging:
|
||||
options:
|
||||
max-size: 50m
|
||||
max-file: "3"
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
services:
|
||||
hotrod:
|
||||
<<: *common
|
||||
image: jaegertracing/example-hotrod:1.61.0
|
||||
command: [ "all" ]
|
||||
environment:
|
||||
- OTEL_EXPORTER_OTLP_ENDPOINT=http://host.docker.internal:4318 #
|
||||
load-hotrod:
|
||||
<<: *common
|
||||
image: "signoz/locust:1.2.3"
|
||||
environment:
|
||||
ATTACKED_HOST: http://hotrod:8080
|
||||
LOCUST_MODE: standalone
|
||||
NO_PROXY: standalone
|
||||
TASK_DELAY_FROM: 5
|
||||
TASK_DELAY_TO: 30
|
||||
QUIET_MODE: "${QUIET_MODE:-false}"
|
||||
LOCUST_OPTS: "--headless -u 10 -r 1"
|
||||
volumes:
|
||||
- ../../../common/locust-scripts:/locust
|
||||
|
||||
networks:
|
||||
signoz-net:
|
||||
name: signoz-net
|
||||
external: true
|
||||
@@ -1,69 +0,0 @@
|
||||
version: "3"
|
||||
x-common: &common
|
||||
networks:
|
||||
- signoz-net
|
||||
extra_hosts:
|
||||
- host.docker.internal:host-gateway
|
||||
logging:
|
||||
options:
|
||||
max-size: 50m
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: global
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
services:
|
||||
otel-agent:
|
||||
<<: *common
|
||||
image: otel/opentelemetry-collector-contrib:0.111.0
|
||||
command:
|
||||
- --config=/etc/otel-collector-config.yaml
|
||||
volumes:
|
||||
- ./otel-agent-config.yaml:/etc/otel-collector-config.yaml
|
||||
- /:/hostfs:ro
|
||||
environment:
|
||||
- SIGNOZ_COLLECTOR_ENDPOINT=http://host.docker.internal:4317 # In case of external SigNoz or cloud, update the endpoint and access token
|
||||
- OTEL_RESOURCE_ATTRIBUTES=host.name={{.Node.Hostname}},os.type={{.Node.Platform.OS}}
|
||||
# - SIGNOZ_ACCESS_TOKEN="<your-access-token>"
|
||||
# Before exposing the ports, make sure the ports are not used by other services
|
||||
# ports:
|
||||
# - "4317:4317"
|
||||
# - "4318:4318"
|
||||
otel-metrics:
|
||||
<<: *common
|
||||
image: otel/opentelemetry-collector-contrib:0.111.0
|
||||
user: 0:0 # If you have security concerns, you can replace this with your `UID:GID` that has necessary permissions to docker.sock
|
||||
command:
|
||||
- --config=/etc/otel-collector-config.yaml
|
||||
volumes:
|
||||
- ./otel-metrics-config.yaml:/etc/otel-collector-config.yaml
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
environment:
|
||||
- SIGNOZ_COLLECTOR_ENDPOINT=http://host.docker.internal:4317 # In case of external SigNoz or cloud, update the endpoint and access token
|
||||
- OTEL_RESOURCE_ATTRIBUTES=host.name={{.Node.Hostname}},os.type={{.Node.Platform.OS}}
|
||||
# - SIGNOZ_ACCESS_TOKEN="<your-access-token>"
|
||||
# Before exposing the ports, make sure the ports are not used by other services
|
||||
# ports:
|
||||
# - "4317:4317"
|
||||
# - "4318:4318"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.role == manager
|
||||
logspout:
|
||||
<<: *common
|
||||
image: "gliderlabs/logspout:v3.2.14"
|
||||
command: syslog+tcp://otel-agent:2255
|
||||
user: root
|
||||
volumes:
|
||||
- /etc/hostname:/etc/host_hostname:ro
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
depends_on:
|
||||
- otel-agent
|
||||
|
||||
networks:
|
||||
signoz-net:
|
||||
name: signoz-net
|
||||
external: true
|
||||
@@ -1,102 +0,0 @@
|
||||
receivers:
|
||||
hostmetrics:
|
||||
collection_interval: 30s
|
||||
root_path: /hostfs
|
||||
scrapers:
|
||||
cpu: {}
|
||||
load: {}
|
||||
memory: {}
|
||||
disk: {}
|
||||
filesystem: {}
|
||||
network: {}
|
||||
otlp:
|
||||
protocols:
|
||||
grpc:
|
||||
endpoint: 0.0.0.0:4317
|
||||
http:
|
||||
endpoint: 0.0.0.0:4318
|
||||
prometheus:
|
||||
config:
|
||||
global:
|
||||
scrape_interval: 60s
|
||||
scrape_configs:
|
||||
- job_name: otel-agent
|
||||
static_configs:
|
||||
- targets:
|
||||
- localhost:8888
|
||||
labels:
|
||||
job_name: otel-agent
|
||||
tcplog/docker:
|
||||
listen_address: "0.0.0.0:2255"
|
||||
operators:
|
||||
- type: regex_parser
|
||||
regex: '^<([0-9]+)>[0-9]+ (?P<timestamp>[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]+)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?) (?P<container_id>\S+) (?P<container_name>\S+) [0-9]+ - -( (?P<body>.*))?'
|
||||
timestamp:
|
||||
parse_from: attributes.timestamp
|
||||
layout: '%Y-%m-%dT%H:%M:%S.%LZ'
|
||||
- type: move
|
||||
from: attributes["body"]
|
||||
to: body
|
||||
- type: remove
|
||||
field: attributes.timestamp
|
||||
# please remove names from below if you want to collect logs from them
|
||||
- type: filter
|
||||
id: signoz_logs_filter
|
||||
expr: 'attributes.container_name matches "^(signoz_(logspout|signoz|otel-collector|clickhouse|zookeeper))|(infra_(logspout|otel-agent|otel-metrics)).*"'
|
||||
processors:
|
||||
batch:
|
||||
send_batch_size: 10000
|
||||
send_batch_max_size: 11000
|
||||
timeout: 10s
|
||||
resourcedetection:
|
||||
# Using OTEL_RESOURCE_ATTRIBUTES envvar, env detector adds custom labels.
|
||||
detectors:
|
||||
# - ec2
|
||||
# - gcp
|
||||
# - azure
|
||||
- env
|
||||
- system
|
||||
timeout: 2s
|
||||
extensions:
|
||||
health_check:
|
||||
endpoint: 0.0.0.0:13133
|
||||
pprof:
|
||||
endpoint: 0.0.0.0:1777
|
||||
exporters:
|
||||
otlp:
|
||||
endpoint: ${env:SIGNOZ_COLLECTOR_ENDPOINT}
|
||||
tls:
|
||||
insecure: true
|
||||
headers:
|
||||
signoz-access-token: ${env:SIGNOZ_ACCESS_TOKEN}
|
||||
# debug: {}
|
||||
service:
|
||||
telemetry:
|
||||
logs:
|
||||
encoding: json
|
||||
metrics:
|
||||
address: 0.0.0.0:8888
|
||||
extensions:
|
||||
- health_check
|
||||
- pprof
|
||||
pipelines:
|
||||
traces:
|
||||
receivers: [otlp]
|
||||
processors: [resourcedetection, batch]
|
||||
exporters: [otlp]
|
||||
metrics:
|
||||
receivers: [otlp]
|
||||
processors: [resourcedetection, batch]
|
||||
exporters: [otlp]
|
||||
metrics/hostmetrics:
|
||||
receivers: [hostmetrics]
|
||||
processors: [resourcedetection, batch]
|
||||
exporters: [otlp]
|
||||
metrics/prometheus:
|
||||
receivers: [prometheus]
|
||||
processors: [resourcedetection, batch]
|
||||
exporters: [otlp]
|
||||
logs:
|
||||
receivers: [otlp, tcplog/docker]
|
||||
processors: [resourcedetection, batch]
|
||||
exporters: [otlp]
|
||||
@@ -1,103 +0,0 @@
|
||||
receivers:
|
||||
prometheus:
|
||||
config:
|
||||
global:
|
||||
scrape_interval: 60s
|
||||
scrape_configs:
|
||||
- job_name: otel-metrics
|
||||
static_configs:
|
||||
- targets:
|
||||
- localhost:8888
|
||||
labels:
|
||||
job_name: otel-metrics
|
||||
# For Docker daemon metrics to be scraped, it must be configured to expose
|
||||
# Prometheus metrics, as documented here: https://docs.docker.com/config/daemon/prometheus/
|
||||
# - job_name: docker-daemon
|
||||
# dockerswarm_sd_configs:
|
||||
# - host: unix:///var/run/docker.sock
|
||||
# role: nodes
|
||||
# relabel_configs:
|
||||
# - source_labels: [__meta_dockerswarm_node_address]
|
||||
# target_label: __address__
|
||||
# replacement: $1:9323
|
||||
- job_name: "dockerswarm"
|
||||
dockerswarm_sd_configs:
|
||||
- host: unix:///var/run/docker.sock
|
||||
role: tasks
|
||||
relabel_configs:
|
||||
- action: keep
|
||||
regex: running
|
||||
source_labels:
|
||||
- __meta_dockerswarm_task_desired_state
|
||||
- action: keep
|
||||
regex: true
|
||||
source_labels:
|
||||
- __meta_dockerswarm_service_label_signoz_io_scrape
|
||||
- regex: ([^:]+)(?::\d+)?
|
||||
replacement: $1
|
||||
source_labels:
|
||||
- __address__
|
||||
target_label: swarm_container_ip
|
||||
- separator: .
|
||||
source_labels:
|
||||
- __meta_dockerswarm_service_name
|
||||
- __meta_dockerswarm_task_slot
|
||||
- __meta_dockerswarm_task_id
|
||||
target_label: swarm_container_name
|
||||
- target_label: __address__
|
||||
source_labels:
|
||||
- swarm_container_ip
|
||||
- __meta_dockerswarm_service_label_signoz_io_port
|
||||
separator: ":"
|
||||
- source_labels:
|
||||
- __meta_dockerswarm_service_label_signoz_io_path
|
||||
target_label: __metrics_path__
|
||||
- source_labels:
|
||||
- __meta_dockerswarm_service_label_com_docker_stack_namespace
|
||||
target_label: namespace
|
||||
- source_labels:
|
||||
- __meta_dockerswarm_service_name
|
||||
target_label: service_name
|
||||
- source_labels:
|
||||
- __meta_dockerswarm_task_id
|
||||
target_label: service_instance_id
|
||||
- source_labels:
|
||||
- __meta_dockerswarm_node_hostname
|
||||
target_label: host_name
|
||||
processors:
|
||||
batch:
|
||||
send_batch_size: 10000
|
||||
send_batch_max_size: 11000
|
||||
timeout: 10s
|
||||
resourcedetection:
|
||||
detectors:
|
||||
- env
|
||||
- system
|
||||
timeout: 2s
|
||||
extensions:
|
||||
health_check:
|
||||
endpoint: 0.0.0.0:13133
|
||||
pprof:
|
||||
endpoint: 0.0.0.0:1777
|
||||
exporters:
|
||||
otlp:
|
||||
endpoint: ${env:SIGNOZ_COLLECTOR_ENDPOINT}
|
||||
tls:
|
||||
insecure: true
|
||||
headers:
|
||||
signoz-access-token: ${env:SIGNOZ_ACCESS_TOKEN}
|
||||
# debug: {}
|
||||
service:
|
||||
telemetry:
|
||||
logs:
|
||||
encoding: json
|
||||
metrics:
|
||||
address: 0.0.0.0:8888
|
||||
extensions:
|
||||
- health_check
|
||||
- pprof
|
||||
pipelines:
|
||||
metrics:
|
||||
receivers: [prometheus]
|
||||
processors: [resourcedetection, batch]
|
||||
exporters: [otlp]
|
||||
@@ -1,39 +0,0 @@
|
||||
version: "3"
|
||||
x-common: &common
|
||||
networks:
|
||||
- signoz-net
|
||||
extra_hosts:
|
||||
- host.docker.internal:host-gateway
|
||||
logging:
|
||||
options:
|
||||
max-size: 50m
|
||||
max-file: "3"
|
||||
restart: unless-stopped
|
||||
services:
|
||||
hotrod:
|
||||
<<: *common
|
||||
image: jaegertracing/example-hotrod:1.61.0
|
||||
container_name: hotrod
|
||||
command: [ "all" ]
|
||||
environment:
|
||||
- OTEL_EXPORTER_OTLP_ENDPOINT=http://host.docker.internal:4318 # In case of external SigNoz or cloud, update the endpoint and access token
|
||||
# - OTEL_OTLP_HEADERS=signoz-access-token=<your-access-token>
|
||||
load-hotrod:
|
||||
<<: *common
|
||||
image: "signoz/locust:1.2.3"
|
||||
container_name: load-hotrod
|
||||
environment:
|
||||
ATTACKED_HOST: http://hotrod:8080
|
||||
LOCUST_MODE: standalone
|
||||
NO_PROXY: standalone
|
||||
TASK_DELAY_FROM: 5
|
||||
TASK_DELAY_TO: 30
|
||||
QUIET_MODE: "${QUIET_MODE:-false}"
|
||||
LOCUST_OPTS: "--headless -u 10 -r 1"
|
||||
volumes:
|
||||
- ../../../common/locust-scripts:/locust
|
||||
|
||||
networks:
|
||||
signoz-net:
|
||||
name: signoz-net
|
||||
external: true
|
||||
@@ -1,43 +0,0 @@
|
||||
version: "3"
|
||||
x-common: &common
|
||||
networks:
|
||||
- signoz-net
|
||||
extra_hosts:
|
||||
- host.docker.internal:host-gateway
|
||||
logging:
|
||||
options:
|
||||
max-size: 50m
|
||||
max-file: "3"
|
||||
restart: unless-stopped
|
||||
services:
|
||||
otel-agent:
|
||||
<<: *common
|
||||
image: otel/opentelemetry-collector-contrib:0.111.0
|
||||
command:
|
||||
- --config=/etc/otel-collector-config.yaml
|
||||
volumes:
|
||||
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
|
||||
- /:/hostfs:ro
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
environment:
|
||||
- SIGNOZ_COLLECTOR_ENDPOINT=http://host.docker.internal:4317 # In case of external SigNoz or cloud, update the endpoint and access token
|
||||
- OTEL_RESOURCE_ATTRIBUTES=host.name=signoz-host,os.type=linux # Replace signoz-host with the actual hostname
|
||||
# - SIGNOZ_ACCESS_TOKEN="<your-access-token>"
|
||||
# Before exposing the ports, make sure the ports are not used by other services
|
||||
# ports:
|
||||
# - "4317:4317"
|
||||
# - "4318:4318"
|
||||
logspout:
|
||||
<<: *common
|
||||
image: "gliderlabs/logspout:v3.2.14"
|
||||
volumes:
|
||||
- /etc/hostname:/etc/host_hostname:ro
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
command: syslog+tcp://otel-agent:2255
|
||||
depends_on:
|
||||
- otel-agent
|
||||
|
||||
networks:
|
||||
signoz-net:
|
||||
name: signoz-net
|
||||
external: true
|
||||
@@ -1,139 +0,0 @@
|
||||
receivers:
|
||||
hostmetrics:
|
||||
collection_interval: 30s
|
||||
root_path: /hostfs
|
||||
scrapers:
|
||||
cpu: {}
|
||||
load: {}
|
||||
memory: {}
|
||||
disk: {}
|
||||
filesystem: {}
|
||||
network: {}
|
||||
otlp:
|
||||
protocols:
|
||||
grpc:
|
||||
endpoint: 0.0.0.0:4317
|
||||
http:
|
||||
endpoint: 0.0.0.0:4318
|
||||
prometheus:
|
||||
config:
|
||||
global:
|
||||
scrape_interval: 60s
|
||||
scrape_configs:
|
||||
- job_name: otel-collector
|
||||
static_configs:
|
||||
- targets:
|
||||
- localhost:8888
|
||||
labels:
|
||||
job_name: otel-collector
|
||||
# For Docker daemon metrics to be scraped, it must be configured to expose
|
||||
# Prometheus metrics, as documented here: https://docs.docker.com/config/daemon/prometheus/
|
||||
# - job_name: docker-daemon
|
||||
# static_configs:
|
||||
# - targets:
|
||||
# - host.docker.internal:9323
|
||||
# labels:
|
||||
# job_name: docker-daemon
|
||||
- job_name: docker-container
|
||||
docker_sd_configs:
|
||||
- host: unix:///var/run/docker.sock
|
||||
relabel_configs:
|
||||
- action: keep
|
||||
regex: true
|
||||
source_labels:
|
||||
- __meta_docker_container_label_signoz_io_scrape
|
||||
- regex: true
|
||||
source_labels:
|
||||
- __meta_docker_container_label_signoz_io_path
|
||||
target_label: __metrics_path__
|
||||
- regex: (.+)
|
||||
source_labels:
|
||||
- __meta_docker_container_label_signoz_io_path
|
||||
target_label: __metrics_path__
|
||||
- separator: ":"
|
||||
source_labels:
|
||||
- __meta_docker_network_ip
|
||||
- __meta_docker_container_label_signoz_io_port
|
||||
target_label: __address__
|
||||
- regex: '/(.*)'
|
||||
replacement: '$1'
|
||||
source_labels:
|
||||
- __meta_docker_container_name
|
||||
target_label: container_name
|
||||
- regex: __meta_docker_container_label_signoz_io_(.+)
|
||||
action: labelmap
|
||||
replacement: $1
|
||||
tcplog/docker:
|
||||
listen_address: "0.0.0.0:2255"
|
||||
operators:
|
||||
- type: regex_parser
|
||||
regex: '^<([0-9]+)>[0-9]+ (?P<timestamp>[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]+)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?) (?P<container_id>\S+) (?P<container_name>\S+) [0-9]+ - -( (?P<body>.*))?'
|
||||
timestamp:
|
||||
parse_from: attributes.timestamp
|
||||
layout: '%Y-%m-%dT%H:%M:%S.%LZ'
|
||||
- type: move
|
||||
from: attributes["body"]
|
||||
to: body
|
||||
- type: remove
|
||||
field: attributes.timestamp
|
||||
# please remove names from below if you want to collect logs from them
|
||||
- type: filter
|
||||
id: signoz_logs_filter
|
||||
expr: 'attributes.container_name matches "^signoz|(signoz-(|otel-collector|clickhouse|zookeeper))|(infra-(logspout|otel-agent)-.*)"'
|
||||
processors:
|
||||
batch:
|
||||
send_batch_size: 10000
|
||||
send_batch_max_size: 11000
|
||||
timeout: 10s
|
||||
resourcedetection:
|
||||
# Using OTEL_RESOURCE_ATTRIBUTES envvar, env detector adds custom labels.
|
||||
detectors:
|
||||
# - ec2
|
||||
# - gcp
|
||||
# - azure
|
||||
- env
|
||||
- system
|
||||
timeout: 2s
|
||||
extensions:
|
||||
health_check:
|
||||
endpoint: 0.0.0.0:13133
|
||||
pprof:
|
||||
endpoint: 0.0.0.0:1777
|
||||
exporters:
|
||||
otlp:
|
||||
endpoint: ${env:SIGNOZ_COLLECTOR_ENDPOINT}
|
||||
tls:
|
||||
insecure: true
|
||||
headers:
|
||||
signoz-access-token: ${env:SIGNOZ_ACCESS_TOKEN}
|
||||
# debug: {}
|
||||
service:
|
||||
telemetry:
|
||||
logs:
|
||||
encoding: json
|
||||
metrics:
|
||||
address: 0.0.0.0:8888
|
||||
extensions:
|
||||
- health_check
|
||||
- pprof
|
||||
pipelines:
|
||||
traces:
|
||||
receivers: [otlp]
|
||||
processors: [resourcedetection, batch]
|
||||
exporters: [otlp]
|
||||
metrics:
|
||||
receivers: [otlp]
|
||||
processors: [resourcedetection, batch]
|
||||
exporters: [otlp]
|
||||
metrics/hostmetrics:
|
||||
receivers: [hostmetrics]
|
||||
processors: [resourcedetection, batch]
|
||||
exporters: [otlp]
|
||||
metrics/prometheus:
|
||||
receivers: [prometheus]
|
||||
processors: [resourcedetection, batch]
|
||||
exporters: [otlp]
|
||||
logs:
|
||||
receivers: [otlp, tcplog/docker]
|
||||
processors: [resourcedetection, batch]
|
||||
exporters: [otlp]
|
||||
@@ -2,6 +2,7 @@ package anomaly
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
@@ -13,7 +14,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -67,7 +67,7 @@ func (p *BaseSeasonalProvider) getResults(ctx context.Context, orgID valuer.UUID
|
||||
instrumentationtypes.CodeNamespace: "anomaly",
|
||||
instrumentationtypes.CodeFunctionName: "getResults",
|
||||
})
|
||||
zap.L().Info("fetching results for current period", zap.Any("currentPeriodQuery", params.CurrentPeriodQuery))
|
||||
slog.InfoContext(ctx, "fetching results for current period", "current_period_query", params.CurrentPeriodQuery)
|
||||
currentPeriodResults, _, err := p.querierV2.QueryRange(ctx, orgID, params.CurrentPeriodQuery)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -78,7 +78,7 @@ func (p *BaseSeasonalProvider) getResults(ctx context.Context, orgID valuer.UUID
|
||||
return nil, err
|
||||
}
|
||||
|
||||
zap.L().Info("fetching results for past period", zap.Any("pastPeriodQuery", params.PastPeriodQuery))
|
||||
slog.InfoContext(ctx, "fetching results for past period", "past_period_query", params.PastPeriodQuery)
|
||||
pastPeriodResults, _, err := p.querierV2.QueryRange(ctx, orgID, params.PastPeriodQuery)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -89,7 +89,7 @@ func (p *BaseSeasonalProvider) getResults(ctx context.Context, orgID valuer.UUID
|
||||
return nil, err
|
||||
}
|
||||
|
||||
zap.L().Info("fetching results for current season", zap.Any("currentSeasonQuery", params.CurrentSeasonQuery))
|
||||
slog.InfoContext(ctx, "fetching results for current season", "current_season_query", params.CurrentSeasonQuery)
|
||||
currentSeasonResults, _, err := p.querierV2.QueryRange(ctx, orgID, params.CurrentSeasonQuery)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -100,7 +100,7 @@ func (p *BaseSeasonalProvider) getResults(ctx context.Context, orgID valuer.UUID
|
||||
return nil, err
|
||||
}
|
||||
|
||||
zap.L().Info("fetching results for past season", zap.Any("pastSeasonQuery", params.PastSeasonQuery))
|
||||
slog.InfoContext(ctx, "fetching results for past season", "past_season_query", params.PastSeasonQuery)
|
||||
pastSeasonResults, _, err := p.querierV2.QueryRange(ctx, orgID, params.PastSeasonQuery)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -111,7 +111,7 @@ func (p *BaseSeasonalProvider) getResults(ctx context.Context, orgID valuer.UUID
|
||||
return nil, err
|
||||
}
|
||||
|
||||
zap.L().Info("fetching results for past 2 season", zap.Any("past2SeasonQuery", params.Past2SeasonQuery))
|
||||
slog.InfoContext(ctx, "fetching results for past 2 season", "past_2_season_query", params.Past2SeasonQuery)
|
||||
past2SeasonResults, _, err := p.querierV2.QueryRange(ctx, orgID, params.Past2SeasonQuery)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -122,7 +122,7 @@ func (p *BaseSeasonalProvider) getResults(ctx context.Context, orgID valuer.UUID
|
||||
return nil, err
|
||||
}
|
||||
|
||||
zap.L().Info("fetching results for past 3 season", zap.Any("past3SeasonQuery", params.Past3SeasonQuery))
|
||||
slog.InfoContext(ctx, "fetching results for past 3 season", "past_3_season_query", params.Past3SeasonQuery)
|
||||
past3SeasonResults, _, err := p.querierV2.QueryRange(ctx, orgID, params.Past3SeasonQuery)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -235,17 +235,17 @@ func (p *BaseSeasonalProvider) getPredictedSeries(
|
||||
if predictedValue < 0 {
|
||||
// this should not happen (except when the data has extreme outliers)
|
||||
// we will use the moving avg of the previous period series in this case
|
||||
zap.L().Warn("predictedValue is less than 0", zap.Float64("predictedValue", predictedValue), zap.Any("labels", series.Labels))
|
||||
slog.Warn("predicted value is less than 0", "predicted_value", predictedValue, "labels", series.Labels)
|
||||
predictedValue = p.getMovingAvg(prevSeries, movingAvgWindowSize, idx)
|
||||
}
|
||||
|
||||
zap.L().Debug("predictedSeries",
|
||||
zap.Float64("movingAvg", movingAvg),
|
||||
zap.Float64("avg", avg),
|
||||
zap.Float64("mean", mean),
|
||||
zap.Any("labels", series.Labels),
|
||||
zap.Float64("predictedValue", predictedValue),
|
||||
zap.Float64("curr", curr.Value),
|
||||
slog.Debug("predicted series",
|
||||
"moving_avg", movingAvg,
|
||||
"avg", avg,
|
||||
"mean", mean,
|
||||
"labels", series.Labels,
|
||||
"predicted_value", predictedValue,
|
||||
"curr", curr.Value,
|
||||
)
|
||||
predictedSeries.Points = append(predictedSeries.Points, v3.Point{
|
||||
Timestamp: curr.Timestamp,
|
||||
@@ -418,7 +418,7 @@ func (p *BaseSeasonalProvider) getAnomalies(ctx context.Context, orgID valuer.UU
|
||||
|
||||
for _, series := range result.Series {
|
||||
stdDev := p.getStdDev(series)
|
||||
zap.L().Info("stdDev", zap.Float64("stdDev", stdDev), zap.Any("labels", series.Labels))
|
||||
slog.InfoContext(ctx, "computed standard deviation", "std_dev", stdDev, "labels", series.Labels)
|
||||
|
||||
pastPeriodSeries := p.getMatchingSeries(pastPeriodResult, series)
|
||||
currentSeasonSeries := p.getMatchingSeries(currentSeasonResult, series)
|
||||
@@ -431,7 +431,7 @@ func (p *BaseSeasonalProvider) getAnomalies(ctx context.Context, orgID valuer.UU
|
||||
pastSeasonSeriesAvg := p.getAvg(pastSeasonSeries)
|
||||
past2SeasonSeriesAvg := p.getAvg(past2SeasonSeries)
|
||||
past3SeasonSeriesAvg := p.getAvg(past3SeasonSeries)
|
||||
zap.L().Info("getAvg", zap.Float64("prevSeriesAvg", prevSeriesAvg), zap.Float64("currentSeasonSeriesAvg", currentSeasonSeriesAvg), zap.Float64("pastSeasonSeriesAvg", pastSeasonSeriesAvg), zap.Float64("past2SeasonSeriesAvg", past2SeasonSeriesAvg), zap.Float64("past3SeasonSeriesAvg", past3SeasonSeriesAvg), zap.Any("labels", series.Labels))
|
||||
slog.InfoContext(ctx, "computed averages", "prev_series_avg", prevSeriesAvg, "current_season_series_avg", currentSeasonSeriesAvg, "past_season_series_avg", pastSeasonSeriesAvg, "past_2_season_series_avg", past2SeasonSeriesAvg, "past_3_season_series_avg", past3SeasonSeriesAvg, "labels", series.Labels)
|
||||
|
||||
predictedSeries := p.getPredictedSeries(
|
||||
series,
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/gorilla/mux"
|
||||
"go.uber.org/zap"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
type CloudIntegrationConnectionParamsResponse struct {
|
||||
@@ -71,7 +71,7 @@ func (ah *APIHandler) CloudIntegrationsGenerateConnectionParams(w http.ResponseW
|
||||
// Return the API Key (PAT) even if the rest of the params can not be deduced.
|
||||
// Params not returned from here will be requested from the user via form inputs.
|
||||
// This enables gracefully degraded but working experience even for non-cloud deployments.
|
||||
zap.L().Info("ingestion params and signoz api url can not be deduced since no license was found")
|
||||
slog.InfoContext(r.Context(), "ingestion params and signoz api url can not be deduced since no license was found")
|
||||
ah.Respond(w, result)
|
||||
return
|
||||
}
|
||||
@@ -103,7 +103,7 @@ func (ah *APIHandler) CloudIntegrationsGenerateConnectionParams(w http.ResponseW
|
||||
result.IngestionKey = ingestionKey
|
||||
|
||||
} else {
|
||||
zap.L().Info("ingestion key can't be deduced since no gateway url has been configured")
|
||||
slog.InfoContext(r.Context(), "ingestion key can't be deduced since no gateway url has been configured")
|
||||
}
|
||||
|
||||
ah.Respond(w, result)
|
||||
@@ -138,9 +138,8 @@ func (ah *APIHandler) getOrCreateCloudIntegrationPAT(ctx context.Context, orgId
|
||||
}
|
||||
}
|
||||
|
||||
zap.L().Info(
|
||||
"no PAT found for cloud integration, creating a new one",
|
||||
zap.String("cloudProvider", cloudProvider),
|
||||
slog.InfoContext(ctx, "no PAT found for cloud integration, creating a new one",
|
||||
"cloud_provider", cloudProvider,
|
||||
)
|
||||
|
||||
newPAT, err := types.NewStorableAPIKey(
|
||||
@@ -287,9 +286,8 @@ func getOrCreateCloudProviderIngestionKey(
|
||||
}
|
||||
}
|
||||
|
||||
zap.L().Info(
|
||||
"no existing ingestion key found for cloud integration, creating a new one",
|
||||
zap.String("cloudProvider", cloudProvider),
|
||||
slog.InfoContext(ctx, "no existing ingestion key found for cloud integration, creating a new one",
|
||||
"cloud_provider", cloudProvider,
|
||||
)
|
||||
createKeyResult, apiErr := requestGateway[createIngestionKeyResponse](
|
||||
ctx, gatewayUrl, licenseKey, "/v1/workspaces/me/keys",
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/licensetypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"go.uber.org/zap"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -35,23 +35,23 @@ func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if constants.FetchFeatures == "true" {
|
||||
zap.L().Debug("fetching license")
|
||||
slog.DebugContext(ctx, "fetching license")
|
||||
license, err := ah.Signoz.Licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to fetch license", zap.Error(err))
|
||||
slog.ErrorContext(ctx, "failed to fetch license", "error", err)
|
||||
} else if license == nil {
|
||||
zap.L().Debug("no active license found")
|
||||
slog.DebugContext(ctx, "no active license found")
|
||||
} else {
|
||||
licenseKey := license.Key
|
||||
|
||||
zap.L().Debug("fetching zeus features")
|
||||
slog.DebugContext(ctx, "fetching zeus features")
|
||||
zeusFeatures, err := fetchZeusFeatures(constants.ZeusFeaturesURL, licenseKey)
|
||||
if err == nil {
|
||||
zap.L().Debug("fetched zeus features", zap.Any("features", zeusFeatures))
|
||||
slog.DebugContext(ctx, "fetched zeus features", "features", zeusFeatures)
|
||||
// merge featureSet and zeusFeatures in featureSet with higher priority to zeusFeatures
|
||||
featureSet = MergeFeatureSets(zeusFeatures, featureSet)
|
||||
} else {
|
||||
zap.L().Error("failed to fetch zeus features", zap.Error(err))
|
||||
slog.ErrorContext(ctx, "failed to fetch zeus features", "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"go.uber.org/zap"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
func (aH *APIHandler) queryRangeV4(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -35,7 +35,7 @@ func (aH *APIHandler) queryRangeV4(w http.ResponseWriter, r *http.Request) {
|
||||
queryRangeParams, apiErrorObj := baseapp.ParseQueryRangeParams(r)
|
||||
|
||||
if apiErrorObj != nil {
|
||||
zap.L().Error("error parsing metric query range params", zap.Error(apiErrorObj.Err))
|
||||
slog.ErrorContext(r.Context(), "error parsing metric query range params", "error", apiErrorObj.Err)
|
||||
RespondError(w, apiErrorObj, nil)
|
||||
return
|
||||
}
|
||||
@@ -44,7 +44,7 @@ func (aH *APIHandler) queryRangeV4(w http.ResponseWriter, r *http.Request) {
|
||||
// add temporality for each metric
|
||||
temporalityErr := aH.PopulateTemporality(r.Context(), orgID, queryRangeParams)
|
||||
if temporalityErr != nil {
|
||||
zap.L().Error("Error while adding temporality for metrics", zap.Error(temporalityErr))
|
||||
slog.ErrorContext(r.Context(), "error while adding temporality for metrics", "error", temporalityErr)
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: temporalityErr}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ import (
|
||||
baseint "github.com/SigNoz/signoz/pkg/query-service/interfaces"
|
||||
baserules "github.com/SigNoz/signoz/pkg/query-service/rules"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/utils"
|
||||
"go.uber.org/zap"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
// Server runs HTTP, Mux and a grpc server
|
||||
@@ -83,6 +83,7 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
|
||||
}
|
||||
|
||||
reader := clickhouseReader.NewReader(
|
||||
signoz.Instrumentation.Logger(),
|
||||
signoz.SQLStore,
|
||||
signoz.TelemetryStore,
|
||||
signoz.Prometheus,
|
||||
@@ -278,7 +279,7 @@ func (s *Server) initListeners() error {
|
||||
return err
|
||||
}
|
||||
|
||||
zap.L().Info(fmt.Sprintf("Query server started listening on %s...", s.httpHostPort))
|
||||
slog.Info(fmt.Sprintf("Query server started listening on %s...", s.httpHostPort))
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -298,31 +299,31 @@ func (s *Server) Start(ctx context.Context) error {
|
||||
}
|
||||
|
||||
go func() {
|
||||
zap.L().Info("Starting HTTP server", zap.Int("port", httpPort), zap.String("addr", s.httpHostPort))
|
||||
slog.Info("Starting HTTP server", "port", httpPort, "addr", s.httpHostPort)
|
||||
|
||||
switch err := s.httpServer.Serve(s.httpConn); err {
|
||||
case nil, http.ErrServerClosed, cmux.ErrListenerClosed:
|
||||
// normal exit, nothing to do
|
||||
default:
|
||||
zap.L().Error("Could not start HTTP server", zap.Error(err))
|
||||
slog.Error("Could not start HTTP server", "error", err)
|
||||
}
|
||||
s.unavailableChannel <- healthcheck.Unavailable
|
||||
}()
|
||||
|
||||
go func() {
|
||||
zap.L().Info("Starting pprof server", zap.String("addr", baseconst.DebugHttpPort))
|
||||
slog.Info("Starting pprof server", "addr", baseconst.DebugHttpPort)
|
||||
|
||||
err = http.ListenAndServe(baseconst.DebugHttpPort, nil)
|
||||
if err != nil {
|
||||
zap.L().Error("Could not start pprof server", zap.Error(err))
|
||||
slog.Error("Could not start pprof server", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
zap.L().Info("Starting OpAmp Websocket server", zap.String("addr", baseconst.OpAmpWsEndpoint))
|
||||
slog.Info("Starting OpAmp Websocket server", "addr", baseconst.OpAmpWsEndpoint)
|
||||
err := s.opampServer.Start(baseconst.OpAmpWsEndpoint)
|
||||
if err != nil {
|
||||
zap.L().Error("opamp ws server failed to start", zap.Error(err))
|
||||
slog.Error("opamp ws server failed to start", "error", err)
|
||||
s.unavailableChannel <- healthcheck.Unavailable
|
||||
}
|
||||
}()
|
||||
@@ -358,10 +359,9 @@ func makeRulesManager(ch baseint.Reader, cache cache.Cache, alertmanager alertma
|
||||
MetadataStore: metadataStore,
|
||||
Prometheus: prometheus,
|
||||
Context: context.Background(),
|
||||
Logger: zap.L(),
|
||||
Reader: ch,
|
||||
Querier: querier,
|
||||
SLogger: providerSettings.Logger,
|
||||
Logger: providerSettings.Logger,
|
||||
Cache: cache,
|
||||
EvalDelay: baseconst.GetEvalDelay(),
|
||||
PrepareTaskFunc: rules.PrepareTaskFunc,
|
||||
@@ -380,7 +380,7 @@ func makeRulesManager(ch baseint.Reader, cache cache.Cache, alertmanager alertma
|
||||
return nil, fmt.Errorf("rule manager error: %v", err)
|
||||
}
|
||||
|
||||
zap.L().Info("rules manager is ready")
|
||||
slog.Info("rules manager is ready")
|
||||
|
||||
return manager, nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package rules
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -116,7 +117,7 @@ func TestAnomalyRule_NoData_AlertOnAbsent(t *testing.T) {
|
||||
|
||||
telemetryStore := telemetrystoretest.New(telemetrystore.Config{}, nil)
|
||||
options := clickhouseReader.NewOptions("primaryNamespace")
|
||||
reader := clickhouseReader.NewReader(nil, telemetryStore, nil, "", time.Second, nil, nil, options)
|
||||
reader := clickhouseReader.NewReader(slog.Default(), nil, telemetryStore, nil, "", time.Second, nil, nil, options)
|
||||
|
||||
rule, err := NewAnomalyRule(
|
||||
"test-anomaly-rule",
|
||||
@@ -247,7 +248,7 @@ func TestAnomalyRule_NoData_AbsentFor(t *testing.T) {
|
||||
|
||||
telemetryStore := telemetrystoretest.New(telemetrystore.Config{}, nil)
|
||||
options := clickhouseReader.NewOptions("primaryNamespace")
|
||||
reader := clickhouseReader.NewReader(nil, telemetryStore, nil, "", time.Second, nil, nil, options)
|
||||
reader := clickhouseReader.NewReader(slog.Default(), nil, telemetryStore, nil, "", time.Second, nil, nil, options)
|
||||
|
||||
rule, err := NewAnomalyRule("test-anomaly-rule", valuer.GenerateUUID(), &postableRule, reader, nil, logger, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/types/ruletypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error) {
|
||||
@@ -34,7 +34,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
|
||||
opts.Rule,
|
||||
opts.Reader,
|
||||
opts.Querier,
|
||||
opts.SLogger,
|
||||
opts.Logger,
|
||||
baserules.WithEvalDelay(opts.ManagerOpts.EvalDelay),
|
||||
baserules.WithSQLStore(opts.SQLStore),
|
||||
baserules.WithQueryParser(opts.ManagerOpts.QueryParser),
|
||||
@@ -57,7 +57,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
|
||||
ruleId,
|
||||
opts.OrgID,
|
||||
opts.Rule,
|
||||
opts.SLogger,
|
||||
opts.Logger,
|
||||
opts.Reader,
|
||||
opts.ManagerOpts.Prometheus,
|
||||
baserules.WithSQLStore(opts.SQLStore),
|
||||
@@ -82,7 +82,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
|
||||
opts.Rule,
|
||||
opts.Reader,
|
||||
opts.Querier,
|
||||
opts.SLogger,
|
||||
opts.Logger,
|
||||
opts.Cache,
|
||||
baserules.WithEvalDelay(opts.ManagerOpts.EvalDelay),
|
||||
baserules.WithSQLStore(opts.SQLStore),
|
||||
@@ -142,7 +142,7 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, *basemodel.Ap
|
||||
parsedRule,
|
||||
opts.Reader,
|
||||
opts.Querier,
|
||||
opts.SLogger,
|
||||
opts.Logger,
|
||||
baserules.WithSendAlways(),
|
||||
baserules.WithSendUnmatched(),
|
||||
baserules.WithSQLStore(opts.SQLStore),
|
||||
@@ -151,7 +151,7 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, *basemodel.Ap
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
zap.L().Error("failed to prepare a new threshold rule for test", zap.String("name", alertname), zap.Error(err))
|
||||
slog.Error("failed to prepare a new threshold rule for test", "name", alertname, "error", err)
|
||||
return 0, basemodel.BadRequest(err)
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, *basemodel.Ap
|
||||
alertname,
|
||||
opts.OrgID,
|
||||
parsedRule,
|
||||
opts.SLogger,
|
||||
opts.Logger,
|
||||
opts.Reader,
|
||||
opts.ManagerOpts.Prometheus,
|
||||
baserules.WithSendAlways(),
|
||||
@@ -173,7 +173,7 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, *basemodel.Ap
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
zap.L().Error("failed to prepare a new promql rule for test", zap.String("name", alertname), zap.Error(err))
|
||||
slog.Error("failed to prepare a new promql rule for test", "name", alertname, "error", err)
|
||||
return 0, basemodel.BadRequest(err)
|
||||
}
|
||||
} else if parsedRule.RuleType == ruletypes.RuleTypeAnomaly {
|
||||
@@ -184,7 +184,7 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, *basemodel.Ap
|
||||
parsedRule,
|
||||
opts.Reader,
|
||||
opts.Querier,
|
||||
opts.SLogger,
|
||||
opts.Logger,
|
||||
opts.Cache,
|
||||
baserules.WithSendAlways(),
|
||||
baserules.WithSendUnmatched(),
|
||||
@@ -193,7 +193,7 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, *basemodel.Ap
|
||||
baserules.WithMetadataStore(opts.ManagerOpts.MetadataStore),
|
||||
)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to prepare a new anomaly rule for test", zap.String("name", alertname), zap.Error(err))
|
||||
slog.Error("failed to prepare a new anomaly rule for test", "name", alertname, "error", err)
|
||||
return 0, basemodel.BadRequest(err)
|
||||
}
|
||||
} else {
|
||||
@@ -205,7 +205,7 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, *basemodel.Ap
|
||||
|
||||
alertsFound, err := rule.Eval(ctx, ts)
|
||||
if err != nil {
|
||||
zap.L().Error("evaluating rule failed", zap.String("rule", rule.Name()), zap.Error(err))
|
||||
slog.Error("evaluating rule failed", "rule", rule.Name(), "error", err)
|
||||
return 0, basemodel.InternalError(fmt.Errorf("rule evaluation failed"))
|
||||
}
|
||||
rule.SendAlerts(ctx, ts, 0, time.Minute, opts.NotifyFunc)
|
||||
|
||||
@@ -8,12 +8,12 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"log/slog"
|
||||
|
||||
"github.com/ClickHouse/clickhouse-go/v2"
|
||||
"github.com/go-co-op/gocron"
|
||||
"github.com/google/uuid"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/SigNoz/signoz/ee/query-service/model"
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
@@ -76,19 +76,19 @@ func (lm *Manager) Start(ctx context.Context) error {
|
||||
func (lm *Manager) UploadUsage(ctx context.Context) {
|
||||
organizations, err := lm.orgGetter.ListByOwnedKeyRange(ctx)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to get organizations", zap.Error(err))
|
||||
slog.ErrorContext(ctx, "failed to get organizations", "error", err)
|
||||
return
|
||||
}
|
||||
for _, organization := range organizations {
|
||||
// check if license is present or not
|
||||
license, err := lm.licenseService.GetActive(ctx, organization.ID)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to get active license", zap.Error(err))
|
||||
slog.ErrorContext(ctx, "failed to get active license", "error", err)
|
||||
return
|
||||
}
|
||||
if license == nil {
|
||||
// we will not start the usage reporting if license is not present.
|
||||
zap.L().Info("no license present, skipping usage reporting")
|
||||
slog.InfoContext(ctx, "no license present, skipping usage reporting")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ func (lm *Manager) UploadUsage(ctx context.Context) {
|
||||
dbusages := []model.UsageDB{}
|
||||
err := lm.clickhouseConn.Select(ctx, &dbusages, fmt.Sprintf(query, db, db), time.Now().Add(-(24 * time.Hour)))
|
||||
if err != nil && !strings.Contains(err.Error(), "doesn't exist") {
|
||||
zap.L().Error("failed to get usage from clickhouse: %v", zap.Error(err))
|
||||
slog.ErrorContext(ctx, "failed to get usage from clickhouse", "error", err)
|
||||
return
|
||||
}
|
||||
for _, u := range dbusages {
|
||||
@@ -125,24 +125,24 @@ func (lm *Manager) UploadUsage(ctx context.Context) {
|
||||
}
|
||||
|
||||
if len(usages) <= 0 {
|
||||
zap.L().Info("no snapshots to upload, skipping.")
|
||||
slog.InfoContext(ctx, "no snapshots to upload, skipping")
|
||||
return
|
||||
}
|
||||
|
||||
zap.L().Info("uploading usage data")
|
||||
slog.InfoContext(ctx, "uploading usage data")
|
||||
|
||||
usagesPayload := []model.Usage{}
|
||||
for _, usage := range usages {
|
||||
usageDataBytes, err := encryption.Decrypt([]byte(usage.ExporterID[:32]), []byte(usage.Data))
|
||||
if err != nil {
|
||||
zap.L().Error("error while decrypting usage data: %v", zap.Error(err))
|
||||
slog.ErrorContext(ctx, "error while decrypting usage data", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
usageData := model.Usage{}
|
||||
err = json.Unmarshal(usageDataBytes, &usageData)
|
||||
if err != nil {
|
||||
zap.L().Error("error while unmarshalling usage data: %v", zap.Error(err))
|
||||
slog.ErrorContext(ctx, "error while unmarshalling usage data", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -163,13 +163,13 @@ func (lm *Manager) UploadUsage(ctx context.Context) {
|
||||
|
||||
body, errv2 := json.Marshal(payload)
|
||||
if errv2 != nil {
|
||||
zap.L().Error("error while marshalling usage payload: %v", zap.Error(errv2))
|
||||
slog.ErrorContext(ctx, "error while marshalling usage payload", "error", errv2)
|
||||
return
|
||||
}
|
||||
|
||||
errv2 = lm.zeus.PutMeters(ctx, payload.LicenseKey.String(), body)
|
||||
if errv2 != nil {
|
||||
zap.L().Error("failed to upload usage: %v", zap.Error(errv2))
|
||||
slog.ErrorContext(ctx, "failed to upload usage", "error", errv2)
|
||||
// not returning error here since it is captured in the failed count
|
||||
return
|
||||
}
|
||||
@@ -179,7 +179,7 @@ func (lm *Manager) UploadUsage(ctx context.Context) {
|
||||
func (lm *Manager) Stop(ctx context.Context) {
|
||||
lm.scheduler.Stop()
|
||||
|
||||
zap.L().Info("sending usage data before shutting down")
|
||||
slog.InfoContext(ctx, "sending usage data before shutting down")
|
||||
// send usage before shutting down
|
||||
lm.UploadUsage(ctx)
|
||||
atomic.StoreUint32(&locker, stateUnlocked)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/sqlschema"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
@@ -32,9 +33,9 @@ func New(ctx context.Context, providerSettings factory.ProviderSettings, config
|
||||
fmter: fmter,
|
||||
settings: settings,
|
||||
operator: sqlschema.NewOperator(fmter, sqlschema.OperatorSupport{
|
||||
DropConstraint: true,
|
||||
ColumnIfNotExistsExists: true,
|
||||
AlterColumnSetNotNull: true,
|
||||
SCreateAndDropConstraint: true,
|
||||
SAlterTableAddAndDropColumnIfNotExistsAndExists: true,
|
||||
SAlterTableAlterColumnSetAndDrop: true,
|
||||
}),
|
||||
}, nil
|
||||
}
|
||||
@@ -72,8 +73,9 @@ WHERE
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if len(columns) == 0 {
|
||||
return nil, nil, sql.ErrNoRows
|
||||
return nil, nil, provider.sqlstore.WrapNotFoundErrf(sql.ErrNoRows, errors.CodeNotFound, "table (%s) not found", tableName)
|
||||
}
|
||||
|
||||
sqlschemaColumns := make([]*sqlschema.Column, 0)
|
||||
@@ -220,7 +222,9 @@ SELECT
|
||||
ci.relname AS index_name,
|
||||
i.indisunique AS unique,
|
||||
i.indisprimary AS primary,
|
||||
a.attname AS column_name
|
||||
a.attname AS column_name,
|
||||
array_position(i.indkey, a.attnum) AS column_position,
|
||||
pg_get_expr(i.indpred, i.indrelid) AS predicate
|
||||
FROM
|
||||
pg_index i
|
||||
LEFT JOIN pg_class ct ON ct.oid = i.indrelid
|
||||
@@ -231,9 +235,10 @@ WHERE
|
||||
a.attnum = ANY(i.indkey)
|
||||
AND con.oid IS NULL
|
||||
AND ct.relkind = 'r'
|
||||
AND ct.relname = ?`, string(name))
|
||||
AND ct.relname = ?
|
||||
ORDER BY index_name, column_position`, string(name))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, provider.sqlstore.WrapNotFoundErrf(err, errors.CodeNotFound, "no indices for table (%s) found", name)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
@@ -242,7 +247,12 @@ WHERE
|
||||
}
|
||||
}()
|
||||
|
||||
uniqueIndicesMap := make(map[string]*sqlschema.UniqueIndex)
|
||||
type indexEntry struct {
|
||||
columns []sqlschema.ColumnName
|
||||
predicate *string
|
||||
}
|
||||
|
||||
uniqueIndicesMap := make(map[string]*indexEntry)
|
||||
for rows.Next() {
|
||||
var (
|
||||
tableName string
|
||||
@@ -250,27 +260,53 @@ WHERE
|
||||
unique bool
|
||||
primary bool
|
||||
columnName string
|
||||
// starts from 0 and is unused in this function, this is to ensure that the column names are in the correct order
|
||||
columnPosition int
|
||||
predicate *string
|
||||
)
|
||||
|
||||
if err := rows.Scan(&tableName, &indexName, &unique, &primary, &columnName); err != nil {
|
||||
if err := rows.Scan(&tableName, &indexName, &unique, &primary, &columnName, &columnPosition, &predicate); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if unique {
|
||||
if _, ok := uniqueIndicesMap[indexName]; !ok {
|
||||
uniqueIndicesMap[indexName] = &sqlschema.UniqueIndex{
|
||||
TableName: name,
|
||||
ColumnNames: []sqlschema.ColumnName{sqlschema.ColumnName(columnName)},
|
||||
uniqueIndicesMap[indexName] = &indexEntry{
|
||||
columns: []sqlschema.ColumnName{sqlschema.ColumnName(columnName)},
|
||||
predicate: predicate,
|
||||
}
|
||||
} else {
|
||||
uniqueIndicesMap[indexName].ColumnNames = append(uniqueIndicesMap[indexName].ColumnNames, sqlschema.ColumnName(columnName))
|
||||
uniqueIndicesMap[indexName].columns = append(uniqueIndicesMap[indexName].columns, sqlschema.ColumnName(columnName))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
indices := make([]sqlschema.Index, 0)
|
||||
for _, index := range uniqueIndicesMap {
|
||||
indices = append(indices, index)
|
||||
for indexName, entry := range uniqueIndicesMap {
|
||||
if entry.predicate != nil {
|
||||
index := &sqlschema.PartialUniqueIndex{
|
||||
TableName: name,
|
||||
ColumnNames: entry.columns,
|
||||
Where: *entry.predicate,
|
||||
}
|
||||
|
||||
if index.Name() == indexName {
|
||||
indices = append(indices, index)
|
||||
} else {
|
||||
indices = append(indices, index.Named(indexName))
|
||||
}
|
||||
} else {
|
||||
index := &sqlschema.UniqueIndex{
|
||||
TableName: name,
|
||||
ColumnNames: entry.columns,
|
||||
}
|
||||
|
||||
if index.Name() == indexName {
|
||||
indices = append(indices, index)
|
||||
} else {
|
||||
indices = append(indices, index.Named(indexName))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return indices, nil
|
||||
|
||||
29
frontend/__mocks__/resizableMock.tsx
Normal file
29
frontend/__mocks__/resizableMock.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { PropsWithChildren } from 'react';
|
||||
|
||||
type CommonProps = PropsWithChildren<{
|
||||
className?: string;
|
||||
minSize?: number;
|
||||
maxSize?: number;
|
||||
defaultSize?: number;
|
||||
direction?: 'horizontal' | 'vertical';
|
||||
autoSaveId?: string;
|
||||
withHandle?: boolean;
|
||||
}>;
|
||||
|
||||
export function ResizablePanelGroup({
|
||||
children,
|
||||
className,
|
||||
}: CommonProps): JSX.Element {
|
||||
return <div className={className}>{children}</div>;
|
||||
}
|
||||
|
||||
export function ResizablePanel({
|
||||
children,
|
||||
className,
|
||||
}: CommonProps): JSX.Element {
|
||||
return <div className={className}>{children}</div>;
|
||||
}
|
||||
|
||||
export function ResizableHandle({ className }: CommonProps): JSX.Element {
|
||||
return <div className={className} />;
|
||||
}
|
||||
@@ -14,6 +14,7 @@ const config: Config.InitialOptions = {
|
||||
'\\.(css|less|scss)$': '<rootDir>/__mocks__/cssMock.ts',
|
||||
'\\.md$': '<rootDir>/__mocks__/cssMock.ts',
|
||||
'^uplot$': '<rootDir>/__mocks__/uplotMock.ts',
|
||||
'^@signozhq/resizable$': '<rootDir>/__mocks__/resizableMock.tsx',
|
||||
'^hooks/useSafeNavigate$': USE_SAFE_NAVIGATE_MOCK_PATH,
|
||||
'^src/hooks/useSafeNavigate$': USE_SAFE_NAVIGATE_MOCK_PATH,
|
||||
'^.*/useSafeNavigate$': USE_SAFE_NAVIGATE_MOCK_PATH,
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
"@signozhq/sonner": "0.1.0",
|
||||
"@signozhq/switch": "0.0.2",
|
||||
"@signozhq/table": "0.3.7",
|
||||
"@signozhq/toggle-group": "^0.0.1",
|
||||
"@signozhq/toggle-group": "0.0.1",
|
||||
"@signozhq/tooltip": "0.0.2",
|
||||
"@tanstack/react-table": "8.20.6",
|
||||
"@tanstack/react-virtual": "3.11.2",
|
||||
|
||||
2021
frontend/public/Images/allInOneLightMode.svg
Normal file
2021
frontend/public/Images/allInOneLightMode.svg
Normal file
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 214 KiB |
@@ -81,8 +81,7 @@ export const interceptorRejected = async (
|
||||
response.config.url !== '/sessions/email_password' &&
|
||||
!(
|
||||
response.config.url === '/sessions' && response.config.method === 'delete'
|
||||
) &&
|
||||
response.config.url !== '/authz/check'
|
||||
)
|
||||
) {
|
||||
try {
|
||||
const accessToken = getLocalStorageApi(LOCALSTORAGE.AUTH_TOKEN);
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
import axios, { AxiosHeaders, AxiosResponse } from 'axios';
|
||||
|
||||
import { interceptorRejected } from './index';
|
||||
|
||||
jest.mock('api/browser/localstorage/get', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(() => 'mock-token'),
|
||||
}));
|
||||
|
||||
jest.mock('api/v2/sessions/rotate/post', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(() =>
|
||||
Promise.resolve({
|
||||
data: { accessToken: 'new-token', refreshToken: 'new-refresh' },
|
||||
}),
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('AppRoutes/utils', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('axios', () => {
|
||||
const actualAxios = jest.requireActual('axios');
|
||||
const mockAxios = jest.fn().mockResolvedValue({ data: 'success' });
|
||||
|
||||
return {
|
||||
...actualAxios,
|
||||
default: Object.assign(mockAxios, {
|
||||
...actualAxios.default,
|
||||
isAxiosError: jest.fn().mockReturnValue(true),
|
||||
create: actualAxios.create,
|
||||
}),
|
||||
__esModule: true,
|
||||
};
|
||||
});
|
||||
|
||||
describe('interceptorRejected', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
((axios as unknown) as jest.Mock).mockResolvedValue({ data: 'success' });
|
||||
((axios.isAxiosError as unknown) as jest.Mock).mockReturnValue(true);
|
||||
});
|
||||
|
||||
it('should preserve array payload structure when retrying a 401 request', async () => {
|
||||
const arrayPayload = [
|
||||
{ relation: 'assignee', object: { resource: { name: 'role' } } },
|
||||
{ relation: 'assignee', object: { resource: { name: 'editor' } } },
|
||||
];
|
||||
|
||||
const error = ({
|
||||
response: {
|
||||
status: 401,
|
||||
config: {
|
||||
url: '/some-endpoint',
|
||||
method: 'POST',
|
||||
baseURL: 'http://localhost/',
|
||||
headers: new AxiosHeaders(),
|
||||
data: JSON.stringify(arrayPayload),
|
||||
},
|
||||
},
|
||||
config: {
|
||||
url: '/some-endpoint',
|
||||
method: 'POST',
|
||||
baseURL: 'http://localhost/',
|
||||
headers: new AxiosHeaders(),
|
||||
data: JSON.stringify(arrayPayload),
|
||||
},
|
||||
} as unknown) as AxiosResponse;
|
||||
|
||||
try {
|
||||
await interceptorRejected(error);
|
||||
} catch {
|
||||
// Expected to reject after retry
|
||||
}
|
||||
|
||||
const mockAxiosFn = (axios as unknown) as jest.Mock;
|
||||
expect(mockAxiosFn.mock.calls.length).toBe(1);
|
||||
const retryCallConfig = mockAxiosFn.mock.calls[0][0];
|
||||
expect(Array.isArray(JSON.parse(retryCallConfig.data))).toBe(true);
|
||||
expect(JSON.parse(retryCallConfig.data)).toEqual(arrayPayload);
|
||||
});
|
||||
|
||||
it('should preserve object payload structure when retrying a 401 request', async () => {
|
||||
const objectPayload = { key: 'value', nested: { data: 123 } };
|
||||
|
||||
const error = ({
|
||||
response: {
|
||||
status: 401,
|
||||
config: {
|
||||
url: '/some-endpoint',
|
||||
method: 'POST',
|
||||
baseURL: 'http://localhost/',
|
||||
headers: new AxiosHeaders(),
|
||||
data: JSON.stringify(objectPayload),
|
||||
},
|
||||
},
|
||||
config: {
|
||||
url: '/some-endpoint',
|
||||
method: 'POST',
|
||||
baseURL: 'http://localhost/',
|
||||
headers: new AxiosHeaders(),
|
||||
data: JSON.stringify(objectPayload),
|
||||
},
|
||||
} as unknown) as AxiosResponse;
|
||||
|
||||
try {
|
||||
await interceptorRejected(error);
|
||||
} catch {
|
||||
// Expected to reject after retry
|
||||
}
|
||||
|
||||
const mockAxiosFn = (axios as unknown) as jest.Mock;
|
||||
expect(mockAxiosFn.mock.calls.length).toBe(1);
|
||||
const retryCallConfig = mockAxiosFn.mock.calls[0][0];
|
||||
expect(JSON.parse(retryCallConfig.data)).toEqual(objectPayload);
|
||||
});
|
||||
|
||||
it('should handle undefined data gracefully when retrying', async () => {
|
||||
const error = ({
|
||||
response: {
|
||||
status: 401,
|
||||
config: {
|
||||
url: '/some-endpoint',
|
||||
method: 'GET',
|
||||
baseURL: 'http://localhost/',
|
||||
headers: new AxiosHeaders(),
|
||||
data: undefined,
|
||||
},
|
||||
},
|
||||
config: {
|
||||
url: '/some-endpoint',
|
||||
method: 'GET',
|
||||
baseURL: 'http://localhost/',
|
||||
headers: new AxiosHeaders(),
|
||||
data: undefined,
|
||||
},
|
||||
} as unknown) as AxiosResponse;
|
||||
|
||||
try {
|
||||
await interceptorRejected(error);
|
||||
} catch {
|
||||
// Expected to reject after retry
|
||||
}
|
||||
|
||||
const mockAxiosFn = (axios as unknown) as jest.Mock;
|
||||
expect(mockAxiosFn.mock.calls.length).toBe(1);
|
||||
const retryCallConfig = mockAxiosFn.mock.calls[0][0];
|
||||
expect(retryCallConfig.data).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -1,14 +1,8 @@
|
||||
function UnAuthorized({
|
||||
width = 137,
|
||||
height = 137,
|
||||
}: {
|
||||
height?: number;
|
||||
width?: number;
|
||||
}): JSX.Element {
|
||||
function UnAuthorized(): JSX.Element {
|
||||
return (
|
||||
<svg
|
||||
width={width}
|
||||
height={height}
|
||||
width="137"
|
||||
height="137"
|
||||
viewBox="0 0 137 137"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { createShortcutActions } from '../../constants/shortcutActions';
|
||||
import { useCmdK } from '../../providers/cmdKProvider';
|
||||
import { ROLES } from '../../types/roles';
|
||||
import { ShiftOverlay } from './ShiftOverlay';
|
||||
import { useShiftHoldOverlay } from './useShiftHoldOverlay';
|
||||
|
||||
type UserRole = 'ADMIN' | 'EDITOR' | 'AUTHOR' | 'VIEWER';
|
||||
export function ShiftHoldOverlayController({
|
||||
userRole,
|
||||
}: {
|
||||
userRole: ROLES;
|
||||
userRole: UserRole;
|
||||
}): JSX.Element | null {
|
||||
const { open: isCmdKOpen } = useCmdK();
|
||||
const noop = (): void => undefined;
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { useMemo } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { ROLES } from 'types/roles';
|
||||
|
||||
import { formatShortcut } from './formatShortcut';
|
||||
|
||||
import './shiftOverlay.scss';
|
||||
|
||||
export type UserRole = 'ADMIN' | 'EDITOR' | 'AUTHOR' | 'VIEWER';
|
||||
export type CmdAction = {
|
||||
id: string;
|
||||
name: string;
|
||||
shortcut?: string[];
|
||||
keywords?: string;
|
||||
section?: string;
|
||||
roles?: ROLES[];
|
||||
roles?: UserRole[];
|
||||
perform: () => void;
|
||||
};
|
||||
|
||||
@@ -33,7 +33,7 @@ function Shortcut({ label, keyHint }: ShortcutProps): JSX.Element {
|
||||
interface ShiftOverlayProps {
|
||||
visible: boolean;
|
||||
actions: CmdAction[];
|
||||
userRole: ROLES;
|
||||
userRole: UserRole;
|
||||
}
|
||||
|
||||
export function ShiftOverlay({
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { useThemeMode } from 'hooks/useDarkMode';
|
||||
import history from 'lib/history';
|
||||
import { ROLES as UserRole } from 'types/roles';
|
||||
|
||||
import { createShortcutActions } from '../../constants/shortcutActions';
|
||||
import { useCmdK } from '../../providers/cmdKProvider';
|
||||
@@ -29,6 +28,7 @@ type CmdAction = {
|
||||
perform: () => void;
|
||||
};
|
||||
|
||||
type UserRole = 'ADMIN' | 'EDITOR' | 'AUTHOR' | 'VIEWER';
|
||||
export function CmdKPalette({
|
||||
userRole,
|
||||
}: {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// ** Helpers
|
||||
import { MetrictypesTypeDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { defaultTraceSelectedColumns } from 'container/OptionsMenu/constants';
|
||||
import { createIdFromObjectFields } from 'lib/createIdFromObjectFields';
|
||||
import { createNewBuilderItemName } from 'lib/newQueryBuilder/createNewBuilderItemName';
|
||||
import { IAttributeValuesResponse } from 'types/api/queryBuilder/getAttributesValues';
|
||||
@@ -548,3 +549,49 @@ export const DATA_TYPE_VS_ATTRIBUTE_VALUES_KEY: Record<
|
||||
[DataTypes.ArrayBool]: 'boolAttributeValues',
|
||||
[DataTypes.EMPTY]: 'stringAttributeValues',
|
||||
};
|
||||
|
||||
export const listViewInitialLogQuery: Query = {
|
||||
...initialQueriesMap.logs,
|
||||
builder: {
|
||||
...initialQueriesMap.logs.builder,
|
||||
queryData: [
|
||||
{
|
||||
...initialQueriesMap.logs.builder.queryData[0],
|
||||
aggregateOperator: LogsAggregatorOperator.NOOP,
|
||||
orderBy: [{ columnName: 'timestamp', order: 'desc' }],
|
||||
offset: 0,
|
||||
pageSize: 100,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const PANEL_TYPES_INITIAL_QUERY: Record<PANEL_TYPES, Query> = {
|
||||
[PANEL_TYPES.TIME_SERIES]: initialQueriesMap.metrics,
|
||||
[PANEL_TYPES.VALUE]: initialQueriesMap.metrics,
|
||||
[PANEL_TYPES.TABLE]: initialQueriesMap.metrics,
|
||||
[PANEL_TYPES.LIST]: listViewInitialLogQuery,
|
||||
[PANEL_TYPES.TRACE]: initialQueriesMap.traces,
|
||||
[PANEL_TYPES.BAR]: initialQueriesMap.metrics,
|
||||
[PANEL_TYPES.PIE]: initialQueriesMap.metrics,
|
||||
[PANEL_TYPES.HISTOGRAM]: initialQueriesMap.metrics,
|
||||
[PANEL_TYPES.EMPTY_WIDGET]: initialQueriesMap.metrics,
|
||||
};
|
||||
|
||||
export const listViewInitialTraceQuery: Query = {
|
||||
// it should be the above commented query
|
||||
...initialQueriesMap.traces,
|
||||
builder: {
|
||||
...initialQueriesMap.traces.builder,
|
||||
queryData: [
|
||||
{
|
||||
...initialQueriesMap.traces.builder.queryData[0],
|
||||
aggregateOperator: LogsAggregatorOperator.NOOP,
|
||||
orderBy: [{ columnName: 'timestamp', order: 'desc' }],
|
||||
offset: 0,
|
||||
pageSize: 10,
|
||||
selectColumns: defaultTraceSelectedColumns,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -18,7 +18,8 @@ import {
|
||||
TowerControl,
|
||||
Workflow,
|
||||
} from 'lucide-react';
|
||||
import { ROLES } from 'types/roles';
|
||||
|
||||
export type UserRole = 'ADMIN' | 'EDITOR' | 'AUTHOR' | 'VIEWER';
|
||||
|
||||
export type CmdAction = {
|
||||
id: string;
|
||||
@@ -27,7 +28,7 @@ export type CmdAction = {
|
||||
keywords?: string;
|
||||
section?: string;
|
||||
icon?: React.ReactNode;
|
||||
roles?: ROLES[];
|
||||
roles?: UserRole[];
|
||||
perform: () => void;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { defaultTraceSelectedColumns } from 'container/OptionsMenu/constants';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { LogsAggregatorOperator } from 'types/common/queryBuilder';
|
||||
|
||||
export const PANEL_TYPES_INITIAL_QUERY = {
|
||||
[PANEL_TYPES.TIME_SERIES]: initialQueriesMap.metrics,
|
||||
[PANEL_TYPES.VALUE]: initialQueriesMap.metrics,
|
||||
[PANEL_TYPES.TABLE]: initialQueriesMap.metrics,
|
||||
[PANEL_TYPES.LIST]: initialQueriesMap.logs,
|
||||
[PANEL_TYPES.TRACE]: initialQueriesMap.traces,
|
||||
[PANEL_TYPES.BAR]: initialQueriesMap.metrics,
|
||||
[PANEL_TYPES.PIE]: initialQueriesMap.metrics,
|
||||
[PANEL_TYPES.HISTOGRAM]: initialQueriesMap.metrics,
|
||||
[PANEL_TYPES.EMPTY_WIDGET]: initialQueriesMap.metrics,
|
||||
};
|
||||
|
||||
export const listViewInitialLogQuery: Query = {
|
||||
...initialQueriesMap.logs,
|
||||
builder: {
|
||||
...initialQueriesMap.logs.builder,
|
||||
queryData: [
|
||||
{
|
||||
...initialQueriesMap.logs.builder.queryData[0],
|
||||
aggregateOperator: LogsAggregatorOperator.NOOP,
|
||||
orderBy: [{ columnName: 'timestamp', order: 'desc' }],
|
||||
offset: 0,
|
||||
pageSize: 100,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const listViewInitialTraceQuery: Query = {
|
||||
// it should be the above commented query
|
||||
...initialQueriesMap.traces,
|
||||
builder: {
|
||||
...initialQueriesMap.traces.builder,
|
||||
queryData: [
|
||||
{
|
||||
...initialQueriesMap.traces.builder.queryData[0],
|
||||
aggregateOperator: LogsAggregatorOperator.NOOP,
|
||||
orderBy: [{ columnName: 'timestamp', order: 'desc' }],
|
||||
offset: 0,
|
||||
pageSize: 10,
|
||||
selectColumns: defaultTraceSelectedColumns,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -1,94 +0,0 @@
|
||||
import { Card, Modal } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import createQueryParams from 'lib/createQueryParams';
|
||||
import history from 'lib/history';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import { LogsAggregatorOperator } from 'types/common/queryBuilder';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { PANEL_TYPES_INITIAL_QUERY } from './constants';
|
||||
import menuItems from './menuItems';
|
||||
import { Text } from './styles';
|
||||
|
||||
import './ComponentSlider.styles.scss';
|
||||
|
||||
function DashboardGraphSlider(): JSX.Element {
|
||||
const { handleToggleDashboardSlider, isDashboardSliderOpen } = useDashboard();
|
||||
|
||||
const onClickHandler = (name: PANEL_TYPES) => (): void => {
|
||||
const id = uuid();
|
||||
handleToggleDashboardSlider(false);
|
||||
logEvent('Dashboard Detail: New panel type selected', {
|
||||
// dashboardId: '',
|
||||
// dashboardName: '',
|
||||
// numberOfPanels: 0, // todo - at this point we don't know these attributes
|
||||
panelType: name,
|
||||
widgetId: id,
|
||||
});
|
||||
const queryParamsLog = {
|
||||
graphType: name,
|
||||
widgetId: id,
|
||||
[QueryParams.compositeQuery]: JSON.stringify({
|
||||
...PANEL_TYPES_INITIAL_QUERY[name],
|
||||
builder: {
|
||||
...PANEL_TYPES_INITIAL_QUERY[name].builder,
|
||||
queryData: [
|
||||
{
|
||||
...PANEL_TYPES_INITIAL_QUERY[name].builder.queryData[0],
|
||||
aggregateOperator: LogsAggregatorOperator.NOOP,
|
||||
orderBy: [{ columnName: 'timestamp', order: 'desc' }],
|
||||
offset: 0,
|
||||
pageSize: 100,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
const queryParams = {
|
||||
graphType: name,
|
||||
widgetId: id,
|
||||
[QueryParams.compositeQuery]: JSON.stringify(
|
||||
PANEL_TYPES_INITIAL_QUERY[name],
|
||||
),
|
||||
};
|
||||
if (name === PANEL_TYPES.LIST) {
|
||||
history.push(
|
||||
`${history.location.pathname}/new?${createQueryParams(queryParamsLog)}`,
|
||||
);
|
||||
} else {
|
||||
history.push(
|
||||
`${history.location.pathname}/new?${createQueryParams(queryParams)}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCardClick = (panelType: PANEL_TYPES): void => {
|
||||
onClickHandler(panelType)();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={isDashboardSliderOpen}
|
||||
onCancel={(): void => {
|
||||
handleToggleDashboardSlider(false);
|
||||
}}
|
||||
rootClassName="graph-selection"
|
||||
footer={null}
|
||||
title="New Panel"
|
||||
>
|
||||
<div className="panel-selection">
|
||||
{menuItems.map(({ name, icon, display }) => (
|
||||
<Card onClick={(): void => handleCardClick(name)} id={name} key={name}>
|
||||
{icon}
|
||||
<Text>{display}</Text>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default DashboardGraphSlider;
|
||||
@@ -1,41 +0,0 @@
|
||||
import { Card as CardComponent, Typography } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Container = styled.div`
|
||||
display: flex;
|
||||
justify-content: right;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
`;
|
||||
|
||||
export const Card = styled(CardComponent)`
|
||||
min-height: 80px;
|
||||
min-width: 120px;
|
||||
overflow-y: auto;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s;
|
||||
|
||||
.ant-card-body {
|
||||
padding: 12px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.ant-typography {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.05);
|
||||
border: 1px solid var(--bg-robin-400);
|
||||
}
|
||||
`;
|
||||
|
||||
export const Text = styled(Typography)`
|
||||
text-align: center;
|
||||
margin-top: 1rem;
|
||||
`;
|
||||
@@ -182,9 +182,7 @@ describe('Dashboard landing page actions header tests', () => {
|
||||
(useLocation as jest.Mock).mockReturnValue(mockLocation);
|
||||
|
||||
const mockContextValue: IDashboardContext = {
|
||||
isDashboardSliderOpen: false,
|
||||
isDashboardLocked: false,
|
||||
handleToggleDashboardSlider: jest.fn(),
|
||||
handleDashboardLockToggle: jest.fn(),
|
||||
dashboardResponse: {} as IDashboardContext['dashboardResponse'],
|
||||
selectedDashboard: (getDashboardById.data as unknown) as Dashboard,
|
||||
|
||||
@@ -40,6 +40,7 @@ import {
|
||||
} from 'lucide-react';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import { usePanelTypeSelectionModalStore } from 'providers/Dashboard/helpers/panelTypeSelectionModalHelper';
|
||||
import { sortLayout } from 'providers/Dashboard/util';
|
||||
import { DashboardData } from 'types/api/dashboard/getAll';
|
||||
import { Props } from 'types/api/dashboard/update';
|
||||
@@ -48,10 +49,10 @@ import { ComponentTypes } from 'utils/permission';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import DashboardHeader from '../components/DashboardHeader/DashboardHeader';
|
||||
import DashboardGraphSlider from '../ComponentsSlider';
|
||||
import DashboardSettings from '../DashboardSettings';
|
||||
import { Base64Icons } from '../DashboardSettings/General/utils';
|
||||
import DashboardVariableSelection from '../DashboardVariablesSelection';
|
||||
import PanelTypeSelectionModal from '../PanelTypeSelectionModal';
|
||||
import SettingsDrawer from './SettingsDrawer';
|
||||
import { VariablesSettingsTab } from './types';
|
||||
import {
|
||||
@@ -69,6 +70,9 @@ interface DashboardDescriptionProps {
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
||||
const { handle } = props;
|
||||
const setIsPanelTypeSelectionModalOpen = usePanelTypeSelectionModalStore(
|
||||
(s) => s.setIsPanelTypeSelectionModalOpen,
|
||||
);
|
||||
const {
|
||||
selectedDashboard,
|
||||
panelMap,
|
||||
@@ -77,7 +81,6 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
||||
setLayouts,
|
||||
isDashboardLocked,
|
||||
setSelectedDashboard,
|
||||
handleToggleDashboardSlider,
|
||||
handleDashboardLockToggle,
|
||||
} = useDashboard();
|
||||
|
||||
@@ -145,14 +148,14 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
||||
const [addPanelPermission] = useComponentPermission(permissions, userRole);
|
||||
|
||||
const onEmptyWidgetHandler = useCallback(() => {
|
||||
handleToggleDashboardSlider(true);
|
||||
setIsPanelTypeSelectionModalOpen(true);
|
||||
logEvent('Dashboard Detail: Add new panel clicked', {
|
||||
dashboardId: selectedDashboard?.id,
|
||||
dashboardName: selectedDashboard?.data.title,
|
||||
numberOfPanels: selectedDashboard?.data.widgets?.length,
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [handleToggleDashboardSlider]);
|
||||
}, [setIsPanelTypeSelectionModalOpen]);
|
||||
|
||||
const handleLockDashboardToggle = (): void => {
|
||||
setIsDashbordSettingsOpen(false);
|
||||
@@ -521,7 +524,7 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
|
||||
<DashboardVariableSelection />
|
||||
</section>
|
||||
)}
|
||||
<DashboardGraphSlider />
|
||||
<PanelTypeSelectionModal />
|
||||
|
||||
<Modal
|
||||
open={isRenameDashboardOpen}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.graph-selection {
|
||||
.panel-type-selection-modal {
|
||||
.ant-modal-content {
|
||||
width: 515px;
|
||||
max-height: 646px;
|
||||
@@ -76,6 +76,11 @@
|
||||
content: none;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-type-text {
|
||||
text-align: center;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +119,7 @@
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.graph-selection {
|
||||
.panel-type-selection-modal {
|
||||
.ant-modal-content {
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
background: var(--bg-vanilla-100);
|
||||
@@ -0,0 +1,68 @@
|
||||
import { memo } from 'react';
|
||||
import { Card, Modal, Typography } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_TYPES, PANEL_TYPES_INITIAL_QUERY } from 'constants/queryBuilder';
|
||||
import createQueryParams from 'lib/createQueryParams';
|
||||
import history from 'lib/history';
|
||||
import { usePanelTypeSelectionModalStore } from 'providers/Dashboard/helpers/panelTypeSelectionModalHelper';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { PanelTypesWithData } from './menuItems';
|
||||
|
||||
import './PanelTypeSelectionModal.styles.scss';
|
||||
|
||||
function PanelTypeSelectionModal(): JSX.Element {
|
||||
const {
|
||||
isPanelTypeSelectionModalOpen,
|
||||
setIsPanelTypeSelectionModalOpen,
|
||||
} = usePanelTypeSelectionModalStore();
|
||||
|
||||
const onClickHandler = (name: PANEL_TYPES) => (): void => {
|
||||
const id = uuid();
|
||||
setIsPanelTypeSelectionModalOpen(false);
|
||||
logEvent('Dashboard Detail: New panel type selected', {
|
||||
panelType: name,
|
||||
widgetId: id,
|
||||
});
|
||||
|
||||
const queryParams = {
|
||||
graphType: name,
|
||||
widgetId: id,
|
||||
[QueryParams.compositeQuery]: JSON.stringify(
|
||||
PANEL_TYPES_INITIAL_QUERY[name],
|
||||
),
|
||||
};
|
||||
|
||||
history.push(
|
||||
`${history.location.pathname}/new?${createQueryParams(queryParams)}`,
|
||||
);
|
||||
};
|
||||
|
||||
const handleCardClick = (panelType: PANEL_TYPES): void => {
|
||||
onClickHandler(panelType)();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={isPanelTypeSelectionModalOpen}
|
||||
onCancel={(): void => {
|
||||
setIsPanelTypeSelectionModalOpen(false);
|
||||
}}
|
||||
rootClassName="panel-type-selection-modal"
|
||||
footer={null}
|
||||
title="New Panel"
|
||||
>
|
||||
<div className="panel-selection">
|
||||
{PanelTypesWithData.map(({ name, icon, display }) => (
|
||||
<Card onClick={(): void => handleCardClick(name)} id={name} key={name}>
|
||||
{icon}
|
||||
<Typography className="panel-type-text">{display}</Typography>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(PanelTypeSelectionModal);
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
Table,
|
||||
} from 'lucide-react';
|
||||
|
||||
const Items: ItemsProps[] = [
|
||||
export const PanelTypesWithData: ItemsProps[] = [
|
||||
{
|
||||
name: PANEL_TYPES.TIME_SERIES,
|
||||
icon: <LineChart size={16} color={Color.BG_ROBIN_400} />,
|
||||
@@ -52,5 +52,3 @@ export interface ItemsProps {
|
||||
icon: JSX.Element;
|
||||
display: string;
|
||||
}
|
||||
|
||||
export default Items;
|
||||
@@ -224,7 +224,7 @@ describe('TimeSeriesPanel utils', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('uses DrawStyle.Line and VisibilityMode.Never when series has multiple valid points', () => {
|
||||
it('uses DrawStyle.Line and showPoints false when series has multiple valid points', () => {
|
||||
const apiResponse = createApiResponse([
|
||||
{
|
||||
metric: {},
|
||||
|
||||
@@ -10,9 +10,9 @@ import getLabelName from 'lib/getLabelName';
|
||||
import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin';
|
||||
import {
|
||||
DrawStyle,
|
||||
FillMode,
|
||||
LineInterpolation,
|
||||
LineStyle,
|
||||
VisibilityMode,
|
||||
} from 'lib/uPlotV2/config/types';
|
||||
import { UPlotConfigBuilder } from 'lib/uPlotV2/config/UPlotConfigBuilder';
|
||||
import { isInvalidPlotValue } from 'lib/uPlotV2/utils/dataUtils';
|
||||
@@ -124,12 +124,12 @@ export const prepareUPlotConfig = ({
|
||||
label: label,
|
||||
colorMapping: widget.customLegendColors ?? {},
|
||||
spanGaps: true,
|
||||
lineStyle: LineStyle.Solid,
|
||||
lineInterpolation: LineInterpolation.Spline,
|
||||
showPoints: hasSingleValidPoint
|
||||
? VisibilityMode.Always
|
||||
: VisibilityMode.Never,
|
||||
lineStyle: widget.lineStyle || LineStyle.Solid,
|
||||
lineInterpolation: widget.lineInterpolation || LineInterpolation.Spline,
|
||||
showPoints:
|
||||
widget.showPoints || hasSingleValidPoint ? true : !!widget.showPoints,
|
||||
pointSize: 5,
|
||||
fillMode: widget.fillMode || FillMode.None,
|
||||
isDarkMode,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,17 +9,18 @@ import DashboardSettings from 'container/DashboardContainer/DashboardSettings';
|
||||
import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import { usePanelTypeSelectionModalStore } from 'providers/Dashboard/helpers/panelTypeSelectionModalHelper';
|
||||
import { ROLES, USER_ROLES } from 'types/roles';
|
||||
import { ComponentTypes } from 'utils/permission';
|
||||
|
||||
import './DashboardEmptyState.styles.scss';
|
||||
|
||||
export default function DashboardEmptyState(): JSX.Element {
|
||||
const {
|
||||
selectedDashboard,
|
||||
isDashboardLocked,
|
||||
handleToggleDashboardSlider,
|
||||
} = useDashboard();
|
||||
const setIsPanelTypeSelectionModalOpen = usePanelTypeSelectionModalStore(
|
||||
(s) => s.setIsPanelTypeSelectionModalOpen,
|
||||
);
|
||||
|
||||
const { selectedDashboard, isDashboardLocked } = useDashboard();
|
||||
|
||||
const variablesSettingsTabHandle = useRef<VariablesSettingsTab>(null);
|
||||
const [isSettingsDrawerOpen, setIsSettingsDrawerOpen] = useState<boolean>(
|
||||
@@ -41,14 +42,14 @@ export default function DashboardEmptyState(): JSX.Element {
|
||||
const [addPanelPermission] = useComponentPermission(permissions, userRole);
|
||||
|
||||
const onEmptyWidgetHandler = useCallback(() => {
|
||||
handleToggleDashboardSlider(true);
|
||||
setIsPanelTypeSelectionModalOpen(true);
|
||||
logEvent('Dashboard Detail: Add new panel clicked', {
|
||||
dashboardId: selectedDashboard?.id,
|
||||
dashboardName: selectedDashboard?.data.title,
|
||||
numberOfPanels: selectedDashboard?.data.widgets?.length,
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [handleToggleDashboardSlider]);
|
||||
}, [setIsPanelTypeSelectionModalOpen]);
|
||||
|
||||
const onConfigureClick = useCallback((): void => {
|
||||
setIsSettingsDrawerOpen(true);
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useCallback } from 'react';
|
||||
import { Select, Typography } from 'antd';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import GraphTypes from 'container/DashboardContainer/ComponentsSlider/menuItems';
|
||||
import { PanelTypesWithData } from 'container/DashboardContainer/PanelTypeSelectionModal/menuItems';
|
||||
import { handleQueryChange } from 'container/NewWidget/utils';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
@@ -59,7 +59,7 @@ function PanelTypeSelector({
|
||||
data-testid="panel-change-select"
|
||||
disabled={disabled}
|
||||
>
|
||||
{GraphTypes.map((item) => (
|
||||
{PanelTypesWithData.map((item) => (
|
||||
<Option key={item.name} value={item.name}>
|
||||
<div className="view-panel-select-option">
|
||||
<div className="icon">{item.icon}</div>
|
||||
|
||||
@@ -5,6 +5,7 @@ import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import { EllipsisIcon, PenLine, Plus, X } from 'lucide-react';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import { usePanelTypeSelectionModalStore } from 'providers/Dashboard/helpers/panelTypeSelectionModalHelper';
|
||||
import { setSelectedRowWidgetId } from 'providers/Dashboard/helpers/selectedRowWidgetIdHelper';
|
||||
import { ROLES, USER_ROLES } from 'types/roles';
|
||||
import { ComponentTypes } from 'utils/permission';
|
||||
@@ -34,11 +35,11 @@ export function WidgetRowHeader(props: WidgetRowHeaderProps): JSX.Element {
|
||||
} = props;
|
||||
const [isRowSettingsOpen, setIsRowSettingsOpen] = useState<boolean>(false);
|
||||
|
||||
const {
|
||||
handleToggleDashboardSlider,
|
||||
selectedDashboard,
|
||||
isDashboardLocked,
|
||||
} = useDashboard();
|
||||
const setIsPanelTypeSelectionModalOpen = usePanelTypeSelectionModalStore(
|
||||
(s) => s.setIsPanelTypeSelectionModalOpen,
|
||||
);
|
||||
|
||||
const { selectedDashboard, isDashboardLocked } = useDashboard();
|
||||
|
||||
const permissions: ComponentTypes[] = ['add_panel'];
|
||||
const { user } = useAppContext();
|
||||
@@ -87,7 +88,7 @@ export function WidgetRowHeader(props: WidgetRowHeaderProps): JSX.Element {
|
||||
}
|
||||
|
||||
setSelectedRowWidgetId(selectedDashboard.id, id);
|
||||
handleToggleDashboardSlider(true);
|
||||
setIsPanelTypeSelectionModalOpen(true);
|
||||
}}
|
||||
>
|
||||
New Panel
|
||||
|
||||
@@ -15,6 +15,7 @@ import ROUTES from 'constants/routes';
|
||||
import { getMetricsListQuery } from 'container/MetricsExplorer/Summary/utils';
|
||||
import { useGetMetricsList } from 'hooks/metricsExplorer/useGetMetricsList';
|
||||
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import history from 'lib/history';
|
||||
import cloneDeep from 'lodash-es/cloneDeep';
|
||||
import { AnimatePresence } from 'motion/react';
|
||||
@@ -43,6 +44,7 @@ const homeInterval = 30 * 60 * 1000;
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
export default function Home(): JSX.Element {
|
||||
const { user } = useAppContext();
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const [startTime, setStartTime] = useState<number | null>(null);
|
||||
const [endTime, setEndTime] = useState<number | null>(null);
|
||||
@@ -680,7 +682,11 @@ export default function Home(): JSX.Element {
|
||||
|
||||
<div className="checklist-img-container">
|
||||
<img
|
||||
src="/Images/allInOne.svg"
|
||||
src={
|
||||
isDarkMode
|
||||
? '/Images/allInOne.svg'
|
||||
: '/Images/allInOneLightMode.svg'
|
||||
}
|
||||
alt="checklist-img"
|
||||
className="checklist-img"
|
||||
/>
|
||||
|
||||
@@ -65,6 +65,35 @@
|
||||
}
|
||||
}
|
||||
|
||||
.new-widget-container {
|
||||
.resizable-panel-left-container {
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.resizable-panel-right-container {
|
||||
overflow-y: auto !important;
|
||||
min-width: 350px;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0.3rem;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: rgb(136, 136, 136);
|
||||
border-radius: 0.625rem;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.widget-resizable-panel-group {
|
||||
.widget-resizable-handle {
|
||||
height: 100vh;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.edit-header {
|
||||
border-bottom: 1px solid var(--bg-vanilla-300);
|
||||
@@ -81,4 +110,11 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.widget-resizable-panel-group {
|
||||
.bg-border {
|
||||
background: var(--bg-vanilla-300);
|
||||
border-color: var(--bg-vanilla-300);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
.fill-mode-selector {
|
||||
.fill-mode-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.fill-mode-label {
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--bg-vanilla-400);
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.fill-mode-selector {
|
||||
.fill-mode-label {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import { ToggleGroup, ToggleGroupItem } from '@signozhq/toggle-group';
|
||||
import { Typography } from 'antd';
|
||||
import { FillMode } from 'lib/uPlotV2/config/types';
|
||||
|
||||
import './FillModeSelector.styles.scss';
|
||||
|
||||
interface FillModeSelectorProps {
|
||||
value: FillMode;
|
||||
onChange: (value: FillMode) => void;
|
||||
}
|
||||
|
||||
export function FillModeSelector({
|
||||
value,
|
||||
onChange,
|
||||
}: FillModeSelectorProps): JSX.Element {
|
||||
return (
|
||||
<section className="fill-mode-selector control-container">
|
||||
<Typography.Text className="section-heading">Fill mode</Typography.Text>
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
value={value}
|
||||
variant="outline"
|
||||
size="lg"
|
||||
onValueChange={(newValue): void => {
|
||||
if (newValue) {
|
||||
onChange(newValue as FillMode);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ToggleGroupItem value={FillMode.None} aria-label="None" title="None">
|
||||
<svg
|
||||
className="fill-mode-icon"
|
||||
viewBox="0 0 48 48"
|
||||
fill="none"
|
||||
stroke="#888"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<rect x="8" y="16" width="32" height="16" stroke="#888" fill="none" />
|
||||
</svg>
|
||||
<Typography.Text className="section-heading-small">None</Typography.Text>
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem value={FillMode.Solid} aria-label="Solid" title="Solid">
|
||||
<svg
|
||||
className="fill-mode-icon"
|
||||
viewBox="0 0 48 48"
|
||||
fill="none"
|
||||
stroke="#888"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<rect x="8" y="16" width="32" height="16" fill="#888" />
|
||||
</svg>
|
||||
<Typography.Text className="section-heading-small">Solid</Typography.Text>
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem
|
||||
value={FillMode.Gradient}
|
||||
aria-label="Gradient"
|
||||
title="Gradient"
|
||||
>
|
||||
<svg
|
||||
className="fill-mode-icon"
|
||||
viewBox="0 0 48 48"
|
||||
fill="none"
|
||||
stroke="#888"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<defs>
|
||||
<linearGradient id="fill-gradient" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0%" stopColor="#888" stopOpacity="0.2" />
|
||||
<stop offset="100%" stopColor="#888" stopOpacity="0.8" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect
|
||||
x="8"
|
||||
y="16"
|
||||
width="32"
|
||||
height="16"
|
||||
fill="url(#fill-gradient)"
|
||||
stroke="#888"
|
||||
/>
|
||||
</svg>
|
||||
<Typography.Text className="section-heading-small">
|
||||
Gradient
|
||||
</Typography.Text>
|
||||
</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
.line-interpolation-selector {
|
||||
.line-interpolation-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.line-interpolation-label {
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--bg-vanilla-400);
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.line-interpolation-selector {
|
||||
.line-interpolation-label {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import { ToggleGroup, ToggleGroupItem } from '@signozhq/toggle-group';
|
||||
import { Typography } from 'antd';
|
||||
import { LineInterpolation } from 'lib/uPlotV2/config/types';
|
||||
|
||||
import './LineInterpolationSelector.styles.scss';
|
||||
|
||||
interface LineInterpolationSelectorProps {
|
||||
value: LineInterpolation;
|
||||
onChange: (value: LineInterpolation) => void;
|
||||
}
|
||||
|
||||
export function LineInterpolationSelector({
|
||||
value,
|
||||
onChange,
|
||||
}: LineInterpolationSelectorProps): JSX.Element {
|
||||
return (
|
||||
<section className="line-interpolation-selector control-container">
|
||||
<Typography.Text className="section-heading">
|
||||
Line interpolation
|
||||
</Typography.Text>
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
value={value}
|
||||
variant="outline"
|
||||
size="lg"
|
||||
onValueChange={(newValue): void => {
|
||||
if (newValue) {
|
||||
onChange(newValue as LineInterpolation);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ToggleGroupItem
|
||||
value={LineInterpolation.Linear}
|
||||
aria-label="Linear"
|
||||
title="Linear"
|
||||
>
|
||||
<svg
|
||||
className="line-interpolation-icon"
|
||||
viewBox="0 0 48 48"
|
||||
fill="none"
|
||||
stroke="#888"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<circle cx="8" cy="32" r="3" fill="#888" />
|
||||
<circle cx="24" cy="16" r="3" fill="#888" />
|
||||
<circle cx="40" cy="32" r="3" fill="#888" />
|
||||
<path d="M8 32 L24 16 L40 32" stroke="#888" />
|
||||
</svg>
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem value={LineInterpolation.Spline} aria-label="Spline">
|
||||
<svg
|
||||
className="line-interpolation-icon"
|
||||
viewBox="0 0 48 48"
|
||||
fill="none"
|
||||
stroke="#888"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<circle cx="8" cy="32" r="3" fill="#888" />
|
||||
<circle cx="24" cy="16" r="3" fill="#888" />
|
||||
<circle cx="40" cy="32" r="3" fill="#888" />
|
||||
<path d="M8 32 C16 8, 32 8, 40 32" />
|
||||
</svg>
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem
|
||||
value={LineInterpolation.StepAfter}
|
||||
aria-label="Step After"
|
||||
>
|
||||
<svg
|
||||
className="line-interpolation-icon"
|
||||
viewBox="0 0 48 48"
|
||||
fill="none"
|
||||
stroke="#888"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<circle cx="8" cy="32" r="3" fill="#888" />
|
||||
<circle cx="24" cy="16" r="3" fill="#888" />
|
||||
<circle cx="40" cy="32" r="3" fill="#888" />
|
||||
<path d="M8 32 V16 H24 V32 H40" />
|
||||
</svg>
|
||||
</ToggleGroupItem>
|
||||
|
||||
<ToggleGroupItem
|
||||
value={LineInterpolation.StepBefore}
|
||||
aria-label="Step Before"
|
||||
>
|
||||
<svg
|
||||
className="line-interpolation-icon"
|
||||
viewBox="0 0 48 48"
|
||||
fill="none"
|
||||
stroke="#888"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<circle cx="8" cy="32" r="3" fill="#888" />
|
||||
<circle cx="24" cy="16" r="3" fill="#888" />
|
||||
<circle cx="40" cy="32" r="3" fill="#888" />
|
||||
<path d="M8 32 H24 V16 H40 V32" />
|
||||
</svg>
|
||||
</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
.line-style-selector {
|
||||
.line-style-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.line-style-label {
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--bg-vanilla-400);
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.line-style-selector {
|
||||
.line-style-label {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { ToggleGroup, ToggleGroupItem } from '@signozhq/toggle-group';
|
||||
import { Typography } from 'antd';
|
||||
import { LineStyle } from 'lib/uPlotV2/config/types';
|
||||
|
||||
import './LineStyleSelector.styles.scss';
|
||||
|
||||
interface LineStyleSelectorProps {
|
||||
value: LineStyle;
|
||||
onChange: (value: LineStyle) => void;
|
||||
}
|
||||
|
||||
export function LineStyleSelector({
|
||||
value,
|
||||
onChange,
|
||||
}: LineStyleSelectorProps): JSX.Element {
|
||||
return (
|
||||
<section className="line-style-selector control-container">
|
||||
<Typography.Text className="section-heading">Line style</Typography.Text>
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
value={value}
|
||||
variant="outline"
|
||||
size="lg"
|
||||
onValueChange={(newValue): void => {
|
||||
if (newValue) {
|
||||
onChange(newValue as LineStyle);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ToggleGroupItem value={LineStyle.Solid} aria-label="Solid" title="Solid">
|
||||
<svg
|
||||
className="line-style-icon"
|
||||
viewBox="0 0 48 48"
|
||||
fill="none"
|
||||
stroke="#888"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M8 24 L40 24" />
|
||||
</svg>
|
||||
<Typography.Text className="section-heading-small">Solid</Typography.Text>
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem
|
||||
value={LineStyle.Dashed}
|
||||
aria-label="Dashed"
|
||||
title="Dashed"
|
||||
>
|
||||
<svg
|
||||
className="line-style-icon"
|
||||
viewBox="0 0 48 48"
|
||||
fill="none"
|
||||
stroke="#888"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeDasharray="6 4"
|
||||
>
|
||||
<path d="M8 24 L40 24" />
|
||||
</svg>
|
||||
<Typography.Text className="section-heading-small">Dashed</Typography.Text>
|
||||
</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +1,30 @@
|
||||
.right-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: 'Space Mono';
|
||||
padding-bottom: 48px;
|
||||
|
||||
.section-heading {
|
||||
color: var(--bg-vanilla-400);
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 18px; /* 138.462% */
|
||||
letter-spacing: 0.52px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.section-heading-small {
|
||||
font-family: 'Space Mono';
|
||||
color: var(--bg-vanilla-400);
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
word-break: initial;
|
||||
line-height: 16px; /* 133.333% */
|
||||
letter-spacing: 0.48px;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
padding: 14px 14px 14px 12px;
|
||||
@@ -34,17 +56,6 @@
|
||||
.name-description {
|
||||
padding: 0 0 4px 0;
|
||||
|
||||
.typography {
|
||||
color: var(--bg-vanilla-400);
|
||||
font-family: 'Space Mono';
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 18px; /* 138.462% */
|
||||
letter-spacing: 0.52px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.name-input {
|
||||
display: flex;
|
||||
padding: 6px 6px 6px 8px;
|
||||
@@ -90,15 +101,21 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.typography {
|
||||
color: var(--bg-vanilla-400);
|
||||
font-family: 'Space Mono';
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 18px; /* 138.462% */
|
||||
letter-spacing: 0.52px;
|
||||
text-transform: uppercase;
|
||||
.toggle-card {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
background: var(--bg-ink-400);
|
||||
}
|
||||
|
||||
.toggle-card-text-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.panel-type-select {
|
||||
@@ -114,55 +131,16 @@
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
background: var(--bg-ink-300);
|
||||
}
|
||||
|
||||
.select-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
.icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.display {
|
||||
color: var(--bg-vanilla-100);
|
||||
font-family: Inter;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 16px; /* 133.333% */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fill-gaps {
|
||||
display: flex;
|
||||
padding: 12px;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
background: var(--bg-ink-400);
|
||||
|
||||
.fill-gaps-text {
|
||||
color: var(--bg-vanilla-400);
|
||||
font-family: 'Space Mono';
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 18px; /* 138.462% */
|
||||
letter-spacing: 0.52px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.fill-gaps-text-description {
|
||||
color: var(--bg-vanilla-400);
|
||||
font-family: Inter;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
opacity: 0.6;
|
||||
line-height: 16px; /* 133.333% */
|
||||
}
|
||||
.toggle-card-description {
|
||||
color: var(--bg-vanilla-400);
|
||||
font-family: Inter;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
opacity: 0.6;
|
||||
line-height: 16px; /* 133.333% */
|
||||
}
|
||||
|
||||
.log-scale,
|
||||
@@ -171,17 +149,6 @@
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.panel-time-text {
|
||||
color: var(--bg-vanilla-400);
|
||||
font-family: 'Space Mono';
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 18px; /* 138.462% */
|
||||
letter-spacing: 0.52px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.y-axis-unit-selector,
|
||||
.y-axis-unit-selector-v2 {
|
||||
display: flex;
|
||||
@@ -189,14 +156,7 @@
|
||||
gap: 8px;
|
||||
|
||||
.heading {
|
||||
color: var(--bg-vanilla-400);
|
||||
font-family: 'Space Mono';
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 18px; /* 138.462% */
|
||||
letter-spacing: 0.52px;
|
||||
text-transform: uppercase;
|
||||
@extend .section-heading;
|
||||
}
|
||||
|
||||
.input {
|
||||
@@ -249,7 +209,6 @@
|
||||
|
||||
.text {
|
||||
color: var(--bg-vanilla-400);
|
||||
font-family: 'Space Mono';
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
@@ -270,30 +229,9 @@
|
||||
.stack-chart {
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
.label {
|
||||
color: var(--bg-vanilla-400);
|
||||
font-family: 'Space Mono';
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 18px; /* 138.462% */
|
||||
letter-spacing: 0.52px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
|
||||
.bucket-config {
|
||||
.label {
|
||||
color: var(--bg-vanilla-400);
|
||||
font-family: 'Space Mono';
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 18px; /* 138.462% */
|
||||
letter-spacing: 0.52px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.bucket-size-label {
|
||||
margin-top: 8px;
|
||||
}
|
||||
@@ -322,7 +260,6 @@
|
||||
|
||||
.label {
|
||||
color: var(--bg-vanilla-400);
|
||||
font-family: 'Space Mono';
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
@@ -354,7 +291,6 @@
|
||||
|
||||
.alerts-text {
|
||||
color: var(--bg-vanilla-400);
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
@@ -400,6 +336,9 @@
|
||||
.lightMode {
|
||||
.right-container {
|
||||
background-color: var(--bg-vanilla-100);
|
||||
.section-heading {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
.header {
|
||||
.header-text {
|
||||
color: var(--bg-ink-400);
|
||||
@@ -427,10 +366,6 @@
|
||||
}
|
||||
|
||||
.panel-config {
|
||||
.typography {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
|
||||
.panel-type-select {
|
||||
.ant-select-selector {
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
@@ -455,14 +390,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
.fill-gaps {
|
||||
.toggle-card {
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
background: var(--bg-vanilla-300);
|
||||
|
||||
.fill-gaps-text {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
.fill-gaps-text-description {
|
||||
.toggle-card-description {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,11 @@ import { MemoryRouter } from 'react-router-dom';
|
||||
import { render as rtlRender, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import {
|
||||
FillMode,
|
||||
LineInterpolation,
|
||||
LineStyle,
|
||||
} from 'lib/uPlotV2/config/types';
|
||||
import { AppContext } from 'providers/App/App';
|
||||
import { IAppContext } from 'providers/App/types';
|
||||
import { ErrorModalProvider } from 'providers/ErrorModalProvider';
|
||||
@@ -165,6 +170,14 @@ describe('RightContainer - Alerts Section', () => {
|
||||
setContextLinks: jest.fn(),
|
||||
enableDrillDown: false,
|
||||
isNewDashboard: false,
|
||||
lineInterpolation: LineInterpolation.Spline,
|
||||
fillMode: FillMode.None,
|
||||
lineStyle: LineStyle.Solid,
|
||||
setLineInterpolation: jest.fn(),
|
||||
setFillMode: jest.fn(),
|
||||
setLineStyle: jest.fn(),
|
||||
showPoints: false,
|
||||
setShowPoints: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
transition: max-height 0.25s ease, opacity 0.25s ease, padding 0.25s ease;
|
||||
transition: max-height 0.1s ease, opacity 0.1s ease, padding 0.1s ease;
|
||||
|
||||
&.open {
|
||||
padding-bottom: 24px;
|
||||
|
||||
@@ -206,3 +206,59 @@ export const panelTypeVsDecimalPrecision: {
|
||||
[PANEL_TYPES.TRACE]: false,
|
||||
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
||||
} as const;
|
||||
|
||||
export const panelTypeVsLineInterpolation: {
|
||||
[key in PANEL_TYPES]: boolean;
|
||||
} = {
|
||||
[PANEL_TYPES.TIME_SERIES]: true,
|
||||
[PANEL_TYPES.VALUE]: false,
|
||||
[PANEL_TYPES.TABLE]: false,
|
||||
[PANEL_TYPES.LIST]: false,
|
||||
[PANEL_TYPES.PIE]: false,
|
||||
[PANEL_TYPES.BAR]: false,
|
||||
[PANEL_TYPES.HISTOGRAM]: false,
|
||||
[PANEL_TYPES.TRACE]: false,
|
||||
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
||||
} as const;
|
||||
|
||||
export const panelTypeVsLineStyle: {
|
||||
[key in PANEL_TYPES]: boolean;
|
||||
} = {
|
||||
[PANEL_TYPES.TIME_SERIES]: true,
|
||||
[PANEL_TYPES.VALUE]: false,
|
||||
[PANEL_TYPES.TABLE]: false,
|
||||
[PANEL_TYPES.LIST]: false,
|
||||
[PANEL_TYPES.PIE]: false,
|
||||
[PANEL_TYPES.BAR]: false,
|
||||
[PANEL_TYPES.HISTOGRAM]: false,
|
||||
[PANEL_TYPES.TRACE]: false,
|
||||
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
||||
} as const;
|
||||
|
||||
export const panelTypeVsFillMode: {
|
||||
[key in PANEL_TYPES]: boolean;
|
||||
} = {
|
||||
[PANEL_TYPES.TIME_SERIES]: true,
|
||||
[PANEL_TYPES.VALUE]: false,
|
||||
[PANEL_TYPES.TABLE]: false,
|
||||
[PANEL_TYPES.LIST]: false,
|
||||
[PANEL_TYPES.PIE]: false,
|
||||
[PANEL_TYPES.BAR]: false,
|
||||
[PANEL_TYPES.HISTOGRAM]: false,
|
||||
[PANEL_TYPES.TRACE]: false,
|
||||
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
||||
} as const;
|
||||
|
||||
export const panelTypeVsShowPoints: {
|
||||
[key in PANEL_TYPES]: boolean;
|
||||
} = {
|
||||
[PANEL_TYPES.TIME_SERIES]: true,
|
||||
[PANEL_TYPES.VALUE]: false,
|
||||
[PANEL_TYPES.TABLE]: false,
|
||||
[PANEL_TYPES.LIST]: false,
|
||||
[PANEL_TYPES.PIE]: false,
|
||||
[PANEL_TYPES.BAR]: false,
|
||||
[PANEL_TYPES.HISTOGRAM]: false,
|
||||
[PANEL_TYPES.TRACE]: false,
|
||||
[PANEL_TYPES.EMPTY_WIDGET]: false,
|
||||
} as const;
|
||||
|
||||
@@ -20,12 +20,18 @@ import {
|
||||
import { PrecisionOption, PrecisionOptionsEnum } from 'components/Graph/types';
|
||||
import TimePreference from 'components/TimePreferenceDropDown';
|
||||
import { PANEL_TYPES, PanelDisplay } from 'constants/queryBuilder';
|
||||
import GraphTypes, {
|
||||
import {
|
||||
ItemsProps,
|
||||
} from 'container/DashboardContainer/ComponentsSlider/menuItems';
|
||||
PanelTypesWithData,
|
||||
} from 'container/DashboardContainer/PanelTypeSelectionModal/menuItems';
|
||||
import { useDashboardVariables } from 'hooks/dashboard/useDashboardVariables';
|
||||
import useCreateAlerts from 'hooks/queryBuilder/useCreateAlerts';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import {
|
||||
FillMode,
|
||||
LineInterpolation,
|
||||
LineStyle,
|
||||
} from 'lib/uPlotV2/config/types';
|
||||
import {
|
||||
Antenna,
|
||||
Axis3D,
|
||||
@@ -34,6 +40,7 @@ import {
|
||||
LayoutDashboard,
|
||||
LineChart,
|
||||
Link,
|
||||
Paintbrush,
|
||||
Pencil,
|
||||
Plus,
|
||||
SlidersHorizontal,
|
||||
@@ -59,11 +66,15 @@ import {
|
||||
panelTypeVsContextLinks,
|
||||
panelTypeVsCreateAlert,
|
||||
panelTypeVsDecimalPrecision,
|
||||
panelTypeVsFillMode,
|
||||
panelTypeVsFillSpan,
|
||||
panelTypeVsLegendColors,
|
||||
panelTypeVsLegendPosition,
|
||||
panelTypeVsLineInterpolation,
|
||||
panelTypeVsLineStyle,
|
||||
panelTypeVsLogScale,
|
||||
panelTypeVsPanelTimePreferences,
|
||||
panelTypeVsShowPoints,
|
||||
panelTypeVsSoftMinMax,
|
||||
panelTypeVsStackingChartPreferences,
|
||||
panelTypeVsThreshold,
|
||||
@@ -71,7 +82,10 @@ import {
|
||||
} from './constants';
|
||||
import ContextLinks from './ContextLinks';
|
||||
import DashboardYAxisUnitSelectorWrapper from './DashboardYAxisUnitSelectorWrapper';
|
||||
import { FillModeSelector } from './FillModeSelector';
|
||||
import LegendColors from './LegendColors/LegendColors';
|
||||
import { LineInterpolationSelector } from './LineInterpolationSelector';
|
||||
import { LineStyleSelector } from './LineStyleSelector';
|
||||
import ThresholdSelector from './Threshold/ThresholdSelector';
|
||||
import { ThresholdProps } from './Threshold/types';
|
||||
import { timePreferance } from './timeItems';
|
||||
@@ -98,6 +112,14 @@ function RightContainer({
|
||||
setTitle,
|
||||
title,
|
||||
selectedGraph,
|
||||
lineInterpolation,
|
||||
setLineInterpolation,
|
||||
fillMode,
|
||||
setFillMode,
|
||||
lineStyle,
|
||||
setLineStyle,
|
||||
showPoints,
|
||||
setShowPoints,
|
||||
bucketCount,
|
||||
bucketWidth,
|
||||
stackedBarChart,
|
||||
@@ -150,7 +172,7 @@ function RightContainer({
|
||||
);
|
||||
|
||||
const selectedGraphType =
|
||||
GraphTypes.find((e) => e.name === selectedGraph)?.display || '';
|
||||
PanelTypesWithData.find((e) => e.name === selectedGraph)?.display || '';
|
||||
|
||||
const onCreateAlertsHandler = useCreateAlerts(selectedWidget, 'panelView');
|
||||
|
||||
@@ -174,9 +196,14 @@ function RightContainer({
|
||||
panelTypeVsContextLinks[selectedGraph] && enableDrillDown;
|
||||
const allowDecimalPrecision = panelTypeVsDecimalPrecision[selectedGraph];
|
||||
|
||||
const allowLineInterpolation = panelTypeVsLineInterpolation[selectedGraph];
|
||||
const allowLineStyle = panelTypeVsLineStyle[selectedGraph];
|
||||
const allowFillMode = panelTypeVsFillMode[selectedGraph];
|
||||
const allowShowPoints = panelTypeVsShowPoints[selectedGraph];
|
||||
|
||||
const { currentQuery } = useQueryBuilder();
|
||||
|
||||
const [graphTypes, setGraphTypes] = useState<ItemsProps[]>(GraphTypes);
|
||||
const [graphTypes, setGraphTypes] = useState<ItemsProps[]>(PanelTypesWithData);
|
||||
|
||||
const dashboardVariableOptions = useMemo<VariableOption[]>(() => {
|
||||
return Object.entries(dashboardVariables).map(([, value]) => ({
|
||||
@@ -200,6 +227,22 @@ function RightContainer({
|
||||
[allowLegendPosition, allowLegendColors],
|
||||
);
|
||||
|
||||
const isChartAppearanceSectionVisible = useMemo(
|
||||
() =>
|
||||
/**
|
||||
* Disabled for now as we are not done with other settings in chart appearance section
|
||||
* TODO: @ahrefabhi Enable this after we are done other settings in chart appearance section
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line sonarjs/no-redundant-boolean
|
||||
false &&
|
||||
(allowFillMode ||
|
||||
allowLineStyle ||
|
||||
allowLineInterpolation ||
|
||||
allowShowPoints),
|
||||
[allowFillMode, allowLineStyle, allowLineInterpolation, allowShowPoints],
|
||||
);
|
||||
|
||||
const updateCursorAndDropdown = (value: string, pos: number): void => {
|
||||
setCursorPos(pos);
|
||||
const lastDollar = value.lastIndexOf('$', pos - 1);
|
||||
@@ -272,7 +315,7 @@ function RightContainer({
|
||||
prev.filter((graph) => graph.name !== PANEL_TYPES.LIST),
|
||||
);
|
||||
} else {
|
||||
setGraphTypes(GraphTypes);
|
||||
setGraphTypes(PanelTypesWithData);
|
||||
}
|
||||
}, [currentQuery]);
|
||||
|
||||
@@ -299,7 +342,7 @@ function RightContainer({
|
||||
|
||||
<SettingsSection title="General" defaultOpen icon={<Pencil size={14} />}>
|
||||
<section className="name-description control-container">
|
||||
<Typography.Text className="typography">Name</Typography.Text>
|
||||
<Typography.Text className="section-heading">Name</Typography.Text>
|
||||
<AutoComplete
|
||||
options={dashboardVariableOptions}
|
||||
value={inputValue}
|
||||
@@ -319,7 +362,7 @@ function RightContainer({
|
||||
onBlur={(): void => setAutoCompleteOpen(false)}
|
||||
/>
|
||||
</AutoComplete>
|
||||
<Typography.Text className="typography">Description</Typography.Text>
|
||||
<Typography.Text className="section-heading">Description</Typography.Text>
|
||||
<TextArea
|
||||
placeholder="Enter the panel description here..."
|
||||
bordered
|
||||
@@ -340,7 +383,7 @@ function RightContainer({
|
||||
icon={<LayoutDashboard size={14} />}
|
||||
>
|
||||
<section className="panel-type control-container">
|
||||
<Typography.Text className="typography">Panel Type</Typography.Text>
|
||||
<Typography.Text className="section-heading">Panel Type</Typography.Text>
|
||||
<Select
|
||||
onChange={setGraphHandler}
|
||||
value={selectedGraph}
|
||||
@@ -361,7 +404,7 @@ function RightContainer({
|
||||
|
||||
{allowPanelTimePreference && (
|
||||
<section className="panel-time-preference control-container">
|
||||
<Typography.Text className="panel-time-text">
|
||||
<Typography.Text className="section-heading">
|
||||
Panel Time Preference
|
||||
</Typography.Text>
|
||||
<TimePreference
|
||||
@@ -375,7 +418,9 @@ function RightContainer({
|
||||
|
||||
{allowStackingBarChart && (
|
||||
<section className="stack-chart control-container">
|
||||
<Typography.Text className="label">Stack series</Typography.Text>
|
||||
<Typography.Text className="section-heading">
|
||||
Stack series
|
||||
</Typography.Text>
|
||||
<Switch
|
||||
checked={stackedBarChart}
|
||||
size="small"
|
||||
@@ -385,10 +430,10 @@ function RightContainer({
|
||||
)}
|
||||
|
||||
{allowFillSpans && (
|
||||
<section className="fill-gaps">
|
||||
<div className="fill-gaps-text-container">
|
||||
<Typography className="fill-gaps-text">Fill gaps</Typography>
|
||||
<Typography.Text className="fill-gaps-text-description">
|
||||
<section className="fill-gaps toggle-card">
|
||||
<div className="toggle-card-text-container">
|
||||
<Typography className="section-heading">Fill gaps</Typography>
|
||||
<Typography.Text className="toggle-card-description">
|
||||
Fill gaps in data with 0 for continuity
|
||||
</Typography.Text>
|
||||
</div>
|
||||
@@ -447,6 +492,36 @@ function RightContainer({
|
||||
</SettingsSection>
|
||||
)}
|
||||
|
||||
{isChartAppearanceSectionVisible && (
|
||||
<SettingsSection title="Chart Appearance" icon={<Paintbrush size={14} />}>
|
||||
{allowFillMode && (
|
||||
<FillModeSelector value={fillMode} onChange={setFillMode} />
|
||||
)}
|
||||
{allowLineStyle && (
|
||||
<LineStyleSelector value={lineStyle} onChange={setLineStyle} />
|
||||
)}
|
||||
{allowLineInterpolation && (
|
||||
<LineInterpolationSelector
|
||||
value={lineInterpolation}
|
||||
onChange={setLineInterpolation}
|
||||
/>
|
||||
)}
|
||||
{allowShowPoints && (
|
||||
<section className="show-points toggle-card">
|
||||
<div className="toggle-card-text-container">
|
||||
<Typography.Text className="section-heading">
|
||||
Show points
|
||||
</Typography.Text>
|
||||
<Typography.Text className="toggle-card-description">
|
||||
Display individual data points on the chart
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<Switch size="small" checked={showPoints} onChange={setShowPoints} />
|
||||
</section>
|
||||
)}
|
||||
</SettingsSection>
|
||||
)}
|
||||
|
||||
{isAxisSectionVisible && (
|
||||
<SettingsSection title="Axes" icon={<Axis3D size={14} />}>
|
||||
{allowSoftMinMax && (
|
||||
@@ -474,7 +549,9 @@ function RightContainer({
|
||||
|
||||
{allowLogScale && (
|
||||
<section className="log-scale control-container">
|
||||
<Typography.Text className="typography">Y Axis Scale</Typography.Text>
|
||||
<Typography.Text className="section-heading">
|
||||
Y Axis Scale
|
||||
</Typography.Text>
|
||||
<Select
|
||||
onChange={(value): void =>
|
||||
setIsLogScale(value === LogScale.LOGARITHMIC)
|
||||
@@ -510,7 +587,7 @@ function RightContainer({
|
||||
<SettingsSection title="Legend" icon={<Layers size={14} />}>
|
||||
{allowLegendPosition && (
|
||||
<section className="legend-position control-container">
|
||||
<Typography.Text className="typography">Position</Typography.Text>
|
||||
<Typography.Text className="section-heading">Position</Typography.Text>
|
||||
<Select
|
||||
onChange={(value: LegendPosition): void => setLegendPosition(value)}
|
||||
value={legendPosition}
|
||||
@@ -547,7 +624,9 @@ function RightContainer({
|
||||
{allowBucketConfig && (
|
||||
<SettingsSection title="Histogram / Buckets">
|
||||
<section className="bucket-config control-container">
|
||||
<Typography.Text className="label">Number of buckets</Typography.Text>
|
||||
<Typography.Text className="section-heading">
|
||||
Number of buckets
|
||||
</Typography.Text>
|
||||
<InputNumber
|
||||
value={bucketCount || null}
|
||||
type="number"
|
||||
@@ -558,7 +637,7 @@ function RightContainer({
|
||||
setBucketCount(val || 0);
|
||||
}}
|
||||
/>
|
||||
<Typography.Text className="label bucket-size-label">
|
||||
<Typography.Text className="section-heading bucket-size-label">
|
||||
Bucket width
|
||||
</Typography.Text>
|
||||
<InputNumber
|
||||
@@ -574,7 +653,7 @@ function RightContainer({
|
||||
}}
|
||||
/>
|
||||
<section className="combine-hist">
|
||||
<Typography.Text className="label">
|
||||
<Typography.Text className="section-heading">
|
||||
Merge all series into one
|
||||
</Typography.Text>
|
||||
<Switch
|
||||
@@ -682,6 +761,14 @@ export interface RightContainerProps {
|
||||
setContextLinks: Dispatch<SetStateAction<ContextLinksData>>;
|
||||
enableDrillDown?: boolean;
|
||||
isNewDashboard: boolean;
|
||||
lineInterpolation: LineInterpolation;
|
||||
setLineInterpolation: Dispatch<SetStateAction<LineInterpolation>>;
|
||||
fillMode: FillMode;
|
||||
setFillMode: Dispatch<SetStateAction<FillMode>>;
|
||||
lineStyle: LineStyle;
|
||||
setLineStyle: Dispatch<SetStateAction<LineStyle>>;
|
||||
showPoints: boolean;
|
||||
setShowPoints: Dispatch<SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
RightContainer.defaultProps = {
|
||||
|
||||
@@ -6,6 +6,11 @@ import { UseQueryResult } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { generatePath } from 'react-router-dom';
|
||||
import { WarningOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
} from '@signozhq/resizable';
|
||||
import { Button, Flex, Modal, Space, Typography } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { PrecisionOption, PrecisionOptionsEnum } from 'components/Graph/types';
|
||||
@@ -24,12 +29,16 @@ import { useDashboardVariables } from 'hooks/dashboard/useDashboardVariables';
|
||||
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
||||
import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import createQueryParams from 'lib/createQueryParams';
|
||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||
import { getDashboardVariables } from 'lib/dashboardVariables/getDashboardVariables';
|
||||
import {
|
||||
FillMode,
|
||||
LineInterpolation,
|
||||
LineStyle,
|
||||
} from 'lib/uPlotV2/config/types';
|
||||
import { cloneDeep, defaultTo, isEmpty, isUndefined } from 'lodash-es';
|
||||
import { Check, X } from 'lucide-react';
|
||||
import { useScrollToWidgetIdStore } from 'providers/Dashboard/helpers/scrollToWidgetIdHelper';
|
||||
@@ -63,12 +72,7 @@ import QueryTypeTag from './LeftContainer/QueryTypeTag';
|
||||
import RightContainer from './RightContainer';
|
||||
import { ThresholdProps } from './RightContainer/Threshold/types';
|
||||
import TimeItems, { timePreferance } from './RightContainer/timeItems';
|
||||
import {
|
||||
Container,
|
||||
LeftContainerWrapper,
|
||||
PanelContainer,
|
||||
RightContainerWrapper,
|
||||
} from './styles';
|
||||
import { Container, PanelContainer } from './styles';
|
||||
import { NewWidgetProps } from './types';
|
||||
import {
|
||||
getDefaultWidgetData,
|
||||
@@ -204,6 +208,18 @@ function NewWidget({
|
||||
const [legendPosition, setLegendPosition] = useState<LegendPosition>(
|
||||
selectedWidget?.legendPosition || LegendPosition.BOTTOM,
|
||||
);
|
||||
const [lineInterpolation, setLineInterpolation] = useState<LineInterpolation>(
|
||||
selectedWidget?.lineInterpolation || LineInterpolation.Spline,
|
||||
);
|
||||
const [fillMode, setFillMode] = useState<FillMode>(
|
||||
selectedWidget?.fillMode || FillMode.None,
|
||||
);
|
||||
const [lineStyle, setLineStyle] = useState<LineStyle>(
|
||||
selectedWidget?.lineStyle || LineStyle.Solid,
|
||||
);
|
||||
const [showPoints, setShowPoints] = useState<boolean>(
|
||||
selectedWidget?.showPoints ?? false,
|
||||
);
|
||||
const [customLegendColors, setCustomLegendColors] = useState<
|
||||
Record<string, string>
|
||||
>(selectedWidget?.customLegendColors || {});
|
||||
@@ -269,6 +285,10 @@ function NewWidget({
|
||||
softMin,
|
||||
softMax,
|
||||
fillSpans: isFillSpans,
|
||||
lineInterpolation,
|
||||
fillMode,
|
||||
lineStyle,
|
||||
showPoints,
|
||||
columnUnits,
|
||||
bucketCount,
|
||||
stackedBarChart,
|
||||
@@ -304,6 +324,10 @@ function NewWidget({
|
||||
stackedBarChart,
|
||||
isLogScale,
|
||||
legendPosition,
|
||||
lineInterpolation,
|
||||
fillMode,
|
||||
lineStyle,
|
||||
showPoints,
|
||||
customLegendColors,
|
||||
contextLinks,
|
||||
selectedWidget.columnWidths,
|
||||
@@ -757,7 +781,7 @@ function NewWidget({
|
||||
}, [query, safeNavigate, dashboardId, currentQuery]);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Container className="new-widget-container">
|
||||
<div className="edit-header">
|
||||
<div className="left-header">
|
||||
<X
|
||||
@@ -811,79 +835,102 @@ function NewWidget({
|
||||
</div>
|
||||
|
||||
<PanelContainer>
|
||||
<LeftContainerWrapper isDarkMode={useIsDarkMode()}>
|
||||
<OverlayScrollbar>
|
||||
{selectedWidget && (
|
||||
<LeftContainer
|
||||
selectedGraph={graphType}
|
||||
selectedLogFields={selectedLogFields}
|
||||
setSelectedLogFields={setSelectedLogFields}
|
||||
selectedTracesFields={selectedTracesFields}
|
||||
setSelectedTracesFields={setSelectedTracesFields}
|
||||
selectedWidget={selectedWidget}
|
||||
selectedTime={selectedTime}
|
||||
requestData={requestData}
|
||||
setRequestData={setRequestData}
|
||||
isLoadingPanelData={isLoadingPanelData}
|
||||
setQueryResponse={setQueryResponse}
|
||||
enableDrillDown={enableDrillDown}
|
||||
selectedDashboard={selectedDashboard}
|
||||
isNewPanel={isNewPanel}
|
||||
/>
|
||||
)}
|
||||
</OverlayScrollbar>
|
||||
</LeftContainerWrapper>
|
||||
|
||||
<RightContainerWrapper>
|
||||
<RightContainer
|
||||
setGraphHandler={setGraphHandler}
|
||||
title={title}
|
||||
setTitle={setTitle}
|
||||
description={description}
|
||||
setDescription={setDescription}
|
||||
stackedBarChart={stackedBarChart}
|
||||
setStackedBarChart={setStackedBarChart}
|
||||
opacity={opacity}
|
||||
yAxisUnit={yAxisUnit}
|
||||
columnUnits={columnUnits}
|
||||
setColumnUnits={setColumnUnits}
|
||||
bucketCount={bucketCount}
|
||||
bucketWidth={bucketWidth}
|
||||
combineHistogram={combineHistogram}
|
||||
setCombineHistogram={setCombineHistogram}
|
||||
setBucketWidth={setBucketWidth}
|
||||
setBucketCount={setBucketCount}
|
||||
setOpacity={setOpacity}
|
||||
selectedNullZeroValue={selectedNullZeroValue}
|
||||
setSelectedNullZeroValue={setSelectedNullZeroValue}
|
||||
selectedGraph={graphType}
|
||||
setSelectedTime={setSelectedTime}
|
||||
selectedTime={selectedTime}
|
||||
setYAxisUnit={setYAxisUnit}
|
||||
decimalPrecision={decimalPrecision}
|
||||
setDecimalPrecision={setDecimalPrecision}
|
||||
thresholds={thresholds}
|
||||
setThresholds={setThresholds}
|
||||
selectedWidget={selectedWidget}
|
||||
isFillSpans={isFillSpans}
|
||||
setIsFillSpans={setIsFillSpans}
|
||||
isLogScale={isLogScale}
|
||||
setIsLogScale={setIsLogScale}
|
||||
legendPosition={legendPosition}
|
||||
setLegendPosition={setLegendPosition}
|
||||
customLegendColors={customLegendColors}
|
||||
setCustomLegendColors={setCustomLegendColors}
|
||||
queryResponse={queryResponse}
|
||||
softMin={softMin}
|
||||
setSoftMin={setSoftMin}
|
||||
softMax={softMax}
|
||||
setSoftMax={setSoftMax}
|
||||
contextLinks={contextLinks}
|
||||
setContextLinks={setContextLinks}
|
||||
enableDrillDown={enableDrillDown}
|
||||
isNewDashboard={isNewDashboard}
|
||||
/>
|
||||
</RightContainerWrapper>
|
||||
<ResizablePanelGroup
|
||||
direction="horizontal"
|
||||
className="widget-resizable-panel-group"
|
||||
autoSaveId="panel-editor"
|
||||
>
|
||||
<ResizablePanel
|
||||
minSize={70}
|
||||
maxSize={80}
|
||||
defaultSize={80}
|
||||
className="resizable-panel-left-container"
|
||||
>
|
||||
<OverlayScrollbar>
|
||||
{selectedWidget && (
|
||||
<LeftContainer
|
||||
selectedDashboard={selectedDashboard}
|
||||
selectedGraph={graphType}
|
||||
selectedLogFields={selectedLogFields}
|
||||
setSelectedLogFields={setSelectedLogFields}
|
||||
selectedTracesFields={selectedTracesFields}
|
||||
setSelectedTracesFields={setSelectedTracesFields}
|
||||
selectedWidget={selectedWidget}
|
||||
selectedTime={selectedTime}
|
||||
requestData={requestData}
|
||||
setRequestData={setRequestData}
|
||||
isLoadingPanelData={isLoadingPanelData}
|
||||
setQueryResponse={setQueryResponse}
|
||||
enableDrillDown={enableDrillDown}
|
||||
/>
|
||||
)}
|
||||
</OverlayScrollbar>
|
||||
</ResizablePanel>
|
||||
<ResizableHandle withHandle className="widget-resizable-handle" />
|
||||
<ResizablePanel
|
||||
minSize={20}
|
||||
maxSize={30}
|
||||
defaultSize={20}
|
||||
className="resizable-panel-right-container"
|
||||
>
|
||||
<RightContainer
|
||||
setGraphHandler={setGraphHandler}
|
||||
title={title}
|
||||
setTitle={setTitle}
|
||||
description={description}
|
||||
setDescription={setDescription}
|
||||
stackedBarChart={stackedBarChart}
|
||||
setStackedBarChart={setStackedBarChart}
|
||||
lineInterpolation={lineInterpolation}
|
||||
setLineInterpolation={setLineInterpolation}
|
||||
fillMode={fillMode}
|
||||
setFillMode={setFillMode}
|
||||
lineStyle={lineStyle}
|
||||
setLineStyle={setLineStyle}
|
||||
showPoints={showPoints}
|
||||
setShowPoints={setShowPoints}
|
||||
opacity={opacity}
|
||||
yAxisUnit={yAxisUnit}
|
||||
columnUnits={columnUnits}
|
||||
setColumnUnits={setColumnUnits}
|
||||
bucketCount={bucketCount}
|
||||
bucketWidth={bucketWidth}
|
||||
combineHistogram={combineHistogram}
|
||||
setCombineHistogram={setCombineHistogram}
|
||||
setBucketWidth={setBucketWidth}
|
||||
setBucketCount={setBucketCount}
|
||||
setOpacity={setOpacity}
|
||||
selectedNullZeroValue={selectedNullZeroValue}
|
||||
setSelectedNullZeroValue={setSelectedNullZeroValue}
|
||||
selectedGraph={graphType}
|
||||
setSelectedTime={setSelectedTime}
|
||||
selectedTime={selectedTime}
|
||||
setYAxisUnit={setYAxisUnit}
|
||||
decimalPrecision={decimalPrecision}
|
||||
setDecimalPrecision={setDecimalPrecision}
|
||||
thresholds={thresholds}
|
||||
setThresholds={setThresholds}
|
||||
selectedWidget={selectedWidget}
|
||||
isFillSpans={isFillSpans}
|
||||
setIsFillSpans={setIsFillSpans}
|
||||
isLogScale={isLogScale}
|
||||
setIsLogScale={setIsLogScale}
|
||||
legendPosition={legendPosition}
|
||||
setLegendPosition={setLegendPosition}
|
||||
customLegendColors={customLegendColors}
|
||||
setCustomLegendColors={setCustomLegendColors}
|
||||
queryResponse={queryResponse}
|
||||
softMin={softMin}
|
||||
setSoftMin={setSoftMin}
|
||||
softMax={softMax}
|
||||
setSoftMax={setSoftMax}
|
||||
contextLinks={contextLinks}
|
||||
setContextLinks={setContextLinks}
|
||||
enableDrillDown={enableDrillDown}
|
||||
isNewDashboard={isNewDashboard}
|
||||
/>
|
||||
</ResizablePanel>
|
||||
</ResizablePanelGroup>
|
||||
</PanelContainer>
|
||||
<Modal
|
||||
title={
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Col, Tag as AntDTag } from 'antd';
|
||||
import { Tag as AntDTag } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Container = styled.div`
|
||||
@@ -8,42 +8,6 @@ export const Container = styled.div`
|
||||
overflow-y: hidden;
|
||||
`;
|
||||
|
||||
export const RightContainerWrapper = styled(Col)`
|
||||
&&& {
|
||||
max-width: 400px;
|
||||
width: 30%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
&::-webkit-scrollbar {
|
||||
width: 0.3rem;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: rgb(136, 136, 136);
|
||||
border-radius: 0.625rem;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
`;
|
||||
|
||||
interface LeftContainerWrapperProps {
|
||||
isDarkMode: boolean;
|
||||
}
|
||||
|
||||
export const LeftContainerWrapper = styled(Col)<LeftContainerWrapperProps>`
|
||||
&&& {
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
border-right: ${({ isDarkMode }): string =>
|
||||
isDarkMode
|
||||
? '1px solid var(--bg-slate-300)'
|
||||
: '1px solid var(--bg-vanilla-300)'};
|
||||
}
|
||||
&::-webkit-scrollbar {
|
||||
width: 0rem;
|
||||
}
|
||||
`;
|
||||
|
||||
export const ButtonContainer = styled.div`
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
||||
@@ -11,11 +11,8 @@ import { getYAxisCategories } from 'components/YAxisUnitSelector/utils';
|
||||
import {
|
||||
initialQueryBuilderFormValuesMap,
|
||||
PANEL_TYPES,
|
||||
} from 'constants/queryBuilder';
|
||||
import {
|
||||
listViewInitialLogQuery,
|
||||
PANEL_TYPES_INITIAL_QUERY,
|
||||
} from 'container/DashboardContainer/ComponentsSlider/constants';
|
||||
} from 'constants/queryBuilder';
|
||||
import {
|
||||
defaultLogsSelectedColumns,
|
||||
defaultTraceSelectedColumns,
|
||||
@@ -549,10 +546,7 @@ export const getDefaultWidgetData = (
|
||||
nullZeroValues: '',
|
||||
opacity: '',
|
||||
panelTypes: name,
|
||||
query:
|
||||
name === PANEL_TYPES.LIST
|
||||
? listViewInitialLogQuery
|
||||
: PANEL_TYPES_INITIAL_QUERY[name],
|
||||
query: PANEL_TYPES_INITIAL_QUERY[name],
|
||||
timePreferance: 'GLOBAL_TIME',
|
||||
softMax: null,
|
||||
softMin: null,
|
||||
|
||||
@@ -12,6 +12,8 @@ import {
|
||||
ATTRIBUTE_TYPES,
|
||||
initialAutocompleteData,
|
||||
initialQueryBuilderFormValuesMap,
|
||||
listViewInitialLogQuery,
|
||||
listViewInitialTraceQuery,
|
||||
mapOfFormulaToFilters,
|
||||
mapOfQueryFilters,
|
||||
PANEL_TYPES,
|
||||
@@ -23,10 +25,6 @@ import {
|
||||
metricsUnknownSpaceAggregateOperatorOptions,
|
||||
metricsUnknownTimeAggregateOperatorOptions,
|
||||
} from 'constants/queryBuilderOperators';
|
||||
import {
|
||||
listViewInitialLogQuery,
|
||||
listViewInitialTraceQuery,
|
||||
} from 'container/DashboardContainer/ComponentsSlider/constants';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { getMetricsOperatorsByAttributeType } from 'lib/newQueryBuilder/getMetricsOperatorsByAttributeType';
|
||||
import { getOperatorsBySourceAndPanelType } from 'lib/newQueryBuilder/getOperatorsBySourceAndPanelType';
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
export const SINGLE_FLIGHT_WAIT_TIME_MS = 50;
|
||||
export const AUTHZ_CACHE_TIME = 20_000;
|
||||
@@ -1,18 +0,0 @@
|
||||
import { buildPermission } from './utils';
|
||||
|
||||
export const IsAdminPermission = buildPermission(
|
||||
'assignee',
|
||||
'role:signoz-admin',
|
||||
);
|
||||
export const IsEditorPermission = buildPermission(
|
||||
'assignee',
|
||||
'role:signoz-editor',
|
||||
);
|
||||
export const IsViewerPermission = buildPermission(
|
||||
'assignee',
|
||||
'role:signoz-viewer',
|
||||
);
|
||||
export const IsAnonymousPermission = buildPermission(
|
||||
'assignee',
|
||||
'role:signoz-anonymous',
|
||||
);
|
||||
@@ -14,7 +14,7 @@ type ResourceTypeMap = {
|
||||
|
||||
type RelationName = keyof RelationsByType;
|
||||
|
||||
export type ResourcesForRelation<R extends RelationName> = Extract<
|
||||
type ResourcesForRelation<R extends RelationName> = Extract<
|
||||
Resource,
|
||||
{ type: RelationsByType[R][number] }
|
||||
>['name'];
|
||||
@@ -50,26 +50,8 @@ export type AuthZCheckResponse = Record<
|
||||
}
|
||||
>;
|
||||
|
||||
export type UseAuthZOptions = {
|
||||
/**
|
||||
* If false, the query/permissions will not be fetched.
|
||||
* Useful when you want to disable the query/permissions for a specific use case, like logout.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
enabled?: boolean;
|
||||
};
|
||||
|
||||
export type UseAuthZResult = {
|
||||
/**
|
||||
* If query is cached, and refetch happens in background, this is false.
|
||||
*/
|
||||
isLoading: boolean;
|
||||
/**
|
||||
* If query is fetching, even if happens in background, this is true.
|
||||
*/
|
||||
isFetching: boolean;
|
||||
error: Error | null;
|
||||
permissions: AuthZCheckResponse | null;
|
||||
refetchPermissions: () => void;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { useQueries } from 'react-query';
|
||||
import { authzCheck } from 'api/generated/services/authz';
|
||||
import type {
|
||||
@@ -6,13 +6,7 @@ import type {
|
||||
AuthtypesTransactionDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
|
||||
import { AUTHZ_CACHE_TIME, SINGLE_FLIGHT_WAIT_TIME_MS } from './constants';
|
||||
import {
|
||||
AuthZCheckResponse,
|
||||
BrandedPermission,
|
||||
UseAuthZOptions,
|
||||
UseAuthZResult,
|
||||
} from './types';
|
||||
import { AuthZCheckResponse, BrandedPermission, UseAuthZResult } from './types';
|
||||
import {
|
||||
gettableTransactionToPermission,
|
||||
permissionToTransactionDto,
|
||||
@@ -20,6 +14,8 @@ import {
|
||||
|
||||
let ctx: Promise<AuthZCheckResponse> | null;
|
||||
let pendingPermissions: BrandedPermission[] = [];
|
||||
const SINGLE_FLIGHT_WAIT_TIME_MS = 50;
|
||||
const AUTHZ_CACHE_TIME = 20_000;
|
||||
|
||||
function dispatchPermission(
|
||||
permission: BrandedPermission,
|
||||
@@ -74,12 +70,7 @@ async function fetchManyPermissions(
|
||||
}, {} as AuthZCheckResponse);
|
||||
}
|
||||
|
||||
export function useAuthZ(
|
||||
permissions: BrandedPermission[],
|
||||
options?: UseAuthZOptions,
|
||||
): UseAuthZResult {
|
||||
const { enabled } = options ?? { enabled: true };
|
||||
|
||||
export function useAuthZ(permissions: BrandedPermission[]): UseAuthZResult {
|
||||
const queryResults = useQueries(
|
||||
permissions.map((permission) => {
|
||||
return {
|
||||
@@ -89,7 +80,6 @@ export function useAuthZ(
|
||||
refetchIntervalInBackground: false,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: true,
|
||||
enabled,
|
||||
queryFn: async (): Promise<AuthZCheckResponse> => {
|
||||
const response = await dispatchPermission(permission);
|
||||
|
||||
@@ -106,10 +96,6 @@ export function useAuthZ(
|
||||
const isLoading = useMemo(() => queryResults.some((q) => q.isLoading), [
|
||||
queryResults,
|
||||
]);
|
||||
const isFetching = useMemo(() => queryResults.some((q) => q.isFetching), [
|
||||
queryResults,
|
||||
]);
|
||||
|
||||
const error = useMemo(
|
||||
() =>
|
||||
!isLoading
|
||||
@@ -135,17 +121,9 @@ export function useAuthZ(
|
||||
}, {} as AuthZCheckResponse);
|
||||
}, [isLoading, error, queryResults]);
|
||||
|
||||
const refetchPermissions = useCallback(() => {
|
||||
for (const query of queryResults) {
|
||||
query.refetch();
|
||||
}
|
||||
}, [queryResults]);
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
isFetching,
|
||||
error,
|
||||
permissions: data ?? null,
|
||||
refetchPermissions,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ import permissionsType from './permissions.type';
|
||||
import {
|
||||
AuthZObject,
|
||||
AuthZRelation,
|
||||
AuthZResource,
|
||||
BrandedPermission,
|
||||
ResourceName,
|
||||
ResourcesForRelation,
|
||||
ResourceType,
|
||||
} from './types';
|
||||
|
||||
@@ -19,10 +19,11 @@ export function buildPermission<R extends AuthZRelation>(
|
||||
return `${relation}${PermissionSeparator}${object}` as BrandedPermission;
|
||||
}
|
||||
|
||||
export function buildObjectString<
|
||||
R extends 'delete' | 'read' | 'update' | 'assignee'
|
||||
>(resource: ResourcesForRelation<R>, objectId: string): AuthZObject<R> {
|
||||
return `${resource}${ObjectSeparator}${objectId}` as AuthZObject<R>;
|
||||
export function buildObjectString(
|
||||
resource: AuthZResource,
|
||||
objectId: string,
|
||||
): `${AuthZResource}${typeof ObjectSeparator}${string}` {
|
||||
return `${resource}${ObjectSeparator}${objectId}` as const;
|
||||
}
|
||||
|
||||
export function parsePermission(
|
||||
|
||||
@@ -3,14 +3,15 @@ import { generateColor } from 'lib/uPlotLib/utils/generateColor';
|
||||
import { calculateWidthBasedOnStepInterval } from 'lib/uPlotV2/utils';
|
||||
import uPlot, { Series } from 'uplot';
|
||||
|
||||
import { generateGradientFill } from '../utils/generateGradientFill';
|
||||
import {
|
||||
BarAlignment,
|
||||
ConfigBuilder,
|
||||
DrawStyle,
|
||||
FillMode,
|
||||
LineInterpolation,
|
||||
LineStyle,
|
||||
SeriesProps,
|
||||
VisibilityMode,
|
||||
} from './types';
|
||||
|
||||
/**
|
||||
@@ -52,7 +53,7 @@ export class UPlotSeriesBuilder extends ConfigBuilder<SeriesProps, Series> {
|
||||
}: {
|
||||
resolvedLineColor: string;
|
||||
}): Partial<Series> {
|
||||
const { lineWidth, lineStyle, lineCap, fillColor } = this.props;
|
||||
const { lineWidth, lineStyle, lineCap, fillColor, fillMode } = this.props;
|
||||
const lineConfig: Partial<Series> = {
|
||||
stroke: resolvedLineColor,
|
||||
width: lineWidth ?? DEFAULT_LINE_WIDTH,
|
||||
@@ -66,12 +67,26 @@ export class UPlotSeriesBuilder extends ConfigBuilder<SeriesProps, Series> {
|
||||
lineConfig.cap = lineCap;
|
||||
}
|
||||
|
||||
if (fillColor) {
|
||||
lineConfig.fill = fillColor;
|
||||
} else if (this.props.drawStyle === DrawStyle.Bar) {
|
||||
lineConfig.fill = resolvedLineColor;
|
||||
/**
|
||||
* Configure area fill based on draw style and fill mode:
|
||||
* - bar charts always use a solid fill with the series color
|
||||
* - histogram uses the same color with a fixed alpha suffix for translucency
|
||||
* - for other series, an explicit fillMode controls whether we use a solid fill
|
||||
* or a vertical gradient from the series color to transparent
|
||||
*/
|
||||
const finalFillColor = fillColor ?? resolvedLineColor;
|
||||
|
||||
if (this.props.drawStyle === DrawStyle.Bar) {
|
||||
lineConfig.fill = finalFillColor;
|
||||
} else if (this.props.drawStyle === DrawStyle.Histogram) {
|
||||
lineConfig.fill = `${resolvedLineColor}40`;
|
||||
lineConfig.fill = `${finalFillColor}40`;
|
||||
} else if (fillMode && fillMode !== FillMode.None) {
|
||||
if (fillMode === FillMode.Solid) {
|
||||
lineConfig.fill = finalFillColor;
|
||||
} else if (fillMode === FillMode.Gradient) {
|
||||
lineConfig.fill = (self: uPlot): CanvasGradient =>
|
||||
generateGradientFill(self, finalFillColor, 'rgba(0, 0, 0, 0)');
|
||||
}
|
||||
}
|
||||
|
||||
return lineConfig;
|
||||
@@ -159,12 +174,8 @@ export class UPlotSeriesBuilder extends ConfigBuilder<SeriesProps, Series> {
|
||||
pointsConfig.show = pointsBuilder;
|
||||
} else if (drawStyle === DrawStyle.Points) {
|
||||
pointsConfig.show = true;
|
||||
} else if (showPoints === VisibilityMode.Never) {
|
||||
pointsConfig.show = false;
|
||||
} else if (showPoints === VisibilityMode.Always) {
|
||||
pointsConfig.show = true;
|
||||
} else {
|
||||
pointsConfig.show = false; // default to hidden
|
||||
pointsConfig.show = !!showPoints;
|
||||
}
|
||||
|
||||
return pointsConfig;
|
||||
|
||||
@@ -2,12 +2,7 @@ import { themeColors } from 'constants/theme';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
import type { SeriesProps } from '../types';
|
||||
import {
|
||||
DrawStyle,
|
||||
LineInterpolation,
|
||||
LineStyle,
|
||||
VisibilityMode,
|
||||
} from '../types';
|
||||
import { DrawStyle, LineInterpolation, LineStyle } from '../types';
|
||||
import { POINT_SIZE_FACTOR, UPlotSeriesBuilder } from '../UPlotSeriesBuilder';
|
||||
|
||||
const createBaseProps = (
|
||||
@@ -168,17 +163,17 @@ describe('UPlotSeriesBuilder', () => {
|
||||
expect(config.points?.show).toBe(pointsBuilder);
|
||||
});
|
||||
|
||||
it('respects VisibilityMode for point visibility when no custom pointsBuilder is given', () => {
|
||||
it('respects showPoints for point visibility when no custom pointsBuilder is given', () => {
|
||||
const neverPointsBuilder = new UPlotSeriesBuilder(
|
||||
createBaseProps({
|
||||
drawStyle: DrawStyle.Line,
|
||||
showPoints: VisibilityMode.Never,
|
||||
showPoints: false,
|
||||
}),
|
||||
);
|
||||
const alwaysPointsBuilder = new UPlotSeriesBuilder(
|
||||
createBaseProps({
|
||||
drawStyle: DrawStyle.Line,
|
||||
showPoints: VisibilityMode.Always,
|
||||
showPoints: true,
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -122,12 +122,6 @@ export enum LineInterpolation {
|
||||
StepBefore = 'stepBefore',
|
||||
}
|
||||
|
||||
export enum VisibilityMode {
|
||||
Always = 'always',
|
||||
Auto = 'auto',
|
||||
Never = 'never',
|
||||
}
|
||||
|
||||
/**
|
||||
* Props for configuring lines
|
||||
*/
|
||||
@@ -163,7 +157,13 @@ export interface BarConfig {
|
||||
export interface PointsConfig {
|
||||
pointColor?: string;
|
||||
pointSize?: number;
|
||||
showPoints?: VisibilityMode;
|
||||
showPoints?: boolean;
|
||||
}
|
||||
|
||||
export enum FillMode {
|
||||
Solid = 'solid',
|
||||
Gradient = 'gradient',
|
||||
None = 'none',
|
||||
}
|
||||
|
||||
export interface SeriesProps extends LineConfig, PointsConfig, BarConfig {
|
||||
@@ -177,6 +177,7 @@ export interface SeriesProps extends LineConfig, PointsConfig, BarConfig {
|
||||
show?: boolean;
|
||||
spanGaps?: boolean;
|
||||
fillColor?: string;
|
||||
fillMode?: FillMode;
|
||||
isDarkMode?: boolean;
|
||||
stepInterval?: number;
|
||||
}
|
||||
|
||||
18
frontend/src/lib/uPlotV2/utils/generateGradientFill.ts
Normal file
18
frontend/src/lib/uPlotV2/utils/generateGradientFill.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import uPlot from 'uplot';
|
||||
|
||||
export function generateGradientFill(
|
||||
uPlotInstance: uPlot,
|
||||
startColor: string,
|
||||
endColor: string,
|
||||
): CanvasGradient {
|
||||
const g = uPlotInstance.ctx.createLinearGradient(
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
uPlotInstance.bbox.height,
|
||||
);
|
||||
g.addColorStop(0, `${startColor}70`);
|
||||
g.addColorStop(0.6, `${startColor}40`);
|
||||
g.addColorStop(1, endColor);
|
||||
return g;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
.unauthorized-page {
|
||||
&__description {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
@@ -1,51 +1,20 @@
|
||||
import { useCallback } from 'react';
|
||||
import { Space, Typography } from 'antd';
|
||||
import UnAuthorized from 'assets/UnAuthorized';
|
||||
import { Container } from 'components/NotFound/styles';
|
||||
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||
import { useQueryState } from 'nuqs';
|
||||
import { handleContactSupport } from 'pages/Integrations/utils';
|
||||
|
||||
import { useAppContext } from '../../providers/App/App';
|
||||
import { USER_ROLES } from '../../types/roles';
|
||||
|
||||
import './index.styles.scss';
|
||||
import { Button, Container } from 'components/NotFound/styles';
|
||||
import ROUTES from 'constants/routes';
|
||||
|
||||
function UnAuthorizePage(): JSX.Element {
|
||||
const [debugCurrentRole] = useQueryState('currentRole');
|
||||
const { user } = useAppContext();
|
||||
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
|
||||
|
||||
const userIsAnonymous =
|
||||
debugCurrentRole === USER_ROLES.ANONYMOUS ||
|
||||
user.role === USER_ROLES.ANONYMOUS;
|
||||
const mistakeMessage = userIsAnonymous
|
||||
? 'If you believe this is a mistake, please contact your administrator or'
|
||||
: 'Please contact your administrator.';
|
||||
|
||||
const handleContactSupportClick = useCallback((): void => {
|
||||
handleContactSupport(isCloudUserVal);
|
||||
}, [isCloudUserVal]);
|
||||
|
||||
return (
|
||||
<Container className="unauthorized-page">
|
||||
<Container>
|
||||
<Space align="center" direction="vertical">
|
||||
<UnAuthorized width={64} height={64} />
|
||||
<Typography.Title level={3}>Access Restricted</Typography.Title>
|
||||
<UnAuthorized />
|
||||
<Typography.Title level={3}>
|
||||
Oops.. you don't have permission to view this page
|
||||
</Typography.Title>
|
||||
|
||||
<p className="unauthorized-page__description">
|
||||
It looks like you don‘t have permission to view this page. <br />
|
||||
{mistakeMessage}
|
||||
{userIsAnonymous ? (
|
||||
<Typography.Link
|
||||
className="contact-support-link"
|
||||
onClick={handleContactSupportClick}
|
||||
>
|
||||
{' '}
|
||||
reach out to us.
|
||||
</Typography.Link>
|
||||
) : null}
|
||||
</p>
|
||||
<Button to={ROUTES.HOME} tabIndex={0} className="periscope-btn primary">
|
||||
Return To Home
|
||||
</Button>
|
||||
</Space>
|
||||
</Container>
|
||||
);
|
||||
|
||||
@@ -19,12 +19,6 @@ import getUserVersion from 'api/v1/version/get';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import dayjs from 'dayjs';
|
||||
import useActiveLicenseV3 from 'hooks/useActiveLicenseV3/useActiveLicenseV3';
|
||||
import {
|
||||
IsAdminPermission,
|
||||
IsEditorPermission,
|
||||
IsViewerPermission,
|
||||
} from 'hooks/useAuthZ/legacy';
|
||||
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
|
||||
import { useGetFeatureFlag } from 'hooks/useGetFeatureFlag';
|
||||
import { useGlobalEventListener } from 'hooks/useGlobalEventListener';
|
||||
import { ChangelogSchema } from 'types/api/changelog/getChangelogByVersion';
|
||||
@@ -40,7 +34,7 @@ import {
|
||||
UserPreference,
|
||||
} from 'types/api/preferences/preference';
|
||||
import { Organization } from 'types/api/user/getOrganization';
|
||||
import { ROLES, USER_ROLES } from 'types/roles';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
|
||||
import { IAppContext, IUser } from './types';
|
||||
import { getUserDefaults } from './utils';
|
||||
@@ -49,7 +43,7 @@ export const AppContext = createContext<IAppContext | undefined>(undefined);
|
||||
|
||||
export function AppProvider({ children }: PropsWithChildren): JSX.Element {
|
||||
// on load of the provider set the user defaults with access token , refresh token from local storage
|
||||
const [defaultUser, setDefaultUser] = useState<IUser>(() => getUserDefaults());
|
||||
const [user, setUser] = useState<IUser>(() => getUserDefaults());
|
||||
const [activeLicense, setActiveLicense] = useState<LicenseResModel | null>(
|
||||
null,
|
||||
);
|
||||
@@ -76,51 +70,18 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
|
||||
// if logged out and trying to hit any route none of these calls will trigger
|
||||
const {
|
||||
data: userData,
|
||||
isFetching: isFetchingUserData,
|
||||
error: userFetchDataError,
|
||||
isFetching: isFetchingUser,
|
||||
error: userFetchError,
|
||||
} = useQuery({
|
||||
queryFn: get,
|
||||
queryKey: ['/api/v1/user/me'],
|
||||
enabled: isLoggedIn,
|
||||
});
|
||||
|
||||
const {
|
||||
permissions: permissionsResult,
|
||||
isFetching: isFetchingPermissions,
|
||||
error: errorOnPermissions,
|
||||
refetchPermissions,
|
||||
} = useAuthZ([IsAdminPermission, IsEditorPermission, IsViewerPermission], {
|
||||
enabled: isLoggedIn,
|
||||
});
|
||||
|
||||
const isFetchingUser = isFetchingUserData || isFetchingPermissions;
|
||||
const userFetchError = userFetchDataError || errorOnPermissions;
|
||||
|
||||
const userRole = useMemo(() => {
|
||||
if (permissionsResult?.[IsAdminPermission]?.isGranted) {
|
||||
return USER_ROLES.ADMIN;
|
||||
}
|
||||
if (permissionsResult?.[IsEditorPermission]?.isGranted) {
|
||||
return USER_ROLES.EDITOR;
|
||||
}
|
||||
if (permissionsResult?.[IsViewerPermission]?.isGranted) {
|
||||
return USER_ROLES.VIEWER;
|
||||
}
|
||||
// if none of the permissions, so anonymous
|
||||
return USER_ROLES.ANONYMOUS;
|
||||
}, [permissionsResult]);
|
||||
|
||||
const user: IUser = useMemo(() => {
|
||||
return {
|
||||
...defaultUser,
|
||||
role: userRole as ROLES,
|
||||
};
|
||||
}, [defaultUser, userRole]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isFetchingUser && userData && userData.data) {
|
||||
setLocalStorageApi(LOCALSTORAGE.LOGGED_IN_USER_EMAIL, userData.data.email);
|
||||
setDefaultUser((prev) => ({
|
||||
setUser((prev) => ({
|
||||
...prev,
|
||||
...userData.data,
|
||||
}));
|
||||
@@ -242,7 +203,7 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
|
||||
}, [userPreferencesData, isFetchingUserPreferences, isLoggedIn]);
|
||||
|
||||
function updateUser(user: IUser): void {
|
||||
setDefaultUser((prev) => ({
|
||||
setUser((prev) => ({
|
||||
...prev,
|
||||
...user,
|
||||
}));
|
||||
@@ -283,7 +244,7 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
|
||||
...org.slice(orgIndex + 1, org.length),
|
||||
];
|
||||
setOrg(updatedOrg);
|
||||
setDefaultUser((prev) => {
|
||||
setUser((prev) => {
|
||||
if (prev.orgId === orgId) {
|
||||
return {
|
||||
...prev,
|
||||
@@ -311,7 +272,7 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
|
||||
// global event listener for AFTER_LOGIN event to start the user fetch post all actions are complete
|
||||
useGlobalEventListener('AFTER_LOGIN', (event) => {
|
||||
if (event.detail) {
|
||||
setDefaultUser((prev) => ({
|
||||
setUser((prev) => ({
|
||||
...prev,
|
||||
accessJwt: event.detail.accessJWT,
|
||||
refreshJwt: event.detail.refreshJWT,
|
||||
@@ -319,14 +280,12 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
|
||||
}));
|
||||
setIsLoggedIn(true);
|
||||
}
|
||||
|
||||
refetchPermissions();
|
||||
});
|
||||
|
||||
// global event listener for LOGOUT event to clean the app context state
|
||||
useGlobalEventListener('LOGOUT', () => {
|
||||
setIsLoggedIn(false);
|
||||
setDefaultUser(getUserDefaults());
|
||||
setUser(getUserDefaults());
|
||||
setActiveLicense(null);
|
||||
setTrialInfo(null);
|
||||
setFeatureFlags(null);
|
||||
|
||||
@@ -1,273 +0,0 @@
|
||||
import { ReactElement } from 'react';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { renderHook, waitFor } from '@testing-library/react';
|
||||
import setLocalStorageApi from 'api/browser/localstorage/set';
|
||||
import {
|
||||
AuthtypesGettableTransactionDTO,
|
||||
AuthtypesTransactionDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { SINGLE_FLIGHT_WAIT_TIME_MS } from 'hooks/useAuthZ/constants';
|
||||
import { server } from 'mocks-server/server';
|
||||
import { rest } from 'msw';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
|
||||
import { AppProvider, useAppContext } from '../App';
|
||||
|
||||
const AUTHZ_CHECK_URL = 'http://localhost/api/v1/authz/check';
|
||||
|
||||
jest.mock('constants/env', () => ({
|
||||
ENVIRONMENT: { baseURL: 'http://localhost', wsURL: '' },
|
||||
}));
|
||||
|
||||
/**
|
||||
* Since we are mocking the check permissions, this is needed
|
||||
*/
|
||||
const waitForSinglePreflightToFinish = async (): Promise<void> =>
|
||||
await new Promise((r) => setTimeout(r, SINGLE_FLIGHT_WAIT_TIME_MS));
|
||||
|
||||
function authzMockResponse(
|
||||
payload: AuthtypesTransactionDTO[],
|
||||
authorizedByIndex: boolean[],
|
||||
): { data: AuthtypesGettableTransactionDTO[]; status: string } {
|
||||
return {
|
||||
data: payload.map((txn, i) => ({
|
||||
relation: txn.relation,
|
||||
object: txn.object,
|
||||
authorized: authorizedByIndex[i] ?? false,
|
||||
})),
|
||||
status: 'success',
|
||||
};
|
||||
}
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
refetchOnWindowFocus: false,
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function createWrapper(): ({
|
||||
children,
|
||||
}: {
|
||||
children: ReactElement;
|
||||
}) => ReactElement {
|
||||
return function Wrapper({
|
||||
children,
|
||||
}: {
|
||||
children: ReactElement;
|
||||
}): ReactElement {
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AppProvider>{children}</AppProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
describe('AppProvider user.role from permissions', () => {
|
||||
beforeEach(() => {
|
||||
queryClient.clear();
|
||||
setLocalStorageApi(LOCALSTORAGE.IS_LOGGED_IN, 'true');
|
||||
});
|
||||
|
||||
it('sets user.role to ADMIN and hasEditPermission to true when admin permission is granted', async () => {
|
||||
server.use(
|
||||
rest.post(AUTHZ_CHECK_URL, async (req, res, ctx) => {
|
||||
const payload = await req.json();
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.json(authzMockResponse(payload, [true, false, false])),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
const wrapper = createWrapper();
|
||||
const { result } = renderHook(() => useAppContext(), { wrapper });
|
||||
|
||||
await waitForSinglePreflightToFinish();
|
||||
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(result.current.user.role).toBe(USER_ROLES.ADMIN);
|
||||
expect(result.current.hasEditPermission).toBe(true);
|
||||
},
|
||||
{ timeout: 2000 },
|
||||
);
|
||||
});
|
||||
|
||||
it('sets user.role to EDITOR and hasEditPermission to true when only editor permission is granted', async () => {
|
||||
server.use(
|
||||
rest.post(AUTHZ_CHECK_URL, async (req, res, ctx) => {
|
||||
const payload = await req.json();
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.json(authzMockResponse(payload, [false, true, false])),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
const wrapper = createWrapper();
|
||||
const { result } = renderHook(() => useAppContext(), { wrapper });
|
||||
|
||||
await waitForSinglePreflightToFinish();
|
||||
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(result.current.user.role).toBe(USER_ROLES.EDITOR);
|
||||
expect(result.current.hasEditPermission).toBe(true);
|
||||
},
|
||||
{ timeout: 2000 },
|
||||
);
|
||||
});
|
||||
|
||||
it('sets user.role to VIEWER and hasEditPermission to false when only viewer permission is granted', async () => {
|
||||
server.use(
|
||||
rest.post(AUTHZ_CHECK_URL, async (req, res, ctx) => {
|
||||
const payload = await req.json();
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.json(authzMockResponse(payload, [false, false, true])),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
const wrapper = createWrapper();
|
||||
const { result } = renderHook(() => useAppContext(), { wrapper });
|
||||
|
||||
await waitForSinglePreflightToFinish();
|
||||
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(result.current.user.role).toBe(USER_ROLES.VIEWER);
|
||||
expect(result.current.hasEditPermission).toBe(false);
|
||||
},
|
||||
{ timeout: 2000 },
|
||||
);
|
||||
});
|
||||
|
||||
it('sets user.role to ANONYMOUS and hasEditPermission to false when no role permission is granted', async () => {
|
||||
server.use(
|
||||
rest.post(AUTHZ_CHECK_URL, async (req, res, ctx) => {
|
||||
const payload = await req.json();
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.json(authzMockResponse(payload, [false, false, false])),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
const wrapper = createWrapper();
|
||||
const { result } = renderHook(() => useAppContext(), { wrapper });
|
||||
|
||||
await waitForSinglePreflightToFinish();
|
||||
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(result.current.user.role).toBe(USER_ROLES.ANONYMOUS);
|
||||
expect(result.current.hasEditPermission).toBe(false);
|
||||
},
|
||||
{ timeout: 2000 },
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* This is expected to not happen, but we'll test it just in case.
|
||||
*/
|
||||
describe('when multiple role permissions are granted', () => {
|
||||
it('prefers ADMIN over EDITOR and VIEWER when multiple role permissions are granted', async () => {
|
||||
server.use(
|
||||
rest.post(AUTHZ_CHECK_URL, async (req, res, ctx) => {
|
||||
const payload = await req.json();
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.json(authzMockResponse(payload, [true, true, true])),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
const wrapper = createWrapper();
|
||||
const { result } = renderHook(() => useAppContext(), { wrapper });
|
||||
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(result.current.user.role).toBe(USER_ROLES.ADMIN);
|
||||
expect(result.current.hasEditPermission).toBe(true);
|
||||
},
|
||||
{ timeout: 300 },
|
||||
);
|
||||
});
|
||||
|
||||
it('prefers EDITOR over VIEWER when editor and viewer permissions are granted', async () => {
|
||||
server.use(
|
||||
rest.post(AUTHZ_CHECK_URL, async (req, res, ctx) => {
|
||||
const payload = await req.json();
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.json(authzMockResponse(payload, [false, true, true])),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
const wrapper = createWrapper();
|
||||
const { result } = renderHook(() => useAppContext(), { wrapper });
|
||||
|
||||
await waitForSinglePreflightToFinish();
|
||||
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(result.current.user.role).toBe(USER_ROLES.EDITOR);
|
||||
expect(result.current.hasEditPermission).toBe(true);
|
||||
},
|
||||
{ timeout: 2000 },
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('AppProvider when authz/check fails', () => {
|
||||
beforeEach(() => {
|
||||
queryClient.clear();
|
||||
setLocalStorageApi(LOCALSTORAGE.IS_LOGGED_IN, 'true');
|
||||
});
|
||||
|
||||
it('sets userFetchError when authz/check returns 500 (same as user fetch error)', async () => {
|
||||
server.use(
|
||||
rest.post(AUTHZ_CHECK_URL, (_, res, ctx) =>
|
||||
res(ctx.status(500), ctx.json({ error: 'Internal Server Error' })),
|
||||
),
|
||||
);
|
||||
|
||||
const wrapper = createWrapper();
|
||||
const { result } = renderHook(() => useAppContext(), { wrapper });
|
||||
|
||||
await waitForSinglePreflightToFinish();
|
||||
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(result.current.userFetchError).toBeTruthy();
|
||||
},
|
||||
{ timeout: 2000 },
|
||||
);
|
||||
});
|
||||
|
||||
it('sets userFetchError when authz/check fails with network error (same as user fetch error)', async () => {
|
||||
server.use(
|
||||
rest.post(AUTHZ_CHECK_URL, (_, res) => res.networkError('Network error')),
|
||||
);
|
||||
|
||||
const wrapper = createWrapper();
|
||||
const { result } = renderHook(() => useAppContext(), { wrapper });
|
||||
|
||||
await waitForSinglePreflightToFinish();
|
||||
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(result.current.userFetchError).toBeTruthy();
|
||||
},
|
||||
{ timeout: 2000 },
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -53,9 +53,7 @@ import { IDashboardContext, WidgetColumnWidths } from './types';
|
||||
import { sortLayout } from './util';
|
||||
|
||||
export const DashboardContext = createContext<IDashboardContext>({
|
||||
isDashboardSliderOpen: false,
|
||||
isDashboardLocked: false,
|
||||
handleToggleDashboardSlider: () => {},
|
||||
handleDashboardLockToggle: () => {},
|
||||
dashboardResponse: {} as UseQueryResult<
|
||||
SuccessResponseV2<Dashboard>,
|
||||
@@ -82,8 +80,6 @@ export function DashboardProvider({
|
||||
children,
|
||||
dashboardId,
|
||||
}: PropsWithChildren<{ dashboardId: string }>): JSX.Element {
|
||||
const [isDashboardSliderOpen, setIsDashboardSlider] = useState<boolean>(false);
|
||||
|
||||
const [isDashboardLocked, setIsDashboardLocked] = useState<boolean>(false);
|
||||
|
||||
const [
|
||||
@@ -288,13 +284,8 @@ export function DashboardProvider({
|
||||
}
|
||||
}, [isVisible]);
|
||||
|
||||
const handleToggleDashboardSlider = (value: boolean): void => {
|
||||
setIsDashboardSlider(value);
|
||||
};
|
||||
|
||||
const { mutate: lockDashboard } = useMutation(locked, {
|
||||
onSuccess: (_, props) => {
|
||||
setIsDashboardSlider(false);
|
||||
setIsDashboardLocked(props.lock);
|
||||
},
|
||||
onError: (error) => {
|
||||
@@ -319,9 +310,7 @@ export function DashboardProvider({
|
||||
|
||||
const value: IDashboardContext = useMemo(
|
||||
() => ({
|
||||
isDashboardSliderOpen,
|
||||
isDashboardLocked,
|
||||
handleToggleDashboardSlider,
|
||||
handleDashboardLockToggle,
|
||||
dashboardResponse,
|
||||
selectedDashboard,
|
||||
@@ -341,7 +330,6 @@ export function DashboardProvider({
|
||||
}),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[
|
||||
isDashboardSliderOpen,
|
||||
isDashboardLocked,
|
||||
dashboardResponse,
|
||||
selectedDashboard,
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { create } from 'zustand';
|
||||
|
||||
interface IPanelTypeSelectionModalState {
|
||||
isPanelTypeSelectionModalOpen: boolean;
|
||||
setIsPanelTypeSelectionModalOpen: (isOpen: boolean) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* This helper is used for selecting the panel type when creating a new panel in the dashboard.
|
||||
* It uses Zustand for state management to keep track of whether the panel type selection modal is open or closed.
|
||||
*/
|
||||
export const usePanelTypeSelectionModalStore = create<IPanelTypeSelectionModalState>(
|
||||
(set) => ({
|
||||
isPanelTypeSelectionModalOpen: false,
|
||||
setIsPanelTypeSelectionModalOpen: (isOpen): void =>
|
||||
set({ isPanelTypeSelectionModalOpen: isOpen }),
|
||||
}),
|
||||
);
|
||||
@@ -9,9 +9,7 @@ export type WidgetColumnWidths = {
|
||||
};
|
||||
|
||||
export interface IDashboardContext {
|
||||
isDashboardSliderOpen: boolean;
|
||||
isDashboardLocked: boolean;
|
||||
handleToggleDashboardSlider: (value: boolean) => void;
|
||||
handleDashboardLockToggle: (value: boolean) => void;
|
||||
dashboardResponse: UseQueryResult<SuccessResponseV2<Dashboard>, unknown>;
|
||||
selectedDashboard: Dashboard | undefined;
|
||||
|
||||
@@ -5,6 +5,11 @@ import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { ThresholdProps } from 'container/NewWidget/RightContainer/Threshold/types';
|
||||
import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems';
|
||||
import { QueryTableProps } from 'container/QueryTable/QueryTable.intefaces';
|
||||
import {
|
||||
FillMode,
|
||||
LineInterpolation,
|
||||
LineStyle,
|
||||
} from 'lib/uPlotV2/config/types';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { IField } from '../logs/fields';
|
||||
@@ -132,6 +137,10 @@ export interface IBaseWidget {
|
||||
legendPosition?: LegendPosition;
|
||||
customLegendColors?: Record<string, string>;
|
||||
contextLinks?: ContextLinksData;
|
||||
lineInterpolation?: LineInterpolation;
|
||||
showPoints?: boolean;
|
||||
lineStyle?: LineStyle;
|
||||
fillMode?: FillMode;
|
||||
}
|
||||
export interface Widgets extends IBaseWidget {
|
||||
query: Query;
|
||||
|
||||
@@ -13,9 +13,6 @@ export interface UserResponse {
|
||||
displayName: string;
|
||||
orgId: string;
|
||||
organization: string;
|
||||
/**
|
||||
* @deprecated This will be removed in the future releases in favor of new AuthZ framework
|
||||
*/
|
||||
role: ROLES;
|
||||
updatedAt?: number;
|
||||
}
|
||||
|
||||
@@ -2,16 +2,14 @@ export type ADMIN = 'ADMIN';
|
||||
export type VIEWER = 'VIEWER';
|
||||
export type EDITOR = 'EDITOR';
|
||||
export type AUTHOR = 'AUTHOR';
|
||||
export type ANONYMOUS = 'ANONYMOUS';
|
||||
|
||||
export type ROLES = ADMIN | VIEWER | EDITOR | AUTHOR | ANONYMOUS;
|
||||
export type ROLES = ADMIN | VIEWER | EDITOR | AUTHOR;
|
||||
|
||||
export const USER_ROLES = {
|
||||
ADMIN: 'ADMIN',
|
||||
VIEWER: 'VIEWER',
|
||||
EDITOR: 'EDITOR',
|
||||
AUTHOR: 'AUTHOR',
|
||||
ANONYMOUS: 'ANONYMOUS',
|
||||
};
|
||||
|
||||
export enum RoleType {
|
||||
|
||||
@@ -69,7 +69,7 @@ export const routePermission: Record<keyof typeof ROUTES, ROLES[]> = {
|
||||
ALERT_OVERVIEW: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
LOGIN: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
FORGOT_PASSWORD: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
NOT_FOUND: ['ADMIN', 'VIEWER', 'EDITOR', 'ANONYMOUS'],
|
||||
NOT_FOUND: ['ADMIN', 'VIEWER', 'EDITOR'],
|
||||
PASSWORD_RESET: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
SERVICE_METRICS: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
SETTINGS: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
@@ -77,7 +77,7 @@ export const routePermission: Record<keyof typeof ROUTES, ROLES[]> = {
|
||||
TRACES_EXPLORER: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
TRACE: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
TRACE_DETAIL: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
UN_AUTHORIZED: ['ADMIN', 'EDITOR', 'VIEWER', 'ANONYMOUS'],
|
||||
UN_AUTHORIZED: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
USAGE_EXPLORER: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
VERSION: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
LOGS: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
@@ -101,7 +101,7 @@ export const routePermission: Record<keyof typeof ROUTES, ROLES[]> = {
|
||||
ROLE_DETAILS: ['ADMIN'],
|
||||
MEMBERS_SETTINGS: ['ADMIN'],
|
||||
BILLING: ['ADMIN'],
|
||||
SUPPORT: ['ADMIN', 'EDITOR', 'VIEWER', 'ANONYMOUS'],
|
||||
SUPPORT: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
SOMETHING_WENT_WRONG: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
LOGS_SAVE_VIEWS: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
TRACES_SAVE_VIEWS: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
|
||||
@@ -5646,7 +5646,7 @@
|
||||
tailwind-merge "^2.5.2"
|
||||
tailwindcss-animate "^1.0.7"
|
||||
|
||||
"@signozhq/toggle-group@^0.0.1":
|
||||
"@signozhq/toggle-group@0.0.1":
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@signozhq/toggle-group/-/toggle-group-0.0.1.tgz#c82ff1da34e77b24da53c2d595ad6b4a0d1b1de4"
|
||||
integrity sha512-871bQayL5MaqsuNOFHKexidu9W2Hlg1y4xmH8C5mGmlfZ4bd0ovJ9OweQrM6Puys3jeMwi69xmJuesYCfKQc1g==
|
||||
|
||||
41
pkg/instrumentation/loghandler/filtering.go
Normal file
41
pkg/instrumentation/loghandler/filtering.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package loghandler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
)
|
||||
|
||||
type filtering struct{}
|
||||
|
||||
func NewFiltering() *filtering {
|
||||
return &filtering{}
|
||||
}
|
||||
|
||||
func (h *filtering) Wrap(next LogHandler) LogHandler {
|
||||
return LogHandlerFunc(func(ctx context.Context, record slog.Record) error {
|
||||
if !filterRecord(record) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return next.Handle(ctx, record)
|
||||
})
|
||||
}
|
||||
|
||||
func filterRecord(record slog.Record) bool {
|
||||
suppress := false
|
||||
record.Attrs(func(a slog.Attr) bool {
|
||||
if a.Value.Kind() == slog.KindAny {
|
||||
if err, ok := a.Value.Any().(error); ok {
|
||||
if errors.Is(err, context.Canceled) {
|
||||
suppress = true
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return !suppress
|
||||
}
|
||||
52
pkg/instrumentation/loghandler/filtering_test.go
Normal file
52
pkg/instrumentation/loghandler/filtering_test.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package loghandler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"testing"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestFiltering_SuppressesContextCanceled(t *testing.T) {
|
||||
filtering := NewFiltering()
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
logger := slog.New(&handler{base: slog.NewJSONHandler(buf, &slog.HandlerOptions{Level: slog.LevelDebug}), wrappers: []Wrapper{filtering}})
|
||||
|
||||
logger.ErrorContext(context.Background(), "operation failed", "error", context.Canceled)
|
||||
|
||||
assert.Empty(t, buf.String(), "log with context.Canceled should be suppressed")
|
||||
}
|
||||
|
||||
func TestFiltering_AllowsOtherErrors(t *testing.T) {
|
||||
filtering := NewFiltering()
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
logger := slog.New(&handler{base: slog.NewJSONHandler(buf, &slog.HandlerOptions{Level: slog.LevelDebug}), wrappers: []Wrapper{filtering}})
|
||||
|
||||
logger.ErrorContext(context.Background(), "operation failed", "error", errors.New(errors.TypeInternal, errors.CodeInternal, "some other error"))
|
||||
|
||||
m := make(map[string]any)
|
||||
err := json.Unmarshal(buf.Bytes(), &m)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "operation failed", m["msg"])
|
||||
}
|
||||
|
||||
func TestFiltering_AllowsLogsWithoutErrors(t *testing.T) {
|
||||
filtering := NewFiltering()
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
logger := slog.New(&handler{base: slog.NewJSONHandler(buf, &slog.HandlerOptions{Level: slog.LevelDebug}), wrappers: []Wrapper{filtering}})
|
||||
|
||||
logger.InfoContext(context.Background(), "normal log", "key", "value")
|
||||
|
||||
m := make(map[string]any)
|
||||
err := json.Unmarshal(buf.Bytes(), &m)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "normal log", m["msg"])
|
||||
}
|
||||
@@ -116,7 +116,7 @@ func New(ctx context.Context, cfg Config, build version.Build, serviceName strin
|
||||
meterProvider: meterProvider,
|
||||
meterProviderShutdownFunc: meterProviderShutdownFunc,
|
||||
prometheusRegistry: prometheusRegistry,
|
||||
logger: NewLogger(cfg, loghandler.NewCorrelation()),
|
||||
logger: NewLogger(cfg, loghandler.NewCorrelation(), loghandler.NewFiltering()),
|
||||
startCh: make(chan struct{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -7,13 +7,14 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"log/slog"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/opamptypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
@@ -121,7 +122,7 @@ func (r *Repo) insertConfig(
|
||||
|
||||
// allowing empty elements for logs - use case is deleting all pipelines
|
||||
if len(elements) == 0 && c.ElementType != opamptypes.ElementTypeLogPipelines {
|
||||
zap.L().Error("insert config called with no elements ", zap.String("ElementType", c.ElementType.StringValue()))
|
||||
slog.ErrorContext(ctx, "insert config called with no elements", "element_type", c.ElementType.StringValue())
|
||||
return errors.NewInvalidInputf(CodeConfigElementsRequired, "config must have atleast one element")
|
||||
}
|
||||
|
||||
@@ -129,13 +130,13 @@ func (r *Repo) insertConfig(
|
||||
// the version can not be set by the user, we want to auto-assign the versions
|
||||
// in a monotonically increasing order starting with 1. hence, we reject insert
|
||||
// requests with version anything other than 0. here, 0 indicates un-assigned
|
||||
zap.L().Error("invalid version assignment while inserting agent config", zap.Int("version", c.Version), zap.String("ElementType", c.ElementType.StringValue()))
|
||||
slog.ErrorContext(ctx, "invalid version assignment while inserting agent config", "version", c.Version, "element_type", c.ElementType.StringValue())
|
||||
return errors.NewInvalidInputf(errors.CodeInvalidInput, "user defined versions are not supported in the agent config")
|
||||
}
|
||||
|
||||
configVersion, err := r.GetLatestVersion(ctx, orgId, c.ElementType)
|
||||
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
||||
zap.L().Error("failed to fetch latest config version", zap.Error(err))
|
||||
slog.ErrorContext(ctx, "failed to fetch latest config version", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -155,11 +156,11 @@ func (r *Repo) insertConfig(
|
||||
// Delete elements first, then version (to respect potential foreign key constraints)
|
||||
_, delErr := r.store.BunDB().NewDelete().Model(new(opamptypes.AgentConfigElement)).Where("version_id = ?", c.ID).Exec(ctx)
|
||||
if delErr != nil {
|
||||
zap.L().Error("failed to delete config elements during cleanup", zap.Error(delErr), zap.String("version_id", c.ID.String()))
|
||||
slog.ErrorContext(ctx, "failed to delete config elements during cleanup", "error", delErr, "version_id", c.ID.String())
|
||||
}
|
||||
_, delErr = r.store.BunDB().NewDelete().Model(new(opamptypes.AgentConfigVersion)).Where("id = ?", c.ID).Where("org_id = ?", orgId).Exec(ctx)
|
||||
if delErr != nil {
|
||||
zap.L().Error("failed to delete config version during cleanup", zap.Error(delErr), zap.String("version_id", c.ID.String()))
|
||||
slog.ErrorContext(ctx, "failed to delete config version during cleanup", "error", delErr, "version_id", c.ID.String())
|
||||
}
|
||||
}
|
||||
}()
|
||||
@@ -170,7 +171,7 @@ func (r *Repo) insertConfig(
|
||||
Model(c).
|
||||
Exec(ctx)
|
||||
if dbErr != nil {
|
||||
zap.L().Error("error in inserting config version: ", zap.Error(dbErr))
|
||||
slog.ErrorContext(ctx, "error in inserting config version", "error", dbErr)
|
||||
return errors.WrapInternalf(dbErr, CodeConfigVersionInsertFailed, "failed to insert config version")
|
||||
}
|
||||
|
||||
@@ -221,7 +222,7 @@ func (r *Repo) updateDeployStatus(ctx context.Context,
|
||||
Where("org_id = ?", orgId).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to update deploy status", zap.Error(err))
|
||||
slog.ErrorContext(ctx, "failed to update deploy status", "error", err)
|
||||
return model.BadRequest(fmt.Errorf("failed to update deploy status"))
|
||||
}
|
||||
|
||||
@@ -239,7 +240,7 @@ func (r *Repo) updateDeployStatusByHash(
|
||||
Where("org_id = ?", orgId).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to update deploy status", zap.Error(err))
|
||||
slog.ErrorContext(ctx, "failed to update deploy status", "error", err)
|
||||
return errors.WrapInternalf(err, CodeConfigDeployStatusUpdateFailed, "failed to update deploy status")
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -17,7 +18,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/types/opamptypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
@@ -36,7 +36,8 @@ type AgentFeatureType string
|
||||
type Manager struct {
|
||||
Repo
|
||||
// lock to make sure only one update is sent to remote agents at a time
|
||||
lock uint32
|
||||
lock uint32
|
||||
logger *slog.Logger
|
||||
|
||||
// For AgentConfigProvider implementation
|
||||
agentFeatures []AgentFeature
|
||||
@@ -67,6 +68,7 @@ func Initiate(options *ManagerOptions) (*Manager, error) {
|
||||
|
||||
m = &Manager{
|
||||
Repo: Repo{options.Store},
|
||||
logger: slog.Default(),
|
||||
agentFeatures: options.AgentFeatures,
|
||||
configSubscribers: map[string]func(){},
|
||||
}
|
||||
@@ -222,19 +224,19 @@ func NotifyConfigUpdate(ctx context.Context) {
|
||||
func Redeploy(ctx context.Context, orgId valuer.UUID, typ opamptypes.ElementType, version int) error {
|
||||
configVersion, err := GetConfigVersion(ctx, orgId, typ, version)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to fetch config version during redeploy", zap.Error(err))
|
||||
slog.ErrorContext(ctx, "failed to fetch config version during redeploy", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if configVersion == nil || (configVersion != nil && configVersion.Config == "") {
|
||||
zap.L().Debug("config version has no conf yaml", zap.Any("configVersion", configVersion))
|
||||
slog.DebugContext(ctx, "config version has no conf yaml", "config_version", configVersion)
|
||||
return errors.NewInvalidInputf(CodeConfigVersionNoConfig, "the config version can not be redeployed")
|
||||
}
|
||||
switch typ {
|
||||
case opamptypes.ElementTypeSamplingRules:
|
||||
var config *tsp.Config
|
||||
if err := yaml.Unmarshal([]byte(configVersion.Config), &config); err != nil {
|
||||
zap.L().Debug("failed to read last conf correctly", zap.Error(err))
|
||||
slog.DebugContext(ctx, "failed to read last conf correctly", "error", err)
|
||||
return model.BadRequest(fmt.Errorf("failed to read the stored config correctly"))
|
||||
}
|
||||
|
||||
@@ -246,7 +248,7 @@ func Redeploy(ctx context.Context, orgId valuer.UUID, typ opamptypes.ElementType
|
||||
opamp.AddToTracePipelineSpec("signoz_tail_sampling")
|
||||
configHash, err := opamp.UpsertControlProcessors(ctx, "traces", processorConf, m.OnConfigUpdate)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to call agent config update for trace processor", zap.Error(err))
|
||||
slog.ErrorContext(ctx, "failed to call agent config update for trace processor", "error", err)
|
||||
return errors.WithAdditionalf(err, "failed to deploy the config")
|
||||
}
|
||||
|
||||
@@ -254,7 +256,7 @@ func Redeploy(ctx context.Context, orgId valuer.UUID, typ opamptypes.ElementType
|
||||
case opamptypes.ElementTypeDropRules:
|
||||
var filterConfig *filterprocessor.Config
|
||||
if err := yaml.Unmarshal([]byte(configVersion.Config), &filterConfig); err != nil {
|
||||
zap.L().Error("failed to read last conf correctly", zap.Error(err))
|
||||
slog.ErrorContext(ctx, "failed to read last conf correctly", "error", err)
|
||||
return model.InternalError(fmt.Errorf("failed to read the stored config correctly"))
|
||||
}
|
||||
processorConf := map[string]interface{}{
|
||||
@@ -264,7 +266,7 @@ func Redeploy(ctx context.Context, orgId valuer.UUID, typ opamptypes.ElementType
|
||||
opamp.AddToMetricsPipelineSpec("filter")
|
||||
configHash, err := opamp.UpsertControlProcessors(ctx, "metrics", processorConf, m.OnConfigUpdate)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to call agent config update for trace processor", zap.Error(err))
|
||||
slog.ErrorContext(ctx, "failed to call agent config update for trace processor", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -290,13 +292,13 @@ func UpsertFilterProcessor(ctx context.Context, orgId valuer.UUID, version int,
|
||||
opamp.AddToMetricsPipelineSpec("filter")
|
||||
configHash, err := opamp.UpsertControlProcessors(ctx, "metrics", processorConf, m.OnConfigUpdate)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to call agent config update for trace processor", zap.Error(err))
|
||||
slog.ErrorContext(ctx, "failed to call agent config update for trace processor", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
processorConfYaml, yamlErr := yaml.Marshal(config)
|
||||
if yamlErr != nil {
|
||||
zap.L().Warn("unexpected error while transforming processor config to yaml", zap.Error(yamlErr))
|
||||
slog.WarnContext(ctx, "unexpected error while transforming processor config to yaml", "error", yamlErr)
|
||||
}
|
||||
|
||||
m.updateDeployStatus(ctx, orgId, opamptypes.ElementTypeDropRules, version, opamptypes.DeployInitiated.StringValue(), "Deployment started", configHash, string(processorConfYaml))
|
||||
@@ -315,7 +317,7 @@ func (m *Manager) OnConfigUpdate(orgId valuer.UUID, agentId string, hash string,
|
||||
message := "Deployment was successful"
|
||||
|
||||
defer func() {
|
||||
zap.L().Info(status, zap.String("agentId", agentId), zap.String("agentResponse", message))
|
||||
m.logger.Info(status, "agent_id", agentId, "agent_response", message)
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
@@ -341,13 +343,13 @@ func UpsertSamplingProcessor(ctx context.Context, orgId valuer.UUID, version int
|
||||
opamp.AddToTracePipelineSpec("signoz_tail_sampling")
|
||||
configHash, err := opamp.UpsertControlProcessors(ctx, "traces", processorConf, m.OnConfigUpdate)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to call agent config update for trace processor", zap.Error(err))
|
||||
slog.ErrorContext(ctx, "failed to call agent config update for trace processor", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
processorConfYaml, yamlErr := yaml.Marshal(config)
|
||||
if yamlErr != nil {
|
||||
zap.L().Warn("unexpected error while transforming processor config to yaml", zap.Error(yamlErr))
|
||||
slog.WarnContext(ctx, "unexpected error while transforming processor config to yaml", "error", yamlErr)
|
||||
}
|
||||
|
||||
m.updateDeployStatus(ctx, orgId, opamptypes.ElementTypeSamplingRules, version, opamptypes.DeployInitiated.StringValue(), "Deployment started", configHash, string(processorConfYaml))
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"github.com/SigNoz/signoz-otel-collector/utils/fingerprint"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func (r *ClickHouseReader) GetQBFilterSuggestionsForLogs(
|
||||
@@ -79,7 +78,7 @@ func (r *ClickHouseReader) GetQBFilterSuggestionsForLogs(
|
||||
)
|
||||
if err != nil {
|
||||
// Do not fail the entire request if only example query generation fails
|
||||
zap.L().Error("could not find attribute values for creating example query", zap.Error(err))
|
||||
r.logger.ErrorContext(ctx, "could not find attribute values for creating example query", "error", err)
|
||||
} else {
|
||||
|
||||
// add example queries for as many attributes as possible.
|
||||
@@ -159,10 +158,7 @@ func (r *ClickHouseReader) getValuesForLogAttributes(
|
||||
*/
|
||||
|
||||
if len(attributes) > 10 {
|
||||
zap.L().Error(
|
||||
"log attribute values requested for too many attributes. This can lead to slow and costly queries",
|
||||
zap.Int("count", len(attributes)),
|
||||
)
|
||||
r.logger.ErrorContext(ctx, "log attribute values requested for too many attributes. This can lead to slow and costly queries", "count", len(attributes))
|
||||
attributes = attributes[:10]
|
||||
}
|
||||
|
||||
@@ -187,7 +183,7 @@ func (r *ClickHouseReader) getValuesForLogAttributes(
|
||||
|
||||
rows, err := r.db.Query(ctx, query, tagKeyQueryArgs...)
|
||||
if err != nil {
|
||||
zap.L().Error("couldn't query attrib values for suggestions", zap.Error(err))
|
||||
r.logger.ErrorContext(ctx, "couldn't query attrib values for suggestions", "error", err)
|
||||
return nil, model.InternalError(fmt.Errorf(
|
||||
"couldn't query attrib values for suggestions: %w", err,
|
||||
))
|
||||
|
||||
@@ -2,17 +2,18 @@ package queryprogress
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"sync"
|
||||
|
||||
"github.com/ClickHouse/clickhouse-go/v2"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
// tracks progress and manages subscriptions for all queries
|
||||
type inMemoryQueryProgressTracker struct {
|
||||
logger *slog.Logger
|
||||
queries map[string]*queryTracker
|
||||
lock sync.RWMutex
|
||||
}
|
||||
@@ -30,7 +31,7 @@ func (tracker *inMemoryQueryProgressTracker) ReportQueryStarted(
|
||||
))
|
||||
}
|
||||
|
||||
tracker.queries[queryId] = newQueryTracker(queryId)
|
||||
tracker.queries[queryId] = newQueryTracker(tracker.logger, queryId)
|
||||
|
||||
return func() {
|
||||
tracker.onQueryFinished(queryId)
|
||||
@@ -93,6 +94,7 @@ func (tracker *inMemoryQueryProgressTracker) getQueryTracker(
|
||||
|
||||
// Tracks progress and manages subscriptions for a single query
|
||||
type queryTracker struct {
|
||||
logger *slog.Logger
|
||||
queryId string
|
||||
isFinished bool
|
||||
|
||||
@@ -102,8 +104,9 @@ type queryTracker struct {
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func newQueryTracker(queryId string) *queryTracker {
|
||||
func newQueryTracker(logger *slog.Logger, queryId string) *queryTracker {
|
||||
return &queryTracker{
|
||||
logger: logger,
|
||||
queryId: queryId,
|
||||
subscriptions: map[string]*queryProgressSubscription{},
|
||||
}
|
||||
@@ -114,10 +117,7 @@ func (qt *queryTracker) handleProgressUpdate(p *clickhouse.Progress) {
|
||||
defer qt.lock.Unlock()
|
||||
|
||||
if qt.isFinished {
|
||||
zap.L().Warn(
|
||||
"received clickhouse progress update for finished query",
|
||||
zap.String("queryId", qt.queryId), zap.Any("progress", p),
|
||||
)
|
||||
qt.logger.Warn("received clickhouse progress update for finished query", "queryId", qt.queryId, "progress", p)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ func (qt *queryTracker) subscribe() (
|
||||
}
|
||||
|
||||
subscriberId := uuid.NewString()
|
||||
subscription := newQueryProgressSubscription()
|
||||
subscription := newQueryProgressSubscription(qt.logger)
|
||||
qt.subscriptions[subscriberId] = subscription
|
||||
|
||||
if qt.progress != nil {
|
||||
@@ -163,11 +163,7 @@ func (qt *queryTracker) unsubscribe(subscriberId string) {
|
||||
defer qt.lock.Unlock()
|
||||
|
||||
if qt.isFinished {
|
||||
zap.L().Debug(
|
||||
"received unsubscribe request after query finished",
|
||||
zap.String("subscriber", subscriberId),
|
||||
zap.String("queryId", qt.queryId),
|
||||
)
|
||||
qt.logger.Debug("received unsubscribe request after query finished", "subscriber", subscriberId, "queryId", qt.queryId)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -183,10 +179,7 @@ func (qt *queryTracker) onFinished() {
|
||||
defer qt.lock.Unlock()
|
||||
|
||||
if qt.isFinished {
|
||||
zap.L().Warn(
|
||||
"receiver query finish report after query finished",
|
||||
zap.String("queryId", qt.queryId),
|
||||
)
|
||||
qt.logger.Warn("receiver query finish report after query finished", "queryId", qt.queryId)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -199,15 +192,17 @@ func (qt *queryTracker) onFinished() {
|
||||
}
|
||||
|
||||
type queryProgressSubscription struct {
|
||||
logger *slog.Logger
|
||||
ch chan model.QueryProgress
|
||||
isClosed bool
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func newQueryProgressSubscription() *queryProgressSubscription {
|
||||
func newQueryProgressSubscription(logger *slog.Logger) *queryProgressSubscription {
|
||||
ch := make(chan model.QueryProgress, 1000)
|
||||
return &queryProgressSubscription{
|
||||
ch: ch,
|
||||
logger: logger,
|
||||
ch: ch,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,10 +212,7 @@ func (ch *queryProgressSubscription) send(progress model.QueryProgress) {
|
||||
defer ch.lock.Unlock()
|
||||
|
||||
if ch.isClosed {
|
||||
zap.L().Error(
|
||||
"can't send query progress: channel already closed.",
|
||||
zap.Any("progress", progress),
|
||||
)
|
||||
ch.logger.Error("can't send query progress: channel already closed.", "progress", progress)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -228,12 +220,9 @@ func (ch *queryProgressSubscription) send(progress model.QueryProgress) {
|
||||
// blocking while sending doesn't happen in the happy path
|
||||
select {
|
||||
case ch.ch <- progress:
|
||||
zap.L().Debug("published query progress", zap.Any("progress", progress))
|
||||
ch.logger.Debug("published query progress", "progress", progress)
|
||||
default:
|
||||
zap.L().Error(
|
||||
"couldn't publish query progress. dropping update.",
|
||||
zap.Any("progress", progress),
|
||||
)
|
||||
ch.logger.Error("couldn't publish query progress. dropping update.", "progress", progress)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package queryprogress
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
|
||||
"github.com/ClickHouse/clickhouse-go/v2"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
)
|
||||
@@ -21,10 +23,11 @@ type QueryProgressTracker interface {
|
||||
SubscribeToQueryProgress(queryId string) (ch <-chan model.QueryProgress, unsubscribe func(), apiErr *model.ApiError)
|
||||
}
|
||||
|
||||
func NewQueryProgressTracker() QueryProgressTracker {
|
||||
func NewQueryProgressTracker(logger *slog.Logger) QueryProgressTracker {
|
||||
// InMemory tracker is useful only for single replica query service setups.
|
||||
// Multi replica setups must use a centralized store for tracking and subscribing to query progress
|
||||
return &inMemoryQueryProgressTracker{
|
||||
logger: logger,
|
||||
queries: map[string]*queryTracker{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package queryprogress
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -12,7 +13,7 @@ import (
|
||||
func TestQueryProgressTracking(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
tracker := NewQueryProgressTracker()
|
||||
tracker := NewQueryProgressTracker(slog.Default())
|
||||
|
||||
testQueryId := "test-query"
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/queryparser"
|
||||
|
||||
"io"
|
||||
"log/slog"
|
||||
"math"
|
||||
"net/http"
|
||||
"regexp"
|
||||
@@ -73,8 +74,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/types/ruletypes"
|
||||
traceFunnels "github.com/SigNoz/signoz/pkg/types/tracefunneltypes"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/integrations/messagingQueues/kafka"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/logparsingpipeline"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
|
||||
@@ -97,6 +96,7 @@ func NewRouter() *mux.Router {
|
||||
|
||||
// APIHandler implements the query service public API
|
||||
type APIHandler struct {
|
||||
logger *slog.Logger
|
||||
reader interfaces.Reader
|
||||
ruleManager *rules.Manager
|
||||
querier interfaces.Querier
|
||||
@@ -212,6 +212,7 @@ func NewAPIHandler(opts APIHandlerOpts, config signoz.Config) (*APIHandler, erro
|
||||
//quickFilterModule := quickfilter.NewAPI(opts.QuickFilterModule)
|
||||
|
||||
aH := &APIHandler{
|
||||
logger: slog.Default(),
|
||||
reader: opts.Reader,
|
||||
temporalityMap: make(map[string]map[v3.Temporality]bool),
|
||||
ruleManager: opts.RuleManager,
|
||||
@@ -251,13 +252,13 @@ func NewAPIHandler(opts APIHandlerOpts, config signoz.Config) (*APIHandler, erro
|
||||
// TODO(nitya): remote this in later for multitenancy.
|
||||
orgs, err := opts.Signoz.Modules.OrgGetter.ListByOwnedKeyRange(context.Background())
|
||||
if err != nil {
|
||||
zap.L().Warn("unexpected error while fetching orgs while initializing base api handler", zap.Error(err))
|
||||
aH.logger.Warn("unexpected error while fetching orgs while initializing base api handler", "error", err)
|
||||
}
|
||||
// if the first org with the first user is created then the setup is complete.
|
||||
if len(orgs) == 1 {
|
||||
count, err := opts.Signoz.Modules.UserGetter.CountByOrgID(context.Background(), orgs[0].ID)
|
||||
if err != nil {
|
||||
zap.L().Warn("unexpected error while fetch user count while initializing base api handler", zap.Error(err))
|
||||
aH.logger.Warn("unexpected error while fetching user count while initializing base api handler", "error", err)
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
@@ -312,7 +313,7 @@ func RespondError(w http.ResponseWriter, apiErr model.BaseApiError, data interfa
|
||||
Data: data,
|
||||
})
|
||||
if err != nil {
|
||||
zap.L().Error("error marshalling json response", zap.Error(err))
|
||||
slog.Error("error marshalling json response", "error", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -344,7 +345,7 @@ func RespondError(w http.ResponseWriter, apiErr model.BaseApiError, data interfa
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(code)
|
||||
if n, err := w.Write(b); err != nil {
|
||||
zap.L().Error("error writing response", zap.Int("bytesWritten", n), zap.Error(err))
|
||||
slog.Error("error writing response", "bytes_written", n, "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,7 +357,7 @@ func writeHttpResponse(w http.ResponseWriter, data interface{}) {
|
||||
Data: data,
|
||||
})
|
||||
if err != nil {
|
||||
zap.L().Error("error marshalling json response", zap.Error(err))
|
||||
slog.Error("error marshalling json response", "error", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -364,7 +365,7 @@ func writeHttpResponse(w http.ResponseWriter, data interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if n, err := w.Write(b); err != nil {
|
||||
zap.L().Error("error writing response", zap.Int("bytesWritten", n), zap.Error(err))
|
||||
slog.Error("error writing response", "bytes_written", n, "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -936,14 +937,14 @@ func (aH *APIHandler) metaForLinks(ctx context.Context, rule *ruletypes.Gettable
|
||||
}
|
||||
keys = model.GetLogFieldsV3(ctx, params, logFields)
|
||||
} else {
|
||||
zap.L().Error("failed to get log fields using empty keys; the link might not work as expected", zap.Error(apiErr))
|
||||
aH.logger.ErrorContext(ctx, "failed to get log fields using empty keys", "error", apiErr)
|
||||
}
|
||||
} else if rule.AlertType == ruletypes.AlertTypeTraces {
|
||||
traceFields, err := aH.reader.GetSpanAttributeKeysByNames(ctx, logsv3.GetFieldNames(rule.PostableRule.RuleCondition.CompositeQuery))
|
||||
if err == nil {
|
||||
keys = traceFields
|
||||
} else {
|
||||
zap.L().Error("failed to get span attributes using empty keys; the link might not work as expected", zap.Error(err))
|
||||
aH.logger.ErrorContext(ctx, "failed to get span attributes using empty keys", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1276,14 +1277,14 @@ func (aH *APIHandler) List(rw http.ResponseWriter, r *http.Request) {
|
||||
|
||||
installedIntegrationDashboards, apiErr := aH.IntegrationsController.GetDashboardsForInstalledIntegrations(ctx, orgID)
|
||||
if apiErr != nil {
|
||||
zap.L().Error("failed to get dashboards for installed integrations", zap.Error(apiErr))
|
||||
aH.logger.ErrorContext(ctx, "failed to get dashboards for installed integrations", "error", apiErr)
|
||||
} else {
|
||||
dashboards = append(dashboards, installedIntegrationDashboards...)
|
||||
}
|
||||
|
||||
cloudIntegrationDashboards, apiErr := aH.CloudIntegrationsController.AvailableDashboards(ctx, orgID)
|
||||
if apiErr != nil {
|
||||
zap.L().Error("failed to get dashboards for cloud integrations", zap.Error(apiErr))
|
||||
aH.logger.ErrorContext(ctx, "failed to get dashboards for cloud integrations", "error", apiErr)
|
||||
} else {
|
||||
dashboards = append(dashboards, cloudIntegrationDashboards...)
|
||||
}
|
||||
@@ -1325,7 +1326,7 @@ func (aH *APIHandler) testRule(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
zap.L().Error("Error in getting req body in test rule API", zap.Error(err))
|
||||
aH.logger.ErrorContext(r.Context(), "error reading request body for test rule", "error", err)
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
@@ -1377,7 +1378,7 @@ func (aH *APIHandler) patchRule(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
zap.L().Error("error in getting req body of patch rule API\n", zap.Error(err))
|
||||
aH.logger.ErrorContext(r.Context(), "error reading request body for patch rule", "error", err)
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
@@ -1407,7 +1408,7 @@ func (aH *APIHandler) editRule(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
zap.L().Error("error in getting req body of edit rule API", zap.Error(err))
|
||||
aH.logger.ErrorContext(r.Context(), "error reading request body for edit rule", "error", err)
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
@@ -1432,7 +1433,7 @@ func (aH *APIHandler) createRule(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
zap.L().Error("Error in getting req body for create rule API", zap.Error(err))
|
||||
aH.logger.ErrorContext(r.Context(), "error reading request body for create rule", "error", err)
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
|
||||
return
|
||||
}
|
||||
@@ -1456,7 +1457,7 @@ func (aH *APIHandler) queryRangeMetrics(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
// zap.L().Info(query, apiError)
|
||||
// TODO: add structured logging for query and apiError if needed
|
||||
|
||||
ctx := r.Context()
|
||||
if to := r.FormValue("timeout"); to != "" {
|
||||
@@ -1478,7 +1479,7 @@ func (aH *APIHandler) queryRangeMetrics(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
if res.Err != nil {
|
||||
zap.L().Error("error in query range metrics", zap.Error(res.Err))
|
||||
aH.logger.ErrorContext(r.Context(), "error in query range metrics", "error", res.Err)
|
||||
}
|
||||
|
||||
if res.Err != nil {
|
||||
@@ -1511,7 +1512,7 @@ func (aH *APIHandler) queryMetrics(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// zap.L().Info(query, apiError)
|
||||
// TODO: add structured logging for query and apiError if needed
|
||||
|
||||
ctx := r.Context()
|
||||
if to := r.FormValue("timeout"); to != "" {
|
||||
@@ -1533,7 +1534,7 @@ func (aH *APIHandler) queryMetrics(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if res.Err != nil {
|
||||
zap.L().Error("error in query range metrics", zap.Error(res.Err))
|
||||
aH.logger.ErrorContext(r.Context(), "error in query range metrics", "error", res.Err)
|
||||
}
|
||||
|
||||
if res.Err != nil {
|
||||
@@ -1636,7 +1637,7 @@ func (aH *APIHandler) getServicesTopLevelOps(w http.ResponseWriter, r *http.Requ
|
||||
var params topLevelOpsParams
|
||||
err := json.NewDecoder(r.Body).Decode(¶ms)
|
||||
if err != nil {
|
||||
zap.L().Error("Error in getting req body for get top operations API", zap.Error(err))
|
||||
aH.logger.ErrorContext(r.Context(), "error reading request body for get top operations", "error", err)
|
||||
}
|
||||
|
||||
if params.Service != "" {
|
||||
@@ -2058,7 +2059,7 @@ func (aH *APIHandler) HandleError(w http.ResponseWriter, err error, statusCode i
|
||||
return false
|
||||
}
|
||||
if statusCode == http.StatusInternalServerError {
|
||||
zap.L().Error("HTTP handler, Internal Server Error", zap.Error(err))
|
||||
aH.logger.Error("internal server error in http handler", "error", err)
|
||||
}
|
||||
structuredResp := structuredResponse{
|
||||
Errors: []structuredError{
|
||||
@@ -2152,7 +2153,7 @@ func (aH *APIHandler) onboardProducers(
|
||||
) {
|
||||
messagingQueue, apiErr := ParseKafkaQueueBody(r)
|
||||
if apiErr != nil {
|
||||
zap.L().Error(apiErr.Err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to parse kafka queue body", "error", apiErr.Err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
@@ -2160,7 +2161,7 @@ func (aH *APIHandler) onboardProducers(
|
||||
chq, err := kafka.BuildClickHouseQuery(messagingQueue, kafka.KafkaQueue, "onboard_producers")
|
||||
|
||||
if err != nil {
|
||||
zap.L().Error(err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to build clickhouse query for onboard producers", "error", err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
@@ -2254,7 +2255,7 @@ func (aH *APIHandler) onboardConsumers(
|
||||
) {
|
||||
messagingQueue, apiErr := ParseKafkaQueueBody(r)
|
||||
if apiErr != nil {
|
||||
zap.L().Error(apiErr.Err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to parse kafka queue body", "error", apiErr.Err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
@@ -2262,7 +2263,7 @@ func (aH *APIHandler) onboardConsumers(
|
||||
chq, err := kafka.BuildClickHouseQuery(messagingQueue, kafka.KafkaQueue, "onboard_consumers")
|
||||
|
||||
if err != nil {
|
||||
zap.L().Error(err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to build clickhouse query for onboard consumers", "error", err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
@@ -2401,7 +2402,7 @@ func (aH *APIHandler) onboardKafka(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
messagingQueue, apiErr := ParseKafkaQueueBody(r)
|
||||
if apiErr != nil {
|
||||
zap.L().Error(apiErr.Err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to parse kafka queue body", "error", apiErr.Err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
@@ -2409,7 +2410,7 @@ func (aH *APIHandler) onboardKafka(w http.ResponseWriter, r *http.Request) {
|
||||
queryRangeParams, err := kafka.BuildBuilderQueriesKafkaOnboarding(messagingQueue)
|
||||
|
||||
if err != nil {
|
||||
zap.L().Error(err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to build kafka onboarding queries", "error", err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
@@ -2511,19 +2512,19 @@ func (aH *APIHandler) getNetworkData(w http.ResponseWriter, r *http.Request) {
|
||||
messagingQueue, apiErr := ParseKafkaQueueBody(r)
|
||||
|
||||
if apiErr != nil {
|
||||
zap.L().Error(apiErr.Err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to parse kafka queue body", "error", apiErr.Err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
|
||||
queryRangeParams, err := kafka.BuildQRParamsWithCache(messagingQueue, "throughput", attributeCache)
|
||||
if err != nil {
|
||||
zap.L().Error(err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to build query range params for throughput", "error", err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
if err := validateQueryRangeParamsV3(queryRangeParams); err != nil {
|
||||
zap.L().Error(err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to validate query range params", "error", err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
@@ -2562,12 +2563,12 @@ func (aH *APIHandler) getNetworkData(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
queryRangeParams, err = kafka.BuildQRParamsWithCache(messagingQueue, "fetch-latency", attributeCache)
|
||||
if err != nil {
|
||||
zap.L().Error(err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to build query range params for fetch latency", "error", err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
if err := validateQueryRangeParamsV3(queryRangeParams); err != nil {
|
||||
zap.L().Error(err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to validate query range params for fetch latency", "error", err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
@@ -2622,7 +2623,7 @@ func (aH *APIHandler) getProducerData(w http.ResponseWriter, r *http.Request) {
|
||||
messagingQueue, apiErr := ParseKafkaQueueBody(r)
|
||||
|
||||
if apiErr != nil {
|
||||
zap.L().Error(apiErr.Err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to parse kafka queue body", "error", apiErr.Err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
@@ -2636,13 +2637,13 @@ func (aH *APIHandler) getProducerData(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
queryRangeParams, err := kafka.BuildQueryRangeParams(messagingQueue, "producer", kafkaSpanEval)
|
||||
if err != nil {
|
||||
zap.L().Error(err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to build query range params for producer", "error", err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if err := validateQueryRangeParamsV3(queryRangeParams); err != nil {
|
||||
zap.L().Error(err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to validate query range params for producer", "error", err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
@@ -2679,7 +2680,7 @@ func (aH *APIHandler) getConsumerData(w http.ResponseWriter, r *http.Request) {
|
||||
messagingQueue, apiErr := ParseKafkaQueueBody(r)
|
||||
|
||||
if apiErr != nil {
|
||||
zap.L().Error(apiErr.Err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to parse kafka queue body", "error", apiErr.Err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
@@ -2689,13 +2690,13 @@ func (aH *APIHandler) getConsumerData(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
queryRangeParams, err := kafka.BuildQueryRangeParams(messagingQueue, "consumer", kafkaSpanEval)
|
||||
if err != nil {
|
||||
zap.L().Error(err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to build query range params for consumer", "error", err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if err := validateQueryRangeParamsV3(queryRangeParams); err != nil {
|
||||
zap.L().Error(err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to validate query range params for consumer", "error", err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
@@ -2737,7 +2738,7 @@ func (aH *APIHandler) getPartitionOverviewLatencyData(w http.ResponseWriter, r *
|
||||
messagingQueue, apiErr := ParseKafkaQueueBody(r)
|
||||
|
||||
if apiErr != nil {
|
||||
zap.L().Error(apiErr.Err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to parse kafka queue body", "error", apiErr.Err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
@@ -2747,13 +2748,13 @@ func (aH *APIHandler) getPartitionOverviewLatencyData(w http.ResponseWriter, r *
|
||||
|
||||
queryRangeParams, err := kafka.BuildQueryRangeParams(messagingQueue, "producer-topic-throughput", kafkaSpanEval)
|
||||
if err != nil {
|
||||
zap.L().Error(err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to build query range params for producer topic throughput", "error", err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if err := validateQueryRangeParamsV3(queryRangeParams); err != nil {
|
||||
zap.L().Error(err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to validate query range params for producer topic throughput", "error", err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
@@ -2795,7 +2796,7 @@ func (aH *APIHandler) getConsumerPartitionLatencyData(w http.ResponseWriter, r *
|
||||
messagingQueue, apiErr := ParseKafkaQueueBody(r)
|
||||
|
||||
if apiErr != nil {
|
||||
zap.L().Error(apiErr.Err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to parse kafka queue body", "error", apiErr.Err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
@@ -2805,13 +2806,13 @@ func (aH *APIHandler) getConsumerPartitionLatencyData(w http.ResponseWriter, r *
|
||||
|
||||
queryRangeParams, err := kafka.BuildQueryRangeParams(messagingQueue, "consumer_partition_latency", kafkaSpanEval)
|
||||
if err != nil {
|
||||
zap.L().Error(err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to build query range params for consumer partition latency", "error", err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if err := validateQueryRangeParamsV3(queryRangeParams); err != nil {
|
||||
zap.L().Error(err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to validate query range params for consumer partition latency", "error", err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
@@ -2855,7 +2856,7 @@ func (aH *APIHandler) getProducerThroughputOverview(w http.ResponseWriter, r *ht
|
||||
|
||||
messagingQueue, apiErr := ParseKafkaQueueBody(r)
|
||||
if apiErr != nil {
|
||||
zap.L().Error(apiErr.Err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to parse kafka queue body", "error", apiErr.Err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
@@ -2866,13 +2867,13 @@ func (aH *APIHandler) getProducerThroughputOverview(w http.ResponseWriter, r *ht
|
||||
|
||||
producerQueryRangeParams, err := kafka.BuildQRParamsWithCache(messagingQueue, "producer-throughput-overview", attributeCache)
|
||||
if err != nil {
|
||||
zap.L().Error(err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to build query range params for producer throughput overview", "error", err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if err := validateQueryRangeParamsV3(producerQueryRangeParams); err != nil {
|
||||
zap.L().Error(err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to validate query range params for producer throughput overview", "error", err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
@@ -2908,12 +2909,12 @@ func (aH *APIHandler) getProducerThroughputOverview(w http.ResponseWriter, r *ht
|
||||
|
||||
queryRangeParams, err := kafka.BuildQRParamsWithCache(messagingQueue, "producer-throughput-overview-byte-rate", attributeCache)
|
||||
if err != nil {
|
||||
zap.L().Error(err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to build query range params for producer throughput byte rate", "error", err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
if err := validateQueryRangeParamsV3(queryRangeParams); err != nil {
|
||||
zap.L().Error(err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to validate query range params for producer throughput byte rate", "error", err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
@@ -2971,7 +2972,7 @@ func (aH *APIHandler) getProducerThroughputDetails(w http.ResponseWriter, r *htt
|
||||
messagingQueue, apiErr := ParseKafkaQueueBody(r)
|
||||
|
||||
if apiErr != nil {
|
||||
zap.L().Error(apiErr.Err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to parse kafka queue body", "error", apiErr.Err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
@@ -2981,13 +2982,13 @@ func (aH *APIHandler) getProducerThroughputDetails(w http.ResponseWriter, r *htt
|
||||
|
||||
queryRangeParams, err := kafka.BuildQueryRangeParams(messagingQueue, "producer-throughput-details", kafkaSpanEval)
|
||||
if err != nil {
|
||||
zap.L().Error(err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to build query range params for producer throughput details", "error", err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if err := validateQueryRangeParamsV3(queryRangeParams); err != nil {
|
||||
zap.L().Error(err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to validate query range params for producer throughput details", "error", err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
@@ -3029,7 +3030,7 @@ func (aH *APIHandler) getConsumerThroughputOverview(w http.ResponseWriter, r *ht
|
||||
messagingQueue, apiErr := ParseKafkaQueueBody(r)
|
||||
|
||||
if apiErr != nil {
|
||||
zap.L().Error(apiErr.Err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to parse kafka queue body", "error", apiErr.Err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
@@ -3039,13 +3040,13 @@ func (aH *APIHandler) getConsumerThroughputOverview(w http.ResponseWriter, r *ht
|
||||
|
||||
queryRangeParams, err := kafka.BuildQueryRangeParams(messagingQueue, "consumer-throughput-overview", kafkaSpanEval)
|
||||
if err != nil {
|
||||
zap.L().Error(err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to build query range params for consumer throughput overview", "error", err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if err := validateQueryRangeParamsV3(queryRangeParams); err != nil {
|
||||
zap.L().Error(err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to validate query range params for consumer throughput overview", "error", err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
@@ -3087,7 +3088,7 @@ func (aH *APIHandler) getConsumerThroughputDetails(w http.ResponseWriter, r *htt
|
||||
messagingQueue, apiErr := ParseKafkaQueueBody(r)
|
||||
|
||||
if apiErr != nil {
|
||||
zap.L().Error(apiErr.Err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to parse kafka queue body", "error", apiErr.Err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
@@ -3097,13 +3098,13 @@ func (aH *APIHandler) getConsumerThroughputDetails(w http.ResponseWriter, r *htt
|
||||
|
||||
queryRangeParams, err := kafka.BuildQueryRangeParams(messagingQueue, "consumer-throughput-details", kafkaSpanEval)
|
||||
if err != nil {
|
||||
zap.L().Error(err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to build query range params for consumer throughput details", "error", err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if err := validateQueryRangeParamsV3(queryRangeParams); err != nil {
|
||||
zap.L().Error(err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to validate query range params for consumer throughput details", "error", err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
@@ -3148,7 +3149,7 @@ func (aH *APIHandler) getProducerConsumerEval(w http.ResponseWriter, r *http.Req
|
||||
messagingQueue, apiErr := ParseKafkaQueueBody(r)
|
||||
|
||||
if apiErr != nil {
|
||||
zap.L().Error(apiErr.Err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to parse kafka queue body", "error", apiErr.Err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
@@ -3158,7 +3159,7 @@ func (aH *APIHandler) getProducerConsumerEval(w http.ResponseWriter, r *http.Req
|
||||
|
||||
queryRangeParams, err := kafka.BuildQueryRangeParams(messagingQueue, "producer-consumer-eval", kafkaSpanEval)
|
||||
if err != nil {
|
||||
zap.L().Error(err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to build query range params for producer consumer eval", "error", err)
|
||||
RespondError(w, &model.ApiError{
|
||||
Typ: model.ErrorBadData,
|
||||
Err: err,
|
||||
@@ -3167,7 +3168,7 @@ func (aH *APIHandler) getProducerConsumerEval(w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
|
||||
if err := validateQueryRangeParamsV3(queryRangeParams); err != nil {
|
||||
zap.L().Error(err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to validate query range params for producer consumer eval", "error", err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
@@ -4255,7 +4256,7 @@ func (aH *APIHandler) CreateLogsPipeline(w http.ResponseWriter, r *http.Request)
|
||||
postable []pipelinetypes.PostablePipeline,
|
||||
) (*logparsingpipeline.PipelinesResponse, error) {
|
||||
if len(postable) == 0 {
|
||||
zap.L().Warn("found no pipelines in the http request, this will delete all the pipelines")
|
||||
aH.logger.WarnContext(r.Context(), "found no pipelines in the http request, this will delete all the pipelines")
|
||||
}
|
||||
|
||||
err := aH.LogsParsingPipelineController.ValidatePipelines(ctx, postable)
|
||||
@@ -4453,7 +4454,7 @@ func (aH *APIHandler) QueryRangeV3Format(w http.ResponseWriter, r *http.Request)
|
||||
queryRangeParams, apiErrorObj := ParseQueryRangeParams(r)
|
||||
|
||||
if apiErrorObj != nil {
|
||||
zap.L().Error(apiErrorObj.Err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "error parsing query range params", "error", apiErrorObj.Err)
|
||||
RespondError(w, apiErrorObj, nil)
|
||||
return
|
||||
}
|
||||
@@ -4515,13 +4516,13 @@ func (aH *APIHandler) queryRangeV3(ctx context.Context, queryRangeParams *v3.Que
|
||||
// check if traceID is used as filter (with equal/similar operator) in traces query if yes add timestamp filter to queryRange params
|
||||
isUsed, traceIDs := tracesV3.TraceIdFilterUsedWithEqual(queryRangeParams)
|
||||
if isUsed && len(traceIDs) > 0 {
|
||||
zap.L().Debug("traceID used as filter in traces query")
|
||||
aH.logger.DebugContext(ctx, "trace_id used as filter in traces query")
|
||||
// query signoz_spans table with traceID to get min and max timestamp
|
||||
min, max, err := aH.reader.GetMinAndMaxTimestampForTraceID(ctx, traceIDs)
|
||||
if err == nil {
|
||||
// add timestamp filter to queryRange params
|
||||
tracesV3.AddTimestampFilters(min, max, queryRangeParams)
|
||||
zap.L().Debug("post adding timestamp filter in traces query", zap.Any("queryRangeParams", queryRangeParams))
|
||||
aH.logger.DebugContext(ctx, "post adding timestamp filter in traces query", "query_range_params", queryRangeParams)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4532,9 +4533,8 @@ func (aH *APIHandler) queryRangeV3(ctx context.Context, queryRangeParams *v3.Que
|
||||
onQueryFinished, apiErr := aH.reader.ReportQueryStartForProgressTracking(queryIdHeader)
|
||||
|
||||
if apiErr != nil {
|
||||
zap.L().Error(
|
||||
"couldn't report query start for progress tracking",
|
||||
zap.String("queryId", queryIdHeader), zap.Error(apiErr),
|
||||
aH.logger.ErrorContext(ctx, "failed to report query start for progress tracking",
|
||||
"query_id", queryIdHeader, "error", apiErr,
|
||||
)
|
||||
|
||||
} else {
|
||||
@@ -4709,7 +4709,7 @@ func (aH *APIHandler) QueryRangeV3(w http.ResponseWriter, r *http.Request) {
|
||||
queryRangeParams, apiErrorObj := ParseQueryRangeParams(r)
|
||||
|
||||
if apiErrorObj != nil {
|
||||
zap.L().Error("error parsing metric query range params", zap.Error(apiErrorObj.Err))
|
||||
aH.logger.ErrorContext(r.Context(), "error parsing metric query range params", "error", apiErrorObj.Err)
|
||||
RespondError(w, apiErrorObj, nil)
|
||||
return
|
||||
}
|
||||
@@ -4717,7 +4717,7 @@ func (aH *APIHandler) QueryRangeV3(w http.ResponseWriter, r *http.Request) {
|
||||
// add temporality for each metric
|
||||
temporalityErr := aH.PopulateTemporality(r.Context(), orgID, queryRangeParams)
|
||||
if temporalityErr != nil {
|
||||
zap.L().Error("Error while adding temporality for metrics", zap.Error(temporalityErr))
|
||||
aH.logger.ErrorContext(r.Context(), "error adding temporality for metrics", "error", temporalityErr)
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: temporalityErr}, nil)
|
||||
return
|
||||
}
|
||||
@@ -4761,9 +4761,8 @@ func (aH *APIHandler) GetQueryProgressUpdates(w http.ResponseWriter, r *http.Req
|
||||
progressCh, unsubscribe, apiErr := aH.reader.SubscribeToQueryProgress(queryId)
|
||||
if apiErr != nil {
|
||||
// Shouldn't happen unless query progress requested after query finished
|
||||
zap.L().Warn(
|
||||
"couldn't subscribe to query progress",
|
||||
zap.String("queryId", queryId), zap.Any("error", apiErr),
|
||||
aH.logger.WarnContext(r.Context(), "failed to subscribe to query progress",
|
||||
"query_id", queryId, "error", apiErr,
|
||||
)
|
||||
return
|
||||
}
|
||||
@@ -4772,25 +4771,22 @@ func (aH *APIHandler) GetQueryProgressUpdates(w http.ResponseWriter, r *http.Req
|
||||
for queryProgress := range progressCh {
|
||||
msg, err := json.Marshal(queryProgress)
|
||||
if err != nil {
|
||||
zap.L().Error(
|
||||
"failed to serialize progress message",
|
||||
zap.String("queryId", queryId), zap.Any("progress", queryProgress), zap.Error(err),
|
||||
aH.logger.ErrorContext(r.Context(), "failed to serialize progress message",
|
||||
"query_id", queryId, "progress", queryProgress, "error", err,
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
err = c.WriteMessage(websocket.TextMessage, msg)
|
||||
if err != nil {
|
||||
zap.L().Error(
|
||||
"failed to write progress msg to websocket",
|
||||
zap.String("queryId", queryId), zap.String("msg", string(msg)), zap.Error(err),
|
||||
aH.logger.ErrorContext(r.Context(), "failed to write progress message to websocket",
|
||||
"query_id", queryId, "msg", string(msg), "error", err,
|
||||
)
|
||||
break
|
||||
|
||||
} else {
|
||||
zap.L().Debug(
|
||||
"wrote progress msg to websocket",
|
||||
zap.String("queryId", queryId), zap.String("msg", string(msg)), zap.Error(err),
|
||||
aH.logger.DebugContext(r.Context(), "wrote progress message to websocket",
|
||||
"query_id", queryId, "msg", string(msg),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -4874,13 +4870,13 @@ func (aH *APIHandler) queryRangeV4(ctx context.Context, queryRangeParams *v3.Que
|
||||
// check if traceID is used as filter (with equal/similar operator) in traces query if yes add timestamp filter to queryRange params
|
||||
isUsed, traceIDs := tracesV3.TraceIdFilterUsedWithEqual(queryRangeParams)
|
||||
if isUsed && len(traceIDs) > 0 {
|
||||
zap.L().Debug("traceID used as filter in traces query")
|
||||
aH.logger.DebugContext(ctx, "trace_id used as filter in traces query")
|
||||
// query signoz_spans table with traceID to get min and max timestamp
|
||||
min, max, err := aH.reader.GetMinAndMaxTimestampForTraceID(ctx, traceIDs)
|
||||
if err == nil {
|
||||
// add timestamp filter to queryRange params
|
||||
tracesV3.AddTimestampFilters(min, max, queryRangeParams)
|
||||
zap.L().Debug("post adding timestamp filter in traces query", zap.Any("queryRangeParams", queryRangeParams))
|
||||
aH.logger.DebugContext(ctx, "post adding timestamp filter in traces query", "query_range_params", queryRangeParams)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4932,7 +4928,7 @@ func (aH *APIHandler) QueryRangeV4(w http.ResponseWriter, r *http.Request) {
|
||||
queryRangeParams, apiErrorObj := ParseQueryRangeParams(r)
|
||||
|
||||
if apiErrorObj != nil {
|
||||
zap.L().Error("error parsing metric query range params", zap.Error(apiErrorObj.Err))
|
||||
aH.logger.ErrorContext(r.Context(), "error parsing metric query range params", "error", apiErrorObj.Err)
|
||||
RespondError(w, apiErrorObj, nil)
|
||||
return
|
||||
}
|
||||
@@ -4941,7 +4937,7 @@ func (aH *APIHandler) QueryRangeV4(w http.ResponseWriter, r *http.Request) {
|
||||
// add temporality for each metric
|
||||
temporalityErr := aH.PopulateTemporality(r.Context(), orgID, queryRangeParams)
|
||||
if temporalityErr != nil {
|
||||
zap.L().Error("Error while adding temporality for metrics", zap.Error(temporalityErr))
|
||||
aH.logger.ErrorContext(r.Context(), "error adding temporality for metrics", "error", temporalityErr)
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: temporalityErr}, nil)
|
||||
return
|
||||
}
|
||||
@@ -4990,7 +4986,7 @@ func (aH *APIHandler) getQueueOverview(w http.ResponseWriter, r *http.Request) {
|
||||
queueListRequest, apiErr := ParseQueueBody(r)
|
||||
|
||||
if apiErr != nil {
|
||||
zap.L().Error(apiErr.Err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to parse queue body", "error", apiErr.Err)
|
||||
RespondError(w, apiErr, nil)
|
||||
return
|
||||
}
|
||||
@@ -4998,7 +4994,7 @@ func (aH *APIHandler) getQueueOverview(w http.ResponseWriter, r *http.Request) {
|
||||
chq, err := queues2.BuildOverviewQuery(queueListRequest)
|
||||
|
||||
if err != nil {
|
||||
zap.L().Error(err.Error())
|
||||
aH.logger.ErrorContext(r.Context(), "failed to build queue overview query", "error", err)
|
||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: fmt.Errorf("error building clickhouse query: %v", err)}, nil)
|
||||
return
|
||||
}
|
||||
@@ -5029,7 +5025,7 @@ func (aH *APIHandler) getDomainList(w http.ResponseWriter, r *http.Request) {
|
||||
// Parse the request body to get third-party query parameters
|
||||
thirdPartyQueryRequest, apiErr := ParseRequestBody(r)
|
||||
if apiErr != nil {
|
||||
zap.L().Error("Failed to parse request body", zap.Error(apiErr))
|
||||
aH.logger.ErrorContext(r.Context(), "failed to parse request body", "error", apiErr)
|
||||
render.Error(w, errorsV2.New(errorsV2.TypeInvalidInput, errorsV2.CodeInvalidInput, apiErr.Error()))
|
||||
return
|
||||
}
|
||||
@@ -5037,7 +5033,7 @@ func (aH *APIHandler) getDomainList(w http.ResponseWriter, r *http.Request) {
|
||||
// Build the v5 query range request for domain listing
|
||||
queryRangeRequest, err := thirdpartyapi.BuildDomainList(thirdPartyQueryRequest)
|
||||
if err != nil {
|
||||
zap.L().Error("Failed to build domain list query", zap.Error(err))
|
||||
aH.logger.ErrorContext(r.Context(), "failed to build domain list query", "error", err)
|
||||
apiErrObj := errorsV2.New(errorsV2.TypeInvalidInput, errorsV2.CodeInvalidInput, err.Error())
|
||||
render.Error(w, apiErrObj)
|
||||
return
|
||||
@@ -5050,7 +5046,7 @@ func (aH *APIHandler) getDomainList(w http.ResponseWriter, r *http.Request) {
|
||||
// Execute the query using the v5 querier
|
||||
result, err := aH.Signoz.Querier.QueryRange(ctx, orgID, queryRangeRequest)
|
||||
if err != nil {
|
||||
zap.L().Error("Query execution failed", zap.Error(err))
|
||||
aH.logger.ErrorContext(r.Context(), "query execution failed", "error", err)
|
||||
apiErrObj := errorsV2.New(errorsV2.TypeInvalidInput, errorsV2.CodeInvalidInput, err.Error())
|
||||
render.Error(w, apiErrObj)
|
||||
return
|
||||
@@ -5089,7 +5085,7 @@ func (aH *APIHandler) getDomainInfo(w http.ResponseWriter, r *http.Request) {
|
||||
// Parse the request body to get third-party query parameters
|
||||
thirdPartyQueryRequest, apiErr := ParseRequestBody(r)
|
||||
if apiErr != nil {
|
||||
zap.L().Error("Failed to parse request body", zap.Error(apiErr))
|
||||
aH.logger.ErrorContext(r.Context(), "failed to parse request body", "error", apiErr)
|
||||
render.Error(w, errorsV2.New(errorsV2.TypeInvalidInput, errorsV2.CodeInvalidInput, apiErr.Error()))
|
||||
return
|
||||
}
|
||||
@@ -5097,7 +5093,7 @@ func (aH *APIHandler) getDomainInfo(w http.ResponseWriter, r *http.Request) {
|
||||
// Build the v5 query range request for domain info
|
||||
queryRangeRequest, err := thirdpartyapi.BuildDomainInfo(thirdPartyQueryRequest)
|
||||
if err != nil {
|
||||
zap.L().Error("Failed to build domain info query", zap.Error(err))
|
||||
aH.logger.ErrorContext(r.Context(), "failed to build domain info query", "error", err)
|
||||
apiErrObj := errorsV2.New(errorsV2.TypeInvalidInput, errorsV2.CodeInvalidInput, err.Error())
|
||||
render.Error(w, apiErrObj)
|
||||
return
|
||||
@@ -5110,7 +5106,7 @@ func (aH *APIHandler) getDomainInfo(w http.ResponseWriter, r *http.Request) {
|
||||
// Execute the query using the v5 querier
|
||||
result, err := aH.Signoz.Querier.QueryRange(ctx, orgID, queryRangeRequest)
|
||||
if err != nil {
|
||||
zap.L().Error("Query execution failed", zap.Error(err))
|
||||
aH.logger.ErrorContext(r.Context(), "query execution failed", "error", err)
|
||||
apiErrObj := errorsV2.New(errorsV2.TypeInvalidInput, errorsV2.CodeInvalidInput, err.Error())
|
||||
render.Error(w, apiErrObj)
|
||||
return
|
||||
|
||||
@@ -17,9 +17,10 @@ import (
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/postprocess"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"log/slog"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
@@ -427,7 +428,7 @@ func (h *HostsRepo) GetHostList(ctx context.Context, orgID valuer.UUID, req mode
|
||||
|
||||
step := int64(math.Max(float64(common.MinAllowedStepInterval(req.Start, req.End)), 60))
|
||||
if step <= 0 {
|
||||
zap.L().Error("step is less than or equal to 0", zap.Int64("step", step))
|
||||
slog.ErrorContext(ctx, "step is less than or equal to 0", "step", step)
|
||||
return resp, errors.New("step is less than or equal to 0")
|
||||
}
|
||||
|
||||
|
||||
@@ -8,10 +8,11 @@ import (
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"log/slog"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||
"github.com/SigNoz/signoz/pkg/types/pipelinetypes"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var lockLogsPipelineSpec sync.RWMutex
|
||||
@@ -154,14 +155,14 @@ func buildCollectorPipelineProcessorsList(
|
||||
|
||||
func checkDuplicateString(pipeline []string) bool {
|
||||
exists := make(map[string]bool, len(pipeline))
|
||||
zap.L().Debug("checking duplicate processors in the pipeline:", zap.Any("pipeline", pipeline))
|
||||
slog.Debug("checking duplicate processors in the pipeline", "pipeline", pipeline)
|
||||
for _, processor := range pipeline {
|
||||
name := processor
|
||||
if _, ok := exists[name]; ok {
|
||||
zap.L().Error(
|
||||
slog.Error(
|
||||
"duplicate processor name detected in generated collector config for log pipelines",
|
||||
zap.String("processor", processor),
|
||||
zap.Any("pipeline", pipeline),
|
||||
"processor", processor,
|
||||
"pipeline", pipeline,
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/google/uuid"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -175,7 +175,7 @@ func (ic *LogParsingPipelineController) getEffectivePipelinesByVersion(
|
||||
if version >= 0 {
|
||||
savedPipelines, err := ic.getPipelinesByVersion(ctx, orgID.String(), version)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to get pipelines for version", zap.Int("version", version), zap.Error(err))
|
||||
slog.ErrorContext(ctx, "failed to get pipelines for version", "version", version, "error", err)
|
||||
return nil, err
|
||||
}
|
||||
result = savedPipelines
|
||||
@@ -227,7 +227,7 @@ func (ic *LogParsingPipelineController) GetPipelinesByVersion(
|
||||
) (*PipelinesResponse, error) {
|
||||
pipelines, err := ic.getEffectivePipelinesByVersion(ctx, orgId, version)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to get pipelines for version", zap.Int("version", version), zap.Error(err))
|
||||
slog.ErrorContext(ctx, "failed to get pipelines for version", "version", version, "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -235,7 +235,7 @@ func (ic *LogParsingPipelineController) GetPipelinesByVersion(
|
||||
if version >= 0 {
|
||||
cv, err := agentConf.GetConfigVersion(ctx, orgId, opamptypes.ElementTypeLogPipelines, version)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to get config for version", zap.Int("version", version), zap.Error(err))
|
||||
slog.ErrorContext(ctx, "failed to get config for version", "version", version, "error", err)
|
||||
return nil, err
|
||||
}
|
||||
configVersion = cv
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"log/slog"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
@@ -13,7 +15,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/pipelinetypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Repo handles DDL and DML ops on ingestion pipeline
|
||||
@@ -80,7 +81,7 @@ func (r *Repo) insertPipeline(
|
||||
Model(&insertRow.StoreablePipeline).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
zap.L().Error("error in inserting pipeline data", zap.Error(err))
|
||||
slog.ErrorContext(ctx, "error in inserting pipeline data", "error", err)
|
||||
return nil, errors.WrapInternalf(err, errors.CodeInternal, "failed to insert pipeline")
|
||||
}
|
||||
|
||||
@@ -136,12 +137,12 @@ func (r *Repo) GetPipeline(
|
||||
Where("org_id = ?", orgID).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to get ingestion pipeline from db", zap.Error(err))
|
||||
slog.ErrorContext(ctx, "failed to get ingestion pipeline from db", "error", err)
|
||||
return nil, errors.WrapInternalf(err, errors.CodeInternal, "failed to get ingestion pipeline from db")
|
||||
}
|
||||
|
||||
if len(storablePipelines) == 0 {
|
||||
zap.L().Warn("No row found for ingestion pipeline id", zap.String("id", id))
|
||||
slog.WarnContext(ctx, "no row found for ingestion pipeline id", "id", id)
|
||||
return nil, errors.NewNotFoundf(errors.CodeNotFound, "no row found for ingestion pipeline id %v", id)
|
||||
}
|
||||
|
||||
@@ -149,11 +150,11 @@ func (r *Repo) GetPipeline(
|
||||
gettablePipeline := pipelinetypes.GettablePipeline{}
|
||||
gettablePipeline.StoreablePipeline = storablePipelines[0]
|
||||
if err := gettablePipeline.ParseRawConfig(); err != nil {
|
||||
zap.L().Error("invalid pipeline config found", zap.String("id", id), zap.Error(err))
|
||||
slog.ErrorContext(ctx, "invalid pipeline config found", "id", id, "error", err)
|
||||
return nil, err
|
||||
}
|
||||
if err := gettablePipeline.ParseFilter(); err != nil {
|
||||
zap.L().Error("invalid pipeline filter found", zap.String("id", id), zap.Error(err))
|
||||
slog.ErrorContext(ctx, "invalid pipeline filter found", "id", id, "error", err)
|
||||
return nil, err
|
||||
}
|
||||
return &gettablePipeline, nil
|
||||
|
||||
@@ -2,12 +2,12 @@ package metrics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func AddMetricValueFilter(mq *v3.BuilderQuery) *v3.MetricValueFilter {
|
||||
@@ -69,7 +69,7 @@ func AddMetricValueFilter(mq *v3.BuilderQuery) *v3.MetricValueFilter {
|
||||
case string:
|
||||
numericValue, err := strconv.ParseFloat(v, 64)
|
||||
if err != nil {
|
||||
zap.L().Warn("invalid type for metric value filter, ignoring", zap.Any("type", reflect.TypeOf(v)), zap.String("value", v))
|
||||
slog.Warn("invalid type for metric value filter, ignoring", "type", reflect.TypeOf(v), "value", v)
|
||||
continue
|
||||
}
|
||||
metricValueFilter = &v3.MetricValueFilter{
|
||||
@@ -111,11 +111,11 @@ func FormattedValue(v interface{}) string {
|
||||
case int, float32, float64, bool:
|
||||
return strings.Join(strings.Fields(fmt.Sprint(x)), ",")
|
||||
default:
|
||||
zap.L().Error("invalid type for formatted value", zap.Any("type", reflect.TypeOf(x[0])))
|
||||
slog.Error("invalid type for formatted value", "type", reflect.TypeOf(x[0]))
|
||||
return ""
|
||||
}
|
||||
default:
|
||||
zap.L().Error("invalid type for formatted value", zap.Any("type", reflect.TypeOf(x)))
|
||||
slog.Error("invalid type for formatted value", "type", reflect.TypeOf(x))
|
||||
return ""
|
||||
}
|
||||
}
|
||||
@@ -144,11 +144,11 @@ func PromFormattedValue(v interface{}) string {
|
||||
}
|
||||
return strings.Join(str, "|")
|
||||
default:
|
||||
zap.L().Error("invalid type for prom formatted value", zap.Any("type", reflect.TypeOf(x[0])))
|
||||
slog.Error("invalid type for prom formatted value", "type", reflect.TypeOf(x[0]))
|
||||
return ""
|
||||
}
|
||||
default:
|
||||
zap.L().Error("invalid type for prom formatted value", zap.Any("type", reflect.TypeOf(x)))
|
||||
slog.Error("invalid type for prom formatted value", "type", reflect.TypeOf(x))
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"log/slog"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
|
||||
@@ -173,14 +173,14 @@ func (receiver *SummaryService) GetMetricsSummary(ctx context.Context, orgID val
|
||||
if data != nil {
|
||||
jsonData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
zap.L().Error("Error marshalling data:", zap.Error(err))
|
||||
slog.Error("error marshalling data", "error", err)
|
||||
return &model.ApiError{Typ: "MarshallingErr", Err: err}
|
||||
}
|
||||
|
||||
var dashboards map[string][]metrics_explorer.Dashboard
|
||||
err = json.Unmarshal(jsonData, &dashboards)
|
||||
if err != nil {
|
||||
zap.L().Error("Error unmarshalling data:", zap.Error(err))
|
||||
slog.Error("error unmarshalling data", "error", err)
|
||||
return &model.ApiError{Typ: "UnMarshallingErr", Err: err}
|
||||
}
|
||||
if _, ok := dashboards[metricName]; ok {
|
||||
@@ -264,7 +264,7 @@ func (receiver *SummaryService) GetRelatedMetrics(ctx context.Context, params *m
|
||||
if err != nil {
|
||||
// If we hit a deadline exceeded error, proceed with only name similarity
|
||||
if errors.Is(err.Err, context.DeadlineExceeded) {
|
||||
zap.L().Warn("Attribute similarity calculation timed out, proceeding with name similarity only")
|
||||
slog.Warn("attribute similarity calculation timed out, proceeding with name similarity only")
|
||||
attrSimilarityScores = make(map[string]metrics_explorer.RelatedMetricsScore)
|
||||
} else {
|
||||
return nil, err
|
||||
@@ -350,12 +350,12 @@ func (receiver *SummaryService) GetRelatedMetrics(ctx context.Context, params *m
|
||||
if names != nil {
|
||||
jsonData, err := json.Marshal(names)
|
||||
if err != nil {
|
||||
zap.L().Error("Error marshalling dashboard data", zap.Error(err))
|
||||
slog.Error("error marshalling dashboard data", "error", err)
|
||||
return &model.ApiError{Typ: "MarshallingErr", Err: err}
|
||||
}
|
||||
err = json.Unmarshal(jsonData, &dashboardsRelatedData)
|
||||
if err != nil {
|
||||
zap.L().Error("Error unmarshalling dashboard data", zap.Error(err))
|
||||
slog.Error("error unmarshalling dashboard data", "error", err)
|
||||
return &model.ApiError{Typ: "UnMarshallingErr", Err: err}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user