mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-05 16:20:47 +08:00
commit
7b315c6766
45
.github/workflows/build.yaml
vendored
45
.github/workflows/build.yaml
vendored
@ -1,40 +1,15 @@
|
||||
name: build-pipeline
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- develop
|
||||
- main
|
||||
- v*
|
||||
paths:
|
||||
- "pkg/**"
|
||||
- "frontend/**"
|
||||
- release/v*
|
||||
|
||||
jobs:
|
||||
get_filters:
|
||||
runs-on: ubuntu-latest
|
||||
# Set job outputs to values from filter step
|
||||
outputs:
|
||||
frontend: ${{ steps.filter.outputs.frontend }}
|
||||
query-service: ${{ steps.filter.outputs.query-service }}
|
||||
flattener: ${{ steps.filter.outputs.flattener }}
|
||||
steps:
|
||||
# For pull requests it's not necessary to checkout the code
|
||||
- uses: dorny/paths-filter@v2
|
||||
id: filter
|
||||
with:
|
||||
filters: |
|
||||
frontend:
|
||||
- 'frontend/**'
|
||||
query-service:
|
||||
- 'pkg/query-service/**'
|
||||
flattener:
|
||||
- 'pkg/processors/flattener/**'
|
||||
|
||||
build-frontend:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- get_filters
|
||||
if: ${{ needs.get_filters.outputs.frontend == 'true' }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
@ -52,9 +27,6 @@ jobs:
|
||||
|
||||
build-query-service:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- get_filters
|
||||
if: ${{ needs.get_filters.outputs.query-service == 'true' }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
@ -62,16 +34,3 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
make build-query-service-amd64
|
||||
|
||||
build-flattener:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- get_filters
|
||||
if: ${{ needs.get_filters.outputs.flattener == 'true' }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Build flattener docker image
|
||||
shell: bash
|
||||
run: |
|
||||
make build-flattener-amd64
|
||||
|
27
.github/workflows/create-issue-on-pr-merge.yml
vendored
Normal file
27
.github/workflows/create-issue-on-pr-merge.yml
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- closed
|
||||
|
||||
env:
|
||||
GITHUB_ACCESS_TOKEN: ${{ secrets.CI_BOT_TOKEN }}
|
||||
PR_NUMBER: ${{ github.event.number }}
|
||||
jobs:
|
||||
create_issue_on_merge:
|
||||
if: github.event.pull_request.merged == true
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Codebase
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: signoz/gh-bot
|
||||
- name: Use Node v16
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16
|
||||
- name: Setup Cache & Install Dependencies
|
||||
uses: bahmutov/npm-install@v1
|
||||
with:
|
||||
install-command: yarn --frozen-lockfile
|
||||
- name: Comment on PR
|
||||
run: node create-issue.js
|
22
.github/workflows/playwright.yaml
vendored
Normal file
22
.github/workflows/playwright.yaml
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
name: Playwright Tests
|
||||
on:
|
||||
deployment_status:
|
||||
jobs:
|
||||
test:
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.deployment_status.state == 'success'
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "14.x"
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Install Playwright
|
||||
run: npx playwright install --with-deps
|
||||
- name: Run Playwright tests
|
||||
run: npm run test:e2e
|
||||
env:
|
||||
# This might depend on your test-runner/language binding
|
||||
PLAYWRIGHT_TEST_BASE_URL: ${{ github.event.deployment_status.target_url }}
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -15,6 +15,7 @@ frontend/build
|
||||
frontend/.vscode
|
||||
frontend/.yarnclean
|
||||
frontend/.temp_cache
|
||||
frontend/test-results
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
@ -27,10 +28,6 @@ frontend/npm-debug.log*
|
||||
frontend/yarn-debug.log*
|
||||
frontend/yarn-error.log*
|
||||
frontend/src/constants/env.ts
|
||||
frontend/cypress/**/*.mp4
|
||||
|
||||
# env file for cypress
|
||||
frontend/cypress.env.json
|
||||
|
||||
.idea
|
||||
|
||||
|
@ -106,32 +106,70 @@ Need to update [https://github.com/SigNoz/charts](https://github.com/SigNoz/char
|
||||
- [k3d](https://k3d.io/#installation)
|
||||
- [minikube](https://minikube.sigs.k8s.io/docs/start/)
|
||||
- create a k8s cluster and make sure `kubectl` points to the locally created k8s cluster
|
||||
- run `helm install -n platform --create-namespace my-release charts/signoz` to install SigNoz chart
|
||||
- run `kubectl -n platform port-forward svc/my-release-frontend 3301:3301` to make SigNoz UI available at [localhost:3301](http://localhost:3301)
|
||||
- run `make dev-install` to install SigNoz chart with `my-release` release name in `platform` namespace.
|
||||
- run `kubectl -n platform port-forward svc/my-release-signoz-frontend 3301:3301` to make SigNoz UI available at [localhost:3301](http://localhost:3301)
|
||||
|
||||
**To install HotROD sample app:**
|
||||
|
||||
```bash
|
||||
curl -sL https://github.com/SigNoz/signoz/raw/main/sample-apps/hotrod/hotrod-install.sh \
|
||||
| HELM_RELEASE=my-release SIGNOZ_NAMESPACE=platform bash
|
||||
```
|
||||
|
||||
**To load data with HotROD sample app:**
|
||||
|
||||
```sh
|
||||
kubectl create ns sample-application
|
||||
|
||||
kubectl -n sample-application apply -f https://raw.githubusercontent.com/SigNoz/signoz/main/sample-apps/hotrod/hotrod.yaml
|
||||
|
||||
```bash
|
||||
kubectl -n sample-application run strzal --image=djbingham/curl \
|
||||
--restart='OnFailure' -i --tty --rm --command -- curl -X POST -F \
|
||||
'locust_count=6' -F 'hatch_rate=2' http://locust-master:8089/swarm
|
||||
--restart='OnFailure' -i --tty --rm --command -- curl -X POST -F \
|
||||
'locust_count=6' -F 'hatch_rate=2' http://locust-master:8089/swarm
|
||||
```
|
||||
|
||||
**To stop the load generation:**
|
||||
|
||||
```sh
|
||||
```bash
|
||||
kubectl -n sample-application run strzal --image=djbingham/curl \
|
||||
--restart='OnFailure' -i --tty --rm --command -- curl \
|
||||
http://locust-master:8089/stop
|
||||
--restart='OnFailure' -i --tty --rm --command -- curl \
|
||||
http://locust-master:8089/stop
|
||||
```
|
||||
|
||||
**To delete HotROD sample app:**
|
||||
|
||||
```bash
|
||||
curl -sL https://github.com/SigNoz/signoz/raw/main/sample-apps/hotrod/hotrod-delete.sh \
|
||||
| HOTROD_NAMESPACE=sample-application bash
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## General Instructions
|
||||
|
||||
**Before making any significant changes, please open an issue**. Each issue
|
||||
should describe the following:
|
||||
|
||||
* Requirement - what kind of use case are you trying to solve?
|
||||
* Proposal - what do you suggest to solve the problem or improve the existing
|
||||
situation?
|
||||
* Any open questions to address
|
||||
|
||||
Discussing your proposed changes ahead of time will make the contribution
|
||||
process smooth for everyone. Once the approach is agreed upon, make your changes
|
||||
and open a pull request(s). Unless your change is small, Please consider submitting different PRs:
|
||||
|
||||
* First PR should include the overall structure of the new component:
|
||||
* Readme, configuration, interfaces or base classes etc...
|
||||
* This PR is usually trivial to review, so the size limit does not apply to
|
||||
it.
|
||||
* Second PR should include the concrete implementation of the component. If the
|
||||
size of this PR is larger than the recommended size consider splitting it in
|
||||
multiple PRs.
|
||||
* If there are multiple sub-component then ideally each one should be implemented as
|
||||
a separate pull request.
|
||||
* Last PR should include changes to any user facing documentation. And should include
|
||||
end to end tests if applicable. The component must be enabled
|
||||
only after sufficient testing, and there is enough confidence in the
|
||||
stability and quality of the component.
|
||||
|
||||
|
||||
You can always reach out to `ankit@signoz.io` to understand more about the repo and product. We are very responsive over email and [slack](https://signoz.io/slack).
|
||||
|
||||
- If you find any bugs, please create an issue
|
||||
|
25
Makefile
25
Makefile
@ -10,7 +10,6 @@ BUILD_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD)
|
||||
|
||||
# Internal variables or constants.
|
||||
FRONTEND_DIRECTORY ?= frontend
|
||||
FLATTENER_DIRECTORY ?= pkg/processors/flattener
|
||||
QUERY_SERVICE_DIRECTORY ?= pkg/query-service
|
||||
STANDALONE_DIRECTORY ?= deploy/docker/clickhouse-setup
|
||||
SWARM_DIRECTORY ?= deploy/docker-swarm/clickhouse-setup
|
||||
@ -20,7 +19,6 @@ DOCKER_TAG ?= latest
|
||||
|
||||
FRONTEND_DOCKER_IMAGE ?= frontend
|
||||
QUERY_SERVICE_DOCKER_IMAGE ?= query-service
|
||||
FLATTERNER_DOCKER_IMAGE ?= flattener-processor
|
||||
|
||||
# Build-time Go variables
|
||||
PACKAGE?=go.signoz.io/query-service
|
||||
@ -31,7 +29,7 @@ gitBranch=${PACKAGE}/version.gitBranch
|
||||
|
||||
LD_FLAGS="-X ${buildHash}=${BUILD_HASH} -X ${buildTime}=${BUILD_TIME} -X ${buildVersion}=${BUILD_VERSION} -X ${gitBranch}=${BUILD_BRANCH}"
|
||||
|
||||
all: build-push-frontend build-push-query-service build-push-flattener
|
||||
all: build-push-frontend build-push-query-service
|
||||
# Steps to build and push docker image of frontend
|
||||
.PHONY: build-frontend-amd64 build-push-frontend
|
||||
# Step to build docker image of frontend in amd64 (used in build pipeline)
|
||||
@ -73,27 +71,6 @@ build-push-query-service:
|
||||
--push --platform linux/arm64,linux/amd64 --build-arg LD_FLAGS=$(LD_FLAGS) \
|
||||
--tag $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) .
|
||||
|
||||
# Steps to build and push docker image of flattener
|
||||
.PHONY: build-flattener-amd64 build-push-flattener
|
||||
# Step to build docker image of flattener in amd64 (used in build pipeline)
|
||||
build-flattener-amd64:
|
||||
@echo "------------------"
|
||||
@echo "--> Building flattener docker image for amd64"
|
||||
@echo "------------------"
|
||||
@cd $(FLATTENER_DIRECTORY) && \
|
||||
docker build -f Dockerfile --no-cache -t $(REPONAME)/$(FLATTERNER_DOCKER_IMAGE):$(DOCKER_TAG) \
|
||||
--build-arg TARGETPLATFORM="linux/amd64" .
|
||||
|
||||
# Step to build and push docker image of flattener in amd64 (used in push pipeline)
|
||||
build-push-flattener:
|
||||
@echo "------------------"
|
||||
@echo "--> Building and pushing flattener docker image"
|
||||
@echo "------------------"
|
||||
@cd $(FLATTENER_DIRECTORY) && \
|
||||
docker buildx build --file Dockerfile --progress plane \
|
||||
--no-cache --push --platform linux/arm64,linux/amd64 \
|
||||
--tag $(REPONAME)/$(FLATTERNER_DOCKER_IMAGE):$(DOCKER_TAG) .
|
||||
|
||||
dev-setup:
|
||||
mkdir -p /var/lib/signoz
|
||||
sqlite3 /var/lib/signoz/signoz.db "VACUUM";
|
||||
|
18
SECURITY.md
Normal file
18
SECURITY.md
Normal file
@ -0,0 +1,18 @@
|
||||
# Security Policy
|
||||
|
||||
SigNoz is looking forward to working with security researchers across the world to keep SigNoz and our users safe. If you have found an issue in our systems/applications, please reach out to us.
|
||||
|
||||
## Supported Versions
|
||||
We always recommend using the latest version of SigNoz to ensure you get all security updates
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you believe you have found a security vulnerability within SigNoz, please let us know right away. We'll try and fix the problem as soon as possible.
|
||||
|
||||
**Do not report vulnerabilities using public GitHub issues**. Instead, email <security@signoz.io> with a detailed account of the issue. Please submit one issue per email, this helps us triage vulnerabilities.
|
||||
|
||||
Once we've received your email we'll keep you updated as we fix the vulnerability.
|
||||
|
||||
## Thanks
|
||||
|
||||
Thank you for keeping SigNoz and our users safe. 🙇
|
@ -28,7 +28,7 @@ services:
|
||||
volumes:
|
||||
- ./data/alertmanager:/data
|
||||
command:
|
||||
- --queryService.url=http://query-service:8080
|
||||
- --queryService.url=http://query-service:8085
|
||||
- --storage.path=/data
|
||||
depends_on:
|
||||
- query-service
|
||||
@ -37,10 +37,11 @@ services:
|
||||
condition: on-failure
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:0.8.0
|
||||
image: signoz/query-service:0.8.1
|
||||
command: ["-config=/root/config/prometheus.yml"]
|
||||
ports:
|
||||
- "8080:8080"
|
||||
# ports:
|
||||
# - "6060:6060" # pprof port
|
||||
# - "8080:8080" # query-service port
|
||||
volumes:
|
||||
- ./prometheus.yml:/root/config/prometheus.yml
|
||||
- ../dashboards:/root/config/dashboards
|
||||
@ -64,7 +65,7 @@ services:
|
||||
- clickhouse
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:0.8.0
|
||||
image: signoz/frontend:0.8.1
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
@ -77,7 +78,7 @@ services:
|
||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
|
||||
otel-collector:
|
||||
image: signoz/otelcontribcol:0.43.0-0.1
|
||||
image: signoz/otelcontribcol:0.45.1-0.2
|
||||
command: ["--config=/etc/otel-collector-config.yaml"]
|
||||
volumes:
|
||||
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
|
||||
@ -85,7 +86,7 @@ services:
|
||||
- "4317:4317" # OTLP gRPC receiver
|
||||
- "4318:4318" # OTLP HTTP receiver
|
||||
# - "8889:8889" # Prometheus metrics exposed by the agent
|
||||
# - "13133" # health_check
|
||||
# - "13133:13133" # health_check
|
||||
# - "14268:14268" # Jaeger receiver
|
||||
# - "55678:55678" # OpenCensus receiver
|
||||
# - "55679:55679" # zpages extension
|
||||
@ -103,7 +104,7 @@ services:
|
||||
- clickhouse
|
||||
|
||||
otel-collector-metrics:
|
||||
image: signoz/otelcontribcol:0.43.0-0.1
|
||||
image: signoz/otelcontribcol:0.45.1-0.2
|
||||
command: ["--config=/etc/otel-collector-metrics-config.yaml"]
|
||||
volumes:
|
||||
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml
|
||||
|
@ -52,7 +52,7 @@ extensions:
|
||||
health_check: {}
|
||||
zpages: {}
|
||||
exporters:
|
||||
clickhouse:
|
||||
clickhousetraces:
|
||||
datasource: tcp://clickhouse:9000/?database=signoz_traces
|
||||
clickhousemetricswrite:
|
||||
endpoint: tcp://clickhouse:9000/?database=signoz_metrics
|
||||
@ -66,7 +66,7 @@ service:
|
||||
traces:
|
||||
receivers: [jaeger, otlp]
|
||||
processors: [signozspanmetrics/prometheus, batch]
|
||||
exporters: [clickhouse]
|
||||
exporters: [clickhousetraces]
|
||||
metrics:
|
||||
receivers: [otlp, hostmetrics]
|
||||
processors: [batch]
|
||||
|
@ -12,13 +12,18 @@ server {
|
||||
gzip_http_version 1.1;
|
||||
|
||||
location / {
|
||||
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0";
|
||||
add_header Last-Modified $date_gmt;
|
||||
if ( $uri = '/index.html' ) {
|
||||
add_header Cache-Control no-store always;
|
||||
}
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location /api/alertmanager {
|
||||
proxy_pass http://alertmanager:9093/api/v2;
|
||||
}
|
||||
|
||||
location /api {
|
||||
proxy_pass http://query-service:8080/api;
|
||||
}
|
||||
|
@ -30,16 +30,19 @@ services:
|
||||
condition: service_healthy
|
||||
restart: on-failure
|
||||
command:
|
||||
- --queryService.url=http://query-service:8080
|
||||
- --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`
|
||||
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:0.8.0
|
||||
image: signoz/query-service:0.8.1
|
||||
container_name: query-service
|
||||
command: ["-config=/root/config/prometheus.yml"]
|
||||
# ports:
|
||||
# - "6060:6060" # pprof port
|
||||
# - "8080:8080" # query-service port
|
||||
volumes:
|
||||
- ./prometheus.yml:/root/config/prometheus.yml
|
||||
- ../dashboards:/root/config/dashboards
|
||||
@ -50,7 +53,6 @@ services:
|
||||
- GODEBUG=netdns=go
|
||||
- TELEMETRY_ENABLED=true
|
||||
- DEPLOYMENT_TYPE=docker-standalone-arm
|
||||
|
||||
restart: on-failure
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--spider", "-q", "localhost:8080/api/v1/version"]
|
||||
@ -62,7 +64,7 @@ services:
|
||||
condition: service_healthy
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:0.8.0
|
||||
image: signoz/frontend:0.8.1
|
||||
container_name: frontend
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
@ -74,7 +76,7 @@ services:
|
||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
|
||||
otel-collector:
|
||||
image: signoz/otelcontribcol:0.43.0-0.1
|
||||
image: signoz/otelcontribcol:0.45.1-0.2
|
||||
command: ["--config=/etc/otel-collector-config.yaml"]
|
||||
volumes:
|
||||
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
|
||||
@ -82,7 +84,7 @@ services:
|
||||
- "4317:4317" # OTLP gRPC receiver
|
||||
- "4318:4318" # OTLP HTTP receiver
|
||||
# - "8889:8889" # Prometheus metrics exposed by the agent
|
||||
# - "13133" # health_check
|
||||
# - "13133:13133" # health_check
|
||||
# - "14268:14268" # Jaeger receiver
|
||||
# - "55678:55678" # OpenCensus receiver
|
||||
# - "55679:55679" # zpages extension
|
||||
@ -95,7 +97,7 @@ services:
|
||||
condition: service_healthy
|
||||
|
||||
otel-collector-metrics:
|
||||
image: signoz/otelcontribcol:0.43.0-0.1
|
||||
image: signoz/otelcontribcol:0.45.1-0.2
|
||||
command: ["--config=/etc/otel-collector-metrics-config.yaml"]
|
||||
volumes:
|
||||
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml
|
||||
|
@ -30,15 +30,18 @@ services:
|
||||
condition: service_healthy
|
||||
restart: on-failure
|
||||
command:
|
||||
- --queryService.url=http://query-service:8080
|
||||
- --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`
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:0.8.0
|
||||
image: signoz/query-service:0.8.1
|
||||
container_name: query-service
|
||||
command: ["-config=/root/config/prometheus.yml"]
|
||||
# ports:
|
||||
# - "6060:6060" # pprof port
|
||||
# - "8080:8080" # query-service port
|
||||
volumes:
|
||||
- ./prometheus.yml:/root/config/prometheus.yml
|
||||
- ../dashboards:/root/config/dashboards
|
||||
@ -60,7 +63,7 @@ services:
|
||||
condition: service_healthy
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:0.8.0
|
||||
image: signoz/frontend:0.8.1
|
||||
container_name: frontend
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
@ -72,7 +75,7 @@ services:
|
||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
|
||||
otel-collector:
|
||||
image: signoz/otelcontribcol:0.43.0-0.1
|
||||
image: signoz/otelcontribcol:0.45.1-0.2
|
||||
command: ["--config=/etc/otel-collector-config.yaml"]
|
||||
volumes:
|
||||
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
|
||||
@ -80,7 +83,7 @@ services:
|
||||
- "4317:4317" # OTLP gRPC receiver
|
||||
- "4318:4318" # OTLP HTTP receiver
|
||||
# - "8889:8889" # Prometheus metrics exposed by the agent
|
||||
# - "13133" # health_check
|
||||
# - "13133:13133" # health_check
|
||||
# - "14268:14268" # Jaeger receiver
|
||||
# - "55678:55678" # OpenCensus receiver
|
||||
# - "55679:55679" # zpages extension
|
||||
@ -93,7 +96,7 @@ services:
|
||||
condition: service_healthy
|
||||
|
||||
otel-collector-metrics:
|
||||
image: signoz/otelcontribcol:0.43.0-0.1
|
||||
image: signoz/otelcontribcol:0.45.1-0.2
|
||||
command: ["--config=/etc/otel-collector-metrics-config.yaml"]
|
||||
volumes:
|
||||
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml
|
||||
|
@ -52,7 +52,7 @@ extensions:
|
||||
health_check: {}
|
||||
zpages: {}
|
||||
exporters:
|
||||
clickhouse:
|
||||
clickhousetraces:
|
||||
datasource: tcp://clickhouse:9000/?database=signoz_traces
|
||||
clickhousemetricswrite:
|
||||
endpoint: tcp://clickhouse:9000/?database=signoz_metrics
|
||||
@ -66,7 +66,7 @@ service:
|
||||
traces:
|
||||
receivers: [jaeger, otlp]
|
||||
processors: [signozspanmetrics/prometheus, batch]
|
||||
exporters: [clickhouse]
|
||||
exporters: [clickhousetraces]
|
||||
metrics:
|
||||
receivers: [otlp, hostmetrics]
|
||||
processors: [batch]
|
||||
|
@ -9,17 +9,18 @@ server {
|
||||
gzip_vary on;
|
||||
gzip_comp_level 6;
|
||||
gzip_buffers 16 8k;
|
||||
gzip_http_version 1.1;
|
||||
gzip_http_version 1.1;
|
||||
|
||||
location / {
|
||||
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0";
|
||||
add_header Last-Modified $date_gmt;
|
||||
if ( $uri = '/index.html' ) {
|
||||
add_header Cache-Control no-store always;
|
||||
}
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location /api/alertmanager{
|
||||
location /api/alertmanager {
|
||||
proxy_pass http://alertmanager:9093/api/v2;
|
||||
}
|
||||
|
||||
|
@ -1,273 +0,0 @@
|
||||
version: "2.4"
|
||||
|
||||
volumes:
|
||||
metadata_data: {}
|
||||
middle_var: {}
|
||||
historical_var: {}
|
||||
broker_var: {}
|
||||
coordinator_var: {}
|
||||
router_var: {}
|
||||
|
||||
# If able to connect to kafka but not able to write to topic otlp_spans look into below link
|
||||
# https://github.com/wurstmeister/kafka-docker/issues/409#issuecomment-428346707
|
||||
|
||||
services:
|
||||
|
||||
zookeeper:
|
||||
image: bitnami/zookeeper:3.6.2-debian-10-r100
|
||||
ports:
|
||||
- "2181:2181"
|
||||
environment:
|
||||
- ALLOW_ANONYMOUS_LOGIN=yes
|
||||
|
||||
|
||||
kafka:
|
||||
# image: wurstmeister/kafka
|
||||
image: bitnami/kafka:2.7.0-debian-10-r1
|
||||
ports:
|
||||
- "9092:9092"
|
||||
hostname: kafka
|
||||
environment:
|
||||
KAFKA_ADVERTISED_HOST_NAME: kafka
|
||||
KAFKA_ADVERTISED_PORT: 9092
|
||||
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
|
||||
ALLOW_PLAINTEXT_LISTENER: 'yes'
|
||||
KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE: 'true'
|
||||
KAFKA_TOPICS: 'otlp_spans:1:1,flattened_spans:1:1'
|
||||
|
||||
healthcheck:
|
||||
# test: ["CMD", "kafka-topics.sh", "--create", "--topic", "otlp_spans", "--zookeeper", "zookeeper:2181"]
|
||||
test: ["CMD", "kafka-topics.sh", "--list", "--zookeeper", "zookeeper:2181"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 10
|
||||
depends_on:
|
||||
- zookeeper
|
||||
|
||||
postgres:
|
||||
container_name: postgres
|
||||
image: postgres:latest
|
||||
volumes:
|
||||
- metadata_data:/var/lib/postgresql/data
|
||||
environment:
|
||||
- POSTGRES_PASSWORD=FoolishPassword
|
||||
- POSTGRES_USER=druid
|
||||
- POSTGRES_DB=druid
|
||||
|
||||
coordinator:
|
||||
image: apache/druid:0.20.0
|
||||
container_name: coordinator
|
||||
volumes:
|
||||
- ./storage:/opt/data
|
||||
- coordinator_var:/opt/druid/var
|
||||
depends_on:
|
||||
- zookeeper
|
||||
- postgres
|
||||
ports:
|
||||
- "8081:8081"
|
||||
command:
|
||||
- coordinator
|
||||
env_file:
|
||||
- environment_tiny/coordinator
|
||||
- environment_tiny/common
|
||||
|
||||
broker:
|
||||
image: apache/druid:0.20.0
|
||||
container_name: broker
|
||||
volumes:
|
||||
- broker_var:/opt/druid/var
|
||||
depends_on:
|
||||
- zookeeper
|
||||
- postgres
|
||||
- coordinator
|
||||
ports:
|
||||
- "8082:8082"
|
||||
command:
|
||||
- broker
|
||||
env_file:
|
||||
- environment_tiny/broker
|
||||
- environment_tiny/common
|
||||
|
||||
historical:
|
||||
image: apache/druid:0.20.0
|
||||
container_name: historical
|
||||
volumes:
|
||||
- ./storage:/opt/data
|
||||
- historical_var:/opt/druid/var
|
||||
depends_on:
|
||||
- zookeeper
|
||||
- postgres
|
||||
- coordinator
|
||||
ports:
|
||||
- "8083:8083"
|
||||
command:
|
||||
- historical
|
||||
env_file:
|
||||
- environment_tiny/historical
|
||||
- environment_tiny/common
|
||||
|
||||
middlemanager:
|
||||
image: apache/druid:0.20.0
|
||||
container_name: middlemanager
|
||||
volumes:
|
||||
- ./storage:/opt/data
|
||||
- middle_var:/opt/druid/var
|
||||
depends_on:
|
||||
- zookeeper
|
||||
- postgres
|
||||
- coordinator
|
||||
ports:
|
||||
- "8091:8091"
|
||||
command:
|
||||
- middleManager
|
||||
env_file:
|
||||
- environment_tiny/middlemanager
|
||||
- environment_tiny/common
|
||||
|
||||
router:
|
||||
image: apache/druid:0.20.0
|
||||
container_name: router
|
||||
volumes:
|
||||
- router_var:/opt/druid/var
|
||||
depends_on:
|
||||
- zookeeper
|
||||
- postgres
|
||||
- coordinator
|
||||
ports:
|
||||
- "8888:8888"
|
||||
command:
|
||||
- router
|
||||
env_file:
|
||||
- environment_tiny/router
|
||||
- environment_tiny/common
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--spider", "-q", "http://router:8888/druid/coordinator/v1/datasources/flattened_spans"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
flatten-processor:
|
||||
image: signoz/flattener-processor:0.4.0
|
||||
container_name: flattener-processor
|
||||
|
||||
depends_on:
|
||||
- kafka
|
||||
- otel-collector
|
||||
ports:
|
||||
- "8000:8000"
|
||||
|
||||
environment:
|
||||
- KAFKA_BROKER=kafka:9092
|
||||
- KAFKA_INPUT_TOPIC=otlp_spans
|
||||
- KAFKA_OUTPUT_TOPIC=flattened_spans
|
||||
|
||||
|
||||
query-service:
|
||||
image: signoz.docker.scarf.sh/signoz/query-service:0.4.1
|
||||
container_name: query-service
|
||||
|
||||
depends_on:
|
||||
router:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- ../dashboards:/root/config/dashboards
|
||||
- ./data/signoz/:/var/lib/signoz/
|
||||
environment:
|
||||
- DruidClientUrl=http://router:8888
|
||||
- DruidDatasource=flattened_spans
|
||||
- STORAGE=druid
|
||||
- POSTHOG_API_KEY=H-htDCae7CR3RV57gUzmol6IAKtm5IMCvbcm_fwnL-w
|
||||
- GODEBUG=netdns=go
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:0.4.1
|
||||
container_name: frontend
|
||||
|
||||
depends_on:
|
||||
- query-service
|
||||
links:
|
||||
- "query-service"
|
||||
ports:
|
||||
- "3301:3301"
|
||||
volumes:
|
||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
|
||||
create-supervisor:
|
||||
image: theithollow/hollowapp-blog:curl
|
||||
container_name: create-supervisor
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- "curl -X POST -H 'Content-Type: application/json' -d @/app/supervisor-spec.json http://router:8888/druid/indexer/v1/supervisor"
|
||||
|
||||
depends_on:
|
||||
- router
|
||||
restart: on-failure:6
|
||||
|
||||
volumes:
|
||||
- ./druid-jobs/supervisor-spec.json:/app/supervisor-spec.json
|
||||
|
||||
|
||||
set-retention:
|
||||
image: theithollow/hollowapp-blog:curl
|
||||
container_name: set-retention
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- "curl -X POST -H 'Content-Type: application/json' -d @/app/retention-spec.json http://router:8888/druid/coordinator/v1/rules/flattened_spans"
|
||||
|
||||
depends_on:
|
||||
- router
|
||||
restart: on-failure:6
|
||||
volumes:
|
||||
- ./druid-jobs/retention-spec.json:/app/retention-spec.json
|
||||
|
||||
otel-collector:
|
||||
image: otel/opentelemetry-collector:0.18.0
|
||||
command: ["--config=/etc/otel-collector-config.yaml", "--mem-ballast-size-mib=683"]
|
||||
volumes:
|
||||
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
|
||||
ports:
|
||||
- "1777:1777" # pprof extension
|
||||
- "8887:8888" # Prometheus metrics exposed by the agent
|
||||
- "14268:14268" # Jaeger receiver
|
||||
- "55678" # OpenCensus receiver
|
||||
- "55680:55680" # OTLP HTTP/2.0 legacy port
|
||||
- "55681:55681" # OTLP HTTP/1.0 receiver
|
||||
- "4317:4317" # OTLP GRPC receiver
|
||||
- "55679:55679" # zpages extension
|
||||
- "13133" # health_check
|
||||
depends_on:
|
||||
kafka:
|
||||
condition: service_healthy
|
||||
|
||||
|
||||
hotrod:
|
||||
image: jaegertracing/example-hotrod:latest
|
||||
container_name: hotrod
|
||||
ports:
|
||||
- "9000:8080"
|
||||
command: ["all"]
|
||||
environment:
|
||||
- JAEGER_ENDPOINT=http://otel-collector:14268/api/traces
|
||||
|
||||
|
||||
load-hotrod:
|
||||
image: "grubykarol/locust:1.2.3-python3.9-alpine3.12"
|
||||
container_name: load-hotrod
|
||||
hostname: load-hotrod
|
||||
ports:
|
||||
- "8089:8089"
|
||||
environment:
|
||||
ATTACKED_HOST: http://hotrod:8080
|
||||
LOCUST_MODE: standalone
|
||||
NO_PROXY: standalone
|
||||
TASK_DELAY_FROM: 5
|
||||
TASK_DELAY_TO: 30
|
||||
QUIET_MODE: "${QUIET_MODE:-false}"
|
||||
LOCUST_OPTS: "--headless -u 10 -r 1"
|
||||
volumes:
|
||||
- ../common/locust-scripts:/locust
|
||||
|
@ -1,269 +0,0 @@
|
||||
version: "2.4"
|
||||
|
||||
volumes:
|
||||
metadata_data: {}
|
||||
middle_var: {}
|
||||
historical_var: {}
|
||||
broker_var: {}
|
||||
coordinator_var: {}
|
||||
router_var: {}
|
||||
|
||||
# If able to connect to kafka but not able to write to topic otlp_spans look into below link
|
||||
# https://github.com/wurstmeister/kafka-docker/issues/409#issuecomment-428346707
|
||||
|
||||
services:
|
||||
|
||||
zookeeper:
|
||||
image: bitnami/zookeeper:3.6.2-debian-10-r100
|
||||
ports:
|
||||
- "2181:2181"
|
||||
environment:
|
||||
- ALLOW_ANONYMOUS_LOGIN=yes
|
||||
|
||||
|
||||
kafka:
|
||||
# image: wurstmeister/kafka
|
||||
image: bitnami/kafka:2.7.0-debian-10-r1
|
||||
ports:
|
||||
- "9092:9092"
|
||||
hostname: kafka
|
||||
environment:
|
||||
KAFKA_ADVERTISED_HOST_NAME: kafka
|
||||
KAFKA_ADVERTISED_PORT: 9092
|
||||
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
|
||||
ALLOW_PLAINTEXT_LISTENER: 'yes'
|
||||
KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE: 'true'
|
||||
KAFKA_TOPICS: 'otlp_spans:1:1,flattened_spans:1:1'
|
||||
|
||||
healthcheck:
|
||||
# test: ["CMD", "kafka-topics.sh", "--create", "--topic", "otlp_spans", "--zookeeper", "zookeeper:2181"]
|
||||
test: ["CMD", "kafka-topics.sh", "--list", "--zookeeper", "zookeeper:2181"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 10
|
||||
depends_on:
|
||||
- zookeeper
|
||||
|
||||
postgres:
|
||||
container_name: postgres
|
||||
image: postgres:latest
|
||||
volumes:
|
||||
- metadata_data:/var/lib/postgresql/data
|
||||
environment:
|
||||
- POSTGRES_PASSWORD=FoolishPassword
|
||||
- POSTGRES_USER=druid
|
||||
- POSTGRES_DB=druid
|
||||
|
||||
coordinator:
|
||||
image: apache/druid:0.20.0
|
||||
container_name: coordinator
|
||||
volumes:
|
||||
- ./storage:/opt/druid/deepStorage
|
||||
- coordinator_var:/opt/druid/data
|
||||
depends_on:
|
||||
- zookeeper
|
||||
- postgres
|
||||
ports:
|
||||
- "8081:8081"
|
||||
command:
|
||||
- coordinator
|
||||
env_file:
|
||||
- environment_small/coordinator
|
||||
|
||||
broker:
|
||||
image: apache/druid:0.20.0
|
||||
container_name: broker
|
||||
volumes:
|
||||
- broker_var:/opt/druid/data
|
||||
depends_on:
|
||||
- zookeeper
|
||||
- postgres
|
||||
- coordinator
|
||||
ports:
|
||||
- "8082:8082"
|
||||
command:
|
||||
- broker
|
||||
env_file:
|
||||
- environment_small/broker
|
||||
|
||||
historical:
|
||||
image: apache/druid:0.20.0
|
||||
container_name: historical
|
||||
volumes:
|
||||
- ./storage:/opt/druid/deepStorage
|
||||
- historical_var:/opt/druid/data
|
||||
depends_on:
|
||||
- zookeeper
|
||||
- postgres
|
||||
- coordinator
|
||||
ports:
|
||||
- "8083:8083"
|
||||
command:
|
||||
- historical
|
||||
env_file:
|
||||
- environment_small/historical
|
||||
|
||||
middlemanager:
|
||||
image: apache/druid:0.20.0
|
||||
container_name: middlemanager
|
||||
volumes:
|
||||
- ./storage:/opt/druid/deepStorage
|
||||
- middle_var:/opt/druid/data
|
||||
depends_on:
|
||||
- zookeeper
|
||||
- postgres
|
||||
- coordinator
|
||||
ports:
|
||||
- "8091:8091"
|
||||
command:
|
||||
- middleManager
|
||||
env_file:
|
||||
- environment_small/middlemanager
|
||||
|
||||
router:
|
||||
image: apache/druid:0.20.0
|
||||
container_name: router
|
||||
volumes:
|
||||
- router_var:/opt/druid/data
|
||||
depends_on:
|
||||
- zookeeper
|
||||
- postgres
|
||||
- coordinator
|
||||
ports:
|
||||
- "8888:8888"
|
||||
command:
|
||||
- router
|
||||
env_file:
|
||||
- environment_small/router
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--spider", "-q", "http://router:8888/druid/coordinator/v1/datasources/flattened_spans"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
flatten-processor:
|
||||
image: signoz/flattener-processor:0.4.0
|
||||
container_name: flattener-processor
|
||||
|
||||
depends_on:
|
||||
- kafka
|
||||
- otel-collector
|
||||
ports:
|
||||
- "8000:8000"
|
||||
|
||||
environment:
|
||||
- KAFKA_BROKER=kafka:9092
|
||||
- KAFKA_INPUT_TOPIC=otlp_spans
|
||||
- KAFKA_OUTPUT_TOPIC=flattened_spans
|
||||
|
||||
|
||||
query-service:
|
||||
image: signoz.docker.scarf.sh/signoz/query-service:0.4.1
|
||||
container_name: query-service
|
||||
|
||||
depends_on:
|
||||
router:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "8080:8080"
|
||||
|
||||
volumes:
|
||||
- ../dashboards:/root/config/dashboards
|
||||
- ./data/signoz/:/var/lib/signoz/
|
||||
environment:
|
||||
- DruidClientUrl=http://router:8888
|
||||
- DruidDatasource=flattened_spans
|
||||
- STORAGE=druid
|
||||
- POSTHOG_API_KEY=H-htDCae7CR3RV57gUzmol6IAKtm5IMCvbcm_fwnL-w
|
||||
- GODEBUG=netdns=go
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:0.4.1
|
||||
container_name: frontend
|
||||
|
||||
depends_on:
|
||||
- query-service
|
||||
links:
|
||||
- "query-service"
|
||||
ports:
|
||||
- "3301:3301"
|
||||
volumes:
|
||||
- ./nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
|
||||
create-supervisor:
|
||||
image: theithollow/hollowapp-blog:curl
|
||||
container_name: create-supervisor
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- "curl -X POST -H 'Content-Type: application/json' -d @/app/supervisor-spec.json http://router:8888/druid/indexer/v1/supervisor"
|
||||
|
||||
depends_on:
|
||||
- router
|
||||
restart: on-failure:6
|
||||
|
||||
volumes:
|
||||
- ./druid-jobs/supervisor-spec.json:/app/supervisor-spec.json
|
||||
|
||||
|
||||
set-retention:
|
||||
image: theithollow/hollowapp-blog:curl
|
||||
container_name: set-retention
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- "curl -X POST -H 'Content-Type: application/json' -d @/app/retention-spec.json http://router:8888/druid/coordinator/v1/rules/flattened_spans"
|
||||
|
||||
depends_on:
|
||||
- router
|
||||
restart: on-failure:6
|
||||
volumes:
|
||||
- ./druid-jobs/retention-spec.json:/app/retention-spec.json
|
||||
|
||||
otel-collector:
|
||||
image: otel/opentelemetry-collector:0.18.0
|
||||
command: ["--config=/etc/otel-collector-config.yaml", "--mem-ballast-size-mib=683"]
|
||||
volumes:
|
||||
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
|
||||
ports:
|
||||
- "1777:1777" # pprof extension
|
||||
- "8887:8888" # Prometheus metrics exposed by the agent
|
||||
- "14268:14268" # Jaeger receiver
|
||||
- "55678" # OpenCensus receiver
|
||||
- "55680:55680" # OTLP HTTP/2.0 leagcy grpc receiver
|
||||
- "55681:55681" # OTLP HTTP/1.0 receiver
|
||||
- "4317:4317" # OTLP GRPC receiver
|
||||
- "55679:55679" # zpages extension
|
||||
- "13133" # health_check
|
||||
depends_on:
|
||||
kafka:
|
||||
condition: service_healthy
|
||||
|
||||
|
||||
hotrod:
|
||||
image: jaegertracing/example-hotrod:latest
|
||||
container_name: hotrod
|
||||
ports:
|
||||
- "9000:8080"
|
||||
command: ["all"]
|
||||
environment:
|
||||
- JAEGER_ENDPOINT=http://otel-collector:14268/api/traces
|
||||
|
||||
|
||||
load-hotrod:
|
||||
image: "grubykarol/locust:1.2.3-python3.9-alpine3.12"
|
||||
container_name: load-hotrod
|
||||
hostname: load-hotrod
|
||||
ports:
|
||||
- "8089:8089"
|
||||
environment:
|
||||
ATTACKED_HOST: http://hotrod:8080
|
||||
LOCUST_MODE: standalone
|
||||
NO_PROXY: standalone
|
||||
TASK_DELAY_FROM: 5
|
||||
TASK_DELAY_TO: 30
|
||||
QUIET_MODE: "${QUIET_MODE:-false}"
|
||||
LOCUST_OPTS: "--headless -u 10 -r 1"
|
||||
volumes:
|
||||
- ./locust-scripts:/locust
|
||||
|
@ -1 +0,0 @@
|
||||
[{"period":"P3D","includeFuture":true,"tieredReplicants":{"_default_tier":1},"type":"loadByPeriod"},{"type":"dropForever"}]
|
@ -1,69 +0,0 @@
|
||||
{
|
||||
"type": "kafka",
|
||||
"dataSchema": {
|
||||
"dataSource": "flattened_spans",
|
||||
"parser": {
|
||||
"type": "string",
|
||||
"parseSpec": {
|
||||
"format": "json",
|
||||
"timestampSpec": {
|
||||
"column": "StartTimeUnixNano",
|
||||
"format": "nano"
|
||||
},
|
||||
"dimensionsSpec": {
|
||||
"dimensions": [
|
||||
"TraceId",
|
||||
"SpanId",
|
||||
"ParentSpanId",
|
||||
"Name",
|
||||
"ServiceName",
|
||||
"References",
|
||||
"Tags",
|
||||
"ExternalHttpMethod",
|
||||
"ExternalHttpUrl",
|
||||
"Component",
|
||||
"DBSystem",
|
||||
"DBName",
|
||||
"DBOperation",
|
||||
"PeerService",
|
||||
{
|
||||
"type": "string",
|
||||
"name": "TagsKeys",
|
||||
"multiValueHandling": "ARRAY"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "TagsValues",
|
||||
"multiValueHandling": "ARRAY"
|
||||
},
|
||||
{ "name": "DurationNano", "type": "Long" },
|
||||
{ "name": "Kind", "type": "int" },
|
||||
{ "name": "StatusCode", "type": "int" }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"metricsSpec" : [
|
||||
{ "type": "quantilesDoublesSketch", "name": "QuantileDuration", "fieldName": "DurationNano" }
|
||||
],
|
||||
"granularitySpec": {
|
||||
"type": "uniform",
|
||||
"segmentGranularity": "DAY",
|
||||
"queryGranularity": "NONE",
|
||||
"rollup": false
|
||||
}
|
||||
},
|
||||
"tuningConfig": {
|
||||
"type": "kafka",
|
||||
"reportParseExceptions": true
|
||||
},
|
||||
"ioConfig": {
|
||||
"topic": "flattened_spans",
|
||||
"replicas": 1,
|
||||
"taskDuration": "PT20M",
|
||||
"completionTimeout": "PT30M",
|
||||
"consumerProperties": {
|
||||
"bootstrap.servers": "kafka:9092"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
# Java tuning
|
||||
DRUID_XMX=512m
|
||||
DRUID_XMS=512m
|
||||
DRUID_MAXNEWSIZE=256m
|
||||
DRUID_NEWSIZE=256m
|
||||
DRUID_MAXDIRECTMEMORYSIZE=768m
|
||||
|
||||
druid_emitter_logging_logLevel=debug
|
||||
|
||||
druid_extensions_loadList=["druid-histogram", "druid-datasketches", "druid-lookups-cached-global", "postgresql-metadata-storage", "druid-kafka-indexing-service"]
|
||||
|
||||
druid_zk_service_host=zookeeper
|
||||
|
||||
druid_metadata_storage_host=
|
||||
druid_metadata_storage_type=postgresql
|
||||
druid_metadata_storage_connector_connectURI=jdbc:postgresql://postgres:5432/druid
|
||||
druid_metadata_storage_connector_user=druid
|
||||
druid_metadata_storage_connector_password=FoolishPassword
|
||||
|
||||
druid_coordinator_balancer_strategy=cachingCost
|
||||
|
||||
druid_indexer_runner_javaOptsArray=["-server", "-Xms512m", "-Xmx512m", "-XX:MaxDirectMemorySize=768m", "-Duser.timezone=UTC", "-Dfile.encoding=UTF-8", "-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager"]
|
||||
druid_indexer_fork_property_druid_processing_buffer_sizeBytes=25000000
|
||||
druid_processing_buffer_sizeBytes=100MiB
|
||||
|
||||
druid_storage_type=local
|
||||
druid_storage_storageDirectory=/opt/druid/deepStorage
|
||||
druid_indexer_logs_type=file
|
||||
druid_indexer_logs_directory=/opt/druid/data/indexing-logs
|
||||
|
||||
druid_processing_numThreads=1
|
||||
druid_processing_numMergeBuffers=2
|
||||
|
||||
DRUID_LOG4J=<?xml version="1.0" encoding="UTF-8" ?><Configuration status="WARN"><Appenders><Console name="Console" target="SYSTEM_OUT"><PatternLayout pattern="%d{ISO8601} %p [%t] %c - %m%n"/></Console></Appenders><Loggers><Root level="info"><AppenderRef ref="Console"/></Root><Logger name="org.apache.druid.jetty.RequestLog" additivity="false" level="DEBUG"><AppenderRef ref="Console"/></Logger></Loggers></Configuration>
|
@ -1,52 +0,0 @@
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
# Java tuning
|
||||
DRUID_XMX=64m
|
||||
DRUID_XMS=64m
|
||||
DRUID_MAXNEWSIZE=256m
|
||||
DRUID_NEWSIZE=256m
|
||||
DRUID_MAXDIRECTMEMORYSIZE=400m
|
||||
|
||||
druid_emitter_logging_logLevel=debug
|
||||
|
||||
druid_extensions_loadList=["druid-histogram", "druid-datasketches", "druid-lookups-cached-global", "postgresql-metadata-storage", "druid-kafka-indexing-service"]
|
||||
|
||||
druid_zk_service_host=zookeeper
|
||||
|
||||
druid_metadata_storage_host=
|
||||
druid_metadata_storage_type=postgresql
|
||||
druid_metadata_storage_connector_connectURI=jdbc:postgresql://postgres:5432/druid
|
||||
druid_metadata_storage_connector_user=druid
|
||||
druid_metadata_storage_connector_password=FoolishPassword
|
||||
|
||||
druid_coordinator_balancer_strategy=cachingCost
|
||||
|
||||
druid_indexer_runner_javaOptsArray=["-server", "-Xms64m", "-Xmx64m", "-XX:MaxDirectMemorySize=400m", "-Duser.timezone=UTC", "-Dfile.encoding=UTF-8", "-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager"]
|
||||
druid_indexer_fork_property_druid_processing_buffer_sizeBytes=25000000
|
||||
|
||||
druid_storage_type=local
|
||||
druid_storage_storageDirectory=/opt/druid/deepStorage
|
||||
druid_indexer_logs_type=file
|
||||
druid_indexer_logs_directory=/opt/druid/data/indexing-logs
|
||||
|
||||
druid_processing_numThreads=1
|
||||
druid_processing_numMergeBuffers=2
|
||||
|
||||
DRUID_LOG4J=<?xml version="1.0" encoding="UTF-8" ?><Configuration status="WARN"><Appenders><Console name="Console" target="SYSTEM_OUT"><PatternLayout pattern="%d{ISO8601} %p [%t] %c - %m%n"/></Console></Appenders><Loggers><Root level="info"><AppenderRef ref="Console"/></Root><Logger name="org.apache.druid.jetty.RequestLog" additivity="false" level="DEBUG"><AppenderRef ref="Console"/></Logger></Loggers></Configuration>
|
@ -1,53 +0,0 @@
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
# Java tuning
|
||||
DRUID_XMX=512m
|
||||
DRUID_XMS=512m
|
||||
DRUID_MAXNEWSIZE=256m
|
||||
DRUID_NEWSIZE=256m
|
||||
DRUID_MAXDIRECTMEMORYSIZE=1280m
|
||||
|
||||
druid_emitter_logging_logLevel=debug
|
||||
|
||||
druid_extensions_loadList=["druid-histogram", "druid-datasketches", "druid-lookups-cached-global", "postgresql-metadata-storage", "druid-kafka-indexing-service"]
|
||||
|
||||
druid_zk_service_host=zookeeper
|
||||
|
||||
druid_metadata_storage_host=
|
||||
druid_metadata_storage_type=postgresql
|
||||
druid_metadata_storage_connector_connectURI=jdbc:postgresql://postgres:5432/druid
|
||||
druid_metadata_storage_connector_user=druid
|
||||
druid_metadata_storage_connector_password=FoolishPassword
|
||||
|
||||
druid_coordinator_balancer_strategy=cachingCost
|
||||
|
||||
druid_indexer_runner_javaOptsArray=["-server", "-Xms512m", "-Xmx512m", "-XX:MaxDirectMemorySize=1280m", "-Duser.timezone=UTC", "-Dfile.encoding=UTF-8", "-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager"]
|
||||
druid_indexer_fork_property_druid_processing_buffer_sizeBytes=25000000
|
||||
druid_processing_buffer_sizeBytes=200MiB
|
||||
|
||||
druid_storage_type=local
|
||||
druid_storage_storageDirectory=/opt/druid/deepStorage
|
||||
druid_indexer_logs_type=file
|
||||
druid_indexer_logs_directory=/opt/druid/data/indexing-logs
|
||||
|
||||
druid_processing_numThreads=2
|
||||
druid_processing_numMergeBuffers=2
|
||||
|
||||
DRUID_LOG4J=<?xml version="1.0" encoding="UTF-8" ?><Configuration status="WARN"><Appenders><Console name="Console" target="SYSTEM_OUT"><PatternLayout pattern="%d{ISO8601} %p [%t] %c - %m%n"/></Console></Appenders><Loggers><Root level="info"><AppenderRef ref="Console"/></Root><Logger name="org.apache.druid.jetty.RequestLog" additivity="false" level="DEBUG"><AppenderRef ref="Console"/></Logger></Loggers></Configuration>
|
@ -1,53 +0,0 @@
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
# Java tuning
|
||||
DRUID_XMX=1g
|
||||
DRUID_XMS=1g
|
||||
DRUID_MAXNEWSIZE=256m
|
||||
DRUID_NEWSIZE=256m
|
||||
DRUID_MAXDIRECTMEMORYSIZE=2g
|
||||
|
||||
druid_emitter_logging_logLevel=debug
|
||||
|
||||
druid_extensions_loadList=["druid-histogram", "druid-datasketches", "druid-lookups-cached-global", "postgresql-metadata-storage", "druid-kafka-indexing-service"]
|
||||
|
||||
druid_zk_service_host=zookeeper
|
||||
|
||||
druid_metadata_storage_host=
|
||||
druid_metadata_storage_type=postgresql
|
||||
druid_metadata_storage_connector_connectURI=jdbc:postgresql://postgres:5432/druid
|
||||
druid_metadata_storage_connector_user=druid
|
||||
druid_metadata_storage_connector_password=FoolishPassword
|
||||
|
||||
druid_coordinator_balancer_strategy=cachingCost
|
||||
|
||||
druid_indexer_runner_javaOptsArray=["-server", "-Xms1g", "-Xmx1g", "-XX:MaxDirectMemorySize=2g", "-Duser.timezone=UTC", "-Dfile.encoding=UTF-8", "-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager"]
|
||||
druid_indexer_fork_property_druid_processing_buffer_sizeBytes=25000000
|
||||
druid_processing_buffer_sizeBytes=200MiB
|
||||
|
||||
druid_storage_type=local
|
||||
druid_storage_storageDirectory=/opt/druid/deepStorage
|
||||
druid_indexer_logs_type=file
|
||||
druid_indexer_logs_directory=/opt/druid/data/indexing-logs
|
||||
|
||||
druid_processing_numThreads=2
|
||||
druid_processing_numMergeBuffers=2
|
||||
|
||||
DRUID_LOG4J=<?xml version="1.0" encoding="UTF-8" ?><Configuration status="WARN"><Appenders><Console name="Console" target="SYSTEM_OUT"><PatternLayout pattern="%d{ISO8601} %p [%t] %c - %m%n"/></Console></Appenders><Loggers><Root level="info"><AppenderRef ref="Console"/></Root><Logger name="org.apache.druid.jetty.RequestLog" additivity="false" level="DEBUG"><AppenderRef ref="Console"/></Logger></Loggers></Configuration>
|
@ -1,52 +0,0 @@
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
# Java tuning
|
||||
DRUID_XMX=128m
|
||||
DRUID_XMS=128m
|
||||
DRUID_MAXNEWSIZE=256m
|
||||
DRUID_NEWSIZE=256m
|
||||
DRUID_MAXDIRECTMEMORYSIZE=128m
|
||||
|
||||
druid_emitter_logging_logLevel=debug
|
||||
|
||||
druid_extensions_loadList=["druid-histogram", "druid-datasketches", "druid-lookups-cached-global", "postgresql-metadata-storage", "druid-kafka-indexing-service"]
|
||||
|
||||
druid_zk_service_host=zookeeper
|
||||
|
||||
druid_metadata_storage_host=
|
||||
druid_metadata_storage_type=postgresql
|
||||
druid_metadata_storage_connector_connectURI=jdbc:postgresql://postgres:5432/druid
|
||||
druid_metadata_storage_connector_user=druid
|
||||
druid_metadata_storage_connector_password=FoolishPassword
|
||||
|
||||
druid_coordinator_balancer_strategy=cachingCost
|
||||
|
||||
druid_indexer_runner_javaOptsArray=["-server", "-Xms128m", "-Xmx128m", "-XX:MaxDirectMemorySize=128m", "-Duser.timezone=UTC", "-Dfile.encoding=UTF-8", "-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager"]
|
||||
druid_indexer_fork_property_druid_processing_buffer_sizeBytes=25000000
|
||||
|
||||
druid_storage_type=local
|
||||
druid_storage_storageDirectory=/opt/druid/deepStorage
|
||||
druid_indexer_logs_type=file
|
||||
druid_indexer_logs_directory=/opt/druid/data/indexing-logs
|
||||
|
||||
druid_processing_numThreads=1
|
||||
druid_processing_numMergeBuffers=2
|
||||
|
||||
DRUID_LOG4J=<?xml version="1.0" encoding="UTF-8" ?><Configuration status="WARN"><Appenders><Console name="Console" target="SYSTEM_OUT"><PatternLayout pattern="%d{ISO8601} %p [%t] %c - %m%n"/></Console></Appenders><Loggers><Root level="info"><AppenderRef ref="Console"/></Root><Logger name="org.apache.druid.jetty.RequestLog" additivity="false" level="DEBUG"><AppenderRef ref="Console"/></Logger></Loggers></Configuration>
|
@ -1,52 +0,0 @@
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
# Java tuning
|
||||
DRUID_XMX=512m
|
||||
DRUID_XMS=512m
|
||||
DRUID_MAXNEWSIZE=256m
|
||||
DRUID_NEWSIZE=256m
|
||||
DRUID_MAXDIRECTMEMORYSIZE=400m
|
||||
|
||||
druid_emitter_logging_logLevel=debug
|
||||
|
||||
# druid_extensions_loadList=["druid-histogram", "druid-datasketches", "druid-lookups-cached-global", "postgresql-metadata-storage", "druid-kafka-indexing-service"]
|
||||
|
||||
|
||||
|
||||
|
||||
druid_zk_service_host=zookeeper
|
||||
|
||||
druid_metadata_storage_host=
|
||||
druid_metadata_storage_type=postgresql
|
||||
druid_metadata_storage_connector_connectURI=jdbc:postgresql://postgres:5432/druid
|
||||
druid_metadata_storage_connector_user=druid
|
||||
druid_metadata_storage_connector_password=FoolishPassword
|
||||
|
||||
druid_coordinator_balancer_strategy=cachingCost
|
||||
|
||||
druid_indexer_runner_javaOptsArray=["-server", "-Xms512m", "-Xmx512m", "-XX:MaxDirectMemorySize=400m", "-Duser.timezone=UTC", "-Dfile.encoding=UTF-8", "-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager"]
|
||||
druid_indexer_fork_property_druid_processing_buffer_sizeBytes=25000000
|
||||
druid_processing_buffer_sizeBytes=50MiB
|
||||
|
||||
|
||||
druid_processing_numThreads=1
|
||||
druid_processing_numMergeBuffers=2
|
||||
|
||||
DRUID_LOG4J=<?xml version="1.0" encoding="UTF-8" ?><Configuration status="WARN"><Appenders><Console name="Console" target="SYSTEM_OUT"><PatternLayout pattern="%d{ISO8601} %p [%t] %c - %m%n"/></Console></Appenders><Loggers><Root level="info"><AppenderRef ref="Console"/></Root><Logger name="org.apache.druid.jetty.RequestLog" additivity="false" level="DEBUG"><AppenderRef ref="Console"/></Logger></Loggers></Configuration>
|
@ -1,26 +0,0 @@
|
||||
# For S3 storage
|
||||
|
||||
# druid_extensions_loadList=["druid-histogram", "druid-datasketches", "druid-lookups-cached-global", "postgresql-metadata-storage", "druid-kafka-indexing-service", "druid-s3-extensions"]
|
||||
|
||||
|
||||
# druid_storage_type=s3
|
||||
# druid_storage_bucket=<s3-bucket-name>
|
||||
# druid_storage_baseKey=druid/segments
|
||||
|
||||
# AWS_ACCESS_KEY_ID=<s3-access-id>
|
||||
# AWS_SECRET_ACCESS_KEY=<s3-access-key>
|
||||
# AWS_REGION=<s3-aws-region>
|
||||
|
||||
# druid_indexer_logs_type=s3
|
||||
# druid_indexer_logs_s3Bucket=<s3-bucket-name>
|
||||
# druid_indexer_logs_s3Prefix=druid/indexing-logs
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# For local storage
|
||||
druid_extensions_loadList=["druid-histogram", "druid-datasketches", "druid-lookups-cached-global", "postgresql-metadata-storage", "druid-kafka-indexing-service"]
|
||||
|
||||
druid_storage_type=local
|
||||
druid_storage_storageDirectory=/opt/data/segments
|
||||
druid_indexer_logs_type=file
|
||||
druid_indexer_logs_directory=/opt/data/indexing-logs
|
||||
|
@ -1,49 +0,0 @@
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
# Java tuning
|
||||
DRUID_XMX=64m
|
||||
DRUID_XMS=64m
|
||||
DRUID_MAXNEWSIZE=256m
|
||||
DRUID_NEWSIZE=256m
|
||||
DRUID_MAXDIRECTMEMORYSIZE=400m
|
||||
|
||||
druid_emitter_logging_logLevel=debug
|
||||
|
||||
# druid_extensions_loadList=["druid-histogram", "druid-datasketches", "druid-lookups-cached-global", "postgresql-metadata-storage", "druid-kafka-indexing-service"]
|
||||
|
||||
|
||||
druid_zk_service_host=zookeeper
|
||||
|
||||
druid_metadata_storage_host=
|
||||
druid_metadata_storage_type=postgresql
|
||||
druid_metadata_storage_connector_connectURI=jdbc:postgresql://postgres:5432/druid
|
||||
druid_metadata_storage_connector_user=druid
|
||||
druid_metadata_storage_connector_password=FoolishPassword
|
||||
|
||||
druid_coordinator_balancer_strategy=cachingCost
|
||||
|
||||
druid_indexer_runner_javaOptsArray=["-server", "-Xms64m", "-Xmx64m", "-XX:MaxDirectMemorySize=400m", "-Duser.timezone=UTC", "-Dfile.encoding=UTF-8", "-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager"]
|
||||
druid_indexer_fork_property_druid_processing_buffer_sizeBytes=25000000
|
||||
|
||||
|
||||
druid_processing_numThreads=1
|
||||
druid_processing_numMergeBuffers=2
|
||||
|
||||
DRUID_LOG4J=<?xml version="1.0" encoding="UTF-8" ?><Configuration status="WARN"><Appenders><Console name="Console" target="SYSTEM_OUT"><PatternLayout pattern="%d{ISO8601} %p [%t] %c - %m%n"/></Console></Appenders><Loggers><Root level="info"><AppenderRef ref="Console"/></Root><Logger name="org.apache.druid.jetty.RequestLog" additivity="false" level="DEBUG"><AppenderRef ref="Console"/></Logger></Loggers></Configuration>
|
@ -1,49 +0,0 @@
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
# Java tuning
|
||||
DRUID_XMX=512m
|
||||
DRUID_XMS=512m
|
||||
DRUID_MAXNEWSIZE=256m
|
||||
DRUID_NEWSIZE=256m
|
||||
DRUID_MAXDIRECTMEMORYSIZE=400m
|
||||
|
||||
druid_emitter_logging_logLevel=debug
|
||||
|
||||
# druid_extensions_loadList=["druid-histogram", "druid-datasketches", "druid-lookups-cached-global", "postgresql-metadata-storage", "druid-kafka-indexing-service"]
|
||||
|
||||
|
||||
druid_zk_service_host=zookeeper
|
||||
|
||||
druid_metadata_storage_host=
|
||||
druid_metadata_storage_type=postgresql
|
||||
druid_metadata_storage_connector_connectURI=jdbc:postgresql://postgres:5432/druid
|
||||
druid_metadata_storage_connector_user=druid
|
||||
druid_metadata_storage_connector_password=FoolishPassword
|
||||
|
||||
druid_coordinator_balancer_strategy=cachingCost
|
||||
|
||||
druid_indexer_runner_javaOptsArray=["-server", "-Xms512m", "-Xmx512m", "-XX:MaxDirectMemorySize=400m", "-Duser.timezone=UTC", "-Dfile.encoding=UTF-8", "-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager"]
|
||||
druid_indexer_fork_property_druid_processing_buffer_sizeBytes=25000000
|
||||
druid_processing_buffer_sizeBytes=50MiB
|
||||
|
||||
druid_processing_numThreads=1
|
||||
druid_processing_numMergeBuffers=2
|
||||
|
||||
DRUID_LOG4J=<?xml version="1.0" encoding="UTF-8" ?><Configuration status="WARN"><Appenders><Console name="Console" target="SYSTEM_OUT"><PatternLayout pattern="%d{ISO8601} %p [%t] %c - %m%n"/></Console></Appenders><Loggers><Root level="info"><AppenderRef ref="Console"/></Root><Logger name="org.apache.druid.jetty.RequestLog" additivity="false" level="DEBUG"><AppenderRef ref="Console"/></Logger></Loggers></Configuration>
|
@ -1,50 +0,0 @@
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
# Java tuning
|
||||
DRUID_XMX=64m
|
||||
DRUID_XMS=64m
|
||||
DRUID_MAXNEWSIZE=256m
|
||||
DRUID_NEWSIZE=256m
|
||||
DRUID_MAXDIRECTMEMORYSIZE=400m
|
||||
|
||||
druid_emitter_logging_logLevel=debug
|
||||
|
||||
# druid_extensions_loadList=["druid-histogram", "druid-datasketches", "druid-lookups-cached-global", "postgresql-metadata-storage", "druid-kafka-indexing-service"]
|
||||
|
||||
|
||||
|
||||
druid_zk_service_host=zookeeper
|
||||
|
||||
druid_metadata_storage_host=
|
||||
druid_metadata_storage_type=postgresql
|
||||
druid_metadata_storage_connector_connectURI=jdbc:postgresql://postgres:5432/druid
|
||||
druid_metadata_storage_connector_user=druid
|
||||
druid_metadata_storage_connector_password=FoolishPassword
|
||||
|
||||
druid_coordinator_balancer_strategy=cachingCost
|
||||
|
||||
druid_indexer_runner_javaOptsArray=["-server", "-Xms256m", "-Xmx256m", "-XX:MaxDirectMemorySize=400m", "-Duser.timezone=UTC", "-Dfile.encoding=UTF-8", "-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager"]
|
||||
druid_indexer_fork_property_druid_processing_buffer_sizeBytes=25000000
|
||||
|
||||
|
||||
druid_processing_numThreads=1
|
||||
druid_processing_numMergeBuffers=2
|
||||
|
||||
DRUID_LOG4J=<?xml version="1.0" encoding="UTF-8" ?><Configuration status="WARN"><Appenders><Console name="Console" target="SYSTEM_OUT"><PatternLayout pattern="%d{ISO8601} %p [%t] %c - %m%n"/></Console></Appenders><Loggers><Root level="info"><AppenderRef ref="Console"/></Root><Logger name="org.apache.druid.jetty.RequestLog" additivity="false" level="DEBUG"><AppenderRef ref="Console"/></Logger></Loggers></Configuration>
|
@ -1,49 +0,0 @@
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
# Java tuning
|
||||
DRUID_XMX=64m
|
||||
DRUID_XMS=64m
|
||||
DRUID_MAXNEWSIZE=256m
|
||||
DRUID_NEWSIZE=256m
|
||||
DRUID_MAXDIRECTMEMORYSIZE=128m
|
||||
|
||||
druid_emitter_logging_logLevel=debug
|
||||
|
||||
# druid_extensions_loadList=["druid-histogram", "druid-datasketches", "druid-lookups-cached-global", "postgresql-metadata-storage", "druid-kafka-indexing-service"]
|
||||
|
||||
|
||||
druid_zk_service_host=zookeeper
|
||||
|
||||
druid_metadata_storage_host=
|
||||
druid_metadata_storage_type=postgresql
|
||||
druid_metadata_storage_connector_connectURI=jdbc:postgresql://postgres:5432/druid
|
||||
druid_metadata_storage_connector_user=druid
|
||||
druid_metadata_storage_connector_password=FoolishPassword
|
||||
|
||||
druid_coordinator_balancer_strategy=cachingCost
|
||||
|
||||
druid_indexer_runner_javaOptsArray=["-server", "-Xms64m", "-Xmx64m", "-XX:MaxDirectMemorySize=128m", "-Duser.timezone=UTC", "-Dfile.encoding=UTF-8", "-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager"]
|
||||
druid_indexer_fork_property_druid_processing_buffer_sizeBytes=25000000
|
||||
|
||||
|
||||
druid_processing_numThreads=1
|
||||
druid_processing_numMergeBuffers=2
|
||||
|
||||
DRUID_LOG4J=<?xml version="1.0" encoding="UTF-8" ?><Configuration status="WARN"><Appenders><Console name="Console" target="SYSTEM_OUT"><PatternLayout pattern="%d{ISO8601} %p [%t] %c - %m%n"/></Console></Appenders><Loggers><Root level="info"><AppenderRef ref="Console"/></Root><Logger name="org.apache.druid.jetty.RequestLog" additivity="false" level="DEBUG"><AppenderRef ref="Console"/></Logger></Loggers></Configuration>
|
@ -1,51 +0,0 @@
|
||||
receivers:
|
||||
otlp:
|
||||
protocols:
|
||||
grpc:
|
||||
http:
|
||||
jaeger:
|
||||
protocols:
|
||||
grpc:
|
||||
thrift_http:
|
||||
processors:
|
||||
batch:
|
||||
send_batch_size: 1000
|
||||
timeout: 10s
|
||||
memory_limiter:
|
||||
# Same as --mem-ballast-size-mib CLI argument
|
||||
ballast_size_mib: 683
|
||||
# 80% of maximum memory up to 2G
|
||||
limit_mib: 1500
|
||||
# 25% of limit up to 2G
|
||||
spike_limit_mib: 512
|
||||
check_interval: 5s
|
||||
queued_retry:
|
||||
num_workers: 4
|
||||
queue_size: 100
|
||||
retry_on_failure: true
|
||||
extensions:
|
||||
health_check: {}
|
||||
zpages: {}
|
||||
exporters:
|
||||
kafka/traces:
|
||||
brokers:
|
||||
- kafka:9092
|
||||
topic: 'otlp_spans'
|
||||
protocol_version: 2.0.0
|
||||
|
||||
kafka/metrics:
|
||||
brokers:
|
||||
- kafka:9092
|
||||
topic: 'otlp_metrics'
|
||||
protocol_version: 2.0.0
|
||||
service:
|
||||
extensions: [health_check, zpages]
|
||||
pipelines:
|
||||
traces:
|
||||
receivers: [jaeger, otlp]
|
||||
processors: [memory_limiter, batch, queued_retry]
|
||||
exporters: [kafka/traces]
|
||||
metrics:
|
||||
receivers: [otlp]
|
||||
processors: [batch]
|
||||
exporters: [kafka/metrics]
|
@ -333,7 +333,6 @@ fi
|
||||
|
||||
# echo -e "👉 ${RED}Two ways to go forward\n"
|
||||
# echo -e "${RED}1) ClickHouse as database (default)\n"
|
||||
# echo -e "${RED}2) Kafka + Druid as datastore \n"
|
||||
# read -p "⚙️ Enter your preference (1/2):" choice_setup
|
||||
|
||||
# while [[ $choice_setup != "1" && $choice_setup != "2" && $choice_setup != "" ]]
|
||||
@ -346,8 +345,6 @@ fi
|
||||
|
||||
# if [[ $choice_setup == "1" || $choice_setup == "" ]];then
|
||||
# setup_type='clickhouse'
|
||||
# else
|
||||
# setup_type='druid'
|
||||
# fi
|
||||
|
||||
setup_type='clickhouse'
|
||||
|
@ -1,4 +1,5 @@
|
||||
node_modules
|
||||
.vscode
|
||||
build
|
||||
.env
|
||||
.env
|
||||
.git
|
||||
|
4
frontend/.husky/commit-msg
Executable file
4
frontend/.husky/commit-msg
Executable file
@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
cd frontend && npm run commitlint
|
@ -1 +1 @@
|
||||
12.13.0
|
||||
16.15.0
|
@ -1,5 +1,5 @@
|
||||
# stage1 as builder
|
||||
FROM node:12.22.0 as builder
|
||||
# Builder stage
|
||||
FROM node:16.15.0-slim as builder
|
||||
|
||||
# Add Maintainer Info
|
||||
LABEL maintainer="signoz"
|
||||
@ -9,24 +9,23 @@ ARG TARGETARCH
|
||||
|
||||
WORKDIR /frontend
|
||||
|
||||
# copy the package.json to install dependencies
|
||||
# Copy the package.json to install dependencies
|
||||
COPY package.json ./
|
||||
|
||||
# Install the dependencies and make the folder
|
||||
RUN yarn install
|
||||
RUN CI=1 yarn install
|
||||
|
||||
COPY . .
|
||||
|
||||
# Build the project and copy the files
|
||||
RUN yarn build
|
||||
|
||||
FROM nginx:1.18-alpine
|
||||
|
||||
#!/bin/sh
|
||||
FROM nginx:1.18-alpine
|
||||
|
||||
COPY conf/default.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
## Remove default nginx index page
|
||||
# Remove default nginx index page
|
||||
RUN rm -rf /usr/share/nginx/html/*
|
||||
|
||||
# Copy from the stahg 1
|
||||
@ -34,4 +33,4 @@ COPY --from=builder /frontend/build /usr/share/nginx/html
|
||||
|
||||
EXPOSE 3301
|
||||
|
||||
ENTRYPOINT ["nginx", "-g", "daemon off;"]
|
||||
ENTRYPOINT ["nginx", "-g", "daemon off;"]
|
||||
|
1
frontend/commitlint.config.js
Normal file
1
frontend/commitlint.config.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = { extends: ['@commitlint/config-conventional'] };
|
@ -1,3 +0,0 @@
|
||||
{
|
||||
"video": false
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-expressions */
|
||||
const Login = ({ email, name }: LoginProps): void => {
|
||||
const emailInput = cy.findByPlaceholderText('name@yourcompany.com');
|
||||
|
||||
emailInput.then((emailInput) => {
|
||||
const element = emailInput[0];
|
||||
// element is present
|
||||
expect(element).not.undefined;
|
||||
expect(element.nodeName).to.be.equal('INPUT');
|
||||
});
|
||||
emailInput.type(email).then((inputElements) => {
|
||||
const inputElement = inputElements[0];
|
||||
const inputValue = inputElement.getAttribute('value');
|
||||
expect(inputValue).to.be.equals(email);
|
||||
});
|
||||
|
||||
const firstNameInput = cy.findByPlaceholderText('Your Name');
|
||||
firstNameInput.then((firstNameInput) => {
|
||||
const element = firstNameInput[0];
|
||||
// element is present
|
||||
expect(element).not.undefined;
|
||||
expect(element.nodeName).to.be.equal('INPUT');
|
||||
});
|
||||
|
||||
firstNameInput.type(name).then((inputElements) => {
|
||||
const inputElement = inputElements[0];
|
||||
const inputValue = inputElement.getAttribute('value');
|
||||
expect(inputValue).to.be.equals(name);
|
||||
});
|
||||
|
||||
const gettingStartedButton = cy.findByText('Get Started');
|
||||
gettingStartedButton.click();
|
||||
|
||||
cy
|
||||
.intercept('POST', '/api/v1/user?email*', {
|
||||
statusCode: 200,
|
||||
})
|
||||
.as('defaultUser');
|
||||
|
||||
cy.wait('@defaultUser');
|
||||
};
|
||||
|
||||
export interface LoginProps {
|
||||
email: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export default Login;
|
@ -1,49 +0,0 @@
|
||||
import {
|
||||
getDefaultOption,
|
||||
getOptions,
|
||||
} from 'container/Header/DateTimeSelection/config';
|
||||
// import { AppState } from 'store/reducers';
|
||||
|
||||
const CheckRouteDefaultGlobalTimeOptions = ({
|
||||
route,
|
||||
}: CheckRouteDefaultGlobalTimeOptionsProps): void => {
|
||||
cy.visit(Cypress.env('baseUrl') + route);
|
||||
|
||||
const allOptions = getOptions(route);
|
||||
|
||||
const defaultValue = getDefaultOption(route);
|
||||
|
||||
const defaultSelectedOption = allOptions.find((e) => e.value === defaultValue);
|
||||
|
||||
expect(defaultSelectedOption).not.undefined;
|
||||
|
||||
cy
|
||||
.findAllByTestId('dropDown')
|
||||
.find('span')
|
||||
.then((el) => {
|
||||
const elements = el.get();
|
||||
|
||||
const item = elements[1];
|
||||
|
||||
expect(defaultSelectedOption?.label).to.be.equals(
|
||||
item.innerText,
|
||||
'Default option is not matching',
|
||||
);
|
||||
});
|
||||
|
||||
// cy
|
||||
// .window()
|
||||
// .its('store')
|
||||
// .invoke('getState')
|
||||
// .then((e: AppState) => {
|
||||
// const { globalTime } = e;
|
||||
// const { maxTime, minTime } = globalTime;
|
||||
// // @TODO match the global min time and max time according to the selected option
|
||||
// });
|
||||
};
|
||||
|
||||
export interface CheckRouteDefaultGlobalTimeOptionsProps {
|
||||
route: string;
|
||||
}
|
||||
|
||||
export default CheckRouteDefaultGlobalTimeOptions;
|
@ -1,11 +0,0 @@
|
||||
const resizeObserverLoopErrRe = /ResizeObserver loop limit exceeded/;
|
||||
|
||||
const unCaughtExpection = (): void => {
|
||||
cy.on('uncaught:exception', (err) => {
|
||||
// returning false here prevents Cypress from
|
||||
// failing the test
|
||||
return !resizeObserverLoopErrRe.test(err.message);
|
||||
});
|
||||
};
|
||||
|
||||
export default unCaughtExpection;
|
@ -1,21 +0,0 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"created_at": 1638083159246,
|
||||
"data": "{}",
|
||||
"id": 1,
|
||||
"name": "First Channels",
|
||||
"type": "slack",
|
||||
"updated_at": 1638083159246
|
||||
},
|
||||
{
|
||||
"created_at": 1638083159246,
|
||||
"data": "{}",
|
||||
"id": 2,
|
||||
"name": "Second Channels",
|
||||
"type": "Slack",
|
||||
"updated_at": 1638083159246
|
||||
}
|
||||
],
|
||||
"message": "Success"
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
[
|
||||
{
|
||||
"serviceName": "frontend",
|
||||
"p99": 1134610000,
|
||||
"avgDuration": 744523000,
|
||||
"numCalls": 267,
|
||||
"callRate": 0.89,
|
||||
"numErrors": 0,
|
||||
"errorRate": 0,
|
||||
"num4XX": 0,
|
||||
"fourXXRate": 0
|
||||
},
|
||||
{
|
||||
"serviceName": "customer",
|
||||
"p99": 734422400,
|
||||
"avgDuration": 348678530,
|
||||
"numCalls": 267,
|
||||
"callRate": 0.89,
|
||||
"numErrors": 0,
|
||||
"errorRate": 0,
|
||||
"num4XX": 0,
|
||||
"fourXXRate": 0
|
||||
},
|
||||
{
|
||||
"serviceName": "driver",
|
||||
"p99": 239234080,
|
||||
"avgDuration": 204662290,
|
||||
"numCalls": 267,
|
||||
"callRate": 0.89,
|
||||
"numErrors": 0,
|
||||
"errorRate": 0,
|
||||
"num4XX": 0,
|
||||
"fourXXRate": 0
|
||||
}
|
||||
]
|
@ -1,28 +0,0 @@
|
||||
{
|
||||
"status": "success",
|
||||
"data": {
|
||||
"rules": [
|
||||
{
|
||||
"labels": { "severity": "warning" },
|
||||
"annotations": {},
|
||||
"state": "firing",
|
||||
"name": "First Rule",
|
||||
"id": 1
|
||||
},
|
||||
{
|
||||
"labels": { "severity": "warning" },
|
||||
"annotations": {},
|
||||
"state": "firing",
|
||||
"name": "Second Rule",
|
||||
"id": 2
|
||||
},
|
||||
{
|
||||
"labels": { "severity": "P0" },
|
||||
"annotations": {},
|
||||
"state": "firing",
|
||||
"name": "Third Rule",
|
||||
"id": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
{ "status": "success", "data": { "resultType": "matrix", "result": [] } }
|
@ -1,29 +0,0 @@
|
||||
{
|
||||
"status": "success",
|
||||
"data": {
|
||||
"resultType": "matrix",
|
||||
"result": [
|
||||
{
|
||||
"metric": {},
|
||||
"values": [
|
||||
[1634741764.961, "0.9"],
|
||||
[1634741824.961, "0.9"],
|
||||
[1634741884.961, "0.8666666666666667"],
|
||||
[1634741944.961, "1"],
|
||||
[1634742004.961, "0.9166666666666666"],
|
||||
[1634742064.961, "0.95"],
|
||||
[1634742124.961, "0.9333333333333333"],
|
||||
[1634742184.961, "0.95"],
|
||||
[1634742244.961, "1.0333333333333334"],
|
||||
[1634742304.961, "0.9333333333333333"],
|
||||
[1634742364.961, "0.9166666666666666"],
|
||||
[1634742424.961, "0.9"],
|
||||
[1634742484.961, "1.0166666666666666"],
|
||||
[1634742544.961, "0.8333333333333334"],
|
||||
[1634742604.961, "0.9166666666666666"],
|
||||
[1634742664.961, "0.95"]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1634742600000000000,
|
||||
"p50": 720048500,
|
||||
"p95": 924409540,
|
||||
"p99": 974744300,
|
||||
"numCalls": 48,
|
||||
"callRate": 0.8,
|
||||
"numErrors": 0,
|
||||
"errorRate": 0
|
||||
},
|
||||
{
|
||||
"timestamp": 1634742540000000000,
|
||||
"p50": 712614000,
|
||||
"p95": 955580700,
|
||||
"p99": 1045595400,
|
||||
"numCalls": 59,
|
||||
"callRate": 0.98333335,
|
||||
"numErrors": 0,
|
||||
"errorRate": 0
|
||||
},
|
||||
{
|
||||
"timestamp": 1634742480000000000,
|
||||
"p50": 720842000,
|
||||
"p95": 887187600,
|
||||
"p99": 943676860,
|
||||
"numCalls": 53,
|
||||
"callRate": 0.8833333,
|
||||
"numErrors": 0,
|
||||
"errorRate": 0
|
||||
},
|
||||
{
|
||||
"timestamp": 1634742420000000000,
|
||||
"p50": 712287000,
|
||||
"p95": 908505540,
|
||||
"p99": 976507650,
|
||||
"numCalls": 58,
|
||||
"callRate": 0.96666664,
|
||||
"numErrors": 0,
|
||||
"errorRate": 0
|
||||
},
|
||||
{
|
||||
"timestamp": 1634742360000000000,
|
||||
"p50": 697125500,
|
||||
"p95": 975581800,
|
||||
"p99": 1190121900,
|
||||
"numCalls": 54,
|
||||
"callRate": 0.9,
|
||||
"numErrors": 0,
|
||||
"errorRate": 0
|
||||
},
|
||||
{
|
||||
"timestamp": 1634742300000000000,
|
||||
"p50": 711592500,
|
||||
"p95": 880559900,
|
||||
"p99": 1100105500,
|
||||
"numCalls": 40,
|
||||
"callRate": 0.6666667,
|
||||
"numErrors": 0,
|
||||
"errorRate": 0
|
||||
}
|
||||
]
|
@ -1,9 +0,0 @@
|
||||
[
|
||||
{
|
||||
"p50": 710824000,
|
||||
"p95": 1003231400,
|
||||
"p99": 1231265500,
|
||||
"numCalls": 299,
|
||||
"name": "HTTP GET /dispatch"
|
||||
}
|
||||
]
|
@ -1,35 +0,0 @@
|
||||
{
|
||||
"items": {
|
||||
"1644926280000000000": { "timestamp": 1644926280000000000, "value": 787 },
|
||||
"1644926340000000000": { "timestamp": 1644926340000000000, "value": 2798 },
|
||||
"1644926400000000000": { "timestamp": 1644926400000000000, "value": 2828 },
|
||||
"1644926460000000000": { "timestamp": 1644926460000000000, "value": 2926 },
|
||||
"1644926520000000000": { "timestamp": 1644926520000000000, "value": 2932 },
|
||||
"1644926580000000000": { "timestamp": 1644926580000000000, "value": 2842 },
|
||||
"1644926640000000000": { "timestamp": 1644926640000000000, "value": 2966 },
|
||||
"1644926700000000000": { "timestamp": 1644926700000000000, "value": 2782 },
|
||||
"1644926760000000000": { "timestamp": 1644926760000000000, "value": 2843 },
|
||||
"1644926820000000000": { "timestamp": 1644926820000000000, "value": 2864 },
|
||||
"1644926880000000000": { "timestamp": 1644926880000000000, "value": 2777 },
|
||||
"1644926940000000000": { "timestamp": 1644926940000000000, "value": 2820 },
|
||||
"1644927000000000000": { "timestamp": 1644927000000000000, "value": 2579 },
|
||||
"1644927060000000000": { "timestamp": 1644927060000000000, "value": 2681 },
|
||||
"1644927120000000000": { "timestamp": 1644927120000000000, "value": 2828 },
|
||||
"1644927180000000000": { "timestamp": 1644927180000000000, "value": 2975 },
|
||||
"1644927240000000000": { "timestamp": 1644927240000000000, "value": 2934 },
|
||||
"1644927300000000000": { "timestamp": 1644927300000000000, "value": 2793 },
|
||||
"1644927360000000000": { "timestamp": 1644927360000000000, "value": 2913 },
|
||||
"1644927420000000000": { "timestamp": 1644927420000000000, "value": 2621 },
|
||||
"1644927480000000000": { "timestamp": 1644927480000000000, "value": 2631 },
|
||||
"1644927540000000000": { "timestamp": 1644927540000000000, "value": 2924 },
|
||||
"1644927600000000000": { "timestamp": 1644927600000000000, "value": 2576 },
|
||||
"1644927660000000000": { "timestamp": 1644927660000000000, "value": 2878 },
|
||||
"1644927720000000000": { "timestamp": 1644927720000000000, "value": 2737 },
|
||||
"1644927780000000000": { "timestamp": 1644927780000000000, "value": 2621 },
|
||||
"1644927840000000000": { "timestamp": 1644927840000000000, "value": 2823 },
|
||||
"1644927900000000000": { "timestamp": 1644927900000000000, "value": 3081 },
|
||||
"1644927960000000000": { "timestamp": 1644927960000000000, "value": 2883 },
|
||||
"1644928020000000000": { "timestamp": 1644928020000000000, "value": 2823 },
|
||||
"1644928080000000000": { "timestamp": 1644928080000000000, "value": 455 }
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
{
|
||||
"serviceName": {
|
||||
"customer": 1642,
|
||||
"driver": 1642,
|
||||
"frontend": 39408,
|
||||
"mysql": 1642,
|
||||
"redis": 22167,
|
||||
"route": 16420
|
||||
},
|
||||
"status": { "error": 4105, "ok": 78816 },
|
||||
"duration": { "maxDuration": 1253979000, "minDuration": 415000 },
|
||||
"operation": {},
|
||||
"httpCode": {},
|
||||
"httpUrl": {},
|
||||
"httpMethod": {},
|
||||
"httpRoute": {},
|
||||
"httpHost": {},
|
||||
"component": {}
|
||||
}
|
@ -1,105 +0,0 @@
|
||||
{
|
||||
"spans": [
|
||||
{
|
||||
"timestamp": "2022-02-15T12:16:09.542074Z",
|
||||
"spanID": "303b39065c6f5df5",
|
||||
"traceID": "00000000000000007fc49fab3cb75958",
|
||||
"serviceName": "customer",
|
||||
"operation": "HTTP GET /customer",
|
||||
"durationNano": 313418000,
|
||||
"httpCode": "200",
|
||||
"httpMethod": "GET"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-02-15T12:16:08.84038Z",
|
||||
"spanID": "557e8303bc802992",
|
||||
"traceID": "000000000000000079310bd1d435a92b",
|
||||
"serviceName": "customer",
|
||||
"operation": "HTTP GET /customer",
|
||||
"durationNano": 318203000,
|
||||
"httpCode": "200",
|
||||
"httpMethod": "GET"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-02-15T12:16:08.867689Z",
|
||||
"spanID": "347113dd916dd20e",
|
||||
"traceID": "00000000000000004c22c0409cee0f66",
|
||||
"serviceName": "customer",
|
||||
"operation": "HTTP GET /customer",
|
||||
"durationNano": 512810000,
|
||||
"httpCode": "200",
|
||||
"httpMethod": "GET"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-02-15T12:16:07.060882Z",
|
||||
"spanID": "0a8d07f72aa1339b",
|
||||
"traceID": "0000000000000000488e11a35959de96",
|
||||
"serviceName": "customer",
|
||||
"operation": "HTTP GET /customer",
|
||||
"durationNano": 588705000,
|
||||
"httpCode": "200",
|
||||
"httpMethod": "GET"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-02-15T12:16:07.134107Z",
|
||||
"spanID": "0acd4ec344675998",
|
||||
"traceID": "00000000000000000292efc7945d9bfa",
|
||||
"serviceName": "customer",
|
||||
"operation": "HTTP GET /customer",
|
||||
"durationNano": 801632000,
|
||||
"httpCode": "200",
|
||||
"httpMethod": "GET"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-02-15T12:16:06.474095Z",
|
||||
"spanID": "3ae72e433301822a",
|
||||
"traceID": "00000000000000001ac3004ff1b7eefe",
|
||||
"serviceName": "customer",
|
||||
"operation": "HTTP GET /customer",
|
||||
"durationNano": 306650000,
|
||||
"httpCode": "200",
|
||||
"httpMethod": "GET"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-02-15T12:16:06.996246Z",
|
||||
"spanID": "1d765427af673039",
|
||||
"traceID": "00000000000000002e78f59fabbcdecf",
|
||||
"serviceName": "customer",
|
||||
"operation": "HTTP GET /customer",
|
||||
"durationNano": 311469000,
|
||||
"httpCode": "200",
|
||||
"httpMethod": "GET"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-02-15T12:16:05.324296Z",
|
||||
"spanID": "0987c90d83298a1d",
|
||||
"traceID": "0000000000000000077bcb960609a350",
|
||||
"serviceName": "customer",
|
||||
"operation": "HTTP GET /customer",
|
||||
"durationNano": 290680000,
|
||||
"httpCode": "200",
|
||||
"httpMethod": "GET"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-02-15T12:16:02.458221Z",
|
||||
"spanID": "5b0d0d403dd9acf4",
|
||||
"traceID": "00000000000000007ae5b0aa69242556",
|
||||
"serviceName": "customer",
|
||||
"operation": "HTTP GET /customer",
|
||||
"durationNano": 262763000,
|
||||
"httpCode": "200",
|
||||
"httpMethod": "GET"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-02-15T12:16:00.584939Z",
|
||||
"spanID": "3beafb277a76b9b4",
|
||||
"traceID": "00000000000000000ab44953c2fd949e",
|
||||
"serviceName": "customer",
|
||||
"operation": "HTTP GET /customer",
|
||||
"durationNano": 302851000,
|
||||
"httpCode": "200",
|
||||
"httpMethod": "GET"
|
||||
}
|
||||
],
|
||||
"totalSpans": 82921
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
/// <reference types="cypress" />
|
||||
import ROUTES from 'constants/routes';
|
||||
|
||||
describe('App Layout', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit(Cypress.env('baseUrl'));
|
||||
});
|
||||
|
||||
it('Check the user is in Logged Out State', async () => {
|
||||
cy.location('pathname').then((e) => {
|
||||
expect(e).to.be.equal(ROUTES.SIGN_UP);
|
||||
});
|
||||
});
|
||||
|
||||
it('Logged In State', () => {
|
||||
const testEmail = 'test@test.com';
|
||||
const firstName = 'Test';
|
||||
|
||||
cy.login({
|
||||
email: testEmail,
|
||||
name: firstName,
|
||||
});
|
||||
});
|
||||
});
|
@ -1,52 +0,0 @@
|
||||
/// <reference types="cypress" />
|
||||
|
||||
import ROUTES from 'constants/routes';
|
||||
|
||||
import defaultAllChannels from '../../fixtures/defaultAllChannels.json';
|
||||
|
||||
describe('Channels', () => {
|
||||
beforeEach(() => {
|
||||
window.localStorage.setItem('isLoggedIn', 'yes');
|
||||
|
||||
cy.visit(Cypress.env('baseUrl') + ROUTES.ALL_CHANNELS);
|
||||
});
|
||||
|
||||
it('Channels', () => {
|
||||
cy
|
||||
.intercept('**channels**', {
|
||||
statusCode: 200,
|
||||
fixture: 'defaultAllChannels',
|
||||
})
|
||||
.as('All Channels');
|
||||
|
||||
cy.wait('@All Channels');
|
||||
|
||||
cy
|
||||
.get('.ant-tabs-tab')
|
||||
.children()
|
||||
.then((e) => {
|
||||
const child = e.get();
|
||||
|
||||
const secondChild = child[1];
|
||||
|
||||
expect(secondChild.outerText).to.be.equals('Alert Channels');
|
||||
|
||||
expect(secondChild.ariaSelected).to.be.equals('true');
|
||||
});
|
||||
|
||||
cy
|
||||
.get('tbody')
|
||||
.should('be.visible')
|
||||
.then((e) => {
|
||||
const allChildren = e.children().get();
|
||||
expect(allChildren.length).to.be.equals(defaultAllChannels.data.length);
|
||||
|
||||
allChildren.forEach((e, index) => {
|
||||
expect(e.firstChild?.textContent).not.null;
|
||||
expect(e.firstChild?.textContent).to.be.equals(
|
||||
defaultAllChannels.data[index].name,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,44 +0,0 @@
|
||||
/// <reference types="cypress" />
|
||||
import ROUTES from 'constants/routes';
|
||||
|
||||
describe('default time', () => {
|
||||
beforeEach(() => {
|
||||
window.localStorage.setItem('isLoggedIn', 'yes');
|
||||
});
|
||||
|
||||
it('Metrics Page default time', () => {
|
||||
cy.checkDefaultGlobalOption({
|
||||
route: ROUTES.APPLICATION,
|
||||
});
|
||||
});
|
||||
|
||||
it('Dashboard Page default time', () => {
|
||||
cy.checkDefaultGlobalOption({
|
||||
route: ROUTES.ALL_DASHBOARD,
|
||||
});
|
||||
});
|
||||
|
||||
it('Trace Page default time', () => {
|
||||
cy.checkDefaultGlobalOption({
|
||||
route: ROUTES.TRACE,
|
||||
});
|
||||
});
|
||||
|
||||
it('Instrumentation Page default time', () => {
|
||||
cy.checkDefaultGlobalOption({
|
||||
route: ROUTES.INSTRUMENTATION,
|
||||
});
|
||||
});
|
||||
|
||||
it('Service Page default time', () => {
|
||||
cy.checkDefaultGlobalOption({
|
||||
route: ROUTES.SERVICE_MAP,
|
||||
});
|
||||
});
|
||||
|
||||
it('Settings Page default time', () => {
|
||||
cy.checkDefaultGlobalOption({
|
||||
route: ROUTES.SETTINGS,
|
||||
});
|
||||
});
|
||||
});
|
@ -1,126 +0,0 @@
|
||||
/// <reference types="cypress" />
|
||||
import getGlobalDropDownFormatedDate from 'lib/getGlobalDropDownFormatedDate';
|
||||
import { AppState } from 'store/reducers';
|
||||
|
||||
import topEndPoints from '../../fixtures/topEndPoints.json';
|
||||
|
||||
describe('Global Time Metrics Application', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit(Cypress.env('baseUrl'));
|
||||
|
||||
const testEmail = 'test@test.com';
|
||||
const firstName = 'Test';
|
||||
|
||||
cy.login({
|
||||
email: testEmail,
|
||||
name: firstName,
|
||||
});
|
||||
});
|
||||
|
||||
it('Metrics Application', async () => {
|
||||
cy
|
||||
.intercept('GET', '/api/v1/services*', {
|
||||
fixture: 'defaultApp.json',
|
||||
})
|
||||
.as('defaultApps');
|
||||
|
||||
cy.wait('@defaultApps');
|
||||
|
||||
//clicking on frontend
|
||||
cy.get('tr:nth-child(1) > td:first-child').click();
|
||||
|
||||
cy
|
||||
.intercept('GET', '/api/v1/service/top_endpoints*', {
|
||||
fixture: 'topEndPoints.json',
|
||||
})
|
||||
.as('topEndPoints');
|
||||
|
||||
cy
|
||||
.intercept('GET', '/api/v1/service/overview?*', {
|
||||
fixture: 'serviceOverview.json',
|
||||
})
|
||||
.as('serviceOverview');
|
||||
|
||||
cy
|
||||
.intercept(
|
||||
'GET',
|
||||
`/api/v1/query_range?query=sum(rate(signoz_latency_count*`,
|
||||
{
|
||||
fixture: 'requestPerSecond.json',
|
||||
},
|
||||
)
|
||||
.as('requestPerSecond');
|
||||
|
||||
cy
|
||||
.window()
|
||||
.its('store')
|
||||
.invoke('getState')
|
||||
.then((e: AppState) => {
|
||||
const { globalTime } = e;
|
||||
|
||||
const { maxTime, minTime } = globalTime;
|
||||
|
||||
// intercepting metrics application call
|
||||
|
||||
cy.wait('@topEndPoints');
|
||||
cy.wait('@serviceOverview');
|
||||
//TODO add errorPercentage also
|
||||
// cy.wait('@errorPercentage');
|
||||
cy.wait('@requestPerSecond');
|
||||
|
||||
cy
|
||||
.get('tbody tr:first-child td:first-child')
|
||||
.then((el) => {
|
||||
const elements = el.get();
|
||||
|
||||
expect(elements.length).to.be.equals(1);
|
||||
|
||||
const element = elements[0];
|
||||
|
||||
expect(element.innerText).to.be.equals(topEndPoints[0].name);
|
||||
})
|
||||
.click();
|
||||
|
||||
cy
|
||||
.findAllByTestId('dropDown')
|
||||
.find('span.ant-select-selection-item')
|
||||
.then((e) => {
|
||||
const elements = e;
|
||||
|
||||
const element = elements[0];
|
||||
|
||||
const customSelectedTime = element.innerText;
|
||||
|
||||
const startTime = new Date(minTime / 1000000);
|
||||
const endTime = new Date(maxTime / 1000000);
|
||||
|
||||
const startString = getGlobalDropDownFormatedDate(startTime);
|
||||
const endString = getGlobalDropDownFormatedDate(endTime);
|
||||
|
||||
const result = `${startString} - ${endString}`;
|
||||
|
||||
expect(customSelectedTime).to.be.equals(result);
|
||||
});
|
||||
|
||||
cy
|
||||
.findByTestId('dropDown')
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.findByTitle('Last 30 min').click();
|
||||
});
|
||||
|
||||
cy
|
||||
.findByTestId('dropDown')
|
||||
.find('span.ant-select-selection-item')
|
||||
.then((e) => {
|
||||
const elements = e;
|
||||
|
||||
const element = elements[0];
|
||||
|
||||
const selectedTime = element.innerText;
|
||||
|
||||
expect(selectedTime).to.be.equals('Last 30 min');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,67 +0,0 @@
|
||||
/// <reference types="cypress" />
|
||||
import ROUTES from 'constants/routes';
|
||||
import convertToNanoSecondsToSecond from 'lib/convertToNanoSecondsToSecond';
|
||||
|
||||
import defaultApps from '../../fixtures/defaultApp.json';
|
||||
|
||||
describe('Metrics', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit(Cypress.env('baseUrl'));
|
||||
|
||||
const testEmail = 'test@test.com';
|
||||
const firstName = 'Test';
|
||||
|
||||
cy.login({
|
||||
email: testEmail,
|
||||
name: firstName,
|
||||
});
|
||||
});
|
||||
|
||||
it('Default Apps', () => {
|
||||
cy
|
||||
.intercept('GET', '/api/v1/services*', {
|
||||
fixture: 'defaultApp.json',
|
||||
})
|
||||
.as('defaultApps');
|
||||
|
||||
cy.wait('@defaultApps');
|
||||
|
||||
cy.location().then((e) => {
|
||||
expect(e.pathname).to.be.equals(ROUTES.APPLICATION);
|
||||
|
||||
cy.get('tbody').then((elements) => {
|
||||
const trElements = elements.children();
|
||||
expect(trElements.length).to.be.equal(defaultApps.length);
|
||||
const getChildren = (row: Element): Element => {
|
||||
if (row.children.length === 0) {
|
||||
return row;
|
||||
}
|
||||
return getChildren(row.children[0]);
|
||||
};
|
||||
|
||||
// this is row element
|
||||
trElements.map((index, element) => {
|
||||
const [
|
||||
applicationElement,
|
||||
p99Element,
|
||||
errorRateElement,
|
||||
rpsElement,
|
||||
] = element.children;
|
||||
const applicationName = getChildren(applicationElement).innerHTML;
|
||||
const p99Name = getChildren(p99Element).innerHTML;
|
||||
const errorRateName = getChildren(errorRateElement).innerHTML;
|
||||
const rpsName = getChildren(rpsElement).innerHTML;
|
||||
const { serviceName, p99, errorRate, callRate } = defaultApps[index];
|
||||
expect(applicationName).to.be.equal(serviceName);
|
||||
expect(p99Name).to.be.equal(convertToNanoSecondsToSecond(p99).toString());
|
||||
expect(errorRateName).to.be.equals(
|
||||
parseFloat(errorRate.toString()).toFixed(2),
|
||||
);
|
||||
expect(rpsName).to.be.equals(callRate.toString());
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
export {};
|
@ -1,130 +0,0 @@
|
||||
/// <reference types="cypress" />
|
||||
|
||||
import ROUTES from 'constants/routes';
|
||||
|
||||
import defaultRules from '../../fixtures/defaultRules.json';
|
||||
|
||||
const defaultRuleRoutes = `**/rules/**`;
|
||||
|
||||
describe('Alerts', () => {
|
||||
beforeEach(() => {
|
||||
window.localStorage.setItem('isLoggedIn', 'yes');
|
||||
|
||||
cy
|
||||
.intercept('get', '*rules*', {
|
||||
fixture: 'defaultRules',
|
||||
})
|
||||
.as('defaultRules');
|
||||
|
||||
cy.visit(Cypress.env('baseUrl') + `${ROUTES.LIST_ALL_ALERT}`);
|
||||
|
||||
cy.wait('@defaultRules');
|
||||
});
|
||||
|
||||
it('Edit Rules Page Failure', async () => {
|
||||
cy
|
||||
.intercept(defaultRuleRoutes, {
|
||||
statusCode: 500,
|
||||
})
|
||||
.as('Get Rules Error');
|
||||
|
||||
cy.get('button.ant-btn.ant-btn-link:nth-child(2)').then((e) => {
|
||||
const firstDelete = e[0];
|
||||
firstDelete.click();
|
||||
|
||||
cy.waitFor('@Get Rules Error');
|
||||
|
||||
cy
|
||||
.window()
|
||||
.location()
|
||||
.then((e) => {
|
||||
expect(e.pathname).to.be.equals(`/alerts/edit/1`);
|
||||
});
|
||||
|
||||
cy.findByText('Something went wrong').then((e) => {
|
||||
expect(e.length).to.be.equals(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Edit Rules Page Success', async () => {
|
||||
const text = 'this is the sample value';
|
||||
|
||||
cy
|
||||
.intercept(defaultRuleRoutes, {
|
||||
statusCode: 200,
|
||||
body: {
|
||||
data: {
|
||||
data: text,
|
||||
},
|
||||
},
|
||||
})
|
||||
.as('Get Rules Success');
|
||||
|
||||
cy.get('button.ant-btn.ant-btn-link:nth-child(2)').then((e) => {
|
||||
const firstDelete = e[0];
|
||||
firstDelete.click();
|
||||
|
||||
cy.waitFor('@Get Rules Success');
|
||||
|
||||
cy.wait(1000);
|
||||
|
||||
cy.findByText('Save').then((e) => {
|
||||
const [el] = e.get();
|
||||
|
||||
el.click();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('All Rules are rendered correctly', async () => {
|
||||
cy
|
||||
.window()
|
||||
.location()
|
||||
.then(({ pathname }) => {
|
||||
expect(pathname).to.be.equals(ROUTES.LIST_ALL_ALERT);
|
||||
|
||||
cy.get('tbody').then((e) => {
|
||||
const tarray = e.children().get();
|
||||
|
||||
expect(tarray.length).to.be.equals(3);
|
||||
|
||||
tarray.forEach(({ children }, index) => {
|
||||
const name = children[1]?.textContent;
|
||||
const label = children[2]?.textContent;
|
||||
|
||||
expect(name).to.be.equals(defaultRules.data.rules[index].name);
|
||||
|
||||
const defaultLabels = defaultRules.data.rules[index].labels;
|
||||
|
||||
expect(label).to.be.equals(defaultLabels['severity']);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Rules are Deleted', async () => {
|
||||
cy
|
||||
.intercept(defaultRuleRoutes, {
|
||||
body: {
|
||||
data: 'Deleted',
|
||||
message: 'Success',
|
||||
},
|
||||
statusCode: 200,
|
||||
})
|
||||
.as('deleteRules');
|
||||
|
||||
cy.get('button.ant-btn.ant-btn-link:first-child').then((e) => {
|
||||
const firstDelete = e[0];
|
||||
|
||||
firstDelete.click();
|
||||
});
|
||||
|
||||
cy.wait('@deleteRules');
|
||||
|
||||
cy.get('tbody').then((e) => {
|
||||
const trray = e.children().get();
|
||||
expect(trray.length).to.be.equals(2);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,160 +0,0 @@
|
||||
/* eslint-disable sonarjs/no-duplicate-string */
|
||||
import ROUTES from 'constants/routes';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { TraceFilterEnum } from 'types/reducer/trace';
|
||||
|
||||
import GraphInitialResponse from '../../fixtures/trace/initialAggregates.json';
|
||||
import FilterInitialResponse from '../../fixtures/trace/initialSpanFilter.json';
|
||||
import TableInitialResponse from '../../fixtures/trace/initialSpans.json';
|
||||
|
||||
const allFilters = '@Filters.all';
|
||||
const allGraphs = '@Graph.all';
|
||||
const allTable = '@Table.all';
|
||||
|
||||
describe('Trace', () => {
|
||||
beforeEach(() => {
|
||||
window.localStorage.setItem('isLoggedIn', 'yes');
|
||||
|
||||
cy
|
||||
.intercept('POST', '**/aggregates', {
|
||||
fixture: 'trace/initialAggregates',
|
||||
})
|
||||
.as('Graph');
|
||||
|
||||
cy
|
||||
.intercept('POST', '**/getFilteredSpans', {
|
||||
fixture: 'trace/initialSpans',
|
||||
})
|
||||
.as('Table');
|
||||
|
||||
cy
|
||||
.intercept('POST', '**/api/v1/getSpanFilters', {
|
||||
fixture: 'trace/initialSpanFilter',
|
||||
})
|
||||
.as('Filters');
|
||||
|
||||
cy.visit(Cypress.env('baseUrl') + `${ROUTES.TRACE}`);
|
||||
});
|
||||
|
||||
it('First Initial Load should go with 3 AJAX request', () => {
|
||||
cy.wait(['@Filters', '@Graph', '@Table']).then((e) => {
|
||||
const [filter, graph, table] = e;
|
||||
|
||||
const { body: filterBody } = filter.request;
|
||||
const { body: graphBody } = graph.request;
|
||||
const { body: tableBody } = table.request;
|
||||
|
||||
expect(filterBody.exclude.length).to.equal(0);
|
||||
expect(filterBody.getFilters.length).to.equal(3);
|
||||
filterBody.getFilters.forEach((filter: TraceFilterEnum) => {
|
||||
expect(filter).to.be.oneOf(['duration', 'status', 'serviceName']);
|
||||
});
|
||||
|
||||
expect(graphBody.function).to.be.equal('count');
|
||||
expect(graphBody.exclude.length).to.be.equal(0);
|
||||
expect(typeof graphBody.exclude).to.be.equal('object');
|
||||
|
||||
expect(tableBody.tags.length).to.be.equal(0);
|
||||
expect(typeof tableBody.tags).equal('object');
|
||||
|
||||
expect(tableBody.exclude.length).equals(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('Render Time Request Response In All 3 Request', () => {
|
||||
cy.wait(['@Filters', '@Graph', '@Table']).then((e) => {
|
||||
const [filter, graph, table] = e;
|
||||
|
||||
expect(filter.response?.body).to.be.not.undefined;
|
||||
expect(filter.response?.body).to.be.not.NaN;
|
||||
|
||||
expect(JSON.stringify(filter.response?.body)).to.be.equals(
|
||||
JSON.stringify(FilterInitialResponse),
|
||||
);
|
||||
|
||||
expect(JSON.stringify(graph.response?.body)).to.be.equals(
|
||||
JSON.stringify(GraphInitialResponse),
|
||||
);
|
||||
|
||||
expect(JSON.stringify(table.response?.body)).to.be.equals(
|
||||
JSON.stringify(TableInitialResponse),
|
||||
);
|
||||
});
|
||||
cy.get(allFilters).should('have.length', 1);
|
||||
cy.get(allGraphs).should('have.length', 1);
|
||||
cy.get(allTable).should('have.length', 1);
|
||||
});
|
||||
|
||||
it('Clear All', () => {
|
||||
cy.wait(['@Filters', '@Graph', '@Table']);
|
||||
|
||||
expect(cy.findAllByText('Clear All')).not.to.be.undefined;
|
||||
|
||||
cy
|
||||
.window()
|
||||
.its('store')
|
||||
.invoke('getState')
|
||||
.then((e: AppState) => {
|
||||
const { traces } = e;
|
||||
expect(traces.isFilterExclude.get('status')).to.be.undefined;
|
||||
expect(traces.selectedFilter.size).to.be.equals(0);
|
||||
});
|
||||
|
||||
cy.findAllByText('Clear All').then((e) => {
|
||||
const [firstStatusClear] = e;
|
||||
|
||||
firstStatusClear.click();
|
||||
|
||||
cy.wait(['@Filters', '@Graph', '@Table']);
|
||||
|
||||
// insuring the api get call
|
||||
cy.get(allFilters).should('have.length', 2);
|
||||
cy.get(allGraphs).should('have.length', 2);
|
||||
cy.get(allTable).should('have.length', 2);
|
||||
|
||||
cy
|
||||
.window()
|
||||
.its('store')
|
||||
.invoke('getState')
|
||||
.then((e: AppState) => {
|
||||
const { traces } = e;
|
||||
|
||||
expect(traces.isFilterExclude.get('status')).to.be.equals(false);
|
||||
expect(traces.userSelectedFilter.get('status')).to.be.undefined;
|
||||
expect(traces.selectedFilter.size).to.be.equals(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Un Selecting one option from status', () => {
|
||||
cy.wait(['@Filters', '@Graph', '@Table']);
|
||||
|
||||
cy.get('input[type="checkbox"]').then((e) => {
|
||||
const [errorCheckbox] = e;
|
||||
errorCheckbox.click();
|
||||
|
||||
cy.wait(['@Filters', '@Graph', '@Table']).then((e) => {
|
||||
const [filter, graph, table] = e;
|
||||
const filterBody = filter.request.body;
|
||||
const graphBody = graph.request.body;
|
||||
const tableBody = table.request.body;
|
||||
|
||||
expect(filterBody.exclude).not.to.be.undefined;
|
||||
expect(filterBody.exclude.length).not.to.be.equal(0);
|
||||
expect(filterBody.exclude[0] === 'status').to.be.true;
|
||||
|
||||
expect(graphBody.exclude).not.to.be.undefined;
|
||||
expect(graphBody.exclude.length).not.to.be.equal(0);
|
||||
expect(graphBody.exclude[0] === 'status').to.be.true;
|
||||
|
||||
expect(tableBody.exclude).not.to.be.undefined;
|
||||
expect(tableBody.exclude.length).not.to.be.equal(0);
|
||||
expect(tableBody.exclude[0] === 'status').to.be.true;
|
||||
});
|
||||
|
||||
cy.get(allFilters).should('have.length', 2);
|
||||
cy.get(allGraphs).should('have.length', 2);
|
||||
cy.get(allTable).should('have.length', 2);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,26 +0,0 @@
|
||||
/// <reference types="cypress" />
|
||||
// ***********************************************************
|
||||
// This example plugins/index.js can be used to load plugins
|
||||
//
|
||||
// You can change the location of this file or turn off loading
|
||||
// the plugins file with the 'pluginsFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/plugins-guide
|
||||
// ***********************************************************
|
||||
|
||||
// This function is called when a project is opened or re-opened (e.g. due to
|
||||
// the project's config changing)
|
||||
|
||||
// cypress/plugins/index.ts
|
||||
|
||||
/// <reference types="cypress" />
|
||||
|
||||
/**
|
||||
* @type {Cypress.PluginConfig}
|
||||
*/
|
||||
module.exports = (): void => {
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export {};
|
@ -1,24 +0,0 @@
|
||||
import '@testing-library/cypress/add-commands';
|
||||
|
||||
import CheckRouteDefaultGlobalTimeOptions, {
|
||||
CheckRouteDefaultGlobalTimeOptionsProps,
|
||||
} from '../CustomFunctions/checkRouteDefaultGlobalTimeOptions';
|
||||
import Login, { LoginProps } from '../CustomFunctions/Login';
|
||||
|
||||
Cypress.Commands.add('login', Login);
|
||||
Cypress.Commands.add(
|
||||
'checkDefaultGlobalOption',
|
||||
CheckRouteDefaultGlobalTimeOptions,
|
||||
);
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace Cypress {
|
||||
interface Chainable {
|
||||
login(props: LoginProps): void;
|
||||
checkDefaultGlobalOption(
|
||||
props: CheckRouteDefaultGlobalTimeOptionsProps,
|
||||
): void;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
// ***********************************************************
|
||||
// This example support/index.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands';
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
@ -1,13 +0,0 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"target": "es5",
|
||||
"lib": ["es5", "dom"],
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
// be explicit about types included
|
||||
// to avoid clashing with Jest types
|
||||
"types": ["cypress", "@testing-library/cypress", "node"],
|
||||
"isolatedModules": false
|
||||
},
|
||||
"include": ["../node_modules/cypress", "./**/*.ts"]
|
||||
}
|
@ -25,6 +25,11 @@ const config: Config.InitialOptions = {
|
||||
setupFilesAfterEnv: ['<rootDir>jest.setup.ts'],
|
||||
testPathIgnorePatterns: ['/node_modules/', '/public/'],
|
||||
moduleDirectories: ['node_modules', 'src'],
|
||||
testEnvironmentOptions: {
|
||||
'jest-playwright': {
|
||||
browsers: ['chromium', 'firefox', 'webkit'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
@ -9,16 +9,17 @@
|
||||
"prettify": "prettier --write .",
|
||||
"lint": "eslint ./src",
|
||||
"lint:fix": "eslint ./src --fix",
|
||||
"cypress:open": "cypress open",
|
||||
"cypress:run": "cypress run",
|
||||
"jest": "jest",
|
||||
"jest:coverage": "jest --coverage",
|
||||
"jest:watch": "jest --watch",
|
||||
"postinstall": "yarn husky:configure",
|
||||
"husky:configure": "cd .. && husky install frontend/.husky"
|
||||
"postinstall": "is-ci || yarn husky:configure",
|
||||
"playwright": "playwright test --config=./playwright.config.ts",
|
||||
"playwright:local:debug": "PWDEBUG=console yarn playwright --headed --browser=chromium",
|
||||
"husky:configure": "cd .. && husky install frontend/.husky && cd frontend && chmod ug+x .husky/*",
|
||||
"commitlint": "commitlint --edit $1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.13.0"
|
||||
"node": ">=16.15.0"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
@ -46,7 +47,6 @@
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "4.3.0",
|
||||
"css-minimizer-webpack-plugin": "^3.2.0",
|
||||
"cypress": "^8.3.0",
|
||||
"d3": "^6.2.0",
|
||||
"d3-flame-graph": "^3.1.1",
|
||||
"d3-tip": "^0.9.1",
|
||||
@ -68,7 +68,7 @@
|
||||
"react-dom": "17.0.0",
|
||||
"react-force-graph": "^1.41.0",
|
||||
"react-graph-vis": "^1.0.5",
|
||||
"react-grid-layout": "^1.2.5",
|
||||
"react-grid-layout": "^1.3.4",
|
||||
"react-i18next": "^11.16.1",
|
||||
"react-query": "^3.34.19",
|
||||
"react-redux": "^7.2.2",
|
||||
@ -109,15 +109,17 @@
|
||||
"@babel/preset-env": "^7.12.17",
|
||||
"@babel/preset-react": "^7.12.13",
|
||||
"@babel/preset-typescript": "^7.12.17",
|
||||
"@commitlint/cli": "^16.2.4",
|
||||
"@commitlint/config-conventional": "^16.2.4",
|
||||
"@jest/globals": "^27.5.1",
|
||||
"@testing-library/cypress": "^8.0.0",
|
||||
"@playwright/test": "^1.22.0",
|
||||
"@testing-library/react-hooks": "^7.0.2",
|
||||
"@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/jest": "^26.0.15",
|
||||
"@types/jest": "^27.5.1",
|
||||
"@types/lodash-es": "^4.17.4",
|
||||
"@types/mini-css-extract-plugin": "^2.5.1",
|
||||
"@types/node": "^16.10.3",
|
||||
@ -136,7 +138,7 @@
|
||||
"@typescript-eslint/parser": "^4.28.2",
|
||||
"autoprefixer": "^9.0.0",
|
||||
"babel-plugin-styled-components": "^1.12.0",
|
||||
"compression-webpack-plugin": "^9.0.0",
|
||||
"compression-webpack-plugin": "9.0.0",
|
||||
"copy-webpack-plugin": "^8.1.0",
|
||||
"critters-webpack-plugin": "^3.0.1",
|
||||
"eslint": "^7.30.0",
|
||||
@ -155,6 +157,8 @@
|
||||
"eslint-plugin-simple-import-sort": "^7.0.0",
|
||||
"eslint-plugin-sonarjs": "^0.12.0",
|
||||
"husky": "^7.0.4",
|
||||
"is-ci": "^3.0.1",
|
||||
"jest-playwright-preset": "^1.7.0",
|
||||
"less-plugin-npm-import": "^2.1.0",
|
||||
"lint-staged": "^12.3.7",
|
||||
"portfinder-sync": "^0.0.2",
|
||||
@ -164,11 +168,15 @@
|
||||
"ts-node": "^10.2.1",
|
||||
"typescript-plugin-css-modules": "^3.4.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0",
|
||||
"webpack-cli": "^4.5.0"
|
||||
"webpack-cli": "^4.9.2"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.(js|jsx|ts|tsx)": [
|
||||
"eslint --fix"
|
||||
]
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "17.0.0",
|
||||
"@types/react-dom": "17.0.0"
|
||||
}
|
||||
}
|
||||
|
21
frontend/playwright.config.ts
Normal file
21
frontend/playwright.config.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { PlaywrightTestConfig } from '@playwright/test';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const config: PlaywrightTestConfig = {
|
||||
forbidOnly: !!process.env.CI,
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
preserveOutput: 'always',
|
||||
name: 'Signoz',
|
||||
testDir: './tests',
|
||||
use: {
|
||||
trace: 'retain-on-failure',
|
||||
baseURL: process.env.FRONTEND_API_ENDPOINT,
|
||||
},
|
||||
updateSnapshots: 'all',
|
||||
fullyParallel: false,
|
||||
quiet: true,
|
||||
};
|
||||
|
||||
export default config;
|
21
frontend/public/locales/en-GB/generalSettings.json
Normal file
21
frontend/public/locales/en-GB/generalSettings.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"total_retention_period": "Total Retention Period",
|
||||
"move_to_s3": "Move to S3\n(should be lower than total retention period)",
|
||||
"status_message": {
|
||||
"success": "Your last call to change retention period to {{total_retention}} {{s3_part}} was successful.",
|
||||
"failed": "Your last call to change retention period to {{total_retention}} {{s3_part}} failed. Please try again.",
|
||||
"pending": "Your last call to change retention period to {{total_retention}} {{s3_part}} is pending. This may take some time.",
|
||||
"s3_part": "and S3 to {{s3_retention}}"
|
||||
},
|
||||
"retention_save_button": {
|
||||
"pending": "Updating {{name}} retention period",
|
||||
"success": "Save"
|
||||
},
|
||||
"retention_request_race_condition": "Your request to change retention period has failed, as another request is still in process.",
|
||||
"retention_error_message": "There was an issue in changing the retention period for {{name}}. Please try again or reach out to support@signoz.io",
|
||||
"retention_failed_message": "There was an issue in changing the retention period. Please try again or reach out to support@signoz.io",
|
||||
"retention_comparison_error": "Total retention period for {{name}} can’t be lower or equal to the period after which data is moved to s3.",
|
||||
"retention_null_value_error": "Retention Period for {{name}} is not set yet. Please set by choosing below",
|
||||
"retention_confirmation": "Are you sure you want to change the retention period?",
|
||||
"retention_confirmation_description": "This will change the amount of storage needed for saving {{name}}."
|
||||
}
|
@ -2,5 +2,8 @@
|
||||
"general": "General",
|
||||
"alert_channels": "Alert Channels",
|
||||
"organization_settings": "Organization Settings",
|
||||
"my_settings": "My Settings"
|
||||
"my_settings": "My Settings",
|
||||
"overview_metrics": "Overview Metrics",
|
||||
"dbcall_metrics": "Database Calls",
|
||||
"external_metrics": "External Calls"
|
||||
}
|
||||
|
@ -13,16 +13,5 @@
|
||||
"general": "General",
|
||||
"alert_channels": "Alert Channels",
|
||||
"all_errors": "All Exceptions"
|
||||
},
|
||||
"settings": {
|
||||
"total_retention_period": "Total Retention Period",
|
||||
"move_to_s3": "Move to S3\n(should be lower than total retention period)",
|
||||
"retention_success_message": "Congrats. The retention periods for {{name}} has been updated successfully.",
|
||||
"retention_error_message": "There was an issue in changing the retention period for {{name}}. Please try again or reach out to support@signoz.io",
|
||||
"retention_failed_message": "There was an issue in changing the retention period. Please try again or reach out to support@signoz.io",
|
||||
"retention_comparison_error": "Total retention period for {{name}} can’t be lower or equal to the period after which data is moved to s3.",
|
||||
"retention_null_value_error": "Retention Period for {{name}} is not set yet. Please set by choosing below",
|
||||
"retention_confirmation": "Are you sure you want to change the retention period?",
|
||||
"retention_confirmation_description": "This will change the amount of storage needed for saving metrics & traces."
|
||||
}
|
||||
}
|
||||
|
21
frontend/public/locales/en/generalSettings.json
Normal file
21
frontend/public/locales/en/generalSettings.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"total_retention_period": "Total Retention Period",
|
||||
"move_to_s3": "Move to S3\n(should be lower than total retention period)",
|
||||
"status_message": {
|
||||
"success": "Your last call to change retention period to {{total_retention}} {{s3_part}} was successful.",
|
||||
"failed": "Your last call to change retention period to {{total_retention}} {{s3_part}} failed. Please try again.",
|
||||
"pending": "Your last call to change retention period to {{total_retention}} {{s3_part}} is pending. This may take some time.",
|
||||
"s3_part": "and S3 to {{s3_retention}}"
|
||||
},
|
||||
"retention_save_button": {
|
||||
"pending": "Updating {{name}} retention period",
|
||||
"success": "Save"
|
||||
},
|
||||
"retention_request_race_condition": "Your request to change retention period has failed, as another request is still in process.",
|
||||
"retention_error_message": "There was an issue in changing the retention period for {{name}}. Please try again or reach out to support@signoz.io",
|
||||
"retention_failed_message": "There was an issue in changing the retention period. Please try again or reach out to support@signoz.io",
|
||||
"retention_comparison_error": "Total retention period for {{name}} can’t be lower or equal to the period after which data is moved to s3.",
|
||||
"retention_null_value_error": "Retention Period for {{name}} is not set yet. Please set by choosing below",
|
||||
"retention_confirmation": "Are you sure you want to change the retention period?",
|
||||
"retention_confirmation_description": "This will change the amount of storage needed for saving {{name}}."
|
||||
}
|
@ -2,5 +2,8 @@
|
||||
"general": "General",
|
||||
"alert_channels": "Alert Channels",
|
||||
"organization_settings": "Organization Settings",
|
||||
"my_settings": "My Settings"
|
||||
"my_settings": "My Settings",
|
||||
"overview_metrics": "Overview Metrics",
|
||||
"dbcall_metrics": "Database Calls",
|
||||
"external_metrics": "External Calls"
|
||||
}
|
||||
|
@ -13,16 +13,5 @@
|
||||
"general": "General",
|
||||
"alert_channels": "Alert Channels",
|
||||
"all_errors": "All Exceptions"
|
||||
},
|
||||
"settings": {
|
||||
"total_retention_period": "Total Retention Period",
|
||||
"move_to_s3": "Move to S3\n(should be lower than total retention period)",
|
||||
"retention_success_message": "Congrats. The retention periods for {{name}} has been updated successfully.",
|
||||
"retention_error_message": "There was an issue in changing the retention period for {{name}}. Please try again or reach out to support@signoz.io",
|
||||
"retention_failed_message": "There was an issue in changing the retention period. Please try again or reach out to support@signoz.io",
|
||||
"retention_comparison_error": "Total retention period for {{name}} can’t be lower or equal to the period after which data is moved to s3.",
|
||||
"retention_null_value_error": "Retention Period for {{name}} is not set yet. Please set by choosing below",
|
||||
"retention_confirmation": "Are you sure you want to change the retention period?",
|
||||
"retention_confirmation_description": "This will change the amount of storage needed for saving metrics & traces."
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
import { notification } from 'antd';
|
||||
import getLocalStorageApi from 'api/browser/localstorage/get';
|
||||
import loginApi from 'api/user/login';
|
||||
import { Logout } from 'api/utils';
|
||||
import Spinner from 'components/Spinner';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import ROUTES from 'constants/routes';
|
||||
@ -103,7 +104,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
history.push(ROUTES.UN_AUTHORIZED);
|
||||
}
|
||||
} else {
|
||||
history.push(ROUTES.SOMETHING_WENT_WRONG);
|
||||
Logout();
|
||||
|
||||
notification.error({
|
||||
message: response.error || t('something_went_wrong'),
|
||||
|
@ -12,10 +12,7 @@ export const ServiceMetricsPage = Loadable(
|
||||
);
|
||||
|
||||
export const ServiceMapPage = Loadable(
|
||||
() =>
|
||||
import(
|
||||
/* webpackChunkName: "ServiceMapPage" */ 'modules/Servicemap/ServiceMap'
|
||||
),
|
||||
() => import(/* webpackChunkName: "ServiceMapPage" */ 'modules/Servicemap'),
|
||||
);
|
||||
|
||||
export const TraceFilter = Loadable(
|
||||
@ -27,10 +24,7 @@ export const TraceDetail = Loadable(
|
||||
);
|
||||
|
||||
export const UsageExplorerPage = Loadable(
|
||||
() =>
|
||||
import(
|
||||
/* webpackChunkName: "UsageExplorerPage" */ 'modules/Usage/UsageExplorerDef'
|
||||
),
|
||||
() => import(/* webpackChunkName: "UsageExplorerPage" */ 'modules/Usage'),
|
||||
);
|
||||
|
||||
export const SignupPage = Loadable(
|
||||
|
@ -2,13 +2,15 @@ import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps } from 'types/api/settings/getRetention';
|
||||
import { PayloadProps, Props } from 'types/api/settings/getRetention';
|
||||
|
||||
const getRetention = async (): Promise<
|
||||
SuccessResponse<PayloadProps> | ErrorResponse
|
||||
> => {
|
||||
const getRetention = async <T extends Props>(
|
||||
props: T,
|
||||
): Promise<SuccessResponse<PayloadProps<T>> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.get<PayloadProps>(`/settings/ttl`);
|
||||
const response = await axios.get<PayloadProps<T>>(
|
||||
`/settings/ttl?type=${props}`,
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
|
@ -11,7 +11,7 @@ const setRetention = async (
|
||||
const response = await axios.post<PayloadProps>(
|
||||
`/settings/ttl?duration=${props.totalDuration}&type=${props.type}${
|
||||
props.coldStorage
|
||||
? `&coldStorage=${props.coldStorage};toColdDuration=${props.toColdDuration}`
|
||||
? `&coldStorage=${props.coldStorage}&toColdDuration=${props.toColdDuration}`
|
||||
: ''
|
||||
}`,
|
||||
);
|
||||
|
@ -5,6 +5,7 @@ import history from 'lib/history';
|
||||
import store from 'store';
|
||||
import {
|
||||
LOGGED_IN,
|
||||
UPDATE_ORG,
|
||||
UPDATE_USER,
|
||||
UPDATE_USER_ACCESS_REFRESH_ACCESS_TOKEN,
|
||||
UPDATE_USER_ORG_ROLE,
|
||||
@ -51,5 +52,12 @@ export const Logout = (): void => {
|
||||
},
|
||||
});
|
||||
|
||||
store.dispatch({
|
||||
type: UPDATE_ORG,
|
||||
payload: {
|
||||
org: [],
|
||||
},
|
||||
});
|
||||
|
||||
history.push(ROUTES.LOGIN);
|
||||
};
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { expect } from '@jest/globals';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import { convertTimeRange, TIME_UNITS } from '../xAxisConfig';
|
||||
|
@ -2,10 +2,11 @@
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
|
||||
import { expect } from '@jest/globals';
|
||||
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';
|
||||
|
||||
import NotFound from './index';
|
||||
|
||||
@ -13,7 +14,9 @@ describe('Not Found page test', () => {
|
||||
it('should render Not Found page without errors', () => {
|
||||
const { asFragment } = render(
|
||||
<MemoryRouter>
|
||||
<NotFound />
|
||||
<Provider store={store}>
|
||||
<NotFound />
|
||||
</Provider>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
|
@ -27,12 +27,19 @@ function RouteTab({
|
||||
onChange={onChange}
|
||||
destroyInactiveTabPane
|
||||
activeKey={activeKey}
|
||||
animated
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...rest}
|
||||
>
|
||||
{routes.map(
|
||||
({ Component, name }): JSX.Element => (
|
||||
<TabPane tab={name} key={name}>
|
||||
({ Component, name, route }): JSX.Element => (
|
||||
<TabPane
|
||||
tabKey={route}
|
||||
animated
|
||||
destroyInactiveTabPane
|
||||
tab={name}
|
||||
key={name}
|
||||
>
|
||||
<Component />
|
||||
</TabPane>
|
||||
),
|
||||
|
@ -18,7 +18,7 @@ const ROUTES = {
|
||||
ALL_CHANNELS: '/settings/channels',
|
||||
CHANNELS_NEW: '/setting/channels/new',
|
||||
CHANNELS_EDIT: '/setting/channels/edit/:id',
|
||||
ALL_ERROR: '/errors',
|
||||
ALL_ERROR: '/exceptions',
|
||||
ERROR_DETAIL: '/error-detail',
|
||||
VERSION: '/status',
|
||||
MY_SETTINGS: '/my-settings',
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { notification, Table, Typography } from 'antd';
|
||||
import { notification, Table, Tooltip, Typography } from 'antd';
|
||||
import { ColumnsType } from 'antd/lib/table';
|
||||
import getAll from 'api/errors/getAll';
|
||||
import ROUTES from 'constants/routes';
|
||||
@ -47,11 +47,13 @@ function AllErrors(): JSX.Element {
|
||||
dataIndex: 'exceptionType',
|
||||
key: 'exceptionType',
|
||||
render: (value, record): JSX.Element => (
|
||||
<Link
|
||||
to={`${ROUTES.ERROR_DETAIL}?serviceName=${record.serviceName}&errorType=${record.exceptionType}`}
|
||||
>
|
||||
{value}
|
||||
</Link>
|
||||
<Tooltip overlay={(): JSX.Element => value}>
|
||||
<Link
|
||||
to={`${ROUTES.ERROR_DETAIL}?serviceName=${record.serviceName}&errorType=${record.exceptionType}`}
|
||||
>
|
||||
{value}
|
||||
</Link>
|
||||
</Tooltip>
|
||||
),
|
||||
sorter: (a, b): number =>
|
||||
a.exceptionType.charCodeAt(0) - b.exceptionType.charCodeAt(0),
|
||||
@ -61,13 +63,15 @@ function AllErrors(): JSX.Element {
|
||||
dataIndex: 'exceptionMessage',
|
||||
key: 'exceptionMessage',
|
||||
render: (value): JSX.Element => (
|
||||
<Typography.Paragraph
|
||||
ellipsis={{
|
||||
rows: 2,
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
</Typography.Paragraph>
|
||||
<Tooltip overlay={(): JSX.Element => value}>
|
||||
<Typography.Paragraph
|
||||
ellipsis={{
|
||||
rows: 2,
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
</Typography.Paragraph>
|
||||
</Tooltip>
|
||||
),
|
||||
},
|
||||
{
|
||||
|
@ -117,7 +117,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
dispatch({
|
||||
type: UPDATE_LATEST_VERSION,
|
||||
payload: {
|
||||
latestVersion: getUserLatestVersionResponse.data.payload.name,
|
||||
latestVersion: getUserLatestVersionResponse.data.payload.tag_name,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -1,35 +1,67 @@
|
||||
import { Button, Col, Modal, notification, Row, Typography } from 'antd';
|
||||
import { LoadingOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
Button,
|
||||
Col,
|
||||
Divider,
|
||||
Modal,
|
||||
notification,
|
||||
Row,
|
||||
Spin,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import setRetentionApi from 'api/settings/setRetention';
|
||||
import TextToolTip from 'components/TextToolTip';
|
||||
import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import find from 'lodash-es/find';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { UseQueryResult } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useInterval } from 'react-use';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import {
|
||||
IDiskType,
|
||||
PayloadProps as GetDisksPayload,
|
||||
} from 'types/api/disks/getDisks';
|
||||
import { PayloadProps as GetRetentionPayload } from 'types/api/settings/getRetention';
|
||||
import { TTTLType } from 'types/api/settings/common';
|
||||
import {
|
||||
PayloadPropsMetrics as GetRetentionPeriodMetricsPayload,
|
||||
PayloadPropsTraces as GetRetentionPeriodTracesPayload,
|
||||
} from 'types/api/settings/getRetention';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import Retention from './Retention';
|
||||
import { ButtonContainer, ErrorText, ErrorTextContainer } from './styles';
|
||||
import StatusMessage from './StatusMessage';
|
||||
import { ActionItemsContainer, ErrorText, ErrorTextContainer } from './styles';
|
||||
|
||||
type NumberOrNull = number | null;
|
||||
|
||||
function GeneralSettings({
|
||||
ttlValuesPayload,
|
||||
metricsTtlValuesPayload,
|
||||
tracesTtlValuesPayload,
|
||||
getAvailableDiskPayload,
|
||||
metricsTtlValuesRefetch,
|
||||
tracesTtlValuesRefetch,
|
||||
}: GeneralSettingsProps): JSX.Element {
|
||||
const { t } = useTranslation();
|
||||
const [modal, setModal] = useState<boolean>(false);
|
||||
const [postApiLoading, setPostApiLoading] = useState<boolean>(false);
|
||||
|
||||
const { t } = useTranslation(['generalSettings']);
|
||||
const [modalMetrics, setModalMetrics] = useState<boolean>(false);
|
||||
const [postApiLoadingMetrics, setPostApiLoadingMetrics] = useState<boolean>(
|
||||
false,
|
||||
);
|
||||
const [postApiLoadingTraces, setPostApiLoadingTraces] = useState<boolean>(
|
||||
false,
|
||||
);
|
||||
const [modalTraces, setModalTraces] = useState<boolean>(false);
|
||||
const [availableDisks] = useState<IDiskType[]>(getAvailableDiskPayload);
|
||||
|
||||
const [currentTTLValues, setCurrentTTLValues] = useState(ttlValuesPayload);
|
||||
const [metricsCurrentTTLValues, setMetricsCurrentTTLValues] = useState(
|
||||
metricsTtlValuesPayload,
|
||||
);
|
||||
const [tracesCurrentTTLValues, setTracesCurrentTTLValues] = useState(
|
||||
tracesTtlValuesPayload,
|
||||
);
|
||||
|
||||
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
|
||||
const [setRetentionPermission] = useComponentPermission(
|
||||
@ -55,195 +87,93 @@ function GeneralSettings({
|
||||
] = useState<NumberOrNull>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentTTLValues) {
|
||||
setMetricsTotalRetentionPeriod(currentTTLValues.metrics_ttl_duration_hrs);
|
||||
setMetricsS3RetentionPeriod(
|
||||
currentTTLValues.metrics_move_ttl_duration_hrs
|
||||
? currentTTLValues.metrics_move_ttl_duration_hrs
|
||||
: null,
|
||||
if (metricsCurrentTTLValues) {
|
||||
setMetricsTotalRetentionPeriod(
|
||||
metricsCurrentTTLValues.metrics_ttl_duration_hrs,
|
||||
);
|
||||
setTracesTotalRetentionPeriod(currentTTLValues.traces_ttl_duration_hrs);
|
||||
setTracesS3RetentionPeriod(
|
||||
currentTTLValues.traces_move_ttl_duration_hrs
|
||||
? currentTTLValues.traces_move_ttl_duration_hrs
|
||||
setMetricsS3RetentionPeriod(
|
||||
metricsCurrentTTLValues.metrics_move_ttl_duration_hrs
|
||||
? metricsCurrentTTLValues.metrics_move_ttl_duration_hrs
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}, [currentTTLValues]);
|
||||
}, [metricsCurrentTTLValues]);
|
||||
|
||||
const onModalToggleHandler = (): void => {
|
||||
setModal((modal) => !modal);
|
||||
useEffect(() => {
|
||||
if (tracesCurrentTTLValues) {
|
||||
setTracesTotalRetentionPeriod(
|
||||
tracesCurrentTTLValues.traces_ttl_duration_hrs,
|
||||
);
|
||||
setTracesS3RetentionPeriod(
|
||||
tracesCurrentTTLValues.traces_move_ttl_duration_hrs
|
||||
? tracesCurrentTTLValues.traces_move_ttl_duration_hrs
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}, [tracesCurrentTTLValues]);
|
||||
|
||||
useInterval(
|
||||
async (): Promise<void> => {
|
||||
if (metricsTtlValuesPayload.status === 'pending') {
|
||||
metricsTtlValuesRefetch();
|
||||
}
|
||||
},
|
||||
metricsTtlValuesPayload.status === 'pending' ? 1000 : null,
|
||||
);
|
||||
|
||||
useInterval(
|
||||
async (): Promise<void> => {
|
||||
if (tracesTtlValuesPayload.status === 'pending') {
|
||||
tracesTtlValuesRefetch();
|
||||
}
|
||||
},
|
||||
tracesTtlValuesPayload.status === 'pending' ? 1000 : null,
|
||||
);
|
||||
|
||||
const onModalToggleHandler = (type: TTTLType): void => {
|
||||
if (type === 'metrics') setModalMetrics((modal) => !modal);
|
||||
if (type === 'traces') setModalTraces((modal) => !modal);
|
||||
};
|
||||
const onPostApiLoadingHandler = (type: TTTLType): void => {
|
||||
if (type === 'metrics') setPostApiLoadingMetrics((modal) => !modal);
|
||||
if (type === 'traces') setPostApiLoadingTraces((modal) => !modal);
|
||||
};
|
||||
|
||||
const onClickSaveHandler = useCallback(() => {
|
||||
if (!setRetentionPermission) {
|
||||
notification.error({
|
||||
message: `Sorry you don't have permission to make these changes`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
onModalToggleHandler();
|
||||
}, [setRetentionPermission]);
|
||||
const onClickSaveHandler = useCallback(
|
||||
(type: TTTLType) => {
|
||||
if (!setRetentionPermission) {
|
||||
notification.error({
|
||||
message: `Sorry you don't have permission to make these changes`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
onModalToggleHandler(type);
|
||||
},
|
||||
[setRetentionPermission],
|
||||
);
|
||||
|
||||
const s3Enabled = useMemo(
|
||||
() => !!find(availableDisks, (disks: IDiskType) => disks?.type === 's3'),
|
||||
[availableDisks],
|
||||
);
|
||||
|
||||
const renderConfig = [
|
||||
{
|
||||
name: 'Metrics',
|
||||
retentionFields: [
|
||||
{
|
||||
name: t('settings.total_retention_period'),
|
||||
value: metricsTotalRetentionPeriod,
|
||||
setValue: setMetricsTotalRetentionPeriod,
|
||||
},
|
||||
{
|
||||
name: t('settings.move_to_s3'),
|
||||
value: metricsS3RetentionPeriod,
|
||||
setValue: setMetricsS3RetentionPeriod,
|
||||
hide: !s3Enabled,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Traces',
|
||||
retentionFields: [
|
||||
{
|
||||
name: t('settings.total_retention_period'),
|
||||
value: tracesTotalRetentionPeriod,
|
||||
setValue: setTracesTotalRetentionPeriod,
|
||||
},
|
||||
{
|
||||
name: t('settings.move_to_s3'),
|
||||
value: tracesS3RetentionPeriod,
|
||||
setValue: setTracesS3RetentionPeriod,
|
||||
hide: !s3Enabled,
|
||||
},
|
||||
],
|
||||
},
|
||||
].map((category): JSX.Element | null => {
|
||||
if (
|
||||
Array.isArray(category.retentionFields) &&
|
||||
category.retentionFields.length > 0
|
||||
) {
|
||||
return (
|
||||
<Col flex="40%" style={{ minWidth: 475 }} key={category.name}>
|
||||
<Typography.Title level={3}>{category.name}</Typography.Title>
|
||||
|
||||
{category.retentionFields.map((retentionField) => (
|
||||
<Retention
|
||||
key={retentionField.name}
|
||||
text={retentionField.name}
|
||||
retentionValue={retentionField.value}
|
||||
setRetentionValue={retentionField.setValue}
|
||||
hide={!!retentionField.hide}
|
||||
/>
|
||||
))}
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
const onOkHandler = async (): Promise<void> => {
|
||||
try {
|
||||
setPostApiLoading(true);
|
||||
const apiCalls = [];
|
||||
|
||||
if (
|
||||
!(
|
||||
currentTTLValues?.metrics_move_ttl_duration_hrs ===
|
||||
metricsS3RetentionPeriod &&
|
||||
currentTTLValues.metrics_ttl_duration_hrs === metricsTotalRetentionPeriod
|
||||
)
|
||||
) {
|
||||
apiCalls.push(() =>
|
||||
setRetentionApi({
|
||||
type: 'metrics',
|
||||
totalDuration: `${metricsTotalRetentionPeriod || -1}h`,
|
||||
coldStorage: s3Enabled ? 's3' : null,
|
||||
toColdDuration: `${metricsS3RetentionPeriod || -1}h`,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
apiCalls.push(() => Promise.resolve(null));
|
||||
}
|
||||
|
||||
if (
|
||||
!(
|
||||
currentTTLValues?.traces_move_ttl_duration_hrs ===
|
||||
tracesS3RetentionPeriod &&
|
||||
currentTTLValues.traces_ttl_duration_hrs === tracesTotalRetentionPeriod
|
||||
)
|
||||
) {
|
||||
apiCalls.push(() =>
|
||||
setRetentionApi({
|
||||
type: 'traces',
|
||||
totalDuration: `${tracesTotalRetentionPeriod || -1}h`,
|
||||
coldStorage: s3Enabled ? 's3' : null,
|
||||
toColdDuration: `${tracesS3RetentionPeriod || -1}h`,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
apiCalls.push(() => Promise.resolve(null));
|
||||
}
|
||||
const apiCallSequence = ['metrics', 'traces'];
|
||||
const apiResponses = await Promise.all(apiCalls.map((api) => api()));
|
||||
|
||||
apiResponses.forEach((apiResponse, idx) => {
|
||||
const name = apiCallSequence[idx];
|
||||
if (apiResponse) {
|
||||
if (apiResponse.statusCode === 200) {
|
||||
notification.success({
|
||||
message: 'Success!',
|
||||
placement: 'topRight',
|
||||
|
||||
description: t('settings.retention_success_message', { name }),
|
||||
});
|
||||
} else {
|
||||
notification.error({
|
||||
message: 'Error',
|
||||
description: t('settings.retention_error_message', { name }),
|
||||
placement: 'topRight',
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
onModalToggleHandler();
|
||||
setPostApiLoading(false);
|
||||
} catch (error) {
|
||||
notification.error({
|
||||
message: 'Error',
|
||||
description: t('settings.retention_failed_message'),
|
||||
placement: 'topRight',
|
||||
});
|
||||
}
|
||||
// Updates the currentTTL Values in order to avoid pushing the same values.
|
||||
setCurrentTTLValues({
|
||||
metrics_ttl_duration_hrs: metricsTotalRetentionPeriod || -1,
|
||||
metrics_move_ttl_duration_hrs: metricsS3RetentionPeriod || -1,
|
||||
traces_ttl_duration_hrs: tracesTotalRetentionPeriod || -1,
|
||||
traces_move_ttl_duration_hrs: tracesS3RetentionPeriod || -1,
|
||||
});
|
||||
|
||||
setModal(false);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
const [isDisabled, errorText] = useMemo((): [boolean, string] => {
|
||||
const [isMetricsSaveDisabled, isTracesSaveDisabled, errorText] = useMemo((): [
|
||||
boolean,
|
||||
boolean,
|
||||
string,
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
] => {
|
||||
// Various methods to return dynamic error message text.
|
||||
const messages = {
|
||||
compareError: (name: string | number): string =>
|
||||
t('settings.retention_comparison_error', { name }),
|
||||
t('retention_comparison_error', { name }),
|
||||
nullValueError: (name: string | number): string =>
|
||||
t('settings.retention_null_value_error', { name }),
|
||||
t('retention_null_value_error', { name }),
|
||||
};
|
||||
|
||||
// Defaults to button not disabled and empty error message text.
|
||||
let isDisabled = false;
|
||||
let isMetricsSaveDisabled = false;
|
||||
let isTracesSaveDisabled = false;
|
||||
let errorText = '';
|
||||
|
||||
if (s3Enabled) {
|
||||
@ -251,19 +181,20 @@ function GeneralSettings({
|
||||
(metricsTotalRetentionPeriod || metricsS3RetentionPeriod) &&
|
||||
Number(metricsTotalRetentionPeriod) <= Number(metricsS3RetentionPeriod)
|
||||
) {
|
||||
isDisabled = true;
|
||||
isMetricsSaveDisabled = true;
|
||||
errorText = messages.compareError('metrics');
|
||||
} else if (
|
||||
(tracesTotalRetentionPeriod || tracesS3RetentionPeriod) &&
|
||||
Number(tracesTotalRetentionPeriod) <= Number(tracesS3RetentionPeriod)
|
||||
) {
|
||||
isDisabled = true;
|
||||
isTracesSaveDisabled = true;
|
||||
errorText = messages.compareError('traces');
|
||||
}
|
||||
}
|
||||
|
||||
if (!metricsTotalRetentionPeriod || !tracesTotalRetentionPeriod) {
|
||||
isDisabled = true;
|
||||
isMetricsSaveDisabled = true;
|
||||
isTracesSaveDisabled = true;
|
||||
if (!metricsTotalRetentionPeriod && !tracesTotalRetentionPeriod) {
|
||||
errorText = messages.nullValueError('metrics and traces');
|
||||
} else if (!metricsTotalRetentionPeriod) {
|
||||
@ -273,25 +204,240 @@ function GeneralSettings({
|
||||
}
|
||||
}
|
||||
if (
|
||||
currentTTLValues?.metrics_ttl_duration_hrs === metricsTotalRetentionPeriod &&
|
||||
currentTTLValues.metrics_move_ttl_duration_hrs ===
|
||||
metricsS3RetentionPeriod &&
|
||||
currentTTLValues.traces_ttl_duration_hrs === tracesTotalRetentionPeriod &&
|
||||
currentTTLValues.traces_move_ttl_duration_hrs === tracesS3RetentionPeriod
|
||||
) {
|
||||
isDisabled = true;
|
||||
}
|
||||
return [isDisabled, errorText];
|
||||
metricsCurrentTTLValues?.metrics_ttl_duration_hrs ===
|
||||
metricsTotalRetentionPeriod &&
|
||||
metricsCurrentTTLValues.metrics_move_ttl_duration_hrs ===
|
||||
metricsS3RetentionPeriod
|
||||
)
|
||||
isMetricsSaveDisabled = true;
|
||||
|
||||
if (
|
||||
tracesCurrentTTLValues.traces_ttl_duration_hrs ===
|
||||
tracesTotalRetentionPeriod &&
|
||||
tracesCurrentTTLValues.traces_move_ttl_duration_hrs ===
|
||||
tracesS3RetentionPeriod
|
||||
)
|
||||
isTracesSaveDisabled = true;
|
||||
|
||||
return [isMetricsSaveDisabled, isTracesSaveDisabled, errorText];
|
||||
}, [
|
||||
currentTTLValues,
|
||||
metricsCurrentTTLValues.metrics_move_ttl_duration_hrs,
|
||||
metricsCurrentTTLValues?.metrics_ttl_duration_hrs,
|
||||
metricsS3RetentionPeriod,
|
||||
metricsTotalRetentionPeriod,
|
||||
s3Enabled,
|
||||
t,
|
||||
tracesCurrentTTLValues.traces_move_ttl_duration_hrs,
|
||||
tracesCurrentTTLValues.traces_ttl_duration_hrs,
|
||||
tracesS3RetentionPeriod,
|
||||
tracesTotalRetentionPeriod,
|
||||
]);
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
const onOkHandler = async (type: TTTLType): Promise<void> => {
|
||||
try {
|
||||
onPostApiLoadingHandler(type);
|
||||
const setTTLResponse = await setRetentionApi({
|
||||
type,
|
||||
totalDuration: `${
|
||||
(type === 'metrics'
|
||||
? metricsTotalRetentionPeriod
|
||||
: tracesTotalRetentionPeriod) || -1
|
||||
}h`,
|
||||
coldStorage: s3Enabled ? 's3' : null,
|
||||
toColdDuration: `${
|
||||
(type === 'metrics'
|
||||
? metricsS3RetentionPeriod
|
||||
: tracesS3RetentionPeriod) || -1
|
||||
}h`,
|
||||
});
|
||||
let hasSetTTLFailed = false;
|
||||
if (setTTLResponse.statusCode === 409) {
|
||||
hasSetTTLFailed = true;
|
||||
notification.error({
|
||||
message: 'Error',
|
||||
description: t('retention_request_race_condition'),
|
||||
placement: 'topRight',
|
||||
});
|
||||
}
|
||||
|
||||
if (type === 'metrics') {
|
||||
metricsTtlValuesRefetch();
|
||||
|
||||
if (!hasSetTTLFailed)
|
||||
// Updates the currentTTL Values in order to avoid pushing the same values.
|
||||
setMetricsCurrentTTLValues({
|
||||
metrics_ttl_duration_hrs: metricsTotalRetentionPeriod || -1,
|
||||
metrics_move_ttl_duration_hrs: metricsS3RetentionPeriod || -1,
|
||||
status: '',
|
||||
});
|
||||
} else if (type === 'traces') {
|
||||
tracesTtlValuesRefetch();
|
||||
|
||||
if (!hasSetTTLFailed)
|
||||
// Updates the currentTTL Values in order to avoid pushing the same values.
|
||||
setTracesCurrentTTLValues({
|
||||
traces_ttl_duration_hrs: tracesTotalRetentionPeriod || -1,
|
||||
traces_move_ttl_duration_hrs: tracesS3RetentionPeriod || -1,
|
||||
status: '',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
notification.error({
|
||||
message: 'Error',
|
||||
description: t('retention_failed_message'),
|
||||
placement: 'topRight',
|
||||
});
|
||||
}
|
||||
|
||||
onPostApiLoadingHandler(type);
|
||||
onModalToggleHandler(type);
|
||||
};
|
||||
|
||||
const renderConfig = [
|
||||
{
|
||||
name: 'Metrics',
|
||||
retentionFields: [
|
||||
{
|
||||
name: t('total_retention_period'),
|
||||
value: metricsTotalRetentionPeriod,
|
||||
setValue: setMetricsTotalRetentionPeriod,
|
||||
},
|
||||
{
|
||||
name: t('move_to_s3'),
|
||||
value: metricsS3RetentionPeriod,
|
||||
setValue: setMetricsS3RetentionPeriod,
|
||||
hide: !s3Enabled,
|
||||
},
|
||||
],
|
||||
save: {
|
||||
modal: modalMetrics,
|
||||
modalOpen: (): void => onClickSaveHandler('metrics'),
|
||||
apiLoading: postApiLoadingMetrics,
|
||||
saveButtonText:
|
||||
metricsTtlValuesPayload.status === 'pending' ? (
|
||||
<span>
|
||||
<Spin spinning size="small" indicator={<LoadingOutlined spin />} />{' '}
|
||||
{t('retention_save_button.pending', { name: 'metrics' })}
|
||||
</span>
|
||||
) : (
|
||||
<span>{t('retention_save_button.success')}</span>
|
||||
),
|
||||
isDisabled:
|
||||
metricsTtlValuesPayload.status === 'pending' || isMetricsSaveDisabled,
|
||||
},
|
||||
statusComponent: (
|
||||
<StatusMessage
|
||||
total_retention={metricsTtlValuesPayload.expected_metrics_ttl_duration_hrs}
|
||||
status={metricsTtlValuesPayload.status}
|
||||
s3_retention={
|
||||
metricsTtlValuesPayload.expected_metrics_move_ttl_duration_hrs
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'Traces',
|
||||
retentionFields: [
|
||||
{
|
||||
name: t('total_retention_period'),
|
||||
value: tracesTotalRetentionPeriod,
|
||||
setValue: setTracesTotalRetentionPeriod,
|
||||
},
|
||||
{
|
||||
name: t('move_to_s3'),
|
||||
value: tracesS3RetentionPeriod,
|
||||
setValue: setTracesS3RetentionPeriod,
|
||||
hide: !s3Enabled,
|
||||
},
|
||||
],
|
||||
save: {
|
||||
modal: modalTraces,
|
||||
modalOpen: (): void => onClickSaveHandler('traces'),
|
||||
apiLoading: postApiLoadingTraces,
|
||||
saveButtonText:
|
||||
tracesTtlValuesPayload.status === 'pending' ? (
|
||||
<span>
|
||||
<Spin spinning size="small" indicator={<LoadingOutlined spin />} />{' '}
|
||||
{t('retention_save_button.pending', { name: 'traces' })}
|
||||
</span>
|
||||
) : (
|
||||
<span>{t('retention_save_button.success')}</span>
|
||||
),
|
||||
isDisabled:
|
||||
tracesTtlValuesPayload.status === 'pending' || isTracesSaveDisabled,
|
||||
},
|
||||
statusComponent: (
|
||||
<StatusMessage
|
||||
total_retention={tracesTtlValuesPayload.expected_traces_ttl_duration_hrs}
|
||||
status={tracesTtlValuesPayload.status}
|
||||
s3_retention={tracesTtlValuesPayload.expected_traces_move_ttl_duration_hrs}
|
||||
/>
|
||||
),
|
||||
},
|
||||
].map((category, idx, renderArr): JSX.Element | null => {
|
||||
if (
|
||||
Array.isArray(category.retentionFields) &&
|
||||
category.retentionFields.length > 0
|
||||
) {
|
||||
return (
|
||||
<React.Fragment key={category.name}>
|
||||
<Col xs={22} xl={11} key={category.name}>
|
||||
<Typography.Title level={3}>{category.name}</Typography.Title>
|
||||
|
||||
{category.retentionFields.map((retentionField) => (
|
||||
<Retention
|
||||
key={retentionField.name}
|
||||
text={retentionField.name}
|
||||
retentionValue={retentionField.value}
|
||||
setRetentionValue={retentionField.setValue}
|
||||
hide={!!retentionField.hide}
|
||||
/>
|
||||
))}
|
||||
<ActionItemsContainer>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={category.save.modalOpen}
|
||||
disabled={category.save.isDisabled}
|
||||
>
|
||||
{category.save.saveButtonText}
|
||||
</Button>
|
||||
{category.statusComponent}
|
||||
</ActionItemsContainer>
|
||||
<Modal
|
||||
title={t('retention_confirmation')}
|
||||
focusTriggerAfterClose
|
||||
forceRender
|
||||
destroyOnClose
|
||||
closable
|
||||
onCancel={(): void =>
|
||||
onModalToggleHandler(category.name.toLowerCase() as TTTLType)
|
||||
}
|
||||
onOk={(): Promise<void> =>
|
||||
onOkHandler(category.name.toLowerCase() as TTTLType)
|
||||
}
|
||||
centered
|
||||
visible={category.save.modal}
|
||||
confirmLoading={category.save.apiLoading}
|
||||
>
|
||||
<Typography>
|
||||
{t('retention_confirmation_description', {
|
||||
name: category.name.toLowerCase(),
|
||||
})}
|
||||
</Typography>
|
||||
</Modal>
|
||||
</Col>
|
||||
{idx < renderArr.length && (
|
||||
<Col xs={0} xl={1} style={{ textAlign: 'center' }}>
|
||||
<Divider type="vertical" dashed style={{ height: '100%' }} />
|
||||
</Col>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
return (
|
||||
<Col xs={24} md={22} xl={20} xxl={18} style={{ margin: 'auto' }}>
|
||||
{Element}
|
||||
@ -306,34 +452,20 @@ function GeneralSettings({
|
||||
</ErrorTextContainer>
|
||||
|
||||
<Row justify="space-around">{renderConfig}</Row>
|
||||
|
||||
<Modal
|
||||
title={t('settings.retention_confirmation')}
|
||||
focusTriggerAfterClose
|
||||
forceRender
|
||||
destroyOnClose
|
||||
closable
|
||||
onCancel={onModalToggleHandler}
|
||||
onOk={onOkHandler}
|
||||
centered
|
||||
visible={modal}
|
||||
confirmLoading={postApiLoading}
|
||||
>
|
||||
<Typography>{t('settings.retention_confirmation_description')}</Typography>
|
||||
</Modal>
|
||||
|
||||
<ButtonContainer>
|
||||
<Button onClick={onClickSaveHandler} disabled={isDisabled} type="primary">
|
||||
Save
|
||||
</Button>
|
||||
</ButtonContainer>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
|
||||
interface GeneralSettingsProps {
|
||||
ttlValuesPayload: GetRetentionPayload;
|
||||
getAvailableDiskPayload: GetDisksPayload;
|
||||
metricsTtlValuesPayload: GetRetentionPeriodMetricsPayload;
|
||||
tracesTtlValuesPayload: GetRetentionPeriodTracesPayload;
|
||||
metricsTtlValuesRefetch: UseQueryResult<
|
||||
ErrorResponse | SuccessResponse<GetRetentionPeriodMetricsPayload>
|
||||
>['refetch'];
|
||||
tracesTtlValuesRefetch: UseQueryResult<
|
||||
ErrorResponse | SuccessResponse<GetRetentionPeriodTracesPayload>
|
||||
>['refetch'];
|
||||
}
|
||||
|
||||
export default GeneralSettings;
|
||||
|
69
frontend/src/container/GeneralSettings/StatusMessage.tsx
Normal file
69
frontend/src/container/GeneralSettings/StatusMessage.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
import { green, orange, volcano } from '@ant-design/colors';
|
||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
import { Card, Col, Row } from 'antd';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TStatus } from 'types/api/settings/getRetention';
|
||||
|
||||
import { convertHoursValueToRelevantUnitString } from './utils';
|
||||
|
||||
function StatusMessage({
|
||||
total_retention,
|
||||
s3_retention,
|
||||
status,
|
||||
}: StatusMessageProps): JSX.Element | null {
|
||||
const { t } = useTranslation(['generalSettings']);
|
||||
|
||||
const messageColor = useMemo((): string => {
|
||||
if (status === 'success') return green[6];
|
||||
if (status === 'pending') return orange[6];
|
||||
if (status === 'failed') return volcano[6];
|
||||
return 'inherit';
|
||||
}, [status]);
|
||||
if (!status) {
|
||||
return null;
|
||||
}
|
||||
const s3Part =
|
||||
s3_retention && s3_retention !== -1
|
||||
? t('status_message.s3_part', {
|
||||
s3_retention: convertHoursValueToRelevantUnitString(s3_retention),
|
||||
})
|
||||
: '';
|
||||
const statusMessage =
|
||||
total_retention && total_retention !== -1
|
||||
? t(`status_message.${status}`, {
|
||||
total_retention: convertHoursValueToRelevantUnitString(total_retention),
|
||||
s3_part: s3Part,
|
||||
})
|
||||
: null;
|
||||
|
||||
return statusMessage ? (
|
||||
<Card
|
||||
style={{
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Row style={{ gap: '1rem', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<Col xs={1}>
|
||||
<InfoCircleOutlined style={{ fontSize: '1.5rem' }} />
|
||||
</Col>
|
||||
|
||||
<Col
|
||||
xs={22}
|
||||
style={{
|
||||
color: messageColor,
|
||||
}}
|
||||
>
|
||||
{statusMessage}
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
) : null;
|
||||
}
|
||||
|
||||
interface StatusMessageProps {
|
||||
status: TStatus;
|
||||
total_retention: number | undefined;
|
||||
s3_retention: number | undefined;
|
||||
}
|
||||
export default StatusMessage;
|
@ -5,15 +5,33 @@ import Spinner from 'components/Spinner';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQueries } from 'react-query';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { TTTLType } from 'types/api/settings/common';
|
||||
import { PayloadProps as GetRetentionPeriodAPIPayloadProps } from 'types/api/settings/getRetention';
|
||||
|
||||
import GeneralSettingsContainer from './GeneralSettings';
|
||||
|
||||
type TRetentionAPIReturn<T extends TTTLType> = Promise<
|
||||
SuccessResponse<GetRetentionPeriodAPIPayloadProps<T>> | ErrorResponse
|
||||
>;
|
||||
|
||||
function GeneralSettings(): JSX.Element {
|
||||
const { t } = useTranslation('common');
|
||||
const [getRetentionPeriodApiResponse, getDisksResponse] = useQueries([
|
||||
|
||||
const [
|
||||
getRetentionPeriodMetricsApiResponse,
|
||||
getRetentionPeriodTracesApiResponse,
|
||||
getDisksResponse,
|
||||
] = useQueries([
|
||||
{
|
||||
queryFn: getRetentionPeriodApi,
|
||||
queryKey: 'getRetentionPeriodApi',
|
||||
queryFn: (): TRetentionAPIReturn<'metrics'> =>
|
||||
getRetentionPeriodApi('metrics'),
|
||||
queryKey: 'getRetentionPeriodApiMetrics',
|
||||
},
|
||||
{
|
||||
queryFn: (): TRetentionAPIReturn<'traces'> =>
|
||||
getRetentionPeriodApi('traces'),
|
||||
queryKey: 'getRetentionPeriodApiTraces',
|
||||
},
|
||||
{
|
||||
queryFn: getDisks,
|
||||
@ -21,21 +39,38 @@ function GeneralSettings(): JSX.Element {
|
||||
},
|
||||
]);
|
||||
|
||||
if (getRetentionPeriodApiResponse.isError || getDisksResponse.isError) {
|
||||
// Error State - When RetentionPeriodMetricsApi or getDiskApi gets errored out.
|
||||
if (getRetentionPeriodMetricsApiResponse.isError || getDisksResponse.isError) {
|
||||
return (
|
||||
<Typography>
|
||||
{getRetentionPeriodApiResponse.data?.error ||
|
||||
{getRetentionPeriodMetricsApiResponse.data?.error ||
|
||||
getDisksResponse.data?.error ||
|
||||
t('something_went_wrong')}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
// Error State - When RetentionPeriodTracesApi or getDiskApi gets errored out.
|
||||
if (getRetentionPeriodTracesApiResponse.isError || getDisksResponse.isError) {
|
||||
return (
|
||||
<Typography>
|
||||
{getRetentionPeriodTracesApiResponse.data?.error ||
|
||||
getDisksResponse.data?.error ||
|
||||
t('something_went_wrong')}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
// Loading State - When Metrics, Traces and Disk API are in progress and the promise has not been resolved/reject.
|
||||
if (
|
||||
getRetentionPeriodApiResponse.isLoading ||
|
||||
getRetentionPeriodMetricsApiResponse.isLoading ||
|
||||
getDisksResponse.isLoading ||
|
||||
!getDisksResponse.data?.payload ||
|
||||
!getRetentionPeriodApiResponse.data?.payload
|
||||
!getRetentionPeriodMetricsApiResponse.data?.payload ||
|
||||
getRetentionPeriodTracesApiResponse.isLoading ||
|
||||
getDisksResponse.isLoading ||
|
||||
!getDisksResponse.data?.payload ||
|
||||
!getRetentionPeriodTracesApiResponse.data?.payload
|
||||
) {
|
||||
return <Spinner tip="Loading.." height="70vh" />;
|
||||
}
|
||||
@ -44,7 +79,10 @@ function GeneralSettings(): JSX.Element {
|
||||
<GeneralSettingsContainer
|
||||
{...{
|
||||
getAvailableDiskPayload: getDisksResponse.data?.payload,
|
||||
ttlValuesPayload: getRetentionPeriodApiResponse.data?.payload,
|
||||
metricsTtlValuesPayload: getRetentionPeriodMetricsApiResponse.data?.payload,
|
||||
metricsTtlValuesRefetch: getRetentionPeriodMetricsApiResponse.refetch,
|
||||
tracesTtlValuesPayload: getRetentionPeriodTracesApiResponse.data?.payload,
|
||||
tracesTtlValuesRefetch: getRetentionPeriodTracesApiResponse.refetch,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
@ -90,3 +90,11 @@ export const RetentionFieldLabel = styled(TypographyComponent)`
|
||||
export const RetentionFieldInputContainer = styled.div`
|
||||
display: inline-flex;
|
||||
`;
|
||||
|
||||
export const ActionItemsContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-top: 10%;
|
||||
`;
|
||||
|
@ -23,9 +23,13 @@ export const TimeUnits: ITimeUnit[] = [
|
||||
},
|
||||
];
|
||||
|
||||
interface ITimeUnitConversion {
|
||||
value: number;
|
||||
timeUnitValue: SettingPeriod;
|
||||
}
|
||||
export const convertHoursValueToRelevantUnit = (
|
||||
value: number,
|
||||
): { value: number; timeUnitValue: SettingPeriod } => {
|
||||
): ITimeUnitConversion => {
|
||||
if (value)
|
||||
for (let idx = TimeUnits.length - 1; idx >= 0; idx -= 1) {
|
||||
const timeUnit = TimeUnits[idx];
|
||||
@ -40,3 +44,13 @@ export const convertHoursValueToRelevantUnit = (
|
||||
}
|
||||
return { value, timeUnitValue: TimeUnits[0].value };
|
||||
};
|
||||
|
||||
export const convertHoursValueToRelevantUnitString = (
|
||||
value: number,
|
||||
): string => {
|
||||
if (!value) return '';
|
||||
const convertedTimeUnit = convertHoursValueToRelevantUnit(value);
|
||||
return `${convertedTimeUnit.value} ${convertedTimeUnit.timeUnitValue}${
|
||||
convertedTimeUnit.value >= 2 ? 's' : ''
|
||||
}`;
|
||||
};
|
||||
|
@ -1,53 +0,0 @@
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { Typography } from 'antd';
|
||||
import React, { useCallback } from 'react';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import {
|
||||
ToggleAddWidget,
|
||||
ToggleAddWidgetProps,
|
||||
} from 'store/actions/dashboard/toggleAddWidget';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import DashboardReducer from 'types/reducer/dashboards';
|
||||
|
||||
import { Button, Container } from './styles';
|
||||
|
||||
function AddWidget({ toggleAddWidget }: Props): JSX.Element {
|
||||
const { isAddWidget } = useSelector<AppState, DashboardReducer>(
|
||||
(state) => state.dashboards,
|
||||
);
|
||||
|
||||
const onToggleHandler = useCallback(() => {
|
||||
toggleAddWidget(true);
|
||||
}, [toggleAddWidget]);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
{!isAddWidget ? (
|
||||
<Button onClick={onToggleHandler} icon={<PlusOutlined />}>
|
||||
Add Widgets
|
||||
</Button>
|
||||
) : (
|
||||
<Typography>Click a widget icon to add it here</Typography>
|
||||
)}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
toggleAddWidget: (
|
||||
props: ToggleAddWidgetProps,
|
||||
) => (dispatch: Dispatch<AppActions>) => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
toggleAddWidget: bindActionCreators(ToggleAddWidget, dispatch),
|
||||
});
|
||||
|
||||
type Props = DispatchProps;
|
||||
|
||||
export default connect(null, mapDispatchToProps)(AddWidget);
|
@ -1,18 +0,0 @@
|
||||
import { Button as ButtonComponent } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Button = styled(ButtonComponent)`
|
||||
&&& {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Container = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
`;
|
16
frontend/src/container/GridGraphLayout/EmptyWidget/index.tsx
Normal file
16
frontend/src/container/GridGraphLayout/EmptyWidget/index.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import { Typography } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
import { Container } from './styles';
|
||||
|
||||
function EmptyWidget(): JSX.Element {
|
||||
return (
|
||||
<Container>
|
||||
<Typography.Paragraph>
|
||||
Click one of the widget types above (Time Series / Value) to add here
|
||||
</Typography.Paragraph>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default EmptyWidget;
|
@ -0,0 +1,8 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Container = styled.div`
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
`;
|
@ -1,7 +1,5 @@
|
||||
import { Button, Typography } from 'antd';
|
||||
import getQueryResult from 'api/widgets/getQuery';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ChartData } from 'chart.js';
|
||||
import { GraphOnClickHandler } from 'components/Graph';
|
||||
import Spinner from 'components/Spinner';
|
||||
import TimePreference from 'components/TimePreferenceDropDown';
|
||||
@ -17,7 +15,8 @@ import GetMaxMinTime from 'lib/getMaxMinTime';
|
||||
import GetMinMax from 'lib/getMinMax';
|
||||
import getStartAndEndTime from 'lib/getStartAndEndTime';
|
||||
import getStep from 'lib/getStep';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useQueries } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
@ -37,13 +36,6 @@ function FullView({
|
||||
GlobalReducer
|
||||
>((state) => state.globalTime);
|
||||
|
||||
const [state, setState] = useState<FullViewState>({
|
||||
error: false,
|
||||
errorMessage: '',
|
||||
loading: true,
|
||||
payload: undefined,
|
||||
});
|
||||
|
||||
const getSelectedTime = useCallback(
|
||||
() =>
|
||||
timeItems.find((e) => e.enum === (widget?.timePreferance || 'GLOBAL_TIME')),
|
||||
@ -55,107 +47,82 @@ function FullView({
|
||||
enum: widget?.timePreferance || 'GLOBAL_TIME',
|
||||
});
|
||||
|
||||
const onFetchDataHandler = useCallback(async () => {
|
||||
try {
|
||||
const maxMinTime = GetMaxMinTime({
|
||||
graphType: widget.panelTypes,
|
||||
maxTime,
|
||||
minTime,
|
||||
});
|
||||
const maxMinTime = GetMaxMinTime({
|
||||
graphType: widget.panelTypes,
|
||||
maxTime,
|
||||
minTime,
|
||||
});
|
||||
|
||||
const getMinMax = (
|
||||
time: timePreferenceType,
|
||||
): { min: string | number; max: string | number } => {
|
||||
if (time === 'GLOBAL_TIME') {
|
||||
const minMax = GetMinMax(globalSelectedTime);
|
||||
return {
|
||||
min: convertToNanoSecondsToSecond(minMax.minTime / 1000),
|
||||
max: convertToNanoSecondsToSecond(minMax.maxTime / 1000),
|
||||
};
|
||||
}
|
||||
|
||||
const minMax = getStartAndEndTime({
|
||||
type: selectedTime.enum,
|
||||
maxTime: maxMinTime.maxTime,
|
||||
minTime: maxMinTime.minTime,
|
||||
});
|
||||
return { min: parseInt(minMax.start, 10), max: parseInt(minMax.end, 10) };
|
||||
const getMinMax = (
|
||||
time: timePreferenceType,
|
||||
): { min: string | number; max: string | number } => {
|
||||
if (time === 'GLOBAL_TIME') {
|
||||
const minMax = GetMinMax(globalSelectedTime);
|
||||
return {
|
||||
min: convertToNanoSecondsToSecond(minMax.minTime / 1000),
|
||||
max: convertToNanoSecondsToSecond(minMax.maxTime / 1000),
|
||||
};
|
||||
|
||||
const queryMinMax = getMinMax(selectedTime.enum);
|
||||
const response = await Promise.all(
|
||||
widget.query
|
||||
.filter((e) => e.query.length !== 0)
|
||||
.map(async (query) => {
|
||||
const result = await getQueryResult({
|
||||
end: queryMinMax.max.toString(),
|
||||
query: query.query,
|
||||
start: queryMinMax.min.toString(),
|
||||
step: `${getStep({
|
||||
start: queryMinMax.min,
|
||||
end: queryMinMax.max,
|
||||
inputFormat: 's',
|
||||
})}`,
|
||||
});
|
||||
return {
|
||||
query: query.query,
|
||||
queryData: result,
|
||||
legend: query.legend,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
const isError = response.find((e) => e.queryData.statusCode !== 200);
|
||||
|
||||
if (isError !== undefined) {
|
||||
setState((state) => ({
|
||||
...state,
|
||||
error: true,
|
||||
errorMessage: isError.queryData.error || 'Something went wrong',
|
||||
loading: false,
|
||||
}));
|
||||
} else {
|
||||
const chartDataSet = getChartData({
|
||||
queryData: response.map((e) => ({
|
||||
query: e.query,
|
||||
legend: e.legend,
|
||||
queryData: e.queryData.payload?.result || [],
|
||||
})),
|
||||
});
|
||||
|
||||
setState((state) => ({
|
||||
...state,
|
||||
loading: false,
|
||||
payload: chartDataSet,
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
setState((state) => ({
|
||||
...state,
|
||||
error: true,
|
||||
errorMessage: (error as AxiosError).toString(),
|
||||
loading: false,
|
||||
}));
|
||||
}
|
||||
}, [widget, maxTime, minTime, selectedTime.enum, globalSelectedTime]);
|
||||
|
||||
useEffect(() => {
|
||||
onFetchDataHandler();
|
||||
}, [onFetchDataHandler]);
|
||||
const minMax = getStartAndEndTime({
|
||||
type: selectedTime.enum,
|
||||
maxTime: maxMinTime.maxTime,
|
||||
minTime: maxMinTime.minTime,
|
||||
});
|
||||
return { min: parseInt(minMax.start, 10), max: parseInt(minMax.end, 10) };
|
||||
};
|
||||
|
||||
if (state.error && !state.loading) {
|
||||
return (
|
||||
<NotFoundContainer>
|
||||
<Typography>{state.errorMessage}</Typography>
|
||||
</NotFoundContainer>
|
||||
);
|
||||
const queryMinMax = getMinMax(selectedTime.enum);
|
||||
|
||||
const queryLength = widget.query.filter((e) => e.query.length !== 0);
|
||||
|
||||
const response = useQueries(
|
||||
queryLength.map((query) => {
|
||||
return {
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
queryFn: () => {
|
||||
return getQueryResult({
|
||||
end: queryMinMax.max.toString(),
|
||||
query: query.query,
|
||||
start: queryMinMax.min.toString(),
|
||||
step: `${getStep({
|
||||
start: queryMinMax.min,
|
||||
end: queryMinMax.max,
|
||||
inputFormat: 's',
|
||||
})}`,
|
||||
});
|
||||
},
|
||||
queryHash: `${query.query}-${query.legend}-${selectedTime.enum}`,
|
||||
retryOnMount: false,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
const isError =
|
||||
response.find((e) => e?.data?.statusCode !== 200) !== undefined ||
|
||||
response.some((e) => e.isError === true);
|
||||
|
||||
const isLoading = response.some((e) => e.isLoading === true);
|
||||
|
||||
const errorMessage = response.find((e) => e.data?.error !== null)?.data?.error;
|
||||
|
||||
const data = response.map((responseOfQuery) =>
|
||||
responseOfQuery?.data?.payload?.result.map((e, index) => ({
|
||||
query: queryLength[index]?.query,
|
||||
queryData: e,
|
||||
legend: queryLength[index]?.legend,
|
||||
})),
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
return <Spinner height="100%" size="large" tip="Loading..." />;
|
||||
}
|
||||
|
||||
if (state.loading || state.payload === undefined) {
|
||||
if (isError || data === undefined || data[0] === undefined) {
|
||||
return (
|
||||
<div>
|
||||
<Spinner height="80vh" size="large" tip="Loading..." />
|
||||
</div>
|
||||
<NotFoundContainer>
|
||||
<Typography>{errorMessage}</Typography>
|
||||
</NotFoundContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@ -169,17 +136,27 @@ function FullView({
|
||||
setSelectedTime,
|
||||
}}
|
||||
/>
|
||||
<Button onClick={onFetchDataHandler} type="primary">
|
||||
<Button
|
||||
onClick={(): void => {
|
||||
response.forEach((e) => e.refetch());
|
||||
}}
|
||||
type="primary"
|
||||
>
|
||||
Refresh
|
||||
</Button>
|
||||
</TimeContainer>
|
||||
)}
|
||||
|
||||
{/* <GraphContainer> */}
|
||||
<GridGraphComponent
|
||||
{...{
|
||||
GRAPH_TYPES: widget.panelTypes,
|
||||
data: state.payload,
|
||||
data: getChartData({
|
||||
queryData: data.map((e) => ({
|
||||
query: e?.map((e) => e.query).join(' ') || '',
|
||||
queryData: e?.map((e) => e.queryData) || [],
|
||||
legend: e?.map((e) => e.legend).join('') || '',
|
||||
})),
|
||||
}),
|
||||
isStacked: widget.isStacked,
|
||||
opacity: widget.opacity,
|
||||
title: widget.title,
|
||||
@ -188,18 +165,10 @@ function FullView({
|
||||
yAxisUnit,
|
||||
}}
|
||||
/>
|
||||
{/* </GraphContainer> */}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
interface FullViewState {
|
||||
loading: boolean;
|
||||
error: boolean;
|
||||
errorMessage: string;
|
||||
payload: ChartData | undefined;
|
||||
}
|
||||
|
||||
interface FullViewProps {
|
||||
widget: Widgets;
|
||||
fullViewOptions?: boolean;
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { Typography } from 'antd';
|
||||
import getQueryResult from 'api/widgets/getQuery';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ChartData } from 'chart.js';
|
||||
import Spinner from 'components/Spinner';
|
||||
import GridGraphComponent from 'container/GridGraphComponent';
|
||||
import getChartData from 'lib/getChartData';
|
||||
import GetMaxMinTime from 'lib/getMaxMinTime';
|
||||
import GetStartAndEndTime from 'lib/getStartAndEndTime';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import isEmpty from 'lodash-es/isEmpty';
|
||||
import React, { memo, useCallback, useState } from 'react';
|
||||
import { Layout } from 'react-grid-layout';
|
||||
import { useQueries } from 'react-query';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
@ -20,6 +21,8 @@ import AppActions from 'types/actions';
|
||||
import { GlobalTime } from 'types/actions/globalTime';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
|
||||
import { LayoutProps } from '..';
|
||||
import EmptyWidget from '../EmptyWidget';
|
||||
import WidgetHeader from '../WidgetHeader';
|
||||
import FullView from './FullView';
|
||||
import { ErrorContainer, FullViewContainer, Modal } from './styles';
|
||||
@ -27,91 +30,65 @@ import { ErrorContainer, FullViewContainer, Modal } from './styles';
|
||||
function GridCardGraph({
|
||||
widget,
|
||||
deleteWidget,
|
||||
isDeleted,
|
||||
name,
|
||||
yAxisUnit,
|
||||
layout = [],
|
||||
setLayout,
|
||||
}: GridCardGraphProps): JSX.Element {
|
||||
const [state, setState] = useState<GridCardGraphState>({
|
||||
loading: true,
|
||||
errorMessage: '',
|
||||
error: false,
|
||||
payload: undefined,
|
||||
});
|
||||
const [hovered, setHovered] = useState(false);
|
||||
const [modal, setModal] = useState(false);
|
||||
const { minTime, maxTime } = useSelector<AppState, GlobalTime>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
const [deleteModal, setDeletModal] = useState(false);
|
||||
const [deleteModal, setDeleteModal] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
(async (): Promise<void> => {
|
||||
try {
|
||||
const getMaxMinTime = GetMaxMinTime({
|
||||
graphType: widget?.panelTypes,
|
||||
maxTime,
|
||||
minTime,
|
||||
});
|
||||
const getMaxMinTime = GetMaxMinTime({
|
||||
graphType: widget?.panelTypes,
|
||||
maxTime,
|
||||
minTime,
|
||||
});
|
||||
|
||||
const { start, end } = GetStartAndEndTime({
|
||||
type: widget.timePreferance,
|
||||
maxTime: getMaxMinTime.maxTime,
|
||||
minTime: getMaxMinTime.minTime,
|
||||
});
|
||||
const { start, end } = GetStartAndEndTime({
|
||||
type: widget?.timePreferance,
|
||||
maxTime: getMaxMinTime.maxTime,
|
||||
minTime: getMaxMinTime.minTime,
|
||||
});
|
||||
|
||||
const response = await Promise.all(
|
||||
widget.query
|
||||
.filter((e) => e.query.length !== 0)
|
||||
.map(async (query) => {
|
||||
const result = await getQueryResult({
|
||||
end,
|
||||
query: query.query,
|
||||
start,
|
||||
step: '60',
|
||||
});
|
||||
const queryLength = widget?.query?.filter((e) => e.query.length !== 0) || [];
|
||||
|
||||
return {
|
||||
query: query.query,
|
||||
queryData: result,
|
||||
legend: query.legend,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
const isError = response.find((e) => e.queryData.statusCode !== 200);
|
||||
|
||||
if (isError !== undefined) {
|
||||
setState((state) => ({
|
||||
...state,
|
||||
error: true,
|
||||
errorMessage: isError.queryData.error || 'Something went wrong',
|
||||
loading: false,
|
||||
}));
|
||||
} else {
|
||||
const chartDataSet = getChartData({
|
||||
queryData: response.map((e) => ({
|
||||
query: e.query,
|
||||
legend: e.legend,
|
||||
queryData: e.queryData.payload?.result || [],
|
||||
})),
|
||||
const response = useQueries(
|
||||
queryLength?.map((query) => {
|
||||
return {
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
queryFn: () => {
|
||||
return getQueryResult({
|
||||
end,
|
||||
query: query?.query,
|
||||
start,
|
||||
step: '60',
|
||||
});
|
||||
},
|
||||
queryHash: `${query?.query}-${query?.legend}-${start}-${end}`,
|
||||
retryOnMount: false,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
setState((state) => ({
|
||||
...state,
|
||||
loading: false,
|
||||
payload: chartDataSet,
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
setState((state) => ({
|
||||
...state,
|
||||
error: true,
|
||||
errorMessage: (error as AxiosError).toString(),
|
||||
loading: false,
|
||||
}));
|
||||
}
|
||||
})();
|
||||
}, [widget, maxTime, minTime]);
|
||||
const isError =
|
||||
response.find((e) => e?.data?.statusCode !== 200) !== undefined ||
|
||||
response.some((e) => e.isError === true);
|
||||
|
||||
const isLoading = response.some((e) => e.isLoading === true);
|
||||
|
||||
const errorMessage = response.find((e) => e.data?.error !== null)?.data?.error;
|
||||
|
||||
const data = response.map((responseOfQuery) =>
|
||||
responseOfQuery?.data?.payload?.result.map((e, index) => ({
|
||||
query: queryLength[index]?.query,
|
||||
queryData: e,
|
||||
legend: queryLength[index]?.legend,
|
||||
})),
|
||||
);
|
||||
|
||||
const onToggleModal = useCallback(
|
||||
(func: React.Dispatch<React.SetStateAction<boolean>>) => {
|
||||
@ -121,18 +98,20 @@ function GridCardGraph({
|
||||
);
|
||||
|
||||
const onDeleteHandler = useCallback(() => {
|
||||
deleteWidget({ widgetId: widget.id });
|
||||
onToggleModal(setDeletModal);
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
isDeleted.current = true;
|
||||
}, [deleteWidget, widget, onToggleModal, isDeleted]);
|
||||
const isEmptyWidget = widget?.id === 'empty' || isEmpty(widget);
|
||||
|
||||
const widgetId = isEmptyWidget ? layout[0].i : widget?.id;
|
||||
|
||||
deleteWidget({ widgetId, setLayout });
|
||||
onToggleModal(setDeleteModal);
|
||||
}, [deleteWidget, layout, onToggleModal, setLayout, widget]);
|
||||
|
||||
const getModals = (): JSX.Element => {
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
destroyOnClose
|
||||
onCancel={(): void => onToggleModal(setDeletModal)}
|
||||
onCancel={(): void => onToggleModal(setDeleteModal)}
|
||||
visible={deleteModal}
|
||||
title="Delete"
|
||||
height="10vh"
|
||||
@ -163,7 +142,16 @@ function GridCardGraph({
|
||||
);
|
||||
};
|
||||
|
||||
if (state.error) {
|
||||
const isEmptyLayout = widget?.id === 'empty' || isEmpty(widget);
|
||||
|
||||
if (isLoading) {
|
||||
return <Spinner height="20vh" tip="Loading..." />;
|
||||
}
|
||||
|
||||
if (
|
||||
(isError || data === undefined || data[0] === undefined) &&
|
||||
!isEmptyLayout
|
||||
) {
|
||||
return (
|
||||
<>
|
||||
{getModals()}
|
||||
@ -172,17 +160,21 @@ function GridCardGraph({
|
||||
title={widget?.title}
|
||||
widget={widget}
|
||||
onView={(): void => onToggleModal(setModal)}
|
||||
onDelete={(): void => onToggleModal(setDeletModal)}
|
||||
onDelete={(): void => onToggleModal(setDeleteModal)}
|
||||
/>
|
||||
|
||||
<ErrorContainer>{state.errorMessage}</ErrorContainer>
|
||||
<ErrorContainer>{errorMessage}</ErrorContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (state.loading === true || state.payload === undefined) {
|
||||
return <Spinner height="20vh" tip="Loading..." />;
|
||||
}
|
||||
const chartData = getChartData({
|
||||
queryData: data.map((e) => ({
|
||||
query: e?.map((e) => e.query).join(' ') || '',
|
||||
queryData: e?.map((e) => e.queryData) || [],
|
||||
legend: e?.map((e) => e.legend).join('') || '',
|
||||
})),
|
||||
});
|
||||
|
||||
return (
|
||||
<span
|
||||
@ -199,38 +191,37 @@ function GridCardGraph({
|
||||
setHovered(false);
|
||||
}}
|
||||
>
|
||||
<WidgetHeader
|
||||
parentHover={hovered}
|
||||
title={widget.title}
|
||||
widget={widget}
|
||||
onView={(): void => onToggleModal(setModal)}
|
||||
onDelete={(): void => onToggleModal(setDeletModal)}
|
||||
/>
|
||||
{!isEmptyLayout && (
|
||||
<WidgetHeader
|
||||
parentHover={hovered}
|
||||
title={widget?.title}
|
||||
widget={widget}
|
||||
onView={(): void => onToggleModal(setModal)}
|
||||
onDelete={(): void => onToggleModal(setDeleteModal)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{getModals()}
|
||||
{!isEmptyLayout && getModals()}
|
||||
|
||||
<GridGraphComponent
|
||||
{...{
|
||||
GRAPH_TYPES: widget.panelTypes,
|
||||
data: state.payload,
|
||||
isStacked: widget.isStacked,
|
||||
opacity: widget.opacity,
|
||||
title: ' ', // empty title to accommodate absolutely positioned widget header
|
||||
name,
|
||||
yAxisUnit,
|
||||
}}
|
||||
/>
|
||||
{!isEmpty(widget) && (
|
||||
<GridGraphComponent
|
||||
{...{
|
||||
GRAPH_TYPES: widget.panelTypes,
|
||||
data: chartData,
|
||||
isStacked: widget.isStacked,
|
||||
opacity: widget.opacity,
|
||||
title: ' ', // empty title to accommodate absolutely positioned widget header
|
||||
name,
|
||||
yAxisUnit,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isEmptyLayout && <EmptyWidget />}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
interface GridCardGraphState {
|
||||
loading: boolean;
|
||||
error: boolean;
|
||||
errorMessage: string;
|
||||
payload: ChartData | undefined;
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
deleteWidget: ({
|
||||
widgetId,
|
||||
@ -239,9 +230,12 @@ interface DispatchProps {
|
||||
|
||||
interface GridCardGraphProps extends DispatchProps {
|
||||
widget: Widgets;
|
||||
isDeleted: React.MutableRefObject<boolean>;
|
||||
name: string;
|
||||
yAxisUnit: string | undefined;
|
||||
// eslint-disable-next-line react/require-default-props
|
||||
layout?: Layout[];
|
||||
// eslint-disable-next-line react/require-default-props
|
||||
setLayout?: React.Dispatch<React.SetStateAction<LayoutProps[]>>;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
@ -250,4 +244,4 @@ const mapDispatchToProps = (
|
||||
deleteWidget: bindActionCreators(DeleteWidget, dispatch),
|
||||
});
|
||||
|
||||
export default connect(null, mapDispatchToProps)(GridCardGraph);
|
||||
export default connect(null, mapDispatchToProps)(memo(GridCardGraph));
|
||||
|
106
frontend/src/container/GridGraphLayout/GraphLayout.tsx
Normal file
106
frontend/src/container/GridGraphLayout/GraphLayout.tsx
Normal file
@ -0,0 +1,106 @@
|
||||
import { PlusOutlined, SaveFilled } from '@ant-design/icons';
|
||||
import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import React from 'react';
|
||||
import { Layout } from 'react-grid-layout';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import { LayoutProps, State } from '.';
|
||||
import {
|
||||
Button,
|
||||
ButtonContainer,
|
||||
Card,
|
||||
CardContainer,
|
||||
ReactGridLayout,
|
||||
} from './styles';
|
||||
|
||||
function GraphLayout({
|
||||
layouts,
|
||||
saveLayoutState,
|
||||
onLayoutSaveHandler,
|
||||
addPanelLoading,
|
||||
onAddPanelHandler,
|
||||
onLayoutChangeHandler,
|
||||
widgets,
|
||||
setLayout,
|
||||
}: GraphLayoutProps): JSX.Element {
|
||||
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
const { isDarkMode } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
|
||||
const [saveLayoutPermission, addPanelPermission] = useComponentPermission(
|
||||
['save_layout', 'add_panel'],
|
||||
role,
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ButtonContainer>
|
||||
{saveLayoutPermission && (
|
||||
<Button
|
||||
loading={saveLayoutState.loading}
|
||||
onClick={(): Promise<void> => onLayoutSaveHandler(layouts)}
|
||||
icon={<SaveFilled />}
|
||||
danger={saveLayoutState.error}
|
||||
>
|
||||
Save Layout
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{addPanelPermission && (
|
||||
<Button
|
||||
loading={addPanelLoading}
|
||||
disabled={addPanelLoading}
|
||||
onClick={onAddPanelHandler}
|
||||
icon={<PlusOutlined />}
|
||||
>
|
||||
Add Panel
|
||||
</Button>
|
||||
)}
|
||||
</ButtonContainer>
|
||||
|
||||
<ReactGridLayout
|
||||
cols={12}
|
||||
rowHeight={100}
|
||||
autoSize
|
||||
width={100}
|
||||
isDraggable={addPanelPermission}
|
||||
isDroppable={addPanelPermission}
|
||||
isResizable={addPanelPermission}
|
||||
useCSSTransforms
|
||||
allowOverlap={false}
|
||||
onLayoutChange={onLayoutChangeHandler}
|
||||
>
|
||||
{layouts.map(({ Component, ...rest }) => {
|
||||
const currentWidget = (widgets || [])?.find((e) => e.id === rest.i);
|
||||
|
||||
return (
|
||||
<CardContainer
|
||||
isDarkMode={isDarkMode}
|
||||
key={currentWidget?.id || 'empty'} // don't change this key
|
||||
data-grid={rest}
|
||||
>
|
||||
<Card>
|
||||
<Component setLayout={setLayout} />
|
||||
</Card>
|
||||
</CardContainer>
|
||||
);
|
||||
})}
|
||||
</ReactGridLayout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
interface GraphLayoutProps {
|
||||
layouts: LayoutProps[];
|
||||
saveLayoutState: State;
|
||||
onLayoutSaveHandler: (layout: Layout[]) => Promise<void>;
|
||||
addPanelLoading: boolean;
|
||||
onAddPanelHandler: VoidFunction;
|
||||
onLayoutChangeHandler: (layout: Layout[]) => Promise<void>;
|
||||
widgets: Widgets[] | undefined;
|
||||
setLayout: React.Dispatch<React.SetStateAction<LayoutProps[]>>;
|
||||
}
|
||||
|
||||
export default GraphLayout;
|
@ -104,7 +104,7 @@ function WidgetHeader({
|
||||
overlay={menu}
|
||||
trigger={['click']}
|
||||
overlayStyle={{ minWidth: 100 }}
|
||||
placement="bottomCenter"
|
||||
placement="bottom"
|
||||
>
|
||||
<HeaderContainer
|
||||
onMouseOver={(): void => setLocalHover(true)}
|
||||
|
@ -1,277 +1,297 @@
|
||||
/* eslint-disable react/no-unstable-nested-components */
|
||||
import { SaveFilled } from '@ant-design/icons';
|
||||
import { notification } from 'antd';
|
||||
import updateDashboardApi from 'api/dashboard/update';
|
||||
import Spinner from 'components/Spinner';
|
||||
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
|
||||
import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import React, { memo, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { Layout } from 'react-grid-layout';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { connect, useDispatch, useSelector } from 'react-redux';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import {
|
||||
ToggleAddWidget,
|
||||
ToggleAddWidgetProps,
|
||||
} from 'store/actions/dashboard/toggleAddWidget';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { UPDATE_DASHBOARD } from 'types/actions/dashboard';
|
||||
import { Dashboard, Widgets } from 'types/api/dashboard/getAll';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
import DashboardReducer from 'types/reducer/dashboards';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import AddWidget from './AddWidget';
|
||||
import Graph from './Graph';
|
||||
import {
|
||||
Button,
|
||||
ButtonContainer,
|
||||
Card,
|
||||
CardContainer,
|
||||
ReactGridLayout,
|
||||
} from './styles';
|
||||
import { updateDashboard } from './utils';
|
||||
import GraphLayoutContainer from './GraphLayout';
|
||||
import { UpdateDashboard } from './utils';
|
||||
|
||||
function GridGraph(): JSX.Element {
|
||||
const { dashboards, loading } = useSelector<AppState, DashboardReducer>(
|
||||
export const getPreLayouts = (
|
||||
widgets: Widgets[] | undefined,
|
||||
layout: Layout[],
|
||||
): LayoutProps[] =>
|
||||
layout.map((e, index) => ({
|
||||
...e,
|
||||
Component: ({ setLayout }: ComponentProps): JSX.Element => {
|
||||
const widget = widgets?.find((widget) => widget.id === e.i);
|
||||
|
||||
return (
|
||||
<Graph
|
||||
name={e.i + index}
|
||||
widget={widget as Widgets}
|
||||
yAxisUnit={widget?.yAxisUnit}
|
||||
layout={layout}
|
||||
setLayout={setLayout}
|
||||
/>
|
||||
);
|
||||
},
|
||||
}));
|
||||
|
||||
function GridGraph(props: Props): JSX.Element {
|
||||
const { toggleAddWidget } = props;
|
||||
const [addPanelLoading, setAddPanelLoading] = useState(false);
|
||||
const { t } = useTranslation(['common']);
|
||||
const { dashboards, isAddWidget } = useSelector<AppState, DashboardReducer>(
|
||||
(state) => state.dashboards,
|
||||
);
|
||||
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
|
||||
const [saveLayoutPermission] = useComponentPermission(['save_layout'], role);
|
||||
const [saveLayoutState, setSaveLayoutState] = useState<State>({
|
||||
loading: false,
|
||||
error: false,
|
||||
errorMessage: '',
|
||||
payload: [],
|
||||
});
|
||||
|
||||
const [selectedDashboard] = dashboards;
|
||||
const { data } = selectedDashboard;
|
||||
const { widgets } = data;
|
||||
const [layouts, setLayout] = useState<LayoutProps[]>([]);
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
|
||||
const AddWidgetWrapper = useCallback(() => <AddWidget />, []);
|
||||
|
||||
const isMounted = useRef(true);
|
||||
const isDeleted = useRef(false);
|
||||
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
|
||||
const [saveLayout] = useComponentPermission(['save_layout'], role);
|
||||
|
||||
const getPreLayouts: () => LayoutProps[] = useCallback(() => {
|
||||
if (widgets === undefined) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// when the layout is not present
|
||||
if (data.layout === undefined) {
|
||||
return widgets.map((e, index) => {
|
||||
return {
|
||||
h: 2,
|
||||
w: 6,
|
||||
y: Infinity,
|
||||
i: (index + 1).toString(),
|
||||
x: (index % 2) * 6,
|
||||
Component: (): JSX.Element => (
|
||||
<Graph
|
||||
name={`${e.id + index}non-expanded`}
|
||||
isDeleted={isDeleted}
|
||||
widget={widgets[index]}
|
||||
yAxisUnit={e.yAxisUnit}
|
||||
/>
|
||||
),
|
||||
};
|
||||
});
|
||||
}
|
||||
return data.layout
|
||||
.filter((_, index) => widgets[index])
|
||||
.map((e, index) => ({
|
||||
...e,
|
||||
Component: (): JSX.Element => {
|
||||
if (widgets[index]) {
|
||||
return (
|
||||
<Graph
|
||||
name={e.i + index}
|
||||
isDeleted={isDeleted}
|
||||
widget={widgets[index]}
|
||||
yAxisUnit={widgets[index].yAxisUnit}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <div />;
|
||||
},
|
||||
}));
|
||||
}, [widgets, data.layout]);
|
||||
const [layouts, setLayout] = useState<LayoutProps[]>(
|
||||
getPreLayouts(widgets, selectedDashboard.data.layout || []),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
loading === false &&
|
||||
(isMounted.current === true || isDeleted.current === true)
|
||||
) {
|
||||
const preLayouts = getPreLayouts();
|
||||
setLayout(() => {
|
||||
const getX = (): number => {
|
||||
if (preLayouts && preLayouts?.length > 0) {
|
||||
const last = preLayouts[(preLayouts?.length || 0) - 1];
|
||||
(async (): Promise<void> => {
|
||||
if (!isAddWidget) {
|
||||
const isEmptyLayoutPresent = layouts.find((e) => e.i === 'empty');
|
||||
if (isEmptyLayoutPresent) {
|
||||
// non empty layout
|
||||
const updatedLayout = layouts.filter((e) => e.i !== 'empty');
|
||||
// non widget
|
||||
const updatedWidget = widgets?.filter((e) => e.id !== 'empty');
|
||||
setLayout(updatedLayout);
|
||||
|
||||
return (last.w + last.x) % 12;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
const updatedDashboard: Dashboard = {
|
||||
...selectedDashboard,
|
||||
data: {
|
||||
...selectedDashboard.data,
|
||||
layout: updatedLayout,
|
||||
widgets: updatedWidget,
|
||||
},
|
||||
};
|
||||
|
||||
return [
|
||||
...preLayouts,
|
||||
{
|
||||
i: (preLayouts.length + 1).toString(),
|
||||
x: getX(),
|
||||
y: Infinity,
|
||||
w: 6,
|
||||
h: 2,
|
||||
Component: AddWidgetWrapper,
|
||||
maxW: 6,
|
||||
isDraggable: false,
|
||||
isResizable: false,
|
||||
isBounded: true,
|
||||
},
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
return (): void => {
|
||||
isMounted.current = false;
|
||||
};
|
||||
}, [widgets, layouts.length, AddWidgetWrapper, loading, getPreLayouts]);
|
||||
|
||||
const onDropHandler = useCallback(
|
||||
async (allLayouts: Layout[], currentLayout: Layout, event: DragEvent) => {
|
||||
event.preventDefault();
|
||||
if (event.dataTransfer) {
|
||||
try {
|
||||
const graphType = event.dataTransfer.getData('text') as GRAPH_TYPES;
|
||||
const generateWidgetId = v4();
|
||||
|
||||
await updateDashboard({
|
||||
data,
|
||||
generateWidgetId,
|
||||
graphType,
|
||||
selectedDashboard,
|
||||
layout: allLayouts
|
||||
.map((e, index) => ({
|
||||
...e,
|
||||
i: index.toString(),
|
||||
// when a new element drops
|
||||
w: e.i === '__dropping-elem__' ? 6 : e.w,
|
||||
h: e.i === '__dropping-elem__' ? 2 : e.h,
|
||||
}))
|
||||
// removing add widgets layout config
|
||||
.filter((e) => e.maxW === undefined),
|
||||
await updateDashboardApi({
|
||||
data: updatedDashboard.data,
|
||||
uuid: updatedDashboard.uuid,
|
||||
});
|
||||
} catch (error) {
|
||||
notification.error({
|
||||
message:
|
||||
error instanceof Error ? error.toString() : 'Something went wrong',
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_DASHBOARD,
|
||||
payload: updatedDashboard,
|
||||
});
|
||||
}
|
||||
}
|
||||
})();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const onLayoutSaveHandler = useCallback(
|
||||
async (layout: Layout[]) => {
|
||||
try {
|
||||
setSaveLayoutState((state) => ({
|
||||
...state,
|
||||
error: false,
|
||||
errorMessage: '',
|
||||
loading: true,
|
||||
}));
|
||||
|
||||
// Save layout only when users has the has the permission to do so.
|
||||
if (saveLayoutPermission) {
|
||||
const response = await updateDashboardApi({
|
||||
data: {
|
||||
title: data.title,
|
||||
description: data.description,
|
||||
name: data.name,
|
||||
tags: data.tags,
|
||||
widgets: data.widgets,
|
||||
layout,
|
||||
},
|
||||
uuid: selectedDashboard.uuid,
|
||||
});
|
||||
if (response.statusCode === 200) {
|
||||
setSaveLayoutState((state) => ({
|
||||
...state,
|
||||
error: false,
|
||||
errorMessage: '',
|
||||
loading: false,
|
||||
}));
|
||||
} else {
|
||||
setSaveLayoutState((state) => ({
|
||||
...state,
|
||||
error: true,
|
||||
errorMessage: response.error || 'Something went wrong',
|
||||
loading: false,
|
||||
}));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
[data, selectedDashboard],
|
||||
[
|
||||
data.description,
|
||||
data.name,
|
||||
data.tags,
|
||||
data.title,
|
||||
data.widgets,
|
||||
saveLayoutPermission,
|
||||
selectedDashboard.uuid,
|
||||
],
|
||||
);
|
||||
|
||||
const onLayoutSaveHandler = async (): Promise<void> => {
|
||||
setSaveLayoutState((state) => ({
|
||||
...state,
|
||||
error: false,
|
||||
errorMessage: '',
|
||||
loading: true,
|
||||
}));
|
||||
const setLayoutFunction = useCallback(
|
||||
(layout: Layout[]) => {
|
||||
setLayout(
|
||||
layout.map((e) => {
|
||||
const currentWidget =
|
||||
widgets?.find((widget) => widget.id === e.i) || ({} as Widgets);
|
||||
|
||||
const response = await updateDashboardApi({
|
||||
data: {
|
||||
title: data.title,
|
||||
description: data.description,
|
||||
name: data.name,
|
||||
tags: data.tags,
|
||||
widgets: data.widgets,
|
||||
layout: saveLayoutState.payload.filter((e) => e.maxW === undefined),
|
||||
},
|
||||
uuid: selectedDashboard.uuid,
|
||||
});
|
||||
if (response.statusCode === 200) {
|
||||
setSaveLayoutState((state) => ({
|
||||
...state,
|
||||
error: false,
|
||||
errorMessage: '',
|
||||
loading: false,
|
||||
}));
|
||||
} else {
|
||||
setSaveLayoutState((state) => ({
|
||||
...state,
|
||||
error: true,
|
||||
errorMessage: response.error || 'Something went wrong',
|
||||
loading: false,
|
||||
}));
|
||||
return {
|
||||
...e,
|
||||
Component: (): JSX.Element => (
|
||||
<Graph
|
||||
name={currentWidget.id}
|
||||
widget={currentWidget}
|
||||
yAxisUnit={currentWidget?.yAxisUnit}
|
||||
layout={layout}
|
||||
setLayout={setLayout}
|
||||
/>
|
||||
),
|
||||
};
|
||||
}),
|
||||
);
|
||||
},
|
||||
[widgets],
|
||||
);
|
||||
|
||||
const onEmptyWidgetHandler = useCallback(async () => {
|
||||
try {
|
||||
const id = 'empty';
|
||||
|
||||
const layout = [
|
||||
{
|
||||
i: id,
|
||||
w: 6,
|
||||
x: 0,
|
||||
h: 2,
|
||||
y: 0,
|
||||
},
|
||||
...(data.layout || []),
|
||||
];
|
||||
|
||||
await UpdateDashboard({
|
||||
data,
|
||||
generateWidgetId: id,
|
||||
graphType: 'EMPTY_WIDGET',
|
||||
selectedDashboard,
|
||||
layout,
|
||||
isRedirected: false,
|
||||
});
|
||||
|
||||
setLayoutFunction(layout);
|
||||
} catch (error) {
|
||||
notification.error({
|
||||
message: error instanceof Error ? error.toString() : 'Something went wrong',
|
||||
});
|
||||
}
|
||||
}, [data, selectedDashboard, setLayoutFunction]);
|
||||
|
||||
const onLayoutChangeHandler = async (layout: Layout[]): Promise<void> => {
|
||||
setLayoutFunction(layout);
|
||||
|
||||
await onLayoutSaveHandler(layout);
|
||||
};
|
||||
|
||||
const onLayoutChangeHandler = (layout: Layout[]): void => {
|
||||
setSaveLayoutState({
|
||||
loading: false,
|
||||
error: false,
|
||||
errorMessage: '',
|
||||
payload: layout,
|
||||
});
|
||||
};
|
||||
const onAddPanelHandler = useCallback(() => {
|
||||
try {
|
||||
setAddPanelLoading(true);
|
||||
const isEmptyLayoutPresent =
|
||||
layouts.find((e) => e.i === 'empty') !== undefined;
|
||||
|
||||
if (layouts.length === 0) {
|
||||
return <Spinner height="40vh" size="large" tip="Loading..." />;
|
||||
}
|
||||
if (!isEmptyLayoutPresent) {
|
||||
onEmptyWidgetHandler()
|
||||
.then(() => {
|
||||
setAddPanelLoading(false);
|
||||
toggleAddWidget(true);
|
||||
})
|
||||
.catch(() => {
|
||||
notification.error(t('something_went_wrong'));
|
||||
});
|
||||
} else {
|
||||
toggleAddWidget(true);
|
||||
setAddPanelLoading(false);
|
||||
}
|
||||
} catch (error) {
|
||||
if (typeof error === 'string') {
|
||||
notification.error({
|
||||
message: error || t('something_went_wrong'),
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [layouts, onEmptyWidgetHandler, t, toggleAddWidget]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{saveLayout && (
|
||||
<ButtonContainer>
|
||||
<Button
|
||||
loading={saveLayoutState.loading}
|
||||
onClick={onLayoutSaveHandler}
|
||||
icon={<SaveFilled />}
|
||||
danger={saveLayoutState.error}
|
||||
>
|
||||
Save Layout
|
||||
</Button>
|
||||
</ButtonContainer>
|
||||
)}
|
||||
|
||||
<ReactGridLayout
|
||||
isResizable
|
||||
isDraggable
|
||||
cols={12}
|
||||
rowHeight={100}
|
||||
autoSize
|
||||
width={100}
|
||||
isDroppable
|
||||
useCSSTransforms
|
||||
onDrop={onDropHandler}
|
||||
onLayoutChange={onLayoutChangeHandler}
|
||||
>
|
||||
{layouts.map(({ Component, ...rest }, index) => {
|
||||
const widget = (widgets || [])[index] || {};
|
||||
|
||||
const type = widget?.panelTypes || 'TIME_SERIES';
|
||||
|
||||
const isQueryType = type === 'VALUE';
|
||||
|
||||
return (
|
||||
<CardContainer key={rest.i} data-grid={rest}>
|
||||
<Card isQueryType={isQueryType}>
|
||||
<Component />
|
||||
</Card>
|
||||
</CardContainer>
|
||||
);
|
||||
})}
|
||||
</ReactGridLayout>
|
||||
</>
|
||||
<GraphLayoutContainer
|
||||
{...{
|
||||
addPanelLoading,
|
||||
layouts,
|
||||
onAddPanelHandler,
|
||||
onLayoutChangeHandler,
|
||||
onLayoutSaveHandler,
|
||||
saveLayoutState,
|
||||
widgets,
|
||||
setLayout,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
interface LayoutProps extends Layout {
|
||||
Component: () => JSX.Element;
|
||||
interface ComponentProps {
|
||||
setLayout: React.Dispatch<React.SetStateAction<LayoutProps[]>>;
|
||||
}
|
||||
|
||||
interface State {
|
||||
export interface LayoutProps extends Layout {
|
||||
Component: (props: ComponentProps) => JSX.Element;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
loading: boolean;
|
||||
error: boolean;
|
||||
payload: Layout[];
|
||||
errorMessage: string;
|
||||
}
|
||||
|
||||
export default memo(GridGraph);
|
||||
interface DispatchProps {
|
||||
toggleAddWidget: (
|
||||
props: ToggleAddWidgetProps,
|
||||
) => (dispatch: Dispatch<AppActions>) => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
toggleAddWidget: bindActionCreators(ToggleAddWidget, dispatch),
|
||||
});
|
||||
|
||||
type Props = DispatchProps;
|
||||
|
||||
export default connect(null, mapDispatchToProps)(GridGraph);
|
||||
|
@ -1,13 +1,11 @@
|
||||
import { Button as ButtonComponent, Card as CardComponent } from 'antd';
|
||||
import { Button as ButtonComponent, Card as CardComponent, Space } from 'antd';
|
||||
import { StyledCSS } from 'container/GantChart/Trace/styles';
|
||||
import RGL, { WidthProvider } from 'react-grid-layout';
|
||||
import styled from 'styled-components';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
const ReactGridLayoutComponent = WidthProvider(RGL);
|
||||
|
||||
interface Props {
|
||||
isQueryType: boolean;
|
||||
}
|
||||
export const Card = styled(CardComponent)<Props>`
|
||||
export const Card = styled(CardComponent)`
|
||||
&&& {
|
||||
height: 100%;
|
||||
}
|
||||
@ -18,20 +16,34 @@ export const Card = styled(CardComponent)<Props>`
|
||||
}
|
||||
`;
|
||||
|
||||
export const CardContainer = styled.div`
|
||||
.react-resizable-handle {
|
||||
position: absolute;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pg08IS0tIEdlbmVyYXRvcjogQWRvYmUgRmlyZXdvcmtzIENTNiwgRXhwb3J0IFNWRyBFeHRlbnNpb24gYnkgQWFyb24gQmVhbGwgKGh0dHA6Ly9maXJld29ya3MuYWJlYWxsLmNvbSkgLiBWZXJzaW9uOiAwLjYuMSAgLS0+DTwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DTxzdmcgaWQ9IlVudGl0bGVkLVBhZ2UlMjAxIiB2aWV3Qm94PSIwIDAgNiA2IiBzdHlsZT0iYmFja2dyb3VuZC1jb2xvcjojZmZmZmZmMDAiIHZlcnNpb249IjEuMSINCXhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbDpzcGFjZT0icHJlc2VydmUiDQl4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjZweCIgaGVpZ2h0PSI2cHgiDT4NCTxnIG9wYWNpdHk9IjAuMzAyIj4NCQk8cGF0aCBkPSJNIDYgNiBMIDAgNiBMIDAgNC4yIEwgNCA0LjIgTCA0LjIgNC4yIEwgNC4yIDAgTCA2IDAgTCA2IDYgTCA2IDYgWiIgZmlsbD0iIzAwMDAwMCIvPg0JPC9nPg08L3N2Zz4=');
|
||||
background-position: bottom right;
|
||||
padding: 0 3px 3px 0;
|
||||
background-repeat: no-repeat;
|
||||
background-origin: content-box;
|
||||
box-sizing: border-box;
|
||||
cursor: se-resize;
|
||||
interface Props {
|
||||
isDarkMode: boolean;
|
||||
}
|
||||
|
||||
export const CardContainer = styled.div<Props>`
|
||||
:hover {
|
||||
.react-resizable-handle {
|
||||
position: absolute;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background-position: bottom right;
|
||||
padding: 0 3px 3px 0;
|
||||
background-repeat: no-repeat;
|
||||
background-origin: content-box;
|
||||
box-sizing: border-box;
|
||||
cursor: se-resize;
|
||||
|
||||
${({ isDarkMode }): StyledCSS => {
|
||||
const uri = `data:image/svg+xml,%3Csvg viewBox='0 0 6 6' style='background-color:%23ffffff00' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' xml:space='preserve' x='0px' y='0px' width='6px' height='6px'%0A%3E%3Cg opacity='0.302'%3E%3Cpath d='M 6 6 L 0 6 L 0 4.2 L 4 4.2 L 4.2 4.2 L 4.2 0 L 6 0 L 6 6 L 6 6 Z' fill='${
|
||||
isDarkMode ? 'white' : 'grey'
|
||||
}'/%3E%3C/g%3E%3C/svg%3E`;
|
||||
|
||||
return css`
|
||||
background-image: ${(): string => `url("${uri}")`};
|
||||
`;
|
||||
}}
|
||||
}
|
||||
`;
|
||||
|
||||
@ -39,9 +51,22 @@ export const ReactGridLayout = styled(ReactGridLayoutComponent)`
|
||||
border: 1px solid #434343;
|
||||
margin-top: 1rem;
|
||||
position: relative;
|
||||
min-height: 40vh;
|
||||
|
||||
.react-grid-item.react-grid-placeholder {
|
||||
background: grey;
|
||||
opacity: 0.2;
|
||||
transition-duration: 100ms;
|
||||
z-index: 2;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
-o-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export const ButtonContainer = styled.div`
|
||||
export const ButtonContainer = styled(Space)`
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
margin-top: 1rem;
|
||||
|
@ -1,18 +1,20 @@
|
||||
import { notification } from 'antd';
|
||||
import updateDashboardApi from 'api/dashboard/update';
|
||||
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
|
||||
import history from 'lib/history';
|
||||
import { Layout } from 'react-grid-layout';
|
||||
import store from 'store';
|
||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||
|
||||
export const updateDashboard = async ({
|
||||
export const UpdateDashboard = async ({
|
||||
data,
|
||||
graphType,
|
||||
generateWidgetId,
|
||||
layout,
|
||||
selectedDashboard,
|
||||
}: UpdateDashboardProps): Promise<void> => {
|
||||
const response = await updateDashboardApi({
|
||||
isRedirected,
|
||||
}: UpdateDashboardProps): Promise<Dashboard | undefined> => {
|
||||
const updatedSelectedDashboard: Dashboard = {
|
||||
...selectedDashboard,
|
||||
data: {
|
||||
title: data.title,
|
||||
description: data.description,
|
||||
@ -46,17 +48,27 @@ export const updateDashboard = async ({
|
||||
layout,
|
||||
},
|
||||
uuid: selectedDashboard.uuid,
|
||||
});
|
||||
};
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
history.push(
|
||||
`${history.location.pathname}/new?graphType=${graphType}&widgetId=${generateWidgetId}`,
|
||||
);
|
||||
} else {
|
||||
const response = await updateDashboardApi(updatedSelectedDashboard);
|
||||
|
||||
if (response.payload) {
|
||||
store.dispatch({
|
||||
type: 'UPDATE_DASHBOARD',
|
||||
payload: response.payload,
|
||||
});
|
||||
}
|
||||
|
||||
if (isRedirected) {
|
||||
if (response.statusCode === 200) {
|
||||
return response.payload;
|
||||
}
|
||||
notification.error({
|
||||
message: response.error || 'Something went wrong',
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
interface UpdateDashboardProps {
|
||||
@ -65,4 +77,5 @@ interface UpdateDashboardProps {
|
||||
generateWidgetId: string;
|
||||
layout: Layout[];
|
||||
selectedDashboard: Dashboard;
|
||||
isRedirected: boolean;
|
||||
}
|
||||
|
@ -27,12 +27,7 @@ import AppReducer from 'types/reducer/app';
|
||||
|
||||
import CurrentOrganization from './CurrentOrganization';
|
||||
import SignedInAS from './SignedInAs';
|
||||
import {
|
||||
Container,
|
||||
LogoutContainer,
|
||||
MenuContainer,
|
||||
ToggleButton,
|
||||
} from './styles';
|
||||
import { Container, LogoutContainer, ToggleButton } from './styles';
|
||||
|
||||
function HeaderContainer({ toggleDarkMode }: Props): JSX.Element {
|
||||
const { isDarkMode, user, currentVersion } = useSelector<AppState, AppReducer>(
|
||||
@ -70,7 +65,7 @@ function HeaderContainer({ toggleDarkMode }: Props): JSX.Element {
|
||||
};
|
||||
|
||||
const menu = (
|
||||
<MenuContainer>
|
||||
<Menu style={{ padding: '1rem' }}>
|
||||
<Menu.ItemGroup>
|
||||
<SignedInAS />
|
||||
<Divider />
|
||||
@ -92,7 +87,7 @@ function HeaderContainer({ toggleDarkMode }: Props): JSX.Element {
|
||||
</div>
|
||||
</LogoutContainer>
|
||||
</Menu.ItemGroup>
|
||||
</MenuContainer>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Menu, Switch, Typography } from 'antd';
|
||||
import { Switch, Typography } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Container = styled.div`
|
||||
@ -48,10 +48,6 @@ export const LogoutContainer = styled.div`
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
export const MenuContainer = styled(Menu)`
|
||||
padding: 1rem;
|
||||
`;
|
||||
|
||||
export interface DarkModeProps {
|
||||
checked?: boolean;
|
||||
defaultChecked?: boolean;
|
||||
|
@ -1,7 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
function IsRouteAccessible(): JSX.Element {
|
||||
return <div>asd</div>;
|
||||
}
|
||||
|
||||
export default IsRouteAccessible;
|
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