Merge pull request #2739 from SigNoz/release/v0.19.0

Release/v0.19.0
This commit is contained in:
Ankit Nayan 2023-05-21 01:39:55 +05:30 committed by GitHub
commit 1ded475b37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
528 changed files with 25198 additions and 3789 deletions

2
.github/CODEOWNERS vendored
View File

@ -2,6 +2,6 @@
# Owners are automatically requested for review for PRs that changes code
# that they own.
* @ankitnayan
/frontend/ @palashgdev @pranshuchittora
/frontend/ @palashgdev
/deploy/ @prashant-shahi
**/query-service/ @srikanthccv

View File

@ -7,12 +7,7 @@ jobs:
lint-commits:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2.3.1
- uses: actions/checkout@v3
with:
# we actually need "github.event.pull_request.commits + 1" commit
fetch-depth: 0
- uses: actions/setup-node@v2.1.0
# or just "yarn" if you depend on "@commitlint/cli" already
- run: yarn add @commitlint/cli
- run: yarn add @commitlint/config-conventional
- run: yarn run commitlint --config ./node_modules/@commitlint/config-conventional/index.js --from HEAD~${{ github.event.pull_request.commits }} --to HEAD
- uses: wagoid/commitlint-github-action@v5

View File

@ -5,7 +5,7 @@ name: VerifyIssue
on:
pull_request:
types: [edited, synchronize, opened, reopened]
types: [edited, opened]
check_run:
jobs:
@ -14,6 +14,6 @@ jobs:
name: Ensure Pull Request has a linked issue.
steps:
- name: Verify Linked Issue
uses: hattan/verify-linked-issue-action@v1.1.0
uses: srikanthccv/verify-linked-issue-action@v0.70
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

2
.gitignore vendored
View File

@ -1,7 +1,5 @@
node_modules
yarn.lock
package.json
deploy/docker/environment_tiny/common_test
frontend/node_modules

View File

