mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 03:46:00 +08:00
commit
1ded475b37
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@ -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
|
||||
|
9
.github/workflows/commitlint.yml
vendored
9
.github/workflows/commitlint.yml
vendored
@ -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
|
||||
|
4
.github/workflows/pr_verify_linked_issue.yml
vendored
4
.github/workflows/pr_verify_linked_issue.yml
vendored
@ -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
2
.gitignore
vendored
@ -1,7 +1,5 @@
|
||||
|
||||
node_modules
|
||||
yarn.lock
|
||||
package.json
|
||||
|
||||
deploy/docker/environment_tiny/common_test
|
||||
frontend/node_modules
|
||||
|
@ -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>  </p>
|
||||
|
||||
|
14
README.md
14
README.md
@ -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>  </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
|
||||
|
||||
|
@ -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>  </p>
|
||||
|
||||
|
@ -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>  </p>
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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]
|
@ -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 "++++++++++++++++++++++++++++++++++++++++"
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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: "",
|
||||
},
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"presets": [
|
||||
"@babel/preset-env",
|
||||
"@babel/preset-react",
|
||||
["@babel/preset-react", { "runtime": "automatic" }],
|
||||
"@babel/preset-typescript"
|
||||
],
|
||||
"plugins": [
|
||||
|
@ -16,6 +16,7 @@ module.exports = {
|
||||
'plugin:sonarjs/recommended',
|
||||
'plugin:import/errors',
|
||||
'plugin:import/warnings',
|
||||
'plugin:react/jsx-runtime',
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
|
@ -1 +1,2 @@
|
||||
network-timeout 600000
|
||||
save-prefix ""
|
||||
|
@ -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
|
||||
|
@ -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 () {},
|
||||
};
|
||||
};
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
|
@ -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';
|
||||
|
@ -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;
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
42
frontend/src/api/queryBuilder/getAttributesValues.ts
Normal file
42
frontend/src/api/queryBuilder/getAttributesValues.ts
Normal 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);
|
||||
}
|
||||
};
|
@ -1,5 +1,3 @@
|
||||
import React from 'react';
|
||||
|
||||
function TimeSeries(): JSX.Element {
|
||||
return (
|
||||
<svg
|
||||
|
@ -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;
|
||||
|
@ -1,5 +1,3 @@
|
||||
import React from 'react';
|
||||
|
||||
function NotFound(): JSX.Element {
|
||||
return (
|
||||
<svg
|
||||
|
@ -1,5 +1,3 @@
|
||||
import React from 'react';
|
||||
|
||||
function SomethingWentWrong(): JSX.Element {
|
||||
return (
|
||||
<svg
|
||||
|
@ -1,5 +1,3 @@
|
||||
import React from 'react';
|
||||
|
||||
function UnAuthorized(): JSX.Element {
|
||||
return (
|
||||
<svg
|
||||
|
59
frontend/src/components/Editor/Editor.test.tsx
Normal file
59
frontend/src/components/Editor/Editor.test.tsx
Normal 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" />);
|
||||
});
|
||||
});
|
@ -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>
|
||||
`;
|
@ -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"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -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';
|
||||
|
@ -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 = {
|
||||
|
49
frontend/src/components/Loadable/Loadable.test.tsx
Normal file
49
frontend/src/components/Loadable/Loadable.test.tsx
Normal 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();
|
||||
});
|
||||
});
|
@ -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());
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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>;
|
||||
|
@ -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;
|
||||
|
@ -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}
|
||||
|
@ -1 +1,3 @@
|
||||
export const rawLineStyle: React.CSSProperties = {};
|
||||
import { CSSProperties } from 'react';
|
||||
|
||||
export const rawLineStyle: CSSProperties = {};
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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',
|
||||
};
|
||||
|
@ -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';
|
||||
|
@ -16,6 +16,4 @@ export const TableBodyContent = styled.div<TableBodyContentProps>`
|
||||
font-size: 0.875rem;
|
||||
|
||||
line-height: 2rem;
|
||||
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
8
frontend/src/components/Logs/styles.ts
Normal file
8
frontend/src/components/Logs/styles.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { Button } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const ButtonContainer = styled(Button)`
|
||||
&&& {
|
||||
padding-left: 0;
|
||||
}
|
||||
`;
|
47
frontend/src/components/MessageTip/MessageTip.test.tsx
Normal file
47
frontend/src/components/MessageTip/MessageTip.test.tsx
Normal 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();
|
||||
});
|
||||
});
|
@ -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>
|
||||
`;
|
@ -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({
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Modal, ModalProps as Props } from 'antd';
|
||||
import React, { ReactElement } from 'react';
|
||||
import { ReactElement } from 'react';
|
||||
|
||||
function CustomModal({
|
||||
title,
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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];
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { Tabs, TabsProps } from 'antd';
|
||||
import history from 'lib/history';
|
||||
import React from 'react';
|
||||
|
||||
function RouteTab({
|
||||
routes,
|
||||
|
@ -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,
|
||||
|
@ -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>`
|
||||
|
@ -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}
|
||||
`;
|
||||
|
53
frontend/src/components/TextToolTip/TextToolTip.test.tsx
Normal file
53
frontend/src/components/TextToolTip/TextToolTip.test.tsx
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
@ -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';
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Value } from './styles';
|
||||
|
||||
function ValueGraph({ value }: ValueGraphProps): JSX.Element {
|
||||
|
@ -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;
|
||||
|
9
frontend/src/constants/alerts.ts
Normal file
9
frontend/src/constants/alerts.ts
Normal 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,
|
||||
};
|
@ -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: '',
|
||||
};
|
||||
|
@ -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',
|
||||
}
|
@ -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',
|
||||
}
|
||||
|
@ -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',
|
||||
}
|
||||
|
235
frontend/src/constants/queryBuilder.ts
Normal file
235
frontend/src/constants/queryBuilder.ts
Normal 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['<'],
|
||||
];
|
304
frontend/src/constants/queryBuilderOperators.ts
Normal file
304
frontend/src/constants/queryBuilderOperators.ts
Normal 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',
|
||||
},
|
||||
];
|
3
frontend/src/constants/reactQueryKeys.ts
Normal file
3
frontend/src/constants/reactQueryKeys.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export const REACT_QUERY_KEY = {
|
||||
GET_ALL_LICENCES: 'GET_ALL_LICENCES',
|
||||
};
|
5
frontend/src/constants/regExp.ts
Normal file
5
frontend/src/constants/regExp.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export const FORMULA_REGEXP = /F\d+/;
|
||||
|
||||
export const HAVING_FILTER_REGEXP = /^[-\d.,\s]+$/;
|
||||
|
||||
export const TYPE_ADDON_REGEXP = /_(.+)/;
|
@ -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',
|
||||
|
@ -38,6 +38,8 @@ const themeColors = {
|
||||
whiteCream: '#ffffffd5',
|
||||
black: '#000000',
|
||||
lightgrey: '#ddd',
|
||||
borderLightGrey: '#d9d9d9',
|
||||
borderDarkGrey: '#424242',
|
||||
};
|
||||
|
||||
export { themeColors };
|
||||
|
@ -1,3 +0,0 @@
|
||||
export enum QueryBuilderKeys {
|
||||
GET_AGGREGATE_ATTRIBUTE = 'GET_AGGREGATE_ATTRIBUTE',
|
||||
}
|
@ -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';
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -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 (
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
interface State {
|
||||
hasError: boolean;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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';
|
||||
|
@ -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',
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user