diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 35b2182350..f9096698cc 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -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 diff --git a/.github/workflows/create-issue-on-pr-merge.yml b/.github/workflows/create-issue-on-pr-merge.yml new file mode 100644 index 0000000000..12910628ed --- /dev/null +++ b/.github/workflows/create-issue-on-pr-merge.yml @@ -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 diff --git a/.github/workflows/playwright.yaml b/.github/workflows/playwright.yaml new file mode 100644 index 0000000000..be8108e6ef --- /dev/null +++ b/.github/workflows/playwright.yaml @@ -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 }} diff --git a/.gitignore b/.gitignore index e876823dcd..0c5487f5f5 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6205d85884..54ff60451b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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 diff --git a/Makefile b/Makefile index 8dc880a971..ac93167fa7 100644 --- a/Makefile +++ b/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"; diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000..000076fe18 --- /dev/null +++ b/SECURITY.md @@ -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 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. 🙇 diff --git a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml index d0a22e95e6..8806533caa 100644 --- a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml +++ b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml @@ -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 diff --git a/deploy/docker-swarm/clickhouse-setup/otel-collector-config.yaml b/deploy/docker-swarm/clickhouse-setup/otel-collector-config.yaml index 9d34907164..5f65e6eb5c 100644 --- a/deploy/docker-swarm/clickhouse-setup/otel-collector-config.yaml +++ b/deploy/docker-swarm/clickhouse-setup/otel-collector-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] diff --git a/deploy/docker-swarm/common/nginx-config.conf b/deploy/docker-swarm/common/nginx-config.conf index 55a780e0f3..3153dff62f 100644 --- a/deploy/docker-swarm/common/nginx-config.conf +++ b/deploy/docker-swarm/common/nginx-config.conf @@ -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; } diff --git a/deploy/docker/clickhouse-setup/docker-compose.arm.yaml b/deploy/docker/clickhouse-setup/docker-compose.arm.yaml index bb5dbb5207..e4197cfbc3 100644 --- a/deploy/docker/clickhouse-setup/docker-compose.arm.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose.arm.yaml @@ -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 diff --git a/deploy/docker/clickhouse-setup/docker-compose.yaml b/deploy/docker/clickhouse-setup/docker-compose.yaml index 7f484c913d..e6d2de2f3c 100644 --- a/deploy/docker/clickhouse-setup/docker-compose.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose.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 diff --git a/deploy/docker/clickhouse-setup/otel-collector-config.yaml b/deploy/docker/clickhouse-setup/otel-collector-config.yaml index c59a8f0e87..bcf7ce58ce 100644 --- a/deploy/docker/clickhouse-setup/otel-collector-config.yaml +++ b/deploy/docker/clickhouse-setup/otel-collector-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] diff --git a/deploy/docker/common/nginx-config.conf b/deploy/docker/common/nginx-config.conf index 3444de7808..705656bb6e 100644 --- a/deploy/docker/common/nginx-config.conf +++ b/deploy/docker/common/nginx-config.conf @@ -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; } diff --git a/deploy/docker/druid-kafka-setup/docker-compose-tiny.yaml b/deploy/docker/druid-kafka-setup/docker-compose-tiny.yaml deleted file mode 100644 index 20ce14e822..0000000000 --- a/deploy/docker/druid-kafka-setup/docker-compose-tiny.yaml +++ /dev/null @@ -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 - diff --git a/deploy/docker/druid-kafka-setup/docker-compose.yaml b/deploy/docker/druid-kafka-setup/docker-compose.yaml deleted file mode 100644 index c47823492f..0000000000 --- a/deploy/docker/druid-kafka-setup/docker-compose.yaml +++ /dev/null @@ -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 - diff --git a/deploy/docker/druid-kafka-setup/druid-jobs/retention-spec.json b/deploy/docker/druid-kafka-setup/druid-jobs/retention-spec.json deleted file mode 100644 index b4b0ebb426..0000000000 --- a/deploy/docker/druid-kafka-setup/druid-jobs/retention-spec.json +++ /dev/null @@ -1 +0,0 @@ -[{"period":"P3D","includeFuture":true,"tieredReplicants":{"_default_tier":1},"type":"loadByPeriod"},{"type":"dropForever"}] \ No newline at end of file diff --git a/deploy/docker/druid-kafka-setup/druid-jobs/supervisor-spec.json b/deploy/docker/druid-kafka-setup/druid-jobs/supervisor-spec.json deleted file mode 100644 index 3528826159..0000000000 --- a/deploy/docker/druid-kafka-setup/druid-jobs/supervisor-spec.json +++ /dev/null @@ -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" - } - } - } diff --git a/deploy/docker/druid-kafka-setup/environment_small/broker b/deploy/docker/druid-kafka-setup/environment_small/broker deleted file mode 100644 index 37d3ef37b4..0000000000 --- a/deploy/docker/druid-kafka-setup/environment_small/broker +++ /dev/null @@ -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= \ No newline at end of file diff --git a/deploy/docker/druid-kafka-setup/environment_small/coordinator b/deploy/docker/druid-kafka-setup/environment_small/coordinator deleted file mode 100644 index 476e6dcdef..0000000000 --- a/deploy/docker/druid-kafka-setup/environment_small/coordinator +++ /dev/null @@ -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= \ No newline at end of file diff --git a/deploy/docker/druid-kafka-setup/environment_small/historical b/deploy/docker/druid-kafka-setup/environment_small/historical deleted file mode 100644 index c614644bca..0000000000 --- a/deploy/docker/druid-kafka-setup/environment_small/historical +++ /dev/null @@ -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= \ No newline at end of file diff --git a/deploy/docker/druid-kafka-setup/environment_small/middlemanager b/deploy/docker/druid-kafka-setup/environment_small/middlemanager deleted file mode 100644 index a5ae2dbee8..0000000000 --- a/deploy/docker/druid-kafka-setup/environment_small/middlemanager +++ /dev/null @@ -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= \ No newline at end of file diff --git a/deploy/docker/druid-kafka-setup/environment_small/router b/deploy/docker/druid-kafka-setup/environment_small/router deleted file mode 100644 index 3e349616fe..0000000000 --- a/deploy/docker/druid-kafka-setup/environment_small/router +++ /dev/null @@ -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= \ No newline at end of file diff --git a/deploy/docker/druid-kafka-setup/environment_tiny/broker b/deploy/docker/druid-kafka-setup/environment_tiny/broker deleted file mode 100644 index c232d64b4b..0000000000 --- a/deploy/docker/druid-kafka-setup/environment_tiny/broker +++ /dev/null @@ -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= diff --git a/deploy/docker/druid-kafka-setup/environment_tiny/common b/deploy/docker/druid-kafka-setup/environment_tiny/common deleted file mode 100644 index 005290157e..0000000000 --- a/deploy/docker/druid-kafka-setup/environment_tiny/common +++ /dev/null @@ -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= -# druid_storage_baseKey=druid/segments - -# AWS_ACCESS_KEY_ID= -# AWS_SECRET_ACCESS_KEY= -# AWS_REGION= - -# druid_indexer_logs_type=s3 -# druid_indexer_logs_s3Bucket= -# 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 - diff --git a/deploy/docker/druid-kafka-setup/environment_tiny/coordinator b/deploy/docker/druid-kafka-setup/environment_tiny/coordinator deleted file mode 100644 index d5b9a4367f..0000000000 --- a/deploy/docker/druid-kafka-setup/environment_tiny/coordinator +++ /dev/null @@ -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= diff --git a/deploy/docker/druid-kafka-setup/environment_tiny/historical b/deploy/docker/druid-kafka-setup/environment_tiny/historical deleted file mode 100644 index cef8b92414..0000000000 --- a/deploy/docker/druid-kafka-setup/environment_tiny/historical +++ /dev/null @@ -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= diff --git a/deploy/docker/druid-kafka-setup/environment_tiny/middlemanager b/deploy/docker/druid-kafka-setup/environment_tiny/middlemanager deleted file mode 100644 index 21abd6fe37..0000000000 --- a/deploy/docker/druid-kafka-setup/environment_tiny/middlemanager +++ /dev/null @@ -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= diff --git a/deploy/docker/druid-kafka-setup/environment_tiny/router b/deploy/docker/druid-kafka-setup/environment_tiny/router deleted file mode 100644 index 5bfab90f4b..0000000000 --- a/deploy/docker/druid-kafka-setup/environment_tiny/router +++ /dev/null @@ -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= diff --git a/deploy/docker/druid-kafka-setup/otel-collector-config.yaml b/deploy/docker/druid-kafka-setup/otel-collector-config.yaml deleted file mode 100644 index ff4b8f496d..0000000000 --- a/deploy/docker/druid-kafka-setup/otel-collector-config.yaml +++ /dev/null @@ -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] \ No newline at end of file diff --git a/deploy/install.sh b/deploy/install.sh index abca7f0878..12814c8980 100755 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -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' diff --git a/frontend/.dockerignore b/frontend/.dockerignore index ec1a1c2616..2e3d78bb02 100644 --- a/frontend/.dockerignore +++ b/frontend/.dockerignore @@ -1,4 +1,5 @@ node_modules .vscode build -.env \ No newline at end of file +.env +.git diff --git a/frontend/.husky/commit-msg b/frontend/.husky/commit-msg new file mode 100755 index 0000000000..818ef8b9b5 --- /dev/null +++ b/frontend/.husky/commit-msg @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +cd frontend && npm run commitlint diff --git a/frontend/.nvmrc b/frontend/.nvmrc index b1051fc5a5..c818c7b005 100644 --- a/frontend/.nvmrc +++ b/frontend/.nvmrc @@ -1 +1 @@ -12.13.0 \ No newline at end of file +16.15.0 \ No newline at end of file diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 530d6acf5f..d7fff6c0bb 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -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;"] \ No newline at end of file +ENTRYPOINT ["nginx", "-g", "daemon off;"] diff --git a/frontend/commitlint.config.js b/frontend/commitlint.config.js new file mode 100644 index 0000000000..422b19445b --- /dev/null +++ b/frontend/commitlint.config.js @@ -0,0 +1 @@ +module.exports = { extends: ['@commitlint/config-conventional'] }; diff --git a/frontend/cypress.json b/frontend/cypress.json deleted file mode 100644 index 01cb545a35..0000000000 --- a/frontend/cypress.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "video": false -} diff --git a/frontend/cypress/CustomFunctions/Login.ts b/frontend/cypress/CustomFunctions/Login.ts deleted file mode 100644 index 3d3c8f791a..0000000000 --- a/frontend/cypress/CustomFunctions/Login.ts +++ /dev/null @@ -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; diff --git a/frontend/cypress/CustomFunctions/checkRouteDefaultGlobalTimeOptions.ts b/frontend/cypress/CustomFunctions/checkRouteDefaultGlobalTimeOptions.ts deleted file mode 100644 index b19b4bdeeb..0000000000 --- a/frontend/cypress/CustomFunctions/checkRouteDefaultGlobalTimeOptions.ts +++ /dev/null @@ -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; diff --git a/frontend/cypress/CustomFunctions/uncaughtExpection.ts b/frontend/cypress/CustomFunctions/uncaughtExpection.ts deleted file mode 100644 index dd423208f3..0000000000 --- a/frontend/cypress/CustomFunctions/uncaughtExpection.ts +++ /dev/null @@ -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; diff --git a/frontend/cypress/fixtures/defaultAllChannels.json b/frontend/cypress/fixtures/defaultAllChannels.json deleted file mode 100644 index c292bc1f2d..0000000000 --- a/frontend/cypress/fixtures/defaultAllChannels.json +++ /dev/null @@ -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" -} diff --git a/frontend/cypress/fixtures/defaultApp.json b/frontend/cypress/fixtures/defaultApp.json deleted file mode 100644 index aca47466de..0000000000 --- a/frontend/cypress/fixtures/defaultApp.json +++ /dev/null @@ -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 - } -] diff --git a/frontend/cypress/fixtures/defaultRules.json b/frontend/cypress/fixtures/defaultRules.json deleted file mode 100644 index bc0ca42ec9..0000000000 --- a/frontend/cypress/fixtures/defaultRules.json +++ /dev/null @@ -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 - } - ] - } -} diff --git a/frontend/cypress/fixtures/errorPercentage.json b/frontend/cypress/fixtures/errorPercentage.json deleted file mode 100644 index 61c326ffc2..0000000000 --- a/frontend/cypress/fixtures/errorPercentage.json +++ /dev/null @@ -1 +0,0 @@ -{ "status": "success", "data": { "resultType": "matrix", "result": [] } } diff --git a/frontend/cypress/fixtures/requestPerSecond.json b/frontend/cypress/fixtures/requestPerSecond.json deleted file mode 100644 index 4cd3062454..0000000000 --- a/frontend/cypress/fixtures/requestPerSecond.json +++ /dev/null @@ -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"] - ] - } - ] - } -} diff --git a/frontend/cypress/fixtures/serviceOverview.json b/frontend/cypress/fixtures/serviceOverview.json deleted file mode 100644 index d3a51c35b2..0000000000 --- a/frontend/cypress/fixtures/serviceOverview.json +++ /dev/null @@ -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 - } -] diff --git a/frontend/cypress/fixtures/topEndPoints.json b/frontend/cypress/fixtures/topEndPoints.json deleted file mode 100644 index 4892b6e93a..0000000000 --- a/frontend/cypress/fixtures/topEndPoints.json +++ /dev/null @@ -1,9 +0,0 @@ -[ - { - "p50": 710824000, - "p95": 1003231400, - "p99": 1231265500, - "numCalls": 299, - "name": "HTTP GET /dispatch" - } -] diff --git a/frontend/cypress/fixtures/trace/initialAggregates.json b/frontend/cypress/fixtures/trace/initialAggregates.json deleted file mode 100644 index b029e9de64..0000000000 --- a/frontend/cypress/fixtures/trace/initialAggregates.json +++ /dev/null @@ -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 } - } -} diff --git a/frontend/cypress/fixtures/trace/initialSpanFilter.json b/frontend/cypress/fixtures/trace/initialSpanFilter.json deleted file mode 100644 index 26bed66ae9..0000000000 --- a/frontend/cypress/fixtures/trace/initialSpanFilter.json +++ /dev/null @@ -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": {} -} diff --git a/frontend/cypress/fixtures/trace/initialSpans.json b/frontend/cypress/fixtures/trace/initialSpans.json deleted file mode 100644 index 9b15100752..0000000000 --- a/frontend/cypress/fixtures/trace/initialSpans.json +++ /dev/null @@ -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 -} diff --git a/frontend/cypress/integration/appLayout/index.spec.ts b/frontend/cypress/integration/appLayout/index.spec.ts deleted file mode 100644 index 4d45291dea..0000000000 --- a/frontend/cypress/integration/appLayout/index.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -/// -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, - }); - }); -}); diff --git a/frontend/cypress/integration/channels/index.spec.ts b/frontend/cypress/integration/channels/index.spec.ts deleted file mode 100644 index e54ed4757f..0000000000 --- a/frontend/cypress/integration/channels/index.spec.ts +++ /dev/null @@ -1,52 +0,0 @@ -/// - -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, - ); - }); - }); - }); -}); diff --git a/frontend/cypress/integration/globalTime/default.spec.ts b/frontend/cypress/integration/globalTime/default.spec.ts deleted file mode 100644 index 70e03e9221..0000000000 --- a/frontend/cypress/integration/globalTime/default.spec.ts +++ /dev/null @@ -1,44 +0,0 @@ -/// -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, - }); - }); -}); diff --git a/frontend/cypress/integration/globalTime/metricsApplication.spec.ts b/frontend/cypress/integration/globalTime/metricsApplication.spec.ts deleted file mode 100644 index e64ef6711e..0000000000 --- a/frontend/cypress/integration/globalTime/metricsApplication.spec.ts +++ /dev/null @@ -1,126 +0,0 @@ -/// -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'); - }); - }); - }); -}); diff --git a/frontend/cypress/integration/metrics/index.spec.ts b/frontend/cypress/integration/metrics/index.spec.ts deleted file mode 100644 index 91cf3b7e38..0000000000 --- a/frontend/cypress/integration/metrics/index.spec.ts +++ /dev/null @@ -1,67 +0,0 @@ -/// -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 {}; diff --git a/frontend/cypress/integration/rules/index.spec.ts b/frontend/cypress/integration/rules/index.spec.ts deleted file mode 100644 index e5349a33e6..0000000000 --- a/frontend/cypress/integration/rules/index.spec.ts +++ /dev/null @@ -1,130 +0,0 @@ -/// - -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); - }); - }); -}); diff --git a/frontend/cypress/integration/trace/index.spec.ts b/frontend/cypress/integration/trace/index.spec.ts deleted file mode 100644 index 1cfecef08e..0000000000 --- a/frontend/cypress/integration/trace/index.spec.ts +++ /dev/null @@ -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); - }); - }); -}); diff --git a/frontend/cypress/plugins/index.ts b/frontend/cypress/plugins/index.ts deleted file mode 100644 index 69e261ef89..0000000000 --- a/frontend/cypress/plugins/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -/// -// *********************************************************** -// 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 - -/// - -/** - * @type {Cypress.PluginConfig} - */ -module.exports = (): void => { - return undefined; -}; - -export {}; diff --git a/frontend/cypress/support/commands.ts b/frontend/cypress/support/commands.ts deleted file mode 100644 index 370f984791..0000000000 --- a/frontend/cypress/support/commands.ts +++ /dev/null @@ -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; - } - } -} diff --git a/frontend/cypress/support/index.ts b/frontend/cypress/support/index.ts deleted file mode 100644 index 37a498fb5b..0000000000 --- a/frontend/cypress/support/index.ts +++ /dev/null @@ -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') diff --git a/frontend/cypress/tsconfig.json b/frontend/cypress/tsconfig.json deleted file mode 100644 index b72f233542..0000000000 --- a/frontend/cypress/tsconfig.json +++ /dev/null @@ -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"] -} diff --git a/frontend/jest.config.ts b/frontend/jest.config.ts index 5b5ebcdbc7..f85772a0e8 100644 --- a/frontend/jest.config.ts +++ b/frontend/jest.config.ts @@ -25,6 +25,11 @@ const config: Config.InitialOptions = { setupFilesAfterEnv: ['jest.setup.ts'], testPathIgnorePatterns: ['/node_modules/', '/public/'], moduleDirectories: ['node_modules', 'src'], + testEnvironmentOptions: { + 'jest-playwright': { + browsers: ['chromium', 'firefox', 'webkit'], + }, + }, }; export default config; diff --git a/frontend/package.json b/frontend/package.json index dad3b0589e..ebaffb5fae 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -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" } } diff --git a/frontend/playwright.config.ts b/frontend/playwright.config.ts new file mode 100644 index 0000000000..3fc61908c7 --- /dev/null +++ b/frontend/playwright.config.ts @@ -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; diff --git a/frontend/public/locales/en-GB/generalSettings.json b/frontend/public/locales/en-GB/generalSettings.json new file mode 100644 index 0000000000..151987397d --- /dev/null +++ b/frontend/public/locales/en-GB/generalSettings.json @@ -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}}." +} diff --git a/frontend/public/locales/en-GB/routes.json b/frontend/public/locales/en-GB/routes.json index f3df8f6c9d..625638d85f 100644 --- a/frontend/public/locales/en-GB/routes.json +++ b/frontend/public/locales/en-GB/routes.json @@ -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" } diff --git a/frontend/public/locales/en-GB/translation.json b/frontend/public/locales/en-GB/translation.json index 7ad8e9a716..682f034e69 100644 --- a/frontend/public/locales/en-GB/translation.json +++ b/frontend/public/locales/en-GB/translation.json @@ -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." } } diff --git a/frontend/public/locales/en/generalSettings.json b/frontend/public/locales/en/generalSettings.json new file mode 100644 index 0000000000..151987397d --- /dev/null +++ b/frontend/public/locales/en/generalSettings.json @@ -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}}." +} diff --git a/frontend/public/locales/en/routes.json b/frontend/public/locales/en/routes.json index f3df8f6c9d..625638d85f 100644 --- a/frontend/public/locales/en/routes.json +++ b/frontend/public/locales/en/routes.json @@ -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" } diff --git a/frontend/public/locales/en/translation.json b/frontend/public/locales/en/translation.json index 7ad8e9a716..682f034e69 100644 --- a/frontend/public/locales/en/translation.json +++ b/frontend/public/locales/en/translation.json @@ -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." } } diff --git a/frontend/src/AppRoutes/Private.tsx b/frontend/src/AppRoutes/Private.tsx index 3e97adec58..436b184ca4 100644 --- a/frontend/src/AppRoutes/Private.tsx +++ b/frontend/src/AppRoutes/Private.tsx @@ -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'), diff --git a/frontend/src/AppRoutes/pageComponents.ts b/frontend/src/AppRoutes/pageComponents.ts index fd59d0bc7b..071b5d77fa 100644 --- a/frontend/src/AppRoutes/pageComponents.ts +++ b/frontend/src/AppRoutes/pageComponents.ts @@ -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( diff --git a/frontend/src/api/settings/getRetention.ts b/frontend/src/api/settings/getRetention.ts index 4e5ad3d6f1..d19ab1a9d5 100644 --- a/frontend/src/api/settings/getRetention.ts +++ b/frontend/src/api/settings/getRetention.ts @@ -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 | ErrorResponse -> => { +const getRetention = async ( + props: T, +): Promise> | ErrorResponse> => { try { - const response = await axios.get(`/settings/ttl`); + const response = await axios.get>( + `/settings/ttl?type=${props}`, + ); return { statusCode: 200, diff --git a/frontend/src/api/settings/setRetention.ts b/frontend/src/api/settings/setRetention.ts index 62aa3559e5..481760bf57 100644 --- a/frontend/src/api/settings/setRetention.ts +++ b/frontend/src/api/settings/setRetention.ts @@ -11,7 +11,7 @@ const setRetention = async ( const response = await axios.post( `/settings/ttl?duration=${props.totalDuration}&type=${props.type}${ props.coldStorage - ? `&coldStorage=${props.coldStorage};toColdDuration=${props.toColdDuration}` + ? `&coldStorage=${props.coldStorage}&toColdDuration=${props.toColdDuration}` : '' }`, ); diff --git a/frontend/src/api/utils.ts b/frontend/src/api/utils.ts index 56867927a8..fdfa6c32a4 100644 --- a/frontend/src/api/utils.ts +++ b/frontend/src/api/utils.ts @@ -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); }; diff --git a/frontend/src/components/Graph/__tests__/xAxisConfig.test.ts b/frontend/src/components/Graph/__tests__/xAxisConfig.test.ts index e0320e29b0..b26a2437c1 100644 --- a/frontend/src/components/Graph/__tests__/xAxisConfig.test.ts +++ b/frontend/src/components/Graph/__tests__/xAxisConfig.test.ts @@ -1,4 +1,3 @@ -import { expect } from '@jest/globals'; import dayjs from 'dayjs'; import { convertTimeRange, TIME_UNITS } from '../xAxisConfig'; diff --git a/frontend/src/components/NotFound/NotFound.test.tsx b/frontend/src/components/NotFound/NotFound.test.tsx index 3f4be8c009..d6daf988f8 100644 --- a/frontend/src/components/NotFound/NotFound.test.tsx +++ b/frontend/src/components/NotFound/NotFound.test.tsx @@ -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( - + + + , ); expect(asFragment()).toMatchSnapshot(); diff --git a/frontend/src/components/RouteTab/index.tsx b/frontend/src/components/RouteTab/index.tsx index 4a565d84b9..1192f2f8e2 100644 --- a/frontend/src/components/RouteTab/index.tsx +++ b/frontend/src/components/RouteTab/index.tsx @@ -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 => ( - + ({ Component, name, route }): JSX.Element => ( + ), diff --git a/frontend/src/constants/routes.ts b/frontend/src/constants/routes.ts index 3c7db9c995..636a3b0758 100644 --- a/frontend/src/constants/routes.ts +++ b/frontend/src/constants/routes.ts @@ -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', diff --git a/frontend/src/container/AllError/index.tsx b/frontend/src/container/AllError/index.tsx index 3f49fdb5a4..51f47c1104 100644 --- a/frontend/src/container/AllError/index.tsx +++ b/frontend/src/container/AllError/index.tsx @@ -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 => ( - - {value} - + value}> + + {value} + + ), 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 => ( - - {value} - + value}> + + {value} + + ), }, { diff --git a/frontend/src/container/AppLayout/index.tsx b/frontend/src/container/AppLayout/index.tsx index 5abb5f4d6f..911dcd018c 100644 --- a/frontend/src/container/AppLayout/index.tsx +++ b/frontend/src/container/AppLayout/index.tsx @@ -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, }, }); } diff --git a/frontend/src/container/GeneralSettings/GeneralSettings.tsx b/frontend/src/container/GeneralSettings/GeneralSettings.tsx index 10c9a1ac58..0f950ec05b 100644 --- a/frontend/src/container/GeneralSettings/GeneralSettings.tsx +++ b/frontend/src/container/GeneralSettings/GeneralSettings.tsx @@ -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(false); - const [postApiLoading, setPostApiLoading] = useState(false); - + const { t } = useTranslation(['generalSettings']); + const [modalMetrics, setModalMetrics] = useState(false); + const [postApiLoadingMetrics, setPostApiLoadingMetrics] = useState( + false, + ); + const [postApiLoadingTraces, setPostApiLoadingTraces] = useState( + false, + ); + const [modalTraces, setModalTraces] = useState(false); const [availableDisks] = useState(getAvailableDiskPayload); - const [currentTTLValues, setCurrentTTLValues] = useState(ttlValuesPayload); + const [metricsCurrentTTLValues, setMetricsCurrentTTLValues] = useState( + metricsTtlValuesPayload, + ); + const [tracesCurrentTTLValues, setTracesCurrentTTLValues] = useState( + tracesTtlValuesPayload, + ); + const { role } = useSelector((state) => state.app); const [setRetentionPermission] = useComponentPermission( @@ -55,195 +87,93 @@ function GeneralSettings({ ] = useState(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 => { + if (metricsTtlValuesPayload.status === 'pending') { + metricsTtlValuesRefetch(); + } + }, + metricsTtlValuesPayload.status === 'pending' ? 1000 : null, + ); + + useInterval( + async (): Promise => { + 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 ( - - {category.name} - - {category.retentionFields.map((retentionField) => ( - - ))} - - ); - } - return null; - }); - - // eslint-disable-next-line sonarjs/cognitive-complexity - const onOkHandler = async (): Promise => { - 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 => { + 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' ? ( + + } />{' '} + {t('retention_save_button.pending', { name: 'metrics' })} + + ) : ( + {t('retention_save_button.success')} + ), + isDisabled: + metricsTtlValuesPayload.status === 'pending' || isMetricsSaveDisabled, + }, + statusComponent: ( + + ), + }, + { + 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' ? ( + + } />{' '} + {t('retention_save_button.pending', { name: 'traces' })} + + ) : ( + {t('retention_save_button.success')} + ), + isDisabled: + tracesTtlValuesPayload.status === 'pending' || isTracesSaveDisabled, + }, + statusComponent: ( + + ), + }, + ].map((category, idx, renderArr): JSX.Element | null => { + if ( + Array.isArray(category.retentionFields) && + category.retentionFields.length > 0 + ) { + return ( + + + {category.name} + + {category.retentionFields.map((retentionField) => ( + + ))} + + + {category.statusComponent} + + + onModalToggleHandler(category.name.toLowerCase() as TTTLType) + } + onOk={(): Promise => + onOkHandler(category.name.toLowerCase() as TTTLType) + } + centered + visible={category.save.modal} + confirmLoading={category.save.apiLoading} + > + + {t('retention_confirmation_description', { + name: category.name.toLowerCase(), + })} + + + + {idx < renderArr.length && ( + + + + )} + + ); + } + return null; + }); + return ( {Element} @@ -306,34 +452,20 @@ function GeneralSettings({ {renderConfig} - - - {t('settings.retention_confirmation_description')} - - - - - ); } interface GeneralSettingsProps { - ttlValuesPayload: GetRetentionPayload; getAvailableDiskPayload: GetDisksPayload; + metricsTtlValuesPayload: GetRetentionPeriodMetricsPayload; + tracesTtlValuesPayload: GetRetentionPeriodTracesPayload; + metricsTtlValuesRefetch: UseQueryResult< + ErrorResponse | SuccessResponse + >['refetch']; + tracesTtlValuesRefetch: UseQueryResult< + ErrorResponse | SuccessResponse + >['refetch']; } export default GeneralSettings; diff --git a/frontend/src/container/GeneralSettings/StatusMessage.tsx b/frontend/src/container/GeneralSettings/StatusMessage.tsx new file mode 100644 index 0000000000..bb62f34007 --- /dev/null +++ b/frontend/src/container/GeneralSettings/StatusMessage.tsx @@ -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 ? ( + + + + + + + + {statusMessage} + + + + ) : null; +} + +interface StatusMessageProps { + status: TStatus; + total_retention: number | undefined; + s3_retention: number | undefined; +} +export default StatusMessage; diff --git a/frontend/src/container/GeneralSettings/index.tsx b/frontend/src/container/GeneralSettings/index.tsx index 633c4822e6..2c66042500 100644 --- a/frontend/src/container/GeneralSettings/index.tsx +++ b/frontend/src/container/GeneralSettings/index.tsx @@ -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 = Promise< + SuccessResponse> | 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 ( - {getRetentionPeriodApiResponse.data?.error || + {getRetentionPeriodMetricsApiResponse.data?.error || getDisksResponse.data?.error || t('something_went_wrong')} ); } + // Error State - When RetentionPeriodTracesApi or getDiskApi gets errored out. + if (getRetentionPeriodTracesApiResponse.isError || getDisksResponse.isError) { + return ( + + {getRetentionPeriodTracesApiResponse.data?.error || + getDisksResponse.data?.error || + t('something_went_wrong')} + + ); + } + + // 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 ; } @@ -44,7 +79,10 @@ function GeneralSettings(): JSX.Element { ); diff --git a/frontend/src/container/GeneralSettings/styles.ts b/frontend/src/container/GeneralSettings/styles.ts index 6dc7b9fe71..9c9e7bb312 100644 --- a/frontend/src/container/GeneralSettings/styles.ts +++ b/frontend/src/container/GeneralSettings/styles.ts @@ -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%; +`; diff --git a/frontend/src/container/GeneralSettings/utils.ts b/frontend/src/container/GeneralSettings/utils.ts index 30efd6786a..639f0a4930 100644 --- a/frontend/src/container/GeneralSettings/utils.ts +++ b/frontend/src/container/GeneralSettings/utils.ts @@ -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' : '' + }`; +}; diff --git a/frontend/src/container/GridGraphLayout/AddWidget/index.tsx b/frontend/src/container/GridGraphLayout/AddWidget/index.tsx deleted file mode 100644 index cbe6116aaf..0000000000 --- a/frontend/src/container/GridGraphLayout/AddWidget/index.tsx +++ /dev/null @@ -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( - (state) => state.dashboards, - ); - - const onToggleHandler = useCallback(() => { - toggleAddWidget(true); - }, [toggleAddWidget]); - - return ( - - {!isAddWidget ? ( - - ) : ( - Click a widget icon to add it here - )} - - ); -} - -interface DispatchProps { - toggleAddWidget: ( - props: ToggleAddWidgetProps, - ) => (dispatch: Dispatch) => void; -} - -const mapDispatchToProps = ( - dispatch: ThunkDispatch, -): DispatchProps => ({ - toggleAddWidget: bindActionCreators(ToggleAddWidget, dispatch), -}); - -type Props = DispatchProps; - -export default connect(null, mapDispatchToProps)(AddWidget); diff --git a/frontend/src/container/GridGraphLayout/AddWidget/styles.ts b/frontend/src/container/GridGraphLayout/AddWidget/styles.ts deleted file mode 100644 index 9a7d6b58f3..0000000000 --- a/frontend/src/container/GridGraphLayout/AddWidget/styles.ts +++ /dev/null @@ -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%; -`; diff --git a/frontend/src/container/GridGraphLayout/EmptyWidget/index.tsx b/frontend/src/container/GridGraphLayout/EmptyWidget/index.tsx new file mode 100644 index 0000000000..df9b7662fe --- /dev/null +++ b/frontend/src/container/GridGraphLayout/EmptyWidget/index.tsx @@ -0,0 +1,16 @@ +import { Typography } from 'antd'; +import React from 'react'; + +import { Container } from './styles'; + +function EmptyWidget(): JSX.Element { + return ( + + + Click one of the widget types above (Time Series / Value) to add here + + + ); +} + +export default EmptyWidget; diff --git a/frontend/src/container/GridGraphLayout/EmptyWidget/styles.ts b/frontend/src/container/GridGraphLayout/EmptyWidget/styles.ts new file mode 100644 index 0000000000..914b47cb9b --- /dev/null +++ b/frontend/src/container/GridGraphLayout/EmptyWidget/styles.ts @@ -0,0 +1,8 @@ +import styled from 'styled-components'; + +export const Container = styled.div` + height: 100%; + display: flex; + justify-content: center; + align-items: center; +`; diff --git a/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx b/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx index ef04a4e3da..e4b4bc8183 100644 --- a/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx +++ b/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx @@ -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({ - 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 ( - - {state.errorMessage} - - ); + 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 ; } - if (state.loading || state.payload === undefined) { + if (isError || data === undefined || data[0] === undefined) { return ( -
- -
+ + {errorMessage} + ); } @@ -169,17 +136,27 @@ function FullView({ setSelectedTime, }} /> - )} - {/* */} ({ + 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, }} /> - {/* */} ); } -interface FullViewState { - loading: boolean; - error: boolean; - errorMessage: string; - payload: ChartData | undefined; -} - interface FullViewProps { widget: Widgets; fullViewOptions?: boolean; diff --git a/frontend/src/container/GridGraphLayout/Graph/index.tsx b/frontend/src/container/GridGraphLayout/Graph/index.tsx index fa87b67638..a57a396fd0 100644 --- a/frontend/src/container/GridGraphLayout/Graph/index.tsx +++ b/frontend/src/container/GridGraphLayout/Graph/index.tsx @@ -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({ - loading: true, - errorMessage: '', - error: false, - payload: undefined, - }); const [hovered, setHovered] = useState(false); const [modal, setModal] = useState(false); const { minTime, maxTime } = useSelector( (state) => state.globalTime, ); - const [deleteModal, setDeletModal] = useState(false); + const [deleteModal, setDeleteModal] = useState(false); - useEffect(() => { - (async (): Promise => { - 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>) => { @@ -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 ( <> 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 ; + } + + 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)} /> - {state.errorMessage} + {errorMessage} ); } - if (state.loading === true || state.payload === undefined) { - return ; - } + 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 ( - onToggleModal(setModal)} - onDelete={(): void => onToggleModal(setDeletModal)} - /> + {!isEmptyLayout && ( + onToggleModal(setModal)} + onDelete={(): void => onToggleModal(setDeleteModal)} + /> + )} - {getModals()} + {!isEmptyLayout && getModals()} - + {!isEmpty(widget) && ( + + )} + + {isEmptyLayout && } ); } -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; 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>; } 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)); diff --git a/frontend/src/container/GridGraphLayout/GraphLayout.tsx b/frontend/src/container/GridGraphLayout/GraphLayout.tsx new file mode 100644 index 0000000000..97fee8ec9d --- /dev/null +++ b/frontend/src/container/GridGraphLayout/GraphLayout.tsx @@ -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((state) => state.app); + const { isDarkMode } = useSelector((state) => state.app); + + const [saveLayoutPermission, addPanelPermission] = useComponentPermission( + ['save_layout', 'add_panel'], + role, + ); + + return ( + <> + + {saveLayoutPermission && ( + + )} + + {addPanelPermission && ( + + )} + + + + {layouts.map(({ Component, ...rest }) => { + const currentWidget = (widgets || [])?.find((e) => e.id === rest.i); + + return ( + + + + + + ); + })} + + + ); +} + +interface GraphLayoutProps { + layouts: LayoutProps[]; + saveLayoutState: State; + onLayoutSaveHandler: (layout: Layout[]) => Promise; + addPanelLoading: boolean; + onAddPanelHandler: VoidFunction; + onLayoutChangeHandler: (layout: Layout[]) => Promise; + widgets: Widgets[] | undefined; + setLayout: React.Dispatch>; +} + +export default GraphLayout; diff --git a/frontend/src/container/GridGraphLayout/WidgetHeader/index.tsx b/frontend/src/container/GridGraphLayout/WidgetHeader/index.tsx index a627bac3e1..d560aaab82 100644 --- a/frontend/src/container/GridGraphLayout/WidgetHeader/index.tsx +++ b/frontend/src/container/GridGraphLayout/WidgetHeader/index.tsx @@ -104,7 +104,7 @@ function WidgetHeader({ overlay={menu} trigger={['click']} overlayStyle={{ minWidth: 100 }} - placement="bottomCenter" + placement="bottom" > setLocalHover(true)} diff --git a/frontend/src/container/GridGraphLayout/index.tsx b/frontend/src/container/GridGraphLayout/index.tsx index 48d51fd7f0..88250f3b7c 100644 --- a/frontend/src/container/GridGraphLayout/index.tsx +++ b/frontend/src/container/GridGraphLayout/index.tsx @@ -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( +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 ( + + ); + }, + })); + +function GridGraph(props: Props): JSX.Element { + const { toggleAddWidget } = props; + const [addPanelLoading, setAddPanelLoading] = useState(false); + const { t } = useTranslation(['common']); + const { dashboards, isAddWidget } = useSelector( (state) => state.dashboards, ); + const { role } = useSelector((state) => state.app); + + const [saveLayoutPermission] = useComponentPermission(['save_layout'], role); const [saveLayoutState, setSaveLayoutState] = useState({ loading: false, error: false, errorMessage: '', payload: [], }); - const [selectedDashboard] = dashboards; const { data } = selectedDashboard; const { widgets } = data; - const [layouts, setLayout] = useState([]); + const dispatch = useDispatch>(); - const AddWidgetWrapper = useCallback(() => , []); - - const isMounted = useRef(true); - const isDeleted = useRef(false); - const { role } = useSelector((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 => ( - - ), - }; - }); - } - return data.layout - .filter((_, index) => widgets[index]) - .map((e, index) => ({ - ...e, - Component: (): JSX.Element => { - if (widgets[index]) { - return ( - - ); - } - return
; - }, - })); - }, [widgets, data.layout]); + const [layouts, setLayout] = useState( + 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 => { + 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 => { - 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 => ( + + ), + }; + }), + ); + }, + [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 => { + 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 ; - } + 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 && ( - - - - )} - - - {layouts.map(({ Component, ...rest }, index) => { - const widget = (widgets || [])[index] || {}; - - const type = widget?.panelTypes || 'TIME_SERIES'; - - const isQueryType = type === 'VALUE'; - - return ( - - - - - - ); - })} - - + ); } -interface LayoutProps extends Layout { - Component: () => JSX.Element; +interface ComponentProps { + setLayout: React.Dispatch>; } -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) => void; +} + +const mapDispatchToProps = ( + dispatch: ThunkDispatch, +): DispatchProps => ({ + toggleAddWidget: bindActionCreators(ToggleAddWidget, dispatch), +}); + +type Props = DispatchProps; + +export default connect(null, mapDispatchToProps)(GridGraph); diff --git a/frontend/src/container/GridGraphLayout/styles.ts b/frontend/src/container/GridGraphLayout/styles.ts index 0b8f4cfc89..9bb4b219bb 100644 --- a/frontend/src/container/GridGraphLayout/styles.ts +++ b/frontend/src/container/GridGraphLayout/styles.ts @@ -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)` +export const Card = styled(CardComponent)` &&& { height: 100%; } @@ -18,20 +16,34 @@ export const Card = styled(CardComponent)` } `; -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` + :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; diff --git a/frontend/src/container/GridGraphLayout/utils.ts b/frontend/src/container/GridGraphLayout/utils.ts index 2bfcb4e61f..67b854368c 100644 --- a/frontend/src/container/GridGraphLayout/utils.ts +++ b/frontend/src/container/GridGraphLayout/utils.ts @@ -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 => { - const response = await updateDashboardApi({ + isRedirected, +}: UpdateDashboardProps): Promise => { + 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; } diff --git a/frontend/src/container/Header/index.tsx b/frontend/src/container/Header/index.tsx index c3c9eb8e84..b2136d9e19 100644 --- a/frontend/src/container/Header/index.tsx +++ b/frontend/src/container/Header/index.tsx @@ -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( @@ -70,7 +65,7 @@ function HeaderContainer({ toggleDarkMode }: Props): JSX.Element { }; const menu = ( - + @@ -92,7 +87,7 @@ function HeaderContainer({ toggleDarkMode }: Props): JSX.Element {
- + ); return ( diff --git a/frontend/src/container/Header/styles.ts b/frontend/src/container/Header/styles.ts index 6355c0c8c7..602634f356 100644 --- a/frontend/src/container/Header/styles.ts +++ b/frontend/src/container/Header/styles.ts @@ -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; diff --git a/frontend/src/container/IsRouteAccessible/index.tsx b/frontend/src/container/IsRouteAccessible/index.tsx deleted file mode 100644 index 3e3b246288..0000000000 --- a/frontend/src/container/IsRouteAccessible/index.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react'; - -function IsRouteAccessible(): JSX.Element { - return
asd
; -} - -export default IsRouteAccessible; diff --git a/frontend/src/container/ListAlertRules/index.tsx b/frontend/src/container/ListAlertRules/index.tsx index abcf1efe58..2b81729e7e 100644 --- a/frontend/src/container/ListAlertRules/index.tsx +++ b/frontend/src/container/ListAlertRules/index.tsx @@ -1,6 +1,7 @@ +import { notification } from 'antd'; import getAll from 'api/alerts/getAll'; import Spinner from 'components/Spinner'; -import React from 'react'; +import React, { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { useQuery } from 'react-query'; @@ -8,15 +9,37 @@ import ListAlert from './ListAlert'; function ListAlertRules(): JSX.Element { const { t } = useTranslation('common'); - const { data, isError, isLoading, refetch } = useQuery('allAlerts', { + const { data, isError, isLoading, refetch, status } = useQuery('allAlerts', { queryFn: getAll, cacheTime: 0, }); + useEffect(() => { + if (status === 'error' || (status === 'success' && data.statusCode >= 400)) { + notification.error({ + message: data?.error || t('something_went_wrong'), + }); + } + }, [data?.error, data?.statusCode, status, t]); + + // api failed to load the data if (isError) { return
{data?.error || t('something_went_wrong')}
; } + // api is successful but error is present + if (status === 'success' && data.statusCode >= 400) { + return ( + + ); + } + + // in case of loading if (isLoading || !data?.payload) { return ; } diff --git a/frontend/src/container/ListOfDashboard/ImportJSON/index.tsx b/frontend/src/container/ListOfDashboard/ImportJSON/index.tsx index 24b2d8b753..99f5fb0eeb 100644 --- a/frontend/src/container/ListOfDashboard/ImportJSON/index.tsx +++ b/frontend/src/container/ListOfDashboard/ImportJSON/index.tsx @@ -77,6 +77,9 @@ function ImportJSON({ ...queryData, queryData: [], })), + error: false, + errorMessage: '', + loading: false, }, })), }; diff --git a/frontend/src/container/ListOfDashboard/index.tsx b/frontend/src/container/ListOfDashboard/index.tsx index 64f3f573c1..cb375b9742 100644 --- a/frontend/src/container/ListOfDashboard/index.tsx +++ b/frontend/src/container/ListOfDashboard/index.tsx @@ -15,11 +15,19 @@ import ROUTES from 'constants/routes'; import SearchFilter from 'container/ListOfDashboard/SearchFilter'; import useComponentPermission from 'hooks/useComponentPermission'; import history from 'lib/history'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { + Dispatch, + useCallback, + useEffect, + useMemo, + useState, +} from 'react'; import { useTranslation } from 'react-i18next'; -import { useSelector } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { generatePath } from 'react-router-dom'; import { AppState } from 'store/reducers'; +import AppActions from 'types/actions'; +import { GET_ALL_DASHBOARD_SUCCESS } from 'types/actions/dashboard'; import { Dashboard } from 'types/api/dashboard/getAll'; import AppReducer from 'types/reducer/app'; import DashboardReducer from 'types/reducer/dashboards'; @@ -36,6 +44,7 @@ function ListOfAllDashboard(): JSX.Element { const { dashboards, loading } = useSelector( (state) => state.dashboards, ); + const dispatch = useDispatch>(); const { role } = useSelector((state) => state.app); const [action, createNewDashboard, newDashboard] = useComponentPermission( @@ -131,6 +140,10 @@ function ListOfAllDashboard(): JSX.Element { }); if (response.statusCode === 200) { + dispatch({ + type: GET_ALL_DASHBOARD_SUCCESS, + payload: [], + }); history.push( generatePath(ROUTES.DASHBOARD, { dashboardId: response.payload.uuid, @@ -151,7 +164,7 @@ function ListOfAllDashboard(): JSX.Element { errorMessage: (error as AxiosError).toString() || 'Something went Wrong', }); } - }, [newDashboardState, t]); + }, [newDashboardState, t, dispatch]); const getText = useCallback(() => { if (!newDashboardState.error && !newDashboardState.loading) { diff --git a/frontend/src/container/MetricsApplication/Tabs/Application.tsx b/frontend/src/container/MetricsApplication/Tabs/Overview.tsx similarity index 95% rename from frontend/src/container/MetricsApplication/Tabs/Application.tsx rename to frontend/src/container/MetricsApplication/Tabs/Overview.tsx index b7c44efac4..fbc1a855ac 100644 --- a/frontend/src/container/MetricsApplication/Tabs/Application.tsx +++ b/frontend/src/container/MetricsApplication/Tabs/Overview.tsx @@ -16,7 +16,7 @@ import MetricReducer from 'types/reducer/metrics'; import { Card, Col, GraphContainer, GraphTitle, Row } from '../styles'; import TopEndpointsTable from '../TopEndpointsTable'; -import { Button, TableContainerCard } from './styles'; +import { Button } from './styles'; function Application({ getWidget }: DashboardProps): JSX.Element { const { servicename } = useParams<{ servicename?: string }>(); @@ -48,7 +48,7 @@ function Application({ getWidget }: DashboardProps): JSX.Element { ); }; - const onClickhandler = async ( + const onClickHandler = async ( event: ChartEvent, elements: ActiveElement[], chart: Chart, @@ -119,7 +119,7 @@ function Application({ getWidget }: DashboardProps): JSX.Element { { - onClickhandler(ChartEvent, activeElements, chart, data, 'Application'); + onClickHandler(ChartEvent, activeElements, chart, data, 'Application'); }} name="application_latency" type="line" @@ -189,7 +189,7 @@ function Application({ getWidget }: DashboardProps): JSX.Element { name="request_per_sec" fullViewOptions={false} onClickHandler={(event, element, chart, data): void => { - onClickhandler(event, element, chart, data, 'Request'); + onClickHandler(event, element, chart, data, 'Request'); }} widget={getWidget([ { @@ -223,7 +223,7 @@ function Application({ getWidget }: DashboardProps): JSX.Element { name="error_percentage_%" fullViewOptions={false} onClickHandler={(ChartEvent, activeElements, chart, data): void => { - onClickhandler(ChartEvent, activeElements, chart, data, 'Error'); + onClickHandler(ChartEvent, activeElements, chart, data, 'Error'); }} widget={getWidget([ { @@ -238,9 +238,9 @@ function Application({ getWidget }: DashboardProps): JSX.Element { - + - + diff --git a/frontend/src/container/MetricsApplication/Tabs/styles.ts b/frontend/src/container/MetricsApplication/Tabs/styles.ts index bf89345063..017ab95419 100644 --- a/frontend/src/container/MetricsApplication/Tabs/styles.ts +++ b/frontend/src/container/MetricsApplication/Tabs/styles.ts @@ -1,8 +1,6 @@ import { Button as ButtonComponent } from 'antd'; import styled from 'styled-components'; -import { Card } from '../styles'; - export const Button = styled(ButtonComponent)` &&& { position: absolute; @@ -10,6 +8,3 @@ export const Button = styled(ButtonComponent)` display: none; } `; -export const TableContainerCard = styled(Card)` - overflow-x: auto; -`; diff --git a/frontend/src/container/MetricsApplication/TopEndpointsTable.tsx b/frontend/src/container/MetricsApplication/TopEndpointsTable.tsx index 9eb189e0ba..5ede2d9c6a 100644 --- a/frontend/src/container/MetricsApplication/TopEndpointsTable.tsx +++ b/frontend/src/container/MetricsApplication/TopEndpointsTable.tsx @@ -1,4 +1,4 @@ -import { Button, Table, Tooltip } from 'antd'; +import { Table, Tooltip, Typography } from 'antd'; import { ColumnsType } from 'antd/lib/table'; import { METRICS_PAGE_QUERY_PARAM } from 'constants/query'; import ROUTES from 'constants/routes'; @@ -51,17 +51,12 @@ function TopEndpointsTable(props: TopEndpointsTableProps): JSX.Element { title: 'Name', dataIndex: 'name', key: 'name', - - // eslint-disable-next-line react/display-name + ellipsis: true, render: (text: string): JSX.Element => ( - + ), }, @@ -101,9 +96,9 @@ function TopEndpointsTable(props: TopEndpointsTableProps): JSX.Element { title={(): string => { return 'Top Endpoints'; }} + tableLayout="fixed" dataSource={data} columns={columns} - pagination={false} rowKey="name" /> ); diff --git a/frontend/src/container/MetricsApplication/index.tsx b/frontend/src/container/MetricsApplication/index.tsx index 51725ab80a..9922fbb8c2 100644 --- a/frontend/src/container/MetricsApplication/index.tsx +++ b/frontend/src/container/MetricsApplication/index.tsx @@ -1,54 +1,109 @@ -import { Tabs } from 'antd'; +import RouteTab from 'components/RouteTab'; +import ROUTES from 'constants/routes'; import React from 'react'; +import { generatePath, useParams } from 'react-router-dom'; +import { useLocation } from 'react-use'; import { Widgets } from 'types/api/dashboard/getAll'; import ResourceAttributesFilter from './ResourceAttributesFilter'; -import Application from './Tabs/Application'; import DBCall from './Tabs/DBCall'; import External from './Tabs/External'; +import Overview from './Tabs/Overview'; -const { TabPane } = Tabs; +const getWidget = (query: Widgets['query']): Widgets => { + return { + description: '', + id: '', + isStacked: false, + nullZeroValues: '', + opacity: '0', + panelTypes: 'TIME_SERIES', + query, + queryData: { + data: [], + error: false, + errorMessage: '', + loading: false, + }, + timePreferance: 'GLOBAL_TIME', + title: '', + stepSize: 60, + }; +}; + +function OverViewTab(): JSX.Element { + return ; +} + +function DbCallTab(): JSX.Element { + return ; +} + +function ExternalTab(): JSX.Element { + return ; +} function ServiceMetrics(): JSX.Element { - const getWidget = (query: Widgets['query']): Widgets => { - return { - description: '', - id: '', - isStacked: false, - nullZeroValues: '', - opacity: '0', - panelTypes: 'TIME_SERIES', - query, - queryData: { - data: [], - error: false, - errorMessage: '', - loading: false, - }, - timePreferance: 'GLOBAL_TIME', - title: '', - stepSize: 60, - }; + const { search } = useLocation(); + const { servicename } = useParams<{ servicename: string }>(); + + const searchParams = new URLSearchParams(search); + const tab = searchParams.get('tab'); + + const overMetrics = 'Overview Metrics'; + const dbCallMetrics = 'Database Calls'; + const externalMetrics = 'External Calls'; + + const getActiveKey = (): string => { + switch (tab) { + case null: { + return overMetrics; + } + case dbCallMetrics: { + return dbCallMetrics; + } + case externalMetrics: { + return externalMetrics; + } + default: { + return overMetrics; + } + } }; + const activeKey = getActiveKey(); + return ( <> - - - - - - - - - - - - - + ); } -export default ServiceMetrics; +export default React.memo(ServiceMetrics); diff --git a/frontend/src/container/MetricsTable/SkipOnBoardModal/index.tsx b/frontend/src/container/MetricsTable/SkipOnBoardModal/index.tsx index b50c0ac9fb..2018d49a9d 100644 --- a/frontend/src/container/MetricsTable/SkipOnBoardModal/index.tsx +++ b/frontend/src/container/MetricsTable/SkipOnBoardModal/index.tsx @@ -18,7 +18,7 @@ function SkipOnBoardingModal({ onContinueClick }: Props): JSX.Element {