@ -85,9 +85,9 @@ Hier findest du die vollständige Liste von unterstützten Programmiersprachen -
### Bereitstellung mit Docker
Bitte folge den [hier](https://signoz.io/docs/deployment/docker/) aufgelisteten Schritten um deine Anwendung mit Docker bereitzustellen.
Bitte folge den [hier](https://signoz.io/docs/install/docker/) aufgelisteten Schritten um deine Anwendung mit Docker bereitzustellen.
Die [Anleitungen zur Fehlerbehebung](https://signoz.io/docs/deployment/troubleshooting) könnten hilfreich sein, falls du auf irgendwelche Schwierigkeiten stößt.
Die [Anleitungen zur Fehlerbehebung](https://signoz.io/docs/install/troubleshooting/) könnten hilfreich sein, falls du auf irgendwelche Schwierigkeiten stößt.
<p>&nbsp </p>

View File

@ -70,7 +70,6 @@ SigNoz helps developers monitor applications and troubleshoot problems in their
<br /><br />
<img align="left" src="https://signoz-public.s3.us-east-2.amazonaws.com/Contributing.svg" width="50px" />
## Join our Slack community
@ -78,7 +77,6 @@ Come say Hi to us on [Slack](https://signoz.io/slack) 👋
<br /><br />
<img align="left" src="https://signoz-public.s3.us-east-2.amazonaws.com/Features.svg" width="50px" />
## Features:
@ -89,13 +87,12 @@ Come say Hi to us on [Slack](https://signoz.io/slack) 👋
- Filter traces by service name, operation, latency, error, tags/annotations.
- Run aggregates on trace data (events/spans) to get business relevant metrics. e.g. You can get error rate and 99th percentile latency of `customer_type: gold` or `deployment_version: v2` or `external_call: paypal`
- Native support for OpenTelemetry Logs, advanced log query builder, and automatic log collection from k8s cluster
- Lightening quick log analytics ([Logs Perf. Benchmark](https://signoz.io/blog/logs-performance-benchmark/))
- Lightning quick log analytics ([Logs Perf. Benchmark](https://signoz.io/blog/logs-performance-benchmark/))
- End-to-End visibility into infrastructure performance, ingest metrics from all kinds of host environments
- Easy to set alerts with DIY query builder
<br /><br />
<img align="left" src="https://signoz-public.s3.us-east-2.amazonaws.com/WhatsCool.svg" width="50px" />
## Why SigNoz?
@ -124,15 +121,14 @@ You can find the complete list of languages here - https://opentelemetry.io/docs
<br /><br />
<img align="left" src="https://signoz-public.s3.us-east-2.amazonaws.com/Philosophy.svg" width="50px" />
## Getting Started
### Deploy using Docker
Please follow the steps listed [here](https://signoz.io/docs/deployment/docker/) to install using docker
Please follow the steps listed [here](https://signoz.io/docs/install/docker/) to install using docker
The [troubleshooting instructions](https://signoz.io/docs/deployment/troubleshooting) may be helpful if you face any issues.
The [troubleshooting instructions](https://signoz.io/docs/install/troubleshooting/) may be helpful if you face any issues.
<p>&nbsp </p>
@ -143,7 +139,6 @@ Please follow the steps listed [here](https://signoz.io/docs/deployment/helm_cha
<br /><br />
<img align="left" src="https://signoz-public.s3.us-east-2.amazonaws.com/UseSigNoz.svg" width="50px" />
## Comparisons to Familiar Tools
@ -185,7 +180,6 @@ We have published benchmarks comparing Loki with SigNoz. Check it out [here](htt
<br /><br />
<img align="left" src="https://signoz-public.s3.us-east-2.amazonaws.com/Contributors.svg" width="50px" />
## Contributing
@ -212,7 +206,6 @@ Not sure how to get started? Just ping us on `#contributing` in our [slack commu
<br /><br />
<img align="left" src="https://signoz-public.s3.us-east-2.amazonaws.com/DevelopingLocally.svg" width="50px" />
## Documentation
@ -220,7 +213,6 @@ You can find docs at https://signoz.io/docs/. If you need any clarification or f
<br /><br />
<img align="left" src="https://signoz-public.s3.us-east-2.amazonaws.com/Contributing.svg" width="50px" />
## Community

View File

@ -84,9 +84,9 @@ Você pode encontrar a lista completa de linguagens aqui - https://opentelemetry
### Implantar usando Docker
Siga as etapas listadas [aqui](https://signoz.io/docs/deployment/docker/) para instalar usando o Docker.
Siga as etapas listadas [aqui](https://signoz.io/docs/install/docker/) para instalar usando o Docker.
Esse [guia para solução de problemas](https://signoz.io/docs/deployment/troubleshooting) pode ser útil se você enfrentar quaisquer problemas.
Esse [guia para solução de problemas](https://signoz.io/docs/install/troubleshooting/) pode ser útil se você enfrentar quaisquer problemas.
<p>&nbsp </p>

View File

@ -80,9 +80,9 @@ SigNoz帮助开发人员监控应用并排查已部署应用中的问题。SigNo
### 使用Docker部署
请按照[这里](https://signoz.io/docs/deployment/docker/)列出的步骤使用Docker来安装
请按照[这里](https://signoz.io/docs/install/docker/)列出的步骤使用Docker来安装
如果你遇到任何问题,这个[排查指南](https://signoz.io/docs/deployment/troubleshooting)会对你有帮助。
如果你遇到任何问题,这个[排查指南](https://signoz.io/docs/install/troubleshooting/)会对你有帮助。
<p>&nbsp </p>

View File

@ -7,9 +7,21 @@
</default>
<s3>
<type>s3</type>
<endpoint>https://BUCKET-NAME.s3.amazonaws.com/data/</endpoint>
<!-- For S3 cold storage,
if region is us-east-1, endpoint can be https://<bucket-name>.s3.amazonaws.com
if region is not us-east-1, endpoint should be https://<bucket-name>.s3-<region>.amazonaws.com
For GCS cold storage,
endpoint should be https://storage.googleapis.com/<bucket-name>/data/
-->
<endpoint>https://BUCKET-NAME.s3-REGION-NAME.amazonaws.com/data/</endpoint>
<access_key_id>ACCESS-KEY-ID</access_key_id>
<secret_access_key>SECRET-ACCESS-KEY</secret_access_key>
<!-- In case of S3, uncomment the below configuration in case you want to read
AWS credentials from the Environment variables if they exist. -->
<!-- <use_environment_credentials>true</use_environment_credentials> -->
<!-- In case of GCS, uncomment the below configuration, since GCS does
not support batch deletion and result in error messages in logs. -->
<!-- <support_batch_delete>false</support_batch_delete> -->
</s3>
</disks>
<policies>

View File

@ -34,7 +34,7 @@ x-clickhouse-depend: &clickhouse-depend
services:
zookeeper-1:
image: bitnami/zookeeper:3.7.0
image: bitnami/zookeeper:3.7.1
hostname: zookeeper-1
user: root
ports:
@ -124,7 +124,7 @@ services:
# - ./data/clickhouse-3/:/var/lib/clickhouse/
alertmanager:
image: signoz/alertmanager:0.23.0-0.2
image: signoz/alertmanager:0.23.1
volumes:
- ./data/alertmanager:/data
command:
@ -137,7 +137,7 @@ services:
condition: on-failure
query-service:
image: signoz/query-service:0.18.3
image: signoz/query-service:0.19.0
command: ["-config=/root/config/prometheus.yml"]
# ports:
# - "6060:6060" # pprof port
@ -166,7 +166,7 @@ services:
<<: *clickhouse-depend
frontend:
image: signoz/frontend:0.18.3
image: signoz/frontend:0.19.0
deploy:
restart_policy:
condition: on-failure
@ -179,8 +179,8 @@ services:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector:
image: signoz/signoz-otel-collector:0.66.7
command: ["--config=/etc/otel-collector-config.yaml"]
image: signoz/signoz-otel-collector:0.76.1
command: ["--config=/etc/otel-collector-config.yaml", "--feature-gates=-pkg.translator.prometheus.NormalizeName"]
user: root # required for reading docker container logs
volumes:
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
@ -208,8 +208,8 @@ services:
<<: *clickhouse-depend
otel-collector-metrics:
image: signoz/signoz-otel-collector:0.66.7
command: ["--config=/etc/otel-collector-metrics-config.yaml"]
image: signoz/signoz-otel-collector:0.76.1
command: ["--config=/etc/otel-collector-metrics-config.yaml", "--feature-gates=-pkg.translator.prometheus.NormalizeName"]
volumes:
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml
# ports:

View File

@ -75,7 +75,7 @@ processors:
timeout: 10s
resourcedetection:
# Using OTEL_RESOURCE_ATTRIBUTES envvar, env detector adds custom labels.
detectors: [env, system] # include ec2 for AWS, gce for GCP and azure for Azure.
detectors: [env, system] # include ec2 for AWS, gcp for GCP and azure for Azure.
timeout: 2s
signozspanmetrics/prometheus:
metrics_exporter: prometheus

View File

@ -7,9 +7,21 @@
</default>
<s3>
<type>s3</type>
<endpoint>https://BUCKET-NAME.s3.amazonaws.com/data/</endpoint>
<!-- For S3 cold storage,
if region is us-east-1, endpoint can be https://<bucket-name>.s3.amazonaws.com
if region is not us-east-1, endpoint should be https://<bucket-name>.s3-<region>.amazonaws.com
For GCS cold storage,
endpoint should be https://storage.googleapis.com/<bucket-name>/data/
-->
<endpoint>https://BUCKET-NAME.s3-REGION-NAME.amazonaws.com/data/</endpoint>
<access_key_id>ACCESS-KEY-ID</access_key_id>
<secret_access_key>SECRET-ACCESS-KEY</secret_access_key>
<!-- In case of S3, uncomment the below configuration in case you want to read
AWS credentials from the Environment variables if they exist. -->
<!-- <use_environment_credentials>true</use_environment_credentials> -->
<!-- In case of GCS, uncomment the below configuration, since GCS does
not support batch deletion and result in error messages in logs. -->
<!-- <support_batch_delete>false</support_batch_delete> -->
</s3>
</disks>
<policies>

View File

@ -27,7 +27,7 @@ services:
alertmanager:
container_name: alertmanager
image: signoz/alertmanager:0.23.0-0.2
image: signoz/alertmanager:0.23.1
volumes:
- ./data/alertmanager:/data
depends_on:
@ -41,8 +41,8 @@ services:
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
otel-collector:
container_name: otel-collector
image: signoz/signoz-otel-collector:0.66.7
command: ["--config=/etc/otel-collector-config.yaml"]
image: signoz/signoz-otel-collector:0.76.1
command: ["--config=/etc/otel-collector-config.yaml", "--feature-gates=-pkg.translator.prometheus.NormalizeName"]
# user: root # required for reading docker container logs
volumes:
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
@ -67,8 +67,8 @@ services:
otel-collector-metrics:
container_name: otel-collector-metrics
image: signoz/signoz-otel-collector:0.66.7
command: ["--config=/etc/otel-collector-metrics-config.yaml"]
image: signoz/signoz-otel-collector:0.76.1
command: ["--config=/etc/otel-collector-metrics-config.yaml", "--feature-gates=-pkg.translator.prometheus.NormalizeName"]
volumes:
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml
# ports:

View File

@ -36,7 +36,7 @@ x-clickhouse-depend: &clickhouse-depend
services:
zookeeper-1:
image: bitnami/zookeeper:3.7.0
image: bitnami/zookeeper:3.7.1
container_name: zookeeper-1
hostname: zookeeper-1
user: root
@ -139,7 +139,7 @@ services:
# - ./user_scripts:/var/lib/clickhouse/user_scripts/
alertmanager:
image: signoz/alertmanager:${ALERTMANAGER_TAG:-0.23.0-0.2}
image: signoz/alertmanager:${ALERTMANAGER_TAG:-0.23.1}
volumes:
- ./data/alertmanager:/data
depends_on:
@ -150,10 +150,10 @@ services:
- --queryService.url=http://query-service:8085
- --storage.path=/data
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
query-service:
image: signoz/query-service:${DOCKER_TAG:-0.18.3}
image: signoz/query-service:${DOCKER_TAG:-0.19.0}
container_name: query-service
command: ["-config=/root/config/prometheus.yml"]
# ports:
@ -181,7 +181,7 @@ services:
<<: *clickhouse-depend
frontend:
image: signoz/frontend:${DOCKER_TAG:-0.18.3}
image: signoz/frontend:${DOCKER_TAG:-0.19.0}
container_name: frontend
restart: on-failure
depends_on:
@ -193,8 +193,8 @@ services:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector:
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.66.7}
command: ["--config=/etc/otel-collector-config.yaml"]
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.76.1}
command: ["--config=/etc/otel-collector-config.yaml", "--feature-gates=-pkg.translator.prometheus.NormalizeName"]
user: root # required for reading docker container logs
volumes:
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
@ -219,8 +219,8 @@ services:
<<: *clickhouse-depend
otel-collector-metrics:
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.66.7}
command: ["--config=/etc/otel-collector-metrics-config.yaml"]
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.76.1}
command: ["--config=/etc/otel-collector-metrics-config.yaml", "--feature-gates=-pkg.translator.prometheus.NormalizeName"]
volumes:
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml
# ports:

View File

@ -70,6 +70,40 @@ receivers:
processors:
logstransform/internal:
operators:
- type: trace_parser
if: '"trace_id" in attributes or "span_id" in attributes'
trace_id:
parse_from: attributes.trace_id
span_id:
parse_from: attributes.span_id
output: remove_trace_id
- type: trace_parser
if: '"traceId" in attributes or "spanId" in attributes'
trace_id:
parse_from: attributes.traceId
span_id:
parse_from: attributes.spanId
output: remove_traceId
- id: remove_traceId
type: remove
if: '"traceId" in attributes'
field: attributes.traceId
output: remove_spanId
- id: remove_spanId
type: remove
if: '"spanId" in attributes'
field: attributes.spanId
- id: remove_trace_id
type: remove
if: '"trace_id" in attributes'
field: attributes.trace_id
output: remove_span_id
- id: remove_span_id
type: remove
if: '"span_id" in attributes'
field: attributes.span_id
batch:
send_batch_size: 10000
send_batch_max_size: 11000
@ -104,7 +138,7 @@ processors:
# retry_on_failure: true
resourcedetection:
# Using OTEL_RESOURCE_ATTRIBUTES envvar, env detector adds custom labels.
detectors: [env, system] # include ec2 for AWS, gce for GCP and azure for Azure.
detectors: [env, system] # include ec2 for AWS, gcp for GCP and azure for Azure.
timeout: 2s
extensions:
@ -172,5 +206,5 @@ service:
exporters: [prometheus]
logs:
receivers: [otlp, filelog/dockercontainers]
processors: [batch]
processors: [logstransform/internal, batch]
exporters: [clickhouselogsexporter]

View File

@ -125,7 +125,7 @@ check_ports_occupied() {
echo "+++++++++++ ERROR ++++++++++++++++++++++"
echo "SigNoz requires ports 3301 & 4317 to be open. Please shut down any other service(s) that may be running on these ports."
echo "You can run SigNoz on another port following this guide https://signoz.io/docs/deployment/docker#troubleshooting"
echo "You can run SigNoz on another port following this guide https://signoz.io/docs/install/troubleshooting/"
echo "++++++++++++++++++++++++++++++++++++++++"
echo ""
exit 1
@ -249,7 +249,7 @@ bye() { # Prints a friendly good bye message and exits the script.
echo ""
echo -e "$sudo_cmd docker-compose -f ./docker/clickhouse-setup/docker-compose.yaml ps -a"
# echo "Please read our troubleshooting guide https://signoz.io/docs/deployment/docker#troubleshooting"
echo "Please read our troubleshooting guide https://signoz.io/docs/install/troubleshooting/"
echo "or reach us for support in #help channel in our Slack Community https://signoz.io/slack"
echo "++++++++++++++++++++++++++++++++++++++++"
@ -500,7 +500,7 @@ if [[ $status_code -ne 200 ]]; then
echo -e "$sudo_cmd docker-compose -f ./docker/clickhouse-setup/docker-compose.yaml ps -a"
echo "Please read our troubleshooting guide https://signoz.io/docs/deployment/docker/#troubleshooting-of-common-issues"
echo "Please read our troubleshooting guide https://signoz.io/docs/install/troubleshooting/"
echo "or reach us on SigNoz for support https://signoz.io/slack"
echo "++++++++++++++++++++++++++++++++++++++++"

View File

@ -5,6 +5,10 @@ import (
)
func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
featureSet := ah.FF().GetFeatureFlags()
featureSet, err := ah.FF().GetFeatureFlags()
if err != nil {
ah.HandleError(w, err, http.StatusInternalServerError)
return
}
ah.Respond(w, featureSet)
}

View File

@ -22,6 +22,8 @@ import (
"go.signoz.io/signoz/ee/query-service/app/db"
"go.signoz.io/signoz/ee/query-service/dao"
"go.signoz.io/signoz/ee/query-service/interfaces"
baseInterface "go.signoz.io/signoz/pkg/query-service/interfaces"
licensepkg "go.signoz.io/signoz/ee/query-service/license"
"go.signoz.io/signoz/ee/query-service/usage"
@ -126,7 +128,8 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
serverOptions.RuleRepoURL,
localDB,
reader,
serverOptions.DisableRules)
serverOptions.DisableRules,
lm)
if err != nil {
return nil, err
@ -402,7 +405,7 @@ func setTimeoutMiddleware(next http.Handler) http.Handler {
// check if route is not excluded
url := r.URL.Path
if _, ok := baseconst.TimeoutExcludedRoutes[url]; !ok {
ctx, cancel = context.WithTimeout(r.Context(), baseconst.ContextTimeout*time.Second)
ctx, cancel = context.WithTimeout(r.Context(), baseconst.ContextTimeout)
defer cancel()
}
@ -544,7 +547,8 @@ func makeRulesManager(
ruleRepoURL string,
db *sqlx.DB,
ch baseint.Reader,
disableRules bool) (*rules.Manager, error) {
disableRules bool,
fm baseInterface.FeatureLookup) (*rules.Manager, error) {
// create engine
pqle, err := pqle.FromConfigPath(promConfigPath)
@ -571,6 +575,7 @@ func makeRulesManager(
Context: context.Background(),
Logger: nil,
DisableRules: disableRules,
FeatureFlags: fm,
}
// create Manager

View File

@ -2,6 +2,7 @@ package license
import (
"context"
"database/sql"
"fmt"
"time"
@ -9,6 +10,7 @@ import (
"go.signoz.io/signoz/ee/query-service/license/sqlite"
"go.signoz.io/signoz/ee/query-service/model"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
"go.uber.org/zap"
)
@ -125,3 +127,79 @@ func (r *Repo) UpdatePlanDetails(ctx context.Context,
return nil
}
func (r *Repo) CreateFeature(req *basemodel.Feature) *basemodel.ApiError {
_, err := r.db.Exec(
`INSERT INTO feature_status (name, active, usage, usage_limit, route)
VALUES (?, ?, ?, ?, ?);`,
req.Name, req.Active, req.Usage, req.UsageLimit, req.Route)
if err != nil {
return &basemodel.ApiError{Typ: basemodel.ErrorInternal, Err: err}
}
return nil
}
func (r *Repo) GetFeature(featureName string) (basemodel.Feature, error) {
var feature basemodel.Feature
err := r.db.Get(&feature,
`SELECT * FROM feature_status WHERE name = ?;`, featureName)
if err != nil {
return feature, err
}
if feature.Name == "" {
return feature, basemodel.ErrFeatureUnavailable{Key: featureName}
}
return feature, nil
}
func (r *Repo) GetAllFeatures() ([]basemodel.Feature, error) {
var feature []basemodel.Feature
err := r.db.Select(&feature,
`SELECT * FROM feature_status;`)
if err != nil {
return feature, err
}
return feature, nil
}
func (r *Repo) UpdateFeature(req basemodel.Feature) error {
_, err := r.db.Exec(
`UPDATE feature_status SET active = ?, usage = ?, usage_limit = ?, route = ? WHERE name = ?;`,
req.Active, req.Usage, req.UsageLimit, req.Route, req.Name)
if err != nil {
return err
}
return nil
}
func (r *Repo) InitFeatures(req basemodel.FeatureSet) error {
// get a feature by name, if it doesn't exist, create it. If it does exist, update it.
for _, feature := range req {
currentFeature, err := r.GetFeature(feature.Name)
if err != nil && err == sql.ErrNoRows {
err := r.CreateFeature(&feature)
if err != nil {
return err
}
continue
} else if err != nil {
return err
}
feature.Usage = currentFeature.Usage
if feature.Usage >= feature.UsageLimit && feature.UsageLimit != -1 {
feature.Active = false
}
err = r.UpdateFeature(feature)
if err != nil {
return err
}
}
return nil
}

View File

@ -96,6 +96,11 @@ func (lm *Manager) SetActive(l *model.License) {
lm.activeFeatures = l.FeatureSet
// set default features
setDefaultFeatures(lm)
err := lm.InitFeatures(lm.activeFeatures)
if err != nil {
zap.S().Panicf("Couldn't activate features: %v", err)
}
if !lm.validatorRunning {
// we want to make sure only one validator runs,
// we already have lock() so good to go
@ -106,9 +111,7 @@ func (lm *Manager) SetActive(l *model.License) {
}
func setDefaultFeatures(lm *Manager) {
for k, v := range baseconstants.DEFAULT_FEATURE_SET {
lm.activeFeatures[k] = v
}
lm.activeFeatures = append(lm.activeFeatures, baseconstants.DEFAULT_FEATURE_SET...)
}
// LoadActiveLicense loads the most recent active license
@ -123,8 +126,13 @@ func (lm *Manager) LoadActiveLicense() error {
} else {
zap.S().Info("No active license found, defaulting to basic plan")
// if no active license is found, we default to basic(free) plan with all default features
lm.activeFeatures = basemodel.BasicPlan
lm.activeFeatures = model.BasicPlan
setDefaultFeatures(lm)
err := lm.InitFeatures(lm.activeFeatures)
if err != nil {
zap.S().Error("Couldn't initialize features: ", err)
return err
}
}
return nil
@ -291,18 +299,31 @@ func (lm *Manager) Activate(ctx context.Context, key string) (licenseResponse *m
// CheckFeature will be internally used by backend routines
// for feature gating
func (lm *Manager) CheckFeature(featureKey string) error {
if value, ok := lm.activeFeatures[featureKey]; ok {
if value {
return nil
}
return basemodel.ErrFeatureUnavailable{Key: featureKey}
feature, err := lm.repo.GetFeature(featureKey)
if err != nil {
return err
}
if feature.Active {
return nil
}
return basemodel.ErrFeatureUnavailable{Key: featureKey}
}
// GetFeatureFlags returns current active features
func (lm *Manager) GetFeatureFlags() basemodel.FeatureSet {
return lm.activeFeatures
func (lm *Manager) GetFeatureFlags() (basemodel.FeatureSet, error) {
return lm.repo.GetAllFeatures()
}
func (lm *Manager) InitFeatures(features basemodel.FeatureSet) error {
return lm.repo.InitFeatures(features)
}
func (lm *Manager) UpdateFeatureFlag(feature basemodel.Feature) error {
return lm.repo.UpdateFeature(feature)
}
func (lm *Manager) GetFeatureFlag(key string) (basemodel.Feature, error) {
return lm.repo.GetFeature(key)
}
// GetRepo return the license repo

View File

@ -2,6 +2,7 @@ package sqlite
import (
"fmt"
"github.com/jmoiron/sqlx"
)
@ -33,5 +34,19 @@ func InitDB(db *sqlx.DB) error {
if err != nil {
return fmt.Errorf("Error in creating licenses table: %s", err.Error())
}
table_schema = `CREATE TABLE IF NOT EXISTS feature_status (
name TEXT PRIMARY KEY,
active bool,
usage INTEGER DEFAULT 0,
usage_limit INTEGER DEFAULT 0,
route TEXT
);`
_, err = db.Exec(table_schema)
if err != nil {
return fmt.Errorf("Error in creating feature_status table: %s", err.Error())
}
return nil
}

View File

@ -11,21 +11,143 @@ const Enterprise = "ENTERPRISE_PLAN"
const DisableUpsell = "DISABLE_UPSELL"
var BasicPlan = basemodel.FeatureSet{
Basic: true,
SSO: false,
DisableUpsell: false,
basemodel.Feature{
Name: SSO,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.OSS,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: DisableUpsell,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.SmartTraceDetail,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.CustomMetricsFunction,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.QueryBuilderPanels,
Active: true,
Usage: 0,
UsageLimit: 5,
Route: "",
},
basemodel.Feature{
Name: basemodel.QueryBuilderAlerts,
Active: true,
Usage: 0,
UsageLimit: 5,
Route: "",
},
}
var ProPlan = basemodel.FeatureSet{
Pro: true,
SSO: true,
basemodel.SmartTraceDetail: true,
basemodel.CustomMetricsFunction: true,
basemodel.Feature{
Name: SSO,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.OSS,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.SmartTraceDetail,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.CustomMetricsFunction,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.QueryBuilderPanels,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.QueryBuilderAlerts,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
}
var EnterprisePlan = basemodel.FeatureSet{
Enterprise: true,
SSO: true,
basemodel.SmartTraceDetail: true,
basemodel.CustomMetricsFunction: true,
basemodel.Feature{
Name: SSO,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.OSS,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.SmartTraceDetail,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.CustomMetricsFunction,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.QueryBuilderPanels,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.QueryBuilderAlerts,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
}

View File

@ -1,7 +1,7 @@
{
"presets": [
"@babel/preset-env",
"@babel/preset-react",
["@babel/preset-react", { "runtime": "automatic" }],
"@babel/preset-typescript"
],
"plugins": [

View File

@ -16,6 +16,7 @@ module.exports = {
'plugin:sonarjs/recommended',
'plugin:import/errors',
'plugin:import/warnings',
'plugin:react/jsx-runtime',
],
parser: '@typescript-eslint/parser',
parserOptions: {

View File

@ -1 +1,2 @@
network-timeout 600000
save-prefix ""

View File

@ -1,5 +1,5 @@
# Builder stage
FROM node:16.15.0-slim as builder
FROM node:16.15.0 as builder
# Add Maintainer Info
LABEL maintainer="signoz"
@ -11,6 +11,8 @@ WORKDIR /frontend
# Copy the package.json and .yarnrc files prior to install dependencies
COPY package.json ./
# Copy lock file
COPY yarn.lock ./
COPY .yarnrc ./
# Install the dependencies and make the folder

View File

@ -1,5 +1,20 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable object-shorthand */
/* eslint-disable func-names */
/**
* Adds custom matchers from the react testing library to all tests
*/
import '@testing-library/jest-dom';
import 'jest-styled-components';
// Mock window.matchMedia
window.matchMedia =
window.matchMedia ||
function (): any {
return {
matches: false,
addListener: function () {},
removeListener: function () {},
};
};

View File

@ -48,15 +48,11 @@
"cross-env": "^7.0.3",
"css-loader": "4.3.0",
"css-minimizer-webpack-plugin": "^3.2.0",
"d3": "^6.2.0",
"d3-flame-graph": "^3.1.1",
"d3-tip": "^0.9.1",
"dayjs": "^1.10.7",
"dompurify": "3.0.0",
"dotenv": "8.2.0",
"event-source-polyfill": "1.0.31",
"file-loader": "6.1.1",
"flat": "^5.0.2",
"fontfaceobserver": "2.3.0",
"history": "4.10.1",
"html-webpack-plugin": "5.1.0",
@ -69,10 +65,10 @@
"less-loader": "^10.2.0",
"lodash-es": "^4.17.21",
"mini-css-extract-plugin": "2.4.5",
"papaparse": "5.4.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-force-graph": "^1.41.0",
"react-graph-vis": "^1.0.5",
"react-grid-layout": "^1.3.4",
"react-i18next": "^11.16.1",
"react-intersection-observer": "9.4.1",
@ -126,16 +122,14 @@
"@types/color": "^3.0.3",
"@types/compression-webpack-plugin": "^9.0.0",
"@types/copy-webpack-plugin": "^8.0.1",
"@types/d3": "^6.2.0",
"@types/d3-tip": "^3.5.5",
"@types/dompurify": "^2.4.0",
"@types/event-source-polyfill": "^1.0.0",
"@types/flat": "^5.0.2",
"@types/fontfaceobserver": "2.1.0",
"@types/jest": "^27.5.1",
"@types/lodash-es": "^4.17.4",
"@types/mini-css-extract-plugin": "^2.5.1",
"@types/node": "^16.10.3",
"@types/papaparse": "5.3.7",
"@types/react": "18.0.26",
"@types/react-dom": "18.0.10",
"@types/react-grid-layout": "^1.1.2",
@ -144,7 +138,6 @@
"@types/react-router-dom": "^5.1.6",
"@types/styled-components": "^5.1.4",
"@types/uuid": "^8.3.1",
"@types/vis": "^4.21.21",
"@types/webpack": "^5.28.0",
"@types/webpack-dev-server": "^4.3.0",
"@typescript-eslint/eslint-plugin": "^4.28.2",
@ -174,7 +167,6 @@
"is-ci": "^3.0.1",
"jest-playwright-preset": "^1.7.0",
"jest-styled-components": "^7.0.8",
"less-plugin-npm-import": "^2.1.0",
"lint-staged": "^12.3.7",
"portfinder-sync": "^0.0.2",
"prettier": "2.2.1",

View File

@ -7,7 +7,7 @@ import { LOCALSTORAGE } from 'constants/localStorage';
import ROUTES from 'constants/routes';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
import React, { useEffect, useMemo } from 'react';
import { ReactChild, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { matchPath, Redirect, useLocation } from 'react-router-dom';
@ -161,7 +161,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
}
interface PrivateRouteProps {
children: React.ReactChild;
children: ReactChild;
}
export default PrivateRoute;

View File

@ -7,7 +7,7 @@ import { NotificationProvider } from 'hooks/useNotifications';
import { ResourceProvider } from 'hooks/useResourceAttribute';
import history from 'lib/history';
import { QueryBuilderProvider } from 'providers/QueryBuilder';
import React, { Suspense } from 'react';
import { Suspense } from 'react';
import { Route, Router, Switch } from 'react-router-dom';
import PrivateRoute from './Private';

View File

@ -1,23 +0,0 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps } from 'types/api/features/getFeatures';
const getFeaturesFlags = async (): Promise<
SuccessResponse<PayloadProps> | ErrorResponse
> => {
try {
const response = await axios.get(`/featureFlags`);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};
export default getFeaturesFlags;

View File

@ -1,17 +1,17 @@
import { ApiV2Instance as axios } from 'api';
import { ApiV3Instance as axios } from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import {
MetricRangePayloadProps,
MetricRangePayloadV3,
MetricsRangeProps,
} from 'types/api/metrics/getQueryRange';
export const getMetricsQueryRange = async (
props: MetricsRangeProps,
): Promise<SuccessResponse<MetricRangePayloadProps> | ErrorResponse> => {
): Promise<SuccessResponse<MetricRangePayloadV3> | ErrorResponse> => {
try {
const response = await axios.post(`/metrics/query_range`, props);
const response = await axios.post('/query_range', props);
return {
statusCode: 200,

View File

@ -1,11 +1,16 @@
import { ApiV3Instance } from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError, AxiosResponse } from 'axios';
import createQueryParams from 'lib/createQueryParams';
// ** Helpers
import { ErrorResponse, SuccessResponse } from 'types/api';
// ** Types
import { IGetAggregateAttributePayload } from 'types/api/queryBuilder/getAggregatorAttribute';
import { IQueryAutocompleteResponse } from 'types/api/queryBuilder/queryAutocompleteResponse';
import {
BaseAutocompleteData,
IQueryAutocompleteResponse,
} from 'types/api/queryBuilder/queryAutocompleteResponse';
import { v4 as uuid } from 'uuid';
export const getAggregateAttribute = async ({
aggregateOperator,
@ -18,14 +23,22 @@ export const getAggregateAttribute = async ({
const response: AxiosResponse<{
data: IQueryAutocompleteResponse;
}> = await ApiV3Instance.get(
`autocomplete/aggregate_attributes?aggregateOperator=${aggregateOperator}&dataSource=${dataSource}&searchText=${searchText}`,
`autocomplete/aggregate_attributes?${createQueryParams({
aggregateOperator,
searchText,
dataSource,
})}`,
);
const payload: BaseAutocompleteData[] =
response.data.data.attributeKeys?.map((item) => ({ ...item, id: uuid() })) ||
[];
return {
statusCode: 200,
error: null,
message: response.statusText,
payload: response.data.data,
payload: { attributeKeys: payload },
};
} catch (e) {
return ErrorResponseHandler(e as AxiosError);

View File

@ -1,16 +1,22 @@
import { ApiV3Instance } from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError, AxiosResponse } from 'axios';
import createQueryParams from 'lib/createQueryParams';
import { ErrorResponse, SuccessResponse } from 'types/api';
// ** Types
import { IGetAttributeKeysPayload } from 'types/api/queryBuilder/getAttributeKeys';
import { IQueryAutocompleteResponse } from 'types/api/queryBuilder/queryAutocompleteResponse';
import {
BaseAutocompleteData,
IQueryAutocompleteResponse,
} from 'types/api/queryBuilder/queryAutocompleteResponse';
import { v4 as uuid } from 'uuid';
export const getAggregateKeys = async ({
aggregateOperator,
searchText,
dataSource,
aggregateAttribute,
tagType,
}: IGetAttributeKeysPayload): Promise<
SuccessResponse<IQueryAutocompleteResponse> | ErrorResponse
> => {
@ -18,14 +24,23 @@ export const getAggregateKeys = async ({
const response: AxiosResponse<{
data: IQueryAutocompleteResponse;
}> = await ApiV3Instance.get(
`autocomplete/attribute_keys?aggregateOperator=${aggregateOperator}&dataSource=${dataSource}&aggregateAttribute=${aggregateAttribute}&searchText=${searchText}`,
`autocomplete/attribute_keys?${createQueryParams({
aggregateOperator,
searchText,
dataSource,
aggregateAttribute,
})}&tagType=${tagType}`,
);
const payload: BaseAutocompleteData[] =
response.data.data.attributeKeys?.map((item) => ({ ...item, id: uuid() })) ||
[];
return {
statusCode: 200,
error: null,
message: response.statusText,
payload: response.data.data,
payload: { attributeKeys: payload },
};
} catch (e) {
return ErrorResponseHandler(e as AxiosError);

View File

@ -0,0 +1,42 @@
import { ApiV3Instance } from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import createQueryParams from 'lib/createQueryParams';
import { ErrorResponse, SuccessResponse } from 'types/api';
import {
IAttributeValuesResponse,
IGetAttributeValuesPayload,
} from 'types/api/queryBuilder/getAttributesValues';
export const getAttributesValues = async ({
aggregateOperator,
dataSource,
aggregateAttribute,
attributeKey,
filterAttributeKeyDataType,
tagType,
searchText,
}: IGetAttributeValuesPayload): Promise<
SuccessResponse<IAttributeValuesResponse> | ErrorResponse
> => {
try {
const response = await ApiV3Instance.get(
`/autocomplete/attribute_values?${createQueryParams({
aggregateOperator,
dataSource,
aggregateAttribute,
attributeKey,
searchText,
})}&filterAttributeKeyDataType=${filterAttributeKeyDataType}&tagType=${tagType}`,
);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};

View File

@ -1,5 +1,3 @@
import React from 'react';
function TimeSeries(): JSX.Element {
return (
<svg

View File

@ -1,4 +1,4 @@
import React from 'react';
import { CSSProperties } from 'react';
function Value(props: ValueProps): JSX.Element {
const { fillColor } = props;
@ -20,7 +20,7 @@ function Value(props: ValueProps): JSX.Element {
}
interface ValueProps {
fillColor: React.CSSProperties['color'];
fillColor: CSSProperties['color'];
}
export default Value;

View File

@ -1,5 +1,3 @@
import React from 'react';
function NotFound(): JSX.Element {
return (
<svg

View File

@ -1,5 +1,3 @@
import React from 'react';
function SomethingWentWrong(): JSX.Element {
return (
<svg

View File

@ -1,5 +1,3 @@
import React from 'react';
function UnAuthorized(): JSX.Element {
return (
<svg

View File

@ -0,0 +1,59 @@
import { render, screen } from '@testing-library/react';
import { useIsDarkMode } from 'hooks/useDarkMode';
import Editor from './index';
jest.mock('hooks/useDarkMode', () => ({
useIsDarkMode: jest.fn(),
}));
describe('Editor', () => {
it('renders correctly with default props', () => {
const { container } = render(<Editor value="" />);
expect(container).toMatchSnapshot();
});
it('renders correctly with custom props', () => {
const customProps = {
value: 'test',
language: 'javascript',
readOnly: true,
height: '50vh',
options: { minimap: { enabled: false } },
};
const { container } = render(
<Editor
value={customProps.value}
height={customProps.height}
language={customProps.language}
options={customProps.options}
readOnly={customProps.readOnly}
/>,
);
expect(container).toMatchSnapshot();
});
it('renders with dark mode theme', () => {
(useIsDarkMode as jest.Mock).mockImplementation(() => true);
const { container } = render(<Editor value="dark mode text" />);
expect(container).toMatchSnapshot();
});
it('renders with light mode theme', () => {
(useIsDarkMode as jest.Mock).mockImplementation(() => false);
const { container } = render(<Editor value="light mode text" />);
expect(container).toMatchSnapshot();
});
it('displays "Loading..." message initially', () => {
const { rerender } = render(<Editor value="initial text" />);
expect(screen.getByText('Loading...')).toBeInTheDocument();
rerender(<Editor value="initial text" />);
});
});

View File

@ -0,0 +1,69 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Editor renders correctly with custom props 1`] = `
<div>
<section
style="display: flex; position: relative; text-align: initial; width: 100%; height: 50vh;"
>
<div
style="display: flex; height: 100%; width: 100%; justify-content: center; align-items: center;"
>
Loading...
</div>
<div
style="width: 100%; display: none;"
/>
</section>
</div>
`;
exports[`Editor renders correctly with default props 1`] = `
<div>
<section
style="display: flex; position: relative; text-align: initial; width: 100%; height: 40vh;"
>
<div
style="display: flex; height: 100%; width: 100%; justify-content: center; align-items: center;"
>
Loading...
</div>
<div
style="width: 100%; display: none;"
/>
</section>
</div>
`;
exports[`Editor renders with dark mode theme 1`] = `
<div>
<section
style="display: flex; position: relative; text-align: initial; width: 100%; height: 40vh;"
>
<div
style="display: flex; height: 100%; width: 100%; justify-content: center; align-items: center;"
>
Loading...
</div>
<div
style="width: 100%; display: none;"
/>
</section>
</div>
`;
exports[`Editor renders with light mode theme 1`] = `
<div>
<section
style="display: flex; position: relative; text-align: initial; width: 100%; height: 40vh;"
>
<div
style="display: flex; height: 100%; width: 100%; justify-content: center; align-items: center;"
>
Loading...
</div>
<div
style="width: 100%; display: none;"
/>
</section>
</div>
`;

View File

@ -1,6 +1,6 @@
import MEditor, { EditorProps } from '@monaco-editor/react';
import { useIsDarkMode } from 'hooks/useDarkMode';
import React, { useMemo } from 'react';
import { useMemo } from 'react';
function Editor({
value,
@ -13,6 +13,8 @@ function Editor({
const isDarkMode = useIsDarkMode();
const onChangeHandler = (newValue?: string): void => {
if (readOnly) return;
if (typeof newValue === 'string' && onChange) onChange(newValue);
};
@ -29,6 +31,7 @@ function Editor({
options={editorOptions}
height={height}
onChange={onChangeHandler}
data-testid="monaco-editor"
/>
);
}

View File

@ -26,7 +26,7 @@ import annotationPlugin from 'chartjs-plugin-annotation';
import dayjs from 'dayjs';
import { useIsDarkMode } from 'hooks/useDarkMode';
import isEqual from 'lodash-es/isEqual';
import React, { memo, useCallback, useEffect, useRef } from 'react';
import { memo, useCallback, useEffect, useRef } from 'react';
import { hasData } from './hasData';
import { getAxisLabelColor } from './helpers';

View File

@ -1,5 +1,12 @@
import { Form, Input, InputProps, InputRef } from 'antd';
import React from 'react';
import {
ChangeEventHandler,
FocusEventHandler,
KeyboardEventHandler,
LegacyRef,
ReactNode,
Ref,
} from 'react';
function InputComponent({
value,
@ -22,7 +29,7 @@ function InputComponent({
type={type}
onChange={onChangeHandler}
value={value}
ref={ref as React.Ref<InputRef>}
ref={ref as Ref<InputRef>}
size={size}
addonBefore={addonBefore}
onBlur={onBlurHandler}
@ -37,15 +44,15 @@ function InputComponent({
interface InputComponentProps extends InputProps {
value: InputProps['value'];
type?: InputProps['type'];
onChangeHandler?: React.ChangeEventHandler<HTMLInputElement>;
onChangeHandler?: ChangeEventHandler<HTMLInputElement>;
placeholder?: InputProps['placeholder'];
ref?: React.LegacyRef<InputRef>;
ref?: LegacyRef<InputRef>;
size?: InputProps['size'];
onBlurHandler?: React.FocusEventHandler<HTMLInputElement>;
onPressEnterHandler?: React.KeyboardEventHandler<HTMLInputElement>;
onBlurHandler?: FocusEventHandler<HTMLInputElement>;
onPressEnterHandler?: KeyboardEventHandler<HTMLInputElement>;
label?: string;
labelOnTop?: boolean;
addonBefore?: React.ReactNode;
addonBefore?: ReactNode;
}
InputComponent.defaultProps = {

View File

@ -0,0 +1,49 @@
import {
render,
screen,
waitForElementToBeRemoved,
} from '@testing-library/react';
import React, { ComponentType, Suspense } from 'react';
import Loadable from './index';
// Sample component to be loaded lazily
function SampleComponent(): JSX.Element {
return <div>Sample Component</div>;
}
const loadSampleComponent = (): Promise<{
default: ComponentType;
}> =>
new Promise<{ default: ComponentType }>((resolve) => {
setTimeout(() => {
resolve({ default: SampleComponent });
}, 500);
});
describe('Loadable', () => {
it('should render the lazily loaded component', async () => {
const LoadableSampleComponent = Loadable(loadSampleComponent);
const { container } = render(
<Suspense fallback={<div>Loading...</div>}>
<LoadableSampleComponent />
</Suspense>,
);
expect(screen.getByText('Loading...')).toBeInTheDocument();
await waitForElementToBeRemoved(() => screen.queryByText('Loading...'));
expect(container.querySelector('div')).toHaveTextContent('Sample Component');
});
it('should call lazy with the provided import path', () => {
const reactLazySpy = jest.spyOn(React, 'lazy');
Loadable(loadSampleComponent);
expect(reactLazySpy).toHaveBeenCalledTimes(1);
expect(reactLazySpy).toHaveBeenCalledWith(expect.any(Function));
reactLazySpy.mockRestore();
});
});

View File

@ -1,8 +1,8 @@
import { ComponentType, lazy } from 'react';
import { ComponentType, lazy, LazyExoticComponent } from 'react';
function Loadable(importPath: {
(): LoadableProps;
}): React.LazyExoticComponent<LazyComponent> {
}): LazyExoticComponent<LazyComponent> {
return lazy(() => importPath());
}

View File

@ -1,13 +1,14 @@
import { Button, Popover } from 'antd';
import { Popover } from 'antd';
import ROUTES from 'constants/routes';
import history from 'lib/history';
import { generateFilterQuery } from 'lib/logs/generateFilterQuery';
import React, { memo, useCallback, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Dispatch } from 'redux';
import { memo, ReactNode, useCallback, useMemo } from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import AppActions from 'types/actions';
import { SET_SEARCH_QUERY_STRING } from 'types/actions/logs';
import { ILogsReducer } from 'types/reducer/logs';
import { ButtonContainer } from './styles';
function AddToQueryHOC({
fieldKey,
fieldValue,
@ -16,7 +17,6 @@ function AddToQueryHOC({
const {
searchFilter: { queryString },
} = useSelector<AppState, ILogsReducer>((store) => store.logs);
const dispatch = useDispatch<Dispatch<AppActions>>();
const generatedQuery = useMemo(
() => generateFilterQuery({ fieldKey, fieldValue, type: 'IN' }),
@ -31,31 +31,27 @@ function AddToQueryHOC({
} else {
updatedQueryString += ` AND ${generatedQuery}`;
}
dispatch({
type: SET_SEARCH_QUERY_STRING,
payload: {
searchQueryString: updatedQueryString,
},
});
}, [dispatch, generatedQuery, queryString]);
history.replace(`${ROUTES.LOGS}?q=${updatedQueryString}`);
}, [generatedQuery, queryString]);
const popOverContent = useMemo(() => <span>Add to query: {fieldKey}</span>, [
fieldKey,
]);
return (
<Button size="small" type="text" onClick={handleQueryAdd}>
<ButtonContainer size="small" type="text" onClick={handleQueryAdd}>
<Popover placement="top" content={popOverContent}>
{children}
</Popover>
</Button>
</ButtonContainer>
);
}
interface AddToQueryHOCProps {
fieldKey: string;
fieldValue: string;
children: React.ReactNode;
children: ReactNode;
}
export default memo(AddToQueryHOC);

View File

@ -1,9 +1,9 @@
import React from 'react';
import { ReactNode } from 'react';
import { CategoryHeadingText } from './styles';
interface ICategoryHeadingProps {
children: React.ReactNode;
children: ReactNode;
}
function CategoryHeading({ children }: ICategoryHeadingProps): JSX.Element {
return <CategoryHeadingText type="secondary">{children}</CategoryHeadingText>;

View File

@ -1,6 +1,6 @@
import { Popover } from 'antd';
import { useNotifications } from 'hooks/useNotifications';
import React, { useCallback, useEffect } from 'react';
import { ReactNode, useCallback, useEffect } from 'react';
import { useCopyToClipboard } from 'react-use';
function CopyClipboardHOC({
@ -22,7 +22,7 @@ function CopyClipboardHOC({
}, [setCopy, textToCopy]);
return (
<span onClick={onClick} onKeyDown={onClick} role="button" tabIndex={0}>
<span onClick={onClick} role="presentation" tabIndex={-1}>
<Popover
placement="top"
content={<span style={{ fontSize: '0.9rem' }}>Copy to clipboard</span>}
@ -35,7 +35,7 @@ function CopyClipboardHOC({
interface CopyClipboardHOCProps {
textToCopy: string;
children: React.ReactNode;
children: ReactNode;
}
export default CopyClipboardHOC;

View File

@ -2,13 +2,12 @@ import { blue, grey, orange } from '@ant-design/colors';
import { CopyFilled, ExpandAltOutlined } from '@ant-design/icons';
import Convert from 'ansi-to-html';
import { Button, Divider, Row, Typography } from 'antd';
import { map } from 'd3';
import dayjs from 'dayjs';
import dompurify from 'dompurify';
import { useNotifications } from 'hooks/useNotifications';
// utils
import { FlatLogData } from 'lib/logs/flatLogData';
import React, { useCallback, useMemo } from 'react';
import { useCallback, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useCopyToClipboard } from 'react-use';
// interfaces
@ -127,7 +126,7 @@ function ListLogView({ logData }: ListLogViewProps): JSX.Element {
</>
</LogContainer>
<div>
{map(updatedSelecedFields, (field) =>
{updatedSelecedFields.map((field) =>
isValidLogField(flattenLogData[field.name] as never) ? (
<LogSelectedField
key={field.name}

View File

@ -1 +1,3 @@
export const rawLineStyle: React.CSSProperties = {};
import { CSSProperties } from 'react';
export const rawLineStyle: CSSProperties = {};

View File

@ -5,7 +5,7 @@ import dayjs from 'dayjs';
import dompurify from 'dompurify';
// hooks
import { useIsDarkMode } from 'hooks/useDarkMode';
import React, { useCallback, useMemo } from 'react';
import { useCallback, useMemo } from 'react';
// interfaces
import { ILog } from 'types/api/logs/log';

View File

@ -1,14 +1,14 @@
import { TableProps } from 'antd';
import React from 'react';
import { CSSProperties } from 'react';
export const defaultCellStyle: React.CSSProperties = {
export const defaultCellStyle: CSSProperties = {
paddingTop: 4,
paddingBottom: 6,
paddingRight: 8,
paddingLeft: 8,
};
export const defaultTableStyle: React.CSSProperties = {
export const defaultTableStyle: CSSProperties = {
minWidth: '40rem',
maxWidth: '40rem',
};

View File

@ -6,7 +6,7 @@ import dayjs from 'dayjs';
import dompurify from 'dompurify';
// utils
import { FlatLogData } from 'lib/logs/flatLogData';
import React, { useMemo } from 'react';
import { useMemo } from 'react';
import { IField } from 'types/api/logs/fields';
// interfaces
import { ILog } from 'types/api/logs/log';

View File

@ -16,6 +16,4 @@ export const TableBodyContent = styled.div<TableBodyContentProps>`
font-size: 0.875rem;
line-height: 2rem;
cursor: pointer;
`;

View File

@ -0,0 +1,8 @@
import { Button } from 'antd';
import styled from 'styled-components';
export const ButtonContainer = styled(Button)`
&&& {
padding-left: 0;
}
`;

View File

@ -0,0 +1,47 @@
import { render, screen } from '@testing-library/react';
import MessageTip from './index';
describe('MessageTip', () => {
it('should not render when show prop is false', () => {
render(
<MessageTip
show={false}
message="Test Message"
action={<button type="button">Close</button>}
/>,
);
const messageTip = screen.queryByRole('alert');
expect(messageTip).toBeNull();
});
it('should render with the provided message and action', () => {
const message = 'Test Message';
const action = <button type="button">Close</button>;
render(<MessageTip show message={message} action={action} />);
const messageTip = screen.getByRole('alert');
const messageText = screen.getByText(message);
const actionButton = screen.getByRole('button', { name: 'Close' });
expect(messageTip).toBeInTheDocument();
expect(messageText).toBeInTheDocument();
expect(actionButton).toBeInTheDocument();
});
// taken from antd docs
// https://github.com/ant-design/ant-design/blob/master/components/alert/__tests__/index.test.tsx
it('custom action', () => {
const { container } = render(
<MessageTip
show
message="Success Tips"
action={<button type="button">Close</button>}
/>,
);
expect(container.firstChild).toMatchSnapshot();
});
});

View File

@ -0,0 +1,54 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`MessageTip custom action 1`] = `
.c0 {
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
<div
class="ant-alert ant-alert-info ant-alert-with-description c0 css-dev-only-do-not-override-1i536d8"
data-show="true"
role="alert"
>
<span
aria-label="info-circle"
class="anticon anticon-info-circle ant-alert-icon"
role="img"
>
<svg
aria-hidden="true"
data-icon="info-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm32 664c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V456c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272zm-32-344a48.01 48.01 0 010-96 48.01 48.01 0 010 96z"
/>
</svg>
</span>
<div
class="ant-alert-content"
>
<div
class="ant-alert-description"
>
Success Tips
</div>
</div>
<div
class="ant-alert-action"
>
<button
type="button"
>
Close
</button>
</div>
</div>
`;

View File

@ -1,11 +1,11 @@
import React from 'react';
import { ReactNode } from 'react';
import { StyledAlert } from './styles';
interface MessageTipProps {
show?: boolean;
message: React.ReactNode | string;
action: React.ReactNode | undefined;
message: ReactNode | string;
action: ReactNode | undefined;
}
function MessageTip({

View File

@ -1,5 +1,5 @@
import { Modal, ModalProps as Props } from 'antd';
import React, { ReactElement } from 'react';
import { ReactElement } from 'react';
function CustomModal({
title,

View File

@ -1,5 +1,4 @@
import { render } from '@testing-library/react';
import React from 'react';
import { Provider } from 'react-redux';
import { MemoryRouter } from 'react-router-dom';
import store from 'store';

View File

@ -2,7 +2,7 @@ import getLocalStorageKey from 'api/browser/localstorage/get';
import NotFoundImage from 'assets/NotFound';
import { LOCALSTORAGE } from 'constants/localStorage';
import ROUTES from 'constants/routes';
import React, { useCallback } from 'react';
import { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { Dispatch } from 'redux';
import AppActions from 'types/actions';

View File

@ -1,7 +1,7 @@
import { Button, Space } from 'antd';
import setFlags from 'api/user/setFlags';
import MessageTip from 'components/MessageTip';
import React, { useCallback } from 'react';
import { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Dispatch } from 'redux';
import { AppState } from 'store/reducers';

View File

@ -1,7 +1,6 @@
import ReleaseNoteProps from 'components/ReleaseNote/ReleaseNoteProps';
import ReleaseNote0120 from 'components/ReleaseNote/Releases/ReleaseNote0120';
import ROUTES from 'constants/routes';
import React from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { UserFlags } from 'types/api/user/setFlags';

View File

@ -1,4 +1,4 @@
import React, { useMemo } from 'react';
import { SyntheticEvent, useMemo } from 'react';
import { Resizable, ResizeCallbackData } from 'react-resizable';
import { enableUserSelectHack } from './config';
@ -37,7 +37,7 @@ function ResizableHeader(props: ResizableHeaderProps): JSX.Element {
}
interface ResizableHeaderProps {
onResize: (e: React.SyntheticEvent<Element>, data: ResizeCallbackData) => void;
onResize: (e: SyntheticEvent<Element>, data: ResizeCallbackData) => void;
width: number;
}

View File

@ -1,7 +1,7 @@
import { Table } from 'antd';
import type { TableProps } from 'antd/es/table';
import { ColumnsType } from 'antd/lib/table';
import React, { useCallback, useMemo, useState } from 'react';
import { SyntheticEvent, useCallback, useMemo, useState } from 'react';
import { ResizeCallbackData } from 'react-resizable';
import ResizableHeader from './ResizableHeader';
@ -12,7 +12,7 @@ function ResizeTable({ columns, ...restprops }: TableProps<any>): JSX.Element {
const handleResize = useCallback(
(index: number) => (
_e: React.SyntheticEvent<Element>,
_e: SyntheticEvent<Element>,
{ size }: ResizeCallbackData,
): void => {
const newColumns = [...columnsData];

View File

@ -1,6 +1,5 @@
import { Tabs, TabsProps } from 'antd';
import history from 'lib/history';
import React from 'react';
function RouteTab({
routes,

View File

@ -1,6 +1,6 @@
import { LoadingOutlined } from '@ant-design/icons';
import { Spin, SpinProps } from 'antd';
import React from 'react';
import { CSSProperties } from 'react';
import { SpinerStyle } from './styles';
@ -15,8 +15,8 @@ function Spinner({ size, tip, height, style }: SpinnerProps): JSX.Element {
interface SpinnerProps {
size?: SpinProps['size'];
tip?: SpinProps['tip'];
height?: React.CSSProperties['height'];
style?: React.CSSProperties;
height?: CSSProperties['height'];
style?: CSSProperties;
}
Spinner.defaultProps = {
size: undefined,

View File

@ -1,8 +1,8 @@
import React from 'react';
import { CSSProperties } from 'react';
import styled from 'styled-components';
interface Props {
height: React.CSSProperties['height'];
height: CSSProperties['height'];
}
export const SpinerStyle = styled.div<Props>`

View File

@ -1,7 +1,7 @@
import * as AntD from 'antd';
import { TextProps } from 'antd/lib/typography/Text';
import { TitleProps } from 'antd/lib/typography/Title';
import React from 'react';
import { HTMLAttributes } from 'react';
import styled, { FlattenSimpleInterpolation } from 'styled-components';
import { IStyledClass } from './types';
@ -51,7 +51,7 @@ const StyledTypographyTitle = styled(Title)<TStyledTypographyTitle>`
${styledClass}
`;
type TStyledDiv = React.HTMLAttributes<HTMLDivElement> & IStyledClass;
type TStyledDiv = HTMLAttributes<HTMLDivElement> & IStyledClass;
const StyledDiv = styled.div<TStyledDiv>`
${styledClass}
`;

View File

@ -0,0 +1,53 @@
import { fireEvent, render, waitFor } from '@testing-library/react';
import TextToolTip from './index';
describe('TextToolTip', () => {
const tooltipText = 'Tooltip Text';
it('displays the tooltip when hovering over the icon', async () => {
const { getByRole, getByText } = render(<TextToolTip text="Tooltip Text" />);
const icon = getByRole('img');
fireEvent.mouseOver(icon);
await waitFor(() => {
const tooltip = getByText(tooltipText);
expect(tooltip).toBeInTheDocument();
});
});
it('renders the tooltip content correctly', async () => {
const { getByRole, getByText } = render(
<TextToolTip text="Tooltip Text" url="https://example.com" />,
);
const icon = getByRole('img');
fireEvent.mouseOver(icon);
await waitFor(() => {
const tooltip = getByText(tooltipText);
const link = getByText('here');
expect(tooltip).toBeInTheDocument();
expect(link).toHaveAttribute('href', 'https://example.com');
expect(link).toHaveAttribute('target', '_blank');
});
});
it('does not display the tooltip by default', () => {
const { queryByText } = render(<TextToolTip text="Tooltip Text" />);
const tooltip = queryByText(tooltipText);
expect(tooltip).toBeNull();
});
it('opens the URL in a new tab when clicked', async () => {
const { getByRole } = render(
<TextToolTip text="Tooltip Text" url="https://example.com" />,
);
const icon = getByRole('img');
fireEvent.mouseOver(icon);
await waitFor(() => {
const link = getByRole('link');
expect(link).toHaveAttribute('href', 'https://example.com');
expect(link).toHaveAttribute('target', '_blank');
});
});
});

View File

@ -3,7 +3,7 @@ import { QuestionCircleFilled } from '@ant-design/icons';
import { Tooltip } from 'antd';
import { themeColors } from 'constants/theme';
import { useIsDarkMode } from 'hooks/useDarkMode';
import React, { useMemo } from 'react';
import { useMemo } from 'react';
import { style } from './styles';

View File

@ -1,6 +1,5 @@
import { Typography } from 'antd';
import { timeItems } from 'container/NewWidget/RightContainer/timeItems';
import React from 'react';
export const menuItems = timeItems.map((item) => ({
key: item.enum,

View File

@ -3,7 +3,7 @@ import TimeItems, {
timePreferance,
timePreferenceType,
} from 'container/NewWidget/RightContainer/timeItems';
import React, { useCallback, useMemo } from 'react';
import { Dispatch, SetStateAction, useCallback, useMemo } from 'react';
import { menuItems } from './config';
import { TextContainer } from './styles';
@ -44,7 +44,7 @@ interface TimeMenuItemOnChangeHandlerEvent {
}
interface TimePreferenceDropDownProps {
setSelectedTime: React.Dispatch<React.SetStateAction<timePreferance>>;
setSelectedTime: Dispatch<SetStateAction<timePreferance>>;
selectedTime: timePreferance;
}

View File

@ -1,5 +1,3 @@
import React from 'react';
import { Value } from './styles';
function ValueGraph({ value }: ValueGraphProps): JSX.Element {

View File

@ -1,5 +1,5 @@
import { Card, Space, Typography } from 'antd';
import React from 'react';
import { ReactChild } from 'react';
import { useTranslation } from 'react-i18next';
import { Container, LeftContainer, Logo } from './styles';
@ -34,7 +34,7 @@ function WelcomeLeftContainer({
interface WelcomeLeftContainerProps {
version: string;
children: React.ReactChild;
children: ReactChild;
}
export default WelcomeLeftContainer;

View File

@ -0,0 +1,9 @@
import { AlertTypes } from 'types/api/alerts/alertTypes';
import { DataSource } from 'types/common/queryBuilder';
export const ALERTS_DATA_SOURCE_MAP: Record<AlertTypes, DataSource> = {
[AlertTypes.METRICS_BASED_ALERT]: DataSource.METRICS,
[AlertTypes.LOGS_BASED_ALERT]: DataSource.LOGS,
[AlertTypes.TRACES_BASED_ALERT]: DataSource.TRACES,
[AlertTypes.EXCEPTIONS_BASED_ALERT]: DataSource.TRACES,
};

View File

@ -1,5 +1,3 @@
import { EAggregateOperator, EReduceOperator } from 'types/common/dashboard';
export const PromQLQueryTemplate = {
query: '',
legend: '',
@ -11,24 +9,3 @@ export const ClickHouseQueryTemplate = {
legend: '',
disabled: false,
};
export const QueryBuilderQueryTemplate = {
metricName: null,
aggregateOperator: EAggregateOperator.NOOP,
tagFilters: {
op: 'AND',
items: [],
},
legend: '',
disabled: false,
// Specific to TIME_SERIES type graph
groupBy: [],
// Specific to VALUE type graph
reduceTo: EReduceOperator['Latest of values in timeframe'],
};
export const QueryBuilderFormulaTemplate = {
expression: '',
disabled: false,
legend: '',
};

View File

@ -1,7 +0,0 @@
// keep this consistent with backend model>features.go
export enum FeatureKeys {
SSO = 'SSO',
ENTERPRISE_PLAN = 'ENTERPRISE_PLAN',
BASIC_PLAN = 'BASIC_PLAN',
DISABLE_UPSELL = 'DISABLE_UPSELL',
}

View File

@ -1,6 +1,11 @@
// keep this consistent with backend constants.go
export enum FeatureKeys {
SSO = 'SSO',
ENTERPRISE_PLAN = 'ENTERPRISE_PLAN',
BASIC_PLAN = 'BASIC_PLAN',
DurationSort = 'DurationSort',
TimestampSort = 'TimestampSort',
SMART_TRACE_DETAIL = 'SMART_TRACE_DETAIL',
CUSTOM_METRICS_FUNCTION = 'CUSTOM_METRICS_FUNCTION',
QUERY_BUILDER_PANELS = 'QUERY_BUILDER_PANELS',
QUERY_BUILDER_ALERTS = 'QUERY_BUILDER_ALERTS',
DISABLE_UPSELL = 'DISABLE_UPSELL',
}

View File

@ -1,5 +1,4 @@
// eslint-disable-next-line @typescript-eslint/naming-convention
export enum METRICS_PAGE_QUERY_PARAM {
export enum QueryParams {
interval = 'interval',
startTime = 'startTime',
endTime = 'endTime',
@ -12,4 +11,5 @@ export enum METRICS_PAGE_QUERY_PARAM {
selectedTags = 'selectedTags',
aggregationOption = 'aggregationOption',
entity = 'entity',
resourceAttributes = 'resourceAttribute',
}

View File

@ -0,0 +1,235 @@
// ** Helpers
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import { createNewBuilderItemName } from 'lib/newQueryBuilder/createNewBuilderItemName';
import { LocalDataType } from 'types/api/queryBuilder/queryAutocompleteResponse';
import {
HavingForm,
IBuilderFormula,
IBuilderQuery,
} from 'types/api/queryBuilder/queryBuilderData';
import {
BoolOperators,
DataSource,
MetricAggregateOperator,
NumberOperators,
PanelTypeKeys,
QueryAdditionalFilter,
ReduceOperators,
StringOperators,
} from 'types/common/queryBuilder';
import { SelectOption } from 'types/common/select';
import { v4 as uuid } from 'uuid';
import {
logsAggregateOperatorOptions,
metricAggregateOperatorOptions,
tracesAggregateOperatorOptions,
} from './queryBuilderOperators';
export const MAX_FORMULAS = 20;
export const MAX_QUERIES = 26;
export const selectValueDivider = '--';
export const formulasNames: string[] = Array.from(
Array(MAX_FORMULAS),
(_, i) => `F${i + 1}`,
);
const alpha: number[] = Array.from(Array(MAX_QUERIES), (_, i) => i + 65);
export const alphabet: string[] = alpha.map((str) => String.fromCharCode(str));
export enum QueryBuilderKeys {
GET_AGGREGATE_ATTRIBUTE = 'GET_AGGREGATE_ATTRIBUTE',
GET_AGGREGATE_KEYS = 'GET_AGGREGATE_KEYS',
GET_ATTRIBUTE_KEY = 'GET_ATTRIBUTE_KEY',
}
export const mapOfOperators = {
metrics: metricAggregateOperatorOptions,
logs: logsAggregateOperatorOptions,
traces: tracesAggregateOperatorOptions,
};
export const mapOfFilters: Record<DataSource, QueryAdditionalFilter[]> = {
metrics: [
// eslint-disable-next-line sonarjs/no-duplicate-string
{ text: 'Aggregation interval', field: 'stepInterval' },
{ text: 'Having', field: 'having' },
],
logs: [
{ text: 'Order by', field: 'orderBy' },
{ text: 'Limit', field: 'limit' },
{ text: 'Having', field: 'having' },
{ text: 'Aggregation interval', field: 'stepInterval' },
],
traces: [
{ text: 'Order by', field: 'orderBy' },
{ text: 'Limit', field: 'limit' },
{ text: 'Having', field: 'having' },
{ text: 'Aggregation interval', field: 'stepInterval' },
],
};
export const REDUCE_TO_VALUES: SelectOption<ReduceOperators, string>[] = [
{ value: 'last', label: 'Latest of values in timeframe' },
{ value: 'sum', label: 'Sum of values in timeframe' },
{ value: 'avg', label: 'Average of values in timeframe' },
{ value: 'max', label: 'Max of values in timeframe' },
{ value: 'min', label: 'Min of values in timeframe' },
];
export const initialHavingValues: HavingForm = {
columnName: '',
op: '',
value: [],
};
export const initialAggregateAttribute: IBuilderQuery['aggregateAttribute'] = {
id: uuid(),
dataType: null,
key: '',
isColumn: null,
type: null,
};
export const initialQueryBuilderFormValues: IBuilderQuery = {
dataSource: DataSource.METRICS,
queryName: createNewBuilderItemName({ existNames: [], sourceNames: alphabet }),
aggregateOperator: MetricAggregateOperator.NOOP,
aggregateAttribute: initialAggregateAttribute,
filters: { items: [], op: 'AND' },
expression: createNewBuilderItemName({
existNames: [],
sourceNames: alphabet,
}),
disabled: false,
having: [],
stepInterval: 30,
limit: null,
orderBy: [],
groupBy: [],
legend: '',
reduceTo: 'sum',
};
export const initialFormulaBuilderFormValues: IBuilderFormula = {
queryName: createNewBuilderItemName({
existNames: [],
sourceNames: formulasNames,
}),
expression: '',
disabled: false,
legend: '',
};
export const operatorsByTypes: Record<LocalDataType, string[]> = {
string: Object.values(StringOperators),
number: Object.values(NumberOperators),
bool: Object.values(BoolOperators),
};
export const PANEL_TYPES: Record<PanelTypeKeys, GRAPH_TYPES> = {
TIME_SERIES: 'graph',
VALUE: 'value',
TABLE: 'table',
LIST: 'list',
EMPTY_WIDGET: 'EMPTY_WIDGET',
};
export type IQueryBuilderState = 'search';
export const QUERY_BUILDER_SEARCH_VALUES = {
MULTIPLY: 'MULTIPLY_VALUE',
SINGLE: 'SINGLE_VALUE',
NON: 'NON_VALUE',
NOT_VALID: 'NOT_VALID',
};
export const OPERATORS = {
IN: 'IN',
NIN: 'NOT_IN',
LIKE: 'LIKE',
NLIKE: 'NOT_LIKE',
'=': '=',
'!=': '!=',
EXISTS: 'EXISTS',
NOT_EXISTS: 'NOT_EXISTS',
CONTAINS: 'CONTAINS',
NOT_CONTAINS: 'NOT_CONTAINS',
'>=': '>=',
'>': '>',
'<=': '<=',
'<': '<',
};
export const QUERY_BUILDER_OPERATORS_BY_TYPES = {
string: [
OPERATORS['='],
OPERATORS['!='],
OPERATORS.IN,
OPERATORS.NIN,
OPERATORS.LIKE,
OPERATORS.NLIKE,
OPERATORS.CONTAINS,
OPERATORS.NOT_CONTAINS,
OPERATORS.EXISTS,
OPERATORS.NOT_EXISTS,
],
int64: [
OPERATORS['='],
OPERATORS['!='],
OPERATORS.IN,
OPERATORS.NIN,
OPERATORS.EXISTS,
OPERATORS.NOT_EXISTS,
OPERATORS['>='],
OPERATORS['>'],
OPERATORS['<='],
OPERATORS['<'],
],
float64: [
OPERATORS['='],
OPERATORS['!='],
OPERATORS.IN,
OPERATORS.NIN,
OPERATORS.EXISTS,
OPERATORS.NOT_EXISTS,
OPERATORS['>='],
OPERATORS['>'],
OPERATORS['<='],
OPERATORS['<'],
],
bool: [
OPERATORS['='],
OPERATORS['!='],
OPERATORS.EXISTS,
OPERATORS.NOT_EXISTS,
],
universal: [
OPERATORS['='],
OPERATORS['!='],
OPERATORS.IN,
OPERATORS.NIN,
OPERATORS.EXISTS,
OPERATORS.NOT_EXISTS,
OPERATORS.LIKE,
OPERATORS.NLIKE,
OPERATORS['>='],
OPERATORS['>'],
OPERATORS['<='],
OPERATORS['<'],
OPERATORS.CONTAINS,
OPERATORS.NOT_CONTAINS,
],
};
export const HAVING_OPERATORS: string[] = [
OPERATORS['='],
OPERATORS['!='],
OPERATORS.IN,
OPERATORS.NIN,
OPERATORS['>='],
OPERATORS['>'],
OPERATORS['<='],
OPERATORS['<'],
];

View File

@ -0,0 +1,304 @@
import {
LogsAggregatorOperator,
MetricAggregateOperator,
TracesAggregatorOperator,
} from 'types/common/queryBuilder';
import { SelectOption } from 'types/common/select';
export const metricAggregateOperatorOptions: SelectOption<string, string>[] = [
{
value: MetricAggregateOperator.NOOP,
label: 'NOOP',
},
{
value: MetricAggregateOperator.COUNT,
label: 'Count',
},
{
value: MetricAggregateOperator.COUNT_DISTINCT,
// eslint-disable-next-line sonarjs/no-duplicate-string
label: 'Count Distinct',
},
{
value: MetricAggregateOperator.SUM,
label: 'Sum',
},
{
value: MetricAggregateOperator.AVG,
label: 'Avg',
},
{
value: MetricAggregateOperator.MAX,
label: 'Max',
},
{
value: MetricAggregateOperator.MIN,
label: 'Min',
},
{
value: MetricAggregateOperator.P05,
label: 'P05',
},
{
value: MetricAggregateOperator.P10,
label: 'P10',
},
{
value: MetricAggregateOperator.P20,
label: 'P20',
},
{
value: MetricAggregateOperator.P25,
label: 'P25',
},
{
value: MetricAggregateOperator.P50,
label: 'P50',
},
{
value: MetricAggregateOperator.P75,
label: 'P75',
},
{
value: MetricAggregateOperator.P90,
label: 'P90',
},
{
value: MetricAggregateOperator.P95,
label: 'P95',
},
{
value: MetricAggregateOperator.P99,
label: 'P99',
},
{
value: MetricAggregateOperator.RATE,
label: 'Rate',
},
{
value: MetricAggregateOperator.SUM_RATE,
label: 'Sum_rate',
},
{
value: MetricAggregateOperator.AVG_RATE,
label: 'Avg_rate',
},
{
value: MetricAggregateOperator.MAX_RATE,
label: 'Max_rate',
},
{
value: MetricAggregateOperator.MIN_RATE,
label: 'Min_rate',
},
{
value: MetricAggregateOperator.RATE_SUM,
label: 'Rate_sum',
},
{
value: MetricAggregateOperator.RATE_AVG,
label: 'Rate_avg',
},
{
value: MetricAggregateOperator.RATE_MIN,
label: 'Rate_min',
},
{
value: MetricAggregateOperator.RATE_MAX,
label: 'Rate_max',
},
{
value: MetricAggregateOperator.HIST_QUANTILE_50,
label: 'Hist_quantile_50',
},
{
value: MetricAggregateOperator.HIST_QUANTILE_75,
label: 'Hist_quantile_75',
},
{
value: MetricAggregateOperator.HIST_QUANTILE_90,
label: 'Hist_quantile_90',
},
{
value: MetricAggregateOperator.HIST_QUANTILE_95,
label: 'Hist_quantile_95',
},
{
value: MetricAggregateOperator.HIST_QUANTILE_99,
label: 'Hist_quantile_99',
},
];
export const tracesAggregateOperatorOptions: SelectOption<string, string>[] = [
{
value: TracesAggregatorOperator.NOOP,
label: 'NOOP',
},
{
value: TracesAggregatorOperator.COUNT,
label: 'Count',
},
{
value: TracesAggregatorOperator.COUNT_DISTINCT,
label: 'Count Distinct',
},
{
value: TracesAggregatorOperator.SUM,
label: 'Sum',
},
{
value: TracesAggregatorOperator.AVG,
label: 'Avg',
},
{
value: TracesAggregatorOperator.MAX,
label: 'Max',
},
{
value: TracesAggregatorOperator.MIN,
label: 'Min',
},
{
value: TracesAggregatorOperator.P05,
label: 'P05',
},
{
value: TracesAggregatorOperator.P10,
label: 'P10',
},
{
value: TracesAggregatorOperator.P20,
label: 'P20',
},
{
value: TracesAggregatorOperator.P25,
label: 'P25',
},
{
value: TracesAggregatorOperator.P50,
label: 'P50',
},
{
value: TracesAggregatorOperator.P75,
label: 'P75',
},
{
value: TracesAggregatorOperator.P90,
label: 'P90',
},
{
value: TracesAggregatorOperator.P95,
label: 'P95',
},
{
value: TracesAggregatorOperator.P99,
label: 'P99',
},
{
value: TracesAggregatorOperator.RATE,
label: 'Rate',
},
{
value: TracesAggregatorOperator.RATE_SUM,
label: 'Rate_sum',
},
{
value: TracesAggregatorOperator.RATE_AVG,
label: 'Rate_avg',
},
{
value: TracesAggregatorOperator.RATE_MIN,
label: 'Rate_min',
},
{
value: TracesAggregatorOperator.RATE_MAX,
label: 'Rate_max',
},
];
export const logsAggregateOperatorOptions: SelectOption<string, string>[] = [
{
value: LogsAggregatorOperator.NOOP,
label: 'NOOP',
},
{
value: LogsAggregatorOperator.COUNT,
label: 'Count',
},
{
value: LogsAggregatorOperator.COUNT_DISTINCT,
label: 'Count Distinct',
},
{
value: LogsAggregatorOperator.SUM,
label: 'Sum',
},
{
value: LogsAggregatorOperator.AVG,
label: 'Avg',
},
{
value: LogsAggregatorOperator.MAX,
label: 'Max',
},
{
value: LogsAggregatorOperator.MIN,
label: 'Min',
},
{
value: LogsAggregatorOperator.P05,
label: 'P05',
},
{
value: LogsAggregatorOperator.P10,
label: 'P10',
},
{
value: LogsAggregatorOperator.P20,
label: 'P20',
},
{
value: LogsAggregatorOperator.P25,
label: 'P25',
},
{
value: LogsAggregatorOperator.P50,
label: 'P50',
},
{
value: LogsAggregatorOperator.P75,
label: 'P75',
},
{
value: LogsAggregatorOperator.P90,
label: 'P90',
},
{
value: LogsAggregatorOperator.P95,
label: 'P95',
},
{
value: LogsAggregatorOperator.P99,
label: 'P99',
},
{
value: LogsAggregatorOperator.RATE,
label: 'Rate',
},
{
value: LogsAggregatorOperator.RATE_SUM,
label: 'Rate_sum',
},
{
value: LogsAggregatorOperator.RATE_AVG,
label: 'Rate_avg',
},
{
value: LogsAggregatorOperator.RATE_MIN,
label: 'Rate_min',
},
{
value: LogsAggregatorOperator.RATE_MAX,
label: 'Rate_max',
},
];

View File

@ -0,0 +1,3 @@
export const REACT_QUERY_KEY = {
GET_ALL_LICENCES: 'GET_ALL_LICENCES',
};

View File

@ -0,0 +1,5 @@
export const FORMULA_REGEXP = /F\d+/;
export const HAVING_FILTER_REGEXP = /^[-\d.,\s]+$/;
export const TYPE_ADDON_REGEXP = /_(.+)/;

View File

@ -16,8 +16,8 @@ const ROUTES = {
LIST_ALL_ALERT: '/alerts',
ALERTS_NEW: '/alerts/new',
ALL_CHANNELS: '/settings/channels',
CHANNELS_NEW: '/setting/channels/new',
CHANNELS_EDIT: '/setting/channels/edit/:id',
CHANNELS_NEW: '/settings/channels/new',
CHANNELS_EDIT: '/settings/channels/:id',
ALL_ERROR: '/exceptions',
ERROR_DETAIL: '/error-detail',
VERSION: '/status',

View File

@ -38,6 +38,8 @@ const themeColors = {
whiteCream: '#ffffffd5',
black: '#000000',
lightgrey: '#ddd',
borderLightGrey: '#d9d9d9',
borderDarkGrey: '#424242',
};
export { themeColors };

View File

@ -1,3 +0,0 @@
export enum QueryBuilderKeys {
GET_AGGREGATE_ATTRIBUTE = 'GET_AGGREGATE_ATTRIBUTE',
}

View File

@ -6,7 +6,7 @@ import ROUTES from 'constants/routes';
import useComponentPermission from 'hooks/useComponentPermission';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
import React, { useCallback, useState } from 'react';
import { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { generatePath } from 'react-router-dom';

View File

@ -1,7 +1,7 @@
import { Button } from 'antd';
import { NotificationInstance } from 'antd/es/notification/interface';
import deleteChannel from 'api/channels/delete';
import React, { useState } from 'react';
import { Dispatch, SetStateAction, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Channels } from 'types/api/channels/getAll';
@ -55,7 +55,7 @@ function Delete({ notifications, setChannels, id }: DeleteProps): JSX.Element {
interface DeleteProps {
notifications: NotificationInstance;
setChannels: React.Dispatch<React.SetStateAction<Channels[]>>;
setChannels: Dispatch<SetStateAction<Channels[]>>;
id: string;
}

View File

@ -7,7 +7,7 @@ import ROUTES from 'constants/routes';
import useComponentPermission from 'hooks/useComponentPermission';
import useFetch from 'hooks/useFetch';
import history from 'lib/history';
import React, { useCallback } from 'react';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';

View File

@ -23,7 +23,7 @@ import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute
import useUrlQuery from 'hooks/useUrlQuery';
import createQueryParams from 'lib/createQueryParams';
import history from 'lib/history';
import React, { useCallback, useEffect, useMemo } from 'react';
import { useCallback, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useQueries } from 'react-query';
import { useSelector } from 'react-redux';

View File

@ -6,7 +6,7 @@ import Header from 'container/Header';
import SideNav from 'container/SideNav';
import TopNav from 'container/TopNav';
import { useNotifications } from 'hooks/useNotifications';
import React, { ReactNode, useEffect, useRef } from 'react';
import { ReactNode, useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { useQueries } from 'react-query';
import { useDispatch, useSelector } from 'react-redux';
@ -18,7 +18,7 @@ import {
UPDATE_CONFIGS,
UPDATE_CURRENT_ERROR,
UPDATE_CURRENT_VERSION,
UPDATE_FEATURE_FLAGS,
UPDATE_FEATURE_FLAG_RESPONSE,
UPDATE_LATEST_VERSION,
UPDATE_LATEST_VERSION_ERROR,
} from 'types/actions/app';
@ -27,7 +27,9 @@ import AppReducer from 'types/reducer/app';
import { ChildrenContainer, Layout } from './styles';
function AppLayout(props: AppLayoutProps): JSX.Element {
const { isLoggedIn } = useSelector<AppState, AppReducer>((state) => state.app);
const { isLoggedIn, user } = useSelector<AppState, AppReducer>(
(state) => state.app,
);
const { pathname } = useLocation();
const { t } = useTranslation();
@ -39,21 +41,21 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
] = useQueries([
{
queryFn: getUserVersion,
queryKey: 'getUserVersion',
queryKey: ['getUserVersion', user?.accessJwt],
enabled: isLoggedIn,
},
{
queryFn: getUserLatestVersion,
queryKey: 'getUserLatestVersion',
queryKey: ['getUserLatestVersion', user?.accessJwt],
enabled: isLoggedIn,
},
{
queryFn: getFeaturesFlags,
queryKey: 'getFeatureFlags',
queryKey: ['getFeatureFlags', user?.accessJwt],
},
{
queryFn: getDynamicConfigs,
queryKey: 'getDynamicConfigs',
queryKey: ['getDynamicConfigs', user?.accessJwt],
},
]);
@ -129,19 +131,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
message: t('oops_something_went_wrong_version'),
});
}
if (
getFeaturesResponse.isFetched &&
getFeaturesResponse.isSuccess &&
getFeaturesResponse.data &&
getFeaturesResponse.data.payload
) {
dispatch({
type: UPDATE_FEATURE_FLAGS,
payload: {
...getFeaturesResponse.data.payload,
},
});
}
if (
getUserVersionResponse.isFetched &&
@ -173,20 +162,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
});
}
if (
getFeaturesResponse.isFetched &&
getFeaturesResponse.isSuccess &&
getFeaturesResponse.data &&
getFeaturesResponse.data.payload
) {
dispatch({
type: UPDATE_FEATURE_FLAGS,
payload: {
...getFeaturesResponse.data.payload,
},
});
}
if (
getDynamicConfigsResponse.isFetched &&
getDynamicConfigsResponse.isSuccess &&
@ -226,6 +201,29 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
notifications,
]);
useEffect(() => {
if (
getFeaturesResponse.isFetched &&
getFeaturesResponse.isSuccess &&
getFeaturesResponse.data &&
getFeaturesResponse.data.payload
) {
dispatch({
type: UPDATE_FEATURE_FLAG_RESPONSE,
payload: {
featureFlag: getFeaturesResponse.data.payload,
refetch: getFeaturesResponse.refetch,
},
});
}
}, [
dispatch,
getFeaturesResponse.data,
getFeaturesResponse.isFetched,
getFeaturesResponse.isSuccess,
getFeaturesResponse.refetch,
]);
const isToDisplayLayout = isLoggedIn;
return (

View File

@ -1,4 +1,4 @@
import React, { PureComponent } from 'react';
import { PureComponent } from 'react';
interface State {
hasError: boolean;

View File

@ -1,4 +1,4 @@
import React from 'react';
import { ReactNode } from 'react';
import { Link } from 'react-router-dom';
function LinkContainer({ children, href }: LinkContainerProps): JSX.Element {
@ -16,7 +16,7 @@ function LinkContainer({ children, href }: LinkContainerProps): JSX.Element {
}
interface LinkContainerProps {
children: React.ReactNode;
children: ReactNode;
href: string;
}

View File

@ -1,7 +1,7 @@
import { Menu, Space } from 'antd';
import Spinner from 'components/Spinner';
import { useIsDarkMode } from 'hooks/useDarkMode';
import React, { Suspense, useMemo } from 'react';
import { lazy, Suspense, useMemo } from 'react';
import { ConfigProps } from 'types/api/dynamicConfigs/getDynamicConfigs';
import ErrorLink from './ErrorLink';
@ -17,7 +17,7 @@ function HelpToolTip({ config }: HelpToolTipProps): JSX.Element {
const items = sortedConfig.map((item) => {
const iconName = `${isDarkMode ? item.darkIcon : item.lightIcon}`;
const Component = React.lazy(
const Component = lazy(
() => import(`@ant-design/icons/es/icons/${iconName}.js`),
);
return {

View File

@ -6,7 +6,7 @@ import {
} from '@ant-design/icons';
import { Dropdown, Space } from 'antd';
import { useIsDarkMode } from 'hooks/useDarkMode';
import React, { useMemo, useState } from 'react';
import { useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { ConfigProps } from 'types/api/dynamicConfigs/getDynamicConfigs';

View File

@ -1,15 +1,17 @@
import { PagerChannel } from './config';
export const PagerInitialConfig: Partial<PagerChannel> = {
description: `{{ range .Alerts -}}
*Alert:* {{ if .Annotations.title }} {{ .Annotations.title }} {{ else }} {{ .Annotations.summary }} {{end}} {{ if .Labels.severity }} - {{ .Labels.severity }}{{ end }}
*Description:* {{ .Annotations.description }}
*Details:*
{{ range .Labels.SortedPairs }} *{{ .Name }}:* {{ .Value }}
{{ end }}
{{ end }}`,
description: `[{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .CommonLabels.alertname }} for {{ .CommonLabels.job }}
{{- if gt (len .CommonLabels) (len .GroupLabels) -}}
{{" "}}(
{{- with .CommonLabels.Remove .GroupLabels.Names }}
{{- range $index, $label := .SortedPairs -}}
{{ if $index }}, {{ end }}
{{- $label.Name }}="{{ $label.Value -}}"
{{- end }}
{{- end -}}
)
{{- end }}`,
severity: '{{ (index .Alerts 0).Labels.severity }}',
client: 'SigNoz Alert Manager',
client_url: 'https://enter-signoz-host-n-port-here/alerts',

View File

@ -9,7 +9,7 @@ import ROUTES from 'constants/routes';
import FormAlertChannels from 'container/FormAlertChannels';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
import React, { useCallback, useState } from 'react';
import { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {

